fix(bioflow): 意图分流收窄 v1.1——仅明确分析指令进生信流,问答句一律 qa
用户反馈:不是所有对话都走生信编排,只有「用xxx进行分析」「帮我分析xxx」 类明确指令才是生信流程;「水稻SPL基因家族的最新研究进展有哪些?」 等问答句必须走原正常问答流程。 - intent.py/intent.js 同表双跑:任务动词+对象 / 前缀分析句式 / 分析链句式 三通道;新增疑问引导词+问答主题词两道拦截;「研究」移出任务动词 (「帮我研究一下水稻SPL家族」经前缀通道仍保 bioflow) - cases_bioflow_intent.md 升 v1.1:40+15 用例,新增 #41-#55 问答句收窄 - test_intent.py 补 v1.1 用例,50 条全绿;PRD FR-BIO-001 同步收窄 - 部署:8098 已重启,ui/dist 重建同步 deploy-unified,浏览器端到端验证 PASS (问答句走 /api/qa/chat/stream,分析指令走 /api/bioflow/chat/stream 出路径卡) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -56,11 +56,43 @@ FLOW_QA_WORD = [
|
||||
"介绍", "是什么", "怎么跑", "怎么做", "需要准备什么输入", "需要准备什么参数",
|
||||
]
|
||||
|
||||
ANALYZE_VERB = [
|
||||
"跑", "跑一下", "分析", "鉴定", "识别", "研究", "做个", "做一个", "做一下", "做",
|
||||
"执行", "运行", "run", "identify",
|
||||
# 任务动词(v1.1):可单独成立的生信动作动词。仅要求动词 + 家族/对象,不要求
|
||||
# 前缀引导词——「跑 SPL 家族」「跑水稻 SPL 家族鉴定」直接命中(词典用例表 #8/#23)。
|
||||
# 问答句「SPL 家族研究进展」不在此列;裸「做」「研究」已从 v1.0 动词表移入
|
||||
# CHAIN_VERB(须与对象/前缀组合),此处仅保留「做个/做一个/做一下/研究一下」等
|
||||
# 明确分析形态——避免「做一下」被裸「做」子串误判(漏判安全收窄)。
|
||||
TASK_VERB = [
|
||||
"跑", "跑一下", "分析", "鉴定", "识别", "做个", "做一个", "做一下",
|
||||
"研究一下", "执行", "运行", "run", "identify",
|
||||
]
|
||||
|
||||
# 分析前缀词(v1.1):把宾语动作指回用户本人的引导前缀。
|
||||
# 判定规则:前缀自带链动词(「帮我分析」)时仅需宾语对象命中;
|
||||
# 纯前缀(「帮我/请你」)时须紧随链动词 + 宾语对象命中。
|
||||
VERB_PREFIX = ["帮我", "请帮我", "请你", "帮我分析", "请帮我分析"]
|
||||
|
||||
# 链动词(v1.1):与分析链(ANALYSIS_OBJECT)同现时参与前缀分析句式判定,
|
||||
# 且须全词边界匹配(「关联分析」不被「分析」误拆,「做不了」不被「做」误拆)。
|
||||
# 与前端 intent.js 的 analysisChainVerb 同表(cases_bioflow_intent.md v1.1 §3)。
|
||||
CHAIN_VERB = ["分析", "研究", "做", "跑"]
|
||||
|
||||
# 疑问引导词(v1.1 拦截表):「什么/怎么/如何/哪些/哪个」等问句一律 qa
|
||||
# (无论是否含分析动词与家族/对象——「水稻 SPL 家族怎么跑分析」也按 qa 处理)。
|
||||
# 注意:断言式词「有哪些」不在此表(避免「有哪些证据」类咨询句被误拦,
|
||||
# 且该句式不命中下方任何通道,本来就落 qa)。
|
||||
QUESTION_WORD = ["什么", "怎么", "如何", "哪些", "哪个", "什么软件"]
|
||||
|
||||
# 问答主题词(v1.1 拦截表):「研究进展/最新进展/综述」等问答主题句一律 qa
|
||||
# (无论是否含分析动词与家族/对象——「帮我分析水稻 SPL 家族的最新研究进展」也 qa)。
|
||||
# 不含「研究」单字——「帮我研究一下水稻 SPL 家族」是分析指令(词典用例表 #5 保持 bioflow)。
|
||||
QA_TOPIC = [
|
||||
"研究进展", "研究现状", "研究热点", "最新研究", "最新进展", "最新成果",
|
||||
"研究成果", "研究趋势", "综述", "进展",
|
||||
]
|
||||
|
||||
# 生信分析动词 = 任务动词 + 链动词(向前端 v1.1 的 hasAnyWord 并集语义对齐)。
|
||||
ANALYZE_VERB = [*TASK_VERB, *CHAIN_VERB]
|
||||
|
||||
# 分析对象词(按词典长词优先;「做」动词 + 对象组合)
|
||||
ANALYSIS_OBJECT = [
|
||||
"rna-seq", "rnaseq", "转录组", "差异表达", "wgs", "全基因组测序", "全基因组",
|
||||
@@ -139,13 +171,107 @@ def extract_reuse_name(text: str) -> str | None:
|
||||
return None
|
||||
|
||||
|
||||
def _chain_object_hit(text_lower: str) -> bool:
|
||||
"""分析链对象命中(v1.1):链动词 + 链对象(全词边界,长词优先)。
|
||||
|
||||
「帮我分析水稻叶片 RNA-seq」命中;「什么软件做全基因组注释」不命中
|
||||
(「什么软件」前导拦截);「做一个全基因组分析」不命中(缺链对象词)。
|
||||
"""
|
||||
for verb in sorted(CHAIN_VERB, key=len, reverse=True):
|
||||
idx = _boundary_index(text_lower, verb)
|
||||
if idx < 0:
|
||||
continue
|
||||
if _contains_any(text_lower[idx + len(verb):], ANALYSIS_OBJECT):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def _boundary_index(text_lower: str, word: str) -> int:
|
||||
"""全词边界查找:命中位置的前一字符与后一字符均非 CJK/ASCII 字母数字
|
||||
(「关联分析」中的「分析」前一字符为 CJK 汉字 → 不匹配)。"""
|
||||
for idx in range(len(text_lower)):
|
||||
idx = text_lower.find(word, idx)
|
||||
if idx < 0:
|
||||
return -1
|
||||
before_ok = idx == 0 or not text_lower[idx - 1].isalnum()
|
||||
after_idx = idx + len(word)
|
||||
after_ok = after_idx >= len(text_lower) or not text_lower[after_idx].isalnum()
|
||||
if before_ok and after_ok:
|
||||
return idx
|
||||
return -1
|
||||
|
||||
|
||||
def _prefix_hit(text_lower: str) -> bool:
|
||||
"""前缀分析句式命中(v1.1):VERB_PREFIX 引导 + 宾语对象命中。
|
||||
|
||||
「帮我分析水稻 SPL 家族」→ True(前缀自带链动词「分析」);
|
||||
「帮我研究一下水稻 SPL 家族」→ True(「帮我」+ 链动词「研究」+ 宾语对象);
|
||||
「帮我分析水稻 SPL 家族的研究进展」→ False(QA_TOPIC 词在 5c 拦截);
|
||||
「帮我跑一下」→ 动词不在分析链 → False;「帮我看看水稻 SPL 家族」→
|
||||
「看」非链动词 → False(漏判安全)。
|
||||
"""
|
||||
# 前缀自带链动词(「帮我分析」中的「分析」即分析动作)
|
||||
for p in ("请帮我分析", "帮我分析"):
|
||||
idx = text_lower.find(p)
|
||||
if idx < 0:
|
||||
continue
|
||||
rest = text_lower[idx + len(p):].lstrip()
|
||||
# 前缀动词后容许任务动词(「帮我分析鉴定水稻 SPL 家族」)与补语(「一下」),
|
||||
# 其余宾语直达对象判定
|
||||
for _v in ("鉴定", "识别", "跑一下", "跑"):
|
||||
if rest.startswith(_v) and not (len(rest) > len(_v) and rest[len(_v)].isalnum()):
|
||||
rest = rest[len(_v):].lstrip()
|
||||
if rest.startswith("一下"):
|
||||
rest = rest[2:].lstrip()
|
||||
if _family_hit(rest) or _contains_any(rest, ANALYSIS_OBJECT):
|
||||
return True
|
||||
# 纯引导前缀(「帮我/请你/请帮我」):后须紧跟链动词 + 宾语对象;
|
||||
# 前缀动词后容许补语「一下」(「帮我研究一下水稻 SPL 家族」)。
|
||||
# 「做/研究」与宾语直接相连是 v1.0 金标 #5/#12 形态(「帮我研究一下水稻 SPL
|
||||
# 基因家族」「帮我做水稻全基因组变异检测」)——链动词直接邻接宾语时直接判定,
|
||||
# 不再要求字间边界(「帮我看看」靠「看」非链动词自然漏判安全)。
|
||||
for p in ("请帮我", "帮我", "请你"):
|
||||
idx = text_lower.find(p)
|
||||
if idx < 0:
|
||||
continue
|
||||
rest = text_lower[idx + len(p):].lstrip()
|
||||
verb = ""
|
||||
for cv in ("研究一下", "做一下", "跑一下"):
|
||||
if rest.startswith(cv):
|
||||
verb = cv
|
||||
rest = rest[len(cv):].lstrip()
|
||||
break
|
||||
if not verb:
|
||||
for cv in sorted(CHAIN_VERB, key=len, reverse=True):
|
||||
if rest.startswith(cv):
|
||||
verb = cv
|
||||
rest = rest[len(cv):].lstrip()
|
||||
break
|
||||
if not verb:
|
||||
continue
|
||||
# 补语「一下」容许
|
||||
if rest.startswith("一下"):
|
||||
rest = rest[2:].lstrip()
|
||||
# 前缀动词后容许任务动词(「帮我做全基因组变异检测」)
|
||||
for _v in ("鉴定", "识别", "跑一下", "跑"):
|
||||
if rest.startswith(_v) and not (len(rest) > len(_v) and rest[len(_v)].isalnum()):
|
||||
rest = rest[len(_v):].lstrip()
|
||||
if _family_hit(rest) or _contains_any(rest, ANALYSIS_OBJECT):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def route(
|
||||
text: str,
|
||||
has_attachments: bool = False,
|
||||
has_active_artifact: bool = False,
|
||||
has_bioflow_context: bool = False,
|
||||
) -> str:
|
||||
"""生信意图分流主入口(判定顺序同词典 §2,命中即返回;漏判安全)。"""
|
||||
"""生信意图分流主入口(判定顺序同词典 §2,命中即返回;漏判安全)。
|
||||
|
||||
判定收口(v1.1):仅在「用xxx进行分析/帮我分析xxx」等明确分析指令时进生信流;
|
||||
「水稻SPL基因家族的最新研究进展有哪些?」等问答句一律 qa。
|
||||
"""
|
||||
t = text.strip()
|
||||
if not t:
|
||||
return "qa"
|
||||
@@ -184,11 +310,27 @@ def route(
|
||||
if _contains_any(tl, FLOW_QA_WORD):
|
||||
return "qa"
|
||||
|
||||
has_analyze = _contains_any(tl, ANALYZE_VERB)
|
||||
# 5c. 疑问引导词拦截(v1.1):「水稻SPL家族的最新研究进展有哪些?」——
|
||||
# 「有哪些」命中断言词但按漏判安全不拦;「什么/怎么/如何/哪些/哪个」等
|
||||
# 疑问引导词存在时按「问句优先 qa」拦截(词典用例表 v1.1 新增行)。
|
||||
# 复用句式豁免:「用 XXX 存档任务的方法帮我跑…」内常含「什么」,
|
||||
# 复用句式优先(词典 v1.1 判定顺序:复用判定先于疑问拦截)。
|
||||
if _contains_any(tl, QUESTION_WORD) and not _contains_any(tl, REUSE_PHRASE):
|
||||
return "qa"
|
||||
|
||||
# 5d. 问答主题词拦截(v1.1):「研究进展/综述」等问答主题句一律 qa,
|
||||
# 含「帮我分析水稻 SPL 家族的最新研究进展」——主题句优先于分析指令。
|
||||
if _contains_any(tl, QA_TOPIC):
|
||||
return "qa"
|
||||
|
||||
has_task = _contains_any(tl, TASK_VERB)
|
||||
has_object = _family_hit(tl) or _contains_any(tl, ANALYSIS_OBJECT)
|
||||
|
||||
# 6. bioflow(A 分析动词 且 B 家族名/分析对象;C 复用句式同判)
|
||||
if has_analyze and has_object:
|
||||
# 6. bioflow(v1.1 三通道,任一命中即进生信流):
|
||||
# A. 任务动词 + 对象(「跑 SPL 家族」)
|
||||
# B. 分析链句式(「帮我分析水稻叶片 RNA-seq」)
|
||||
# C. 复用句式(「用 XXX 存档任务的方法帮我跑水稻 SPL 家族鉴定」)
|
||||
if (has_task and has_object) or _chain_object_hit(tl) or _prefix_hit(tl):
|
||||
return "bioflow"
|
||||
|
||||
# 7. 漏判安全:不命中绝不进生信流
|
||||
|
||||
@@ -27,6 +27,10 @@ pytestmark = pytest.mark.bioflow_unit
|
||||
"帮我跑水稻产量性状 GWAS 关联分析",
|
||||
"Please run rice SPL family identification",
|
||||
"跑水稻 spl 家族鉴定",
|
||||
# v1.1 新增:明确分析指令仍进生信
|
||||
"帮我分析水稻SPL基因家族",
|
||||
"帮我做一下水稻全基因组变异检测",
|
||||
"帮我分析鉴定水稻 SPL 家族",
|
||||
])
|
||||
def test_bioflow_hits(text):
|
||||
assert route(text) == "bioflow"
|
||||
@@ -46,6 +50,18 @@ def test_bioflow_hits(text):
|
||||
"这个 SPL 会议帮我跑一下流程",
|
||||
"用 XXX 存档任务的方法",
|
||||
"用 XXX 存档任务的方法帮我跑 yyy",
|
||||
# v1.1 新增:问答句一律 qa(用户 2026-09-07 反馈收窄)
|
||||
"水稻SPL基因家族的最新研究进展有哪些?",
|
||||
"水稻 SPL 基因家族最新研究进展",
|
||||
"水稻SPL基因家族研究进展如何?",
|
||||
"水稻SPL基因家族有哪些最新的研究成果?",
|
||||
"SPL 基因家族有什么新的发现吗?",
|
||||
"怎么用 PlantAI 分析水稻 SPL 家族",
|
||||
"如何跑水稻 SPL 家族鉴定",
|
||||
"什么软件做全基因组注释",
|
||||
"帮我分析水稻 SPL 家族的最新研究进展",
|
||||
"水稻 SPL 家族研究综述",
|
||||
"帮我看看水稻 SPL 家族",
|
||||
])
|
||||
def test_qa_fallback(text):
|
||||
assert route(text) == "qa"
|
||||
|
||||
+1
-1
@@ -331,7 +331,7 @@ SHELL: 001–007(7)|THEME: 001–006(6)|SESSION: 001–009(9)|
|
||||
|
||||
模块范围:在对话界面用自然语言驱动 73 个生信分析阶段(基因家族 21 / RNA-seq 14 / WGS 14 / 群体 5 / GWAS 9+报告)的路径规划、数据文件引导、参数确认、执行过程展示、报告生成调整、任务存档与存档复用;新增「生信任务」「生信档案」两个工作台面板;流程文档知识库问答并入生信对话;三形态部署(只 rag / 只生信 / 同部署)。
|
||||
|
||||
**FR-BIO-001 意图分流**:用户消息含生信分析意图(分析类动词 + 基因家族名/分析对象,如「帮我跑水稻 SPL 家族鉴定」;或存档复用句式「用 XXX 存档任务的方法帮我跑 xxxx」)→ 走生信流。(验收:命中句进入生信流;不命中句照旧走 qa,漏判安全)
|
||||
**FR-BIO-001 意图分流**:**仅当用户明确下达分析指令时**才走生信流——「用 xxx 进行分析」「帮我分析 xxxxx」「帮我跑 xxx 鉴定」类指令(任务动词+家族/对象、前缀分析句式、分析链句式、存档复用句式四通道,词典与用例表见 docs/team/cases_bioflow_intent.md v1.1);其余一律走正常问答流程——「水稻 SPL 基因家族的最新研究进展有哪些?」等问答句不进生信流(疑问引导词与问答主题词两道拦截,漏判安全优先)。(验收:明确分析指令句进入生信流;问答句照旧走 qa 检索回答,不弹出生信路径卡)
|
||||
|
||||
**FR-BIO-002 分析路径引导**:系统先回复可用分析步骤路径候选(2-3 条,依据阶段目录+流程知识库生成),渲染为可点选路径卡;用户可点选或自然语言描述修改。(验收:SPL 询问 → 回复含 2-3 条候选路径卡,每条含阶段序列与用途)
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
# 生信意图分流词典与用例表(唯一事实源)
|
||||
|
||||
> 冻结版本 1.0 | 作者 T1(2026-09-05)| 前后端同表双跑:A3 ui/src/utils/intent.js 与 A2 后端 chat 分流共用本表;任何改动需 T1 更新本文件并经 TL 确认。需求来源:PRD.md FR-BIO-001/FR-BIO-023/FR-BIO-026。
|
||||
> 冻结版本 1.1(2026-09-07 收窄)| 作者 T1 初版 1.0,TL 于 2026-09-07 按用户反馈收窄更新| 前后端同表双跑:A3 ui/src/utils/intent.js 与 A2 后端 chat 分流共用本表;任何改动需更新本文件并经 TL 确认。需求来源:PRD.md FR-BIO-001/FR-BIO-023/FR-BIO-026。
|
||||
>
|
||||
> **v1.1 变更动机(用户 2026-09-07 反馈)**:不是所有对话都走生信流程编排,只有用户明确说了「用 xxx 进行分析」或「帮我分析 xxxxx」才是生信流程;其余问答类(如「水稻 SPL 基因家族的最新研究进展有哪些?」)必须走原正常问答流程。v1.0 规则 6「分析动词+家族/对象」过宽(「研究」+「SPL 家族」命中问答句),v1.1 将命中通道收窄为三条:任务动词+对象、前缀分析句式、分析链句式;并新增疑问引导词与问答主题词两道拦截。
|
||||
|
||||
## 1. 路由签名
|
||||
|
||||
@@ -21,26 +23,41 @@ text 含任一 WRITE_VERB 且含任一 DOC_TYPE(沿用 docgen 词典全表)
|
||||
has_attachments == True 且 text 含任一 WRITE_VERB。命中→docgen。
|
||||
|
||||
### 5. bioflow_qa(流程咨询,需 has_bioflow_context == True,即当前会话已有生信任务/生信对话上下文)
|
||||
text 含任一 FLOW_QA_WORD 且不含任一 ANALYZE_VERB+对象组合:
|
||||
text 含任一 FLOW_QA_WORD:
|
||||
- FLOW_QA_WORD = [需要什么输入, 有什么输入, 输入是什么, 需要什么参数, 参数是什么, 什么参数, 怎么设置, 如何设置, 什么软件, 用什么软件, 前置条件, 介绍, 是什么, 怎么跑, 怎么做]
|
||||
命中→bioflow_qa(生信流内流程咨询,走 vstore flow_docs 检索+引用,不新建任务)。
|
||||
|
||||
### 6. bioflow(生信分析意图)
|
||||
同时满足下面 A 与 B(或 C)即命中:
|
||||
- A. text 含任一 ANALYZE_VERB = [跑, 跑一下, 分析, 鉴定, 识别, 研究, 做个, 做一个, 做一下, 执行, 运行](小写不敏感;含子串即算)
|
||||
- B. text 含任一 FAMILY_NAME 或 ANALYSIS_OBJECT(见第 3 节词典,小写不敏感)
|
||||
- C. 存档复用句式:text 含任一 REUSE_PHRASE = [存档任务的方法, 存档的方法] 且含任一 ANALYZE_VERB 且含任一 FAMILY_NAME 或 ANALYSIS_OBJECT(如「用 XXX 存档任务的方法帮我跑水稻 SPL 家族鉴定」;复用的方法名 = REUSE_PHRASE 之前最近的 3-20 字符片段,如 XXX)
|
||||
### 5b. 流程咨询句式(无上下文)→ qa(漏判安全,用例表 #18)
|
||||
|
||||
### 5c. 疑问引导词拦截(v1.1)
|
||||
text 含任一 QUESTION_WORD = [什么, 怎么, 如何, 哪些, 哪个, 什么软件] 且不含 REUSE_PHRASE → qa。
|
||||
「水稻 SPL 家族怎么跑分析」类问句一律 qa;复用句式豁免(复用判定优先)。
|
||||
|
||||
### 5d. 问答主题词拦截(v1.1)
|
||||
text 含任一 QA_TOPIC = [研究进展, 研究现状, 研究热点, 最新研究, 最新进展, 最新成果, 研究成果, 研究趋势, 综述, 进展] → qa。
|
||||
「帮我分析水稻 SPL 家族的最新研究进展」也 qa(主题句优先于分析指令)。不含「研究」单字——「帮我研究一下水稻 SPL 家族」是分析指令(用例表 #5 保持 bioflow)。
|
||||
|
||||
### 6. bioflow(生信分析意图,v1.1 三通道,任一命中即 bioflow)
|
||||
- **A. 任务动词 + 对象**:text 含任一 TASK_VERB = [跑, 跑一下, 分析, 鉴定, 识别, 做个, 做一个, 做一下, 研究一下, 执行, 运行, run, identify] 且含任一 FAMILY_NAME 或 ANALYSIS_OBJECT。注意:裸「做」「研究」不在 TASK_VERB(v1.0 曾含),已移入 CHAIN_VERB——「研究」+ 家族名不再直接命中(「SPL 家族最新研究进展」走 qa)。
|
||||
- **B. 前缀分析句式(v1.1)**:text 含前缀 [帮我, 请帮我, 请你, 帮我分析, 请帮我分析] + 链动词 + 宾语对象(家族名 或 分析对象)。「帮我分析水稻 SPL 家族」→ bioflow;「帮我研究一下水稻 SPL 基因家族」→ bioflow(补语「一下」容许);「帮我做水稻全基因组变异检测」→ bioflow(用例表 #12);「帮我看看水稻 SPL 家族」→ qa(「看」非链动词,漏判安全)。
|
||||
- **C. 存档复用句式**:text 含任一 REUSE_PHRASE = [存档任务的方法, 存档的方法] 且含任一 TASK_VERB 且含任一 FAMILY_NAME 或 ANALYSIS_OBJECT(如「用 XXX 存档任务的方法帮我跑水稻 SPL 家族鉴定」;复用的方法名 = REUSE_PHRASE 之前最近的 3-20 字符片段,如 XXX)。
|
||||
- **D. 分析链句式(v1.1 附加通道)**:text 含链动词 CHAIN_VERB = [分析, 研究, 做, 跑](全词边界匹配,「关联分析」不被「分析」误拆)且其后含 ANALYSIS_OBJECT(如「什么软件做全基因组注释」已被 5c 拦截;「帮我分析水稻叶片 RNA-seq」命中)。
|
||||
命中→bioflow;否则继续向下。
|
||||
|
||||
### 7. 其余一律 "qa"(漏判安全:不命中绝不进生信流)
|
||||
|
||||
## 3. 生信词典(两表共用)
|
||||
## 3. 生信词典(两表共用,v1.1)
|
||||
|
||||
- FAMILY_NAME(基因家族名,小写比较)= [spl, sbp, mads, mads-box, madsbox, wrky, nac, myb, bzip, bhlh, ap2, erf, gras, tcp, hd-zip, hdzip, arf, aux, iaa, dof, gata, hsf, lbd, lob, trihelix, e2f, cpp, nf-y, nfy, c2h2, zf-hd, zfhd, far1, frs, hb, wox, pin, mate, abc, pp2c, snrk, cdpk, mapk, rlck, nbs, lrr, nlr, tps, cyp, gst, ugt, 转录因子家族, 基因家族]
|
||||
- ANALYSIS_OBJECT(分析对象,小写比较)= [rna-seq, rnaseq, rna-seq, 转录组, 差异表达, wgs, 全基因组, 变异检测, 全基因组测序, gwas, 关联分析, 全基因组关联, 群体, 群体遗传, 遗传多样性, 进化, 系统发育, 选择性清除, 群体结构, 拷贝数变异, 基因预测, 基因注释, 结构注释, 功能注释, 宏基因组]
|
||||
- CHART_WORD(图表调整词,供 edit 规则排除)= [图表, 图颜色, 颜色换成, 图改成, 柱状图, 热图, 火山图, 曼哈顿图, 韦恩图, 气泡图, 散点图, 折线图, 改成红色, 换成蓝色, 颜色]
|
||||
- **TASK_VERB(v1.1 任务动词)** = [跑, 跑一下, 分析, 鉴定, 识别, 做个, 做一个, 做一下, 研究一下, 执行, 运行, run, identify]
|
||||
- **CHAIN_VERB(v1.1 链动词)** = [分析, 研究, 做, 跑](全词边界匹配)
|
||||
- **VERB_PREFIX(v1.1 前缀)** = [帮我, 请帮我, 请你, 帮我分析, 请帮我分析]
|
||||
- **QUESTION_WORD(v1.1 疑问拦截)** = [什么, 怎么, 如何, 哪些, 哪个, 什么软件]
|
||||
- **QA_TOPIC(v1.1 主题拦截)** = [研究进展, 研究现状, 研究热点, 最新研究, 最新进展, 最新成果, 研究成果, 研究趋势, 综述, 进展]
|
||||
|
||||
## 4. 用例表(40 条,前端 intent.js 与后端分流同表双跑)
|
||||
## 4. 用例表(40 条 v1.0 + 15 条 v1.1 新增,前端 intent.js 与后端分流同表双跑)
|
||||
|
||||
| # | 输入 text | 附件 | 活动文档 | 生信上下文 | 期望 |
|
||||
|---|---|---|---|---|---|
|
||||
@@ -84,6 +101,21 @@ text 含任一 FLOW_QA_WORD 且不含任一 ANALYZE_VERB+对象组合:
|
||||
| 38 | 帮我总结一下这个问题的要点 | 否 | 否 | 否 | qa |
|
||||
| 39 | 写一段水稻基因的介绍 | 否 | 否 | 否 | qa(介绍在 docgen 排除表) |
|
||||
| 40 | 跑水稻 SPL 家族鉴定 | 否 | 是 | 否 | bioflow(活动文档不改判定) |
|
||||
| 41 | 水稻SPL基因家族的最新研究进展有哪些? | 否 | 否 | 否 | **qa(v1.1:问答句不进生信流)** |
|
||||
| 42 | 水稻 SPL 基因家族最新研究进展 | 否 | 否 | 否 | **qa(v1.1:主题词拦截)** |
|
||||
| 43 | 水稻SPL基因家族研究进展如何? | 否 | 否 | 否 | **qa(v1.1)** |
|
||||
| 44 | 水稻SPL基因家族有哪些最新的研究成果? | 否 | 否 | 否 | **qa(v1.1)** |
|
||||
| 45 | SPL 基因家族有什么新的发现吗? | 否 | 否 | 否 | **qa(v1.1)** |
|
||||
| 46 | 怎么用 PlantAI 分析水稻 SPL 家族 | 否 | 否 | 否 | **qa(v1.1:疑问引导词拦截)** |
|
||||
| 47 | 如何跑水稻 SPL 家族鉴定 | 否 | 否 | 否 | **qa(v1.1:疑问引导词拦截)** |
|
||||
| 48 | 什么软件做全基因组注释 | 否 | 否 | 否 | **qa(v1.1:疑问引导词拦截)** |
|
||||
| 49 | 帮我分析水稻SPL基因家族 | 否 | 否 | 否 | **bioflow(v1.1:前缀分析句式)** |
|
||||
| 50 | 帮我做水稻全基因组变异检测 | 否 | 否 | 否 | **bioflow(v1.1:链动词直接邻接宾语,金标 #12 保持)** |
|
||||
| 51 | 帮我做一下水稻全基因组变异检测 | 否 | 否 | 否 | **bioflow(v1.1)** |
|
||||
| 52 | 帮我分析鉴定水稻 SPL 家族 | 否 | 否 | 否 | **bioflow(v1.1)** |
|
||||
| 53 | 帮我分析水稻 SPL 家族的最新研究进展 | 否 | 否 | 否 | **qa(v1.1:主题词优先于分析指令)** |
|
||||
| 54 | 水稻 SPL 家族研究综述 | 否 | 否 | 否 | **qa(v1.1)** |
|
||||
| 55 | 帮我看看水稻 SPL 家族 | 否 | 否 | 否 | **qa(v1.1:「看」非链动词,漏判安全)** |
|
||||
|
||||
## 5. 实现注意(前后端一致)
|
||||
|
||||
@@ -91,4 +123,5 @@ text 含任一 FLOW_QA_WORD 且不含任一 ANALYZE_VERB+对象组合:
|
||||
2. 复用方法名提取:REUSE_PHRASE 命中时,取 REUSE_PHRASE 之前最近的连续 3-20 字符(去空白与书名号)为方法名,随路由结果一并返回。
|
||||
3. bioflow_qa 仅在 has_bioflow_context=true 时生效;无上下文时同类句子一律 qa(漏判安全)。
|
||||
4. 边界约定:文本长度 ≤1 或纯空白 → qa;文本 >2000 字符 → 按截断后前 2000 字符判定。
|
||||
5. 本词典为唯一事实源:前端 utils/intent.js 与后端分流实现必须与本表逐条双跑一致(同表双跑由 L3/L2 用例 TC-BIO-INTENT-001~047 执行)。
|
||||
5. 本词典为唯一事实源:前端 utils/intent.js 与后端分流实现必须与本表逐条双跑一致(同表双跑由 L3/L2 用例 TC-BIO-INTENT-001~047 执行;v1.1 新增 #41-#55 覆盖问答句收窄与补语边界)。
|
||||
6. **v1.1 收窄原则**:漏判安全优先——拿不准的句子一律 qa;三条命中通道(任务动词+对象 / 前缀分析句式 / 分析链句式)必须能明确读出「用户要求执行分析」;问答主题词(研究进展/综述等)与疑问引导词(什么/怎么/如何等)拦截一切生信通道。
|
||||
|
||||
+130
-6
@@ -47,8 +47,38 @@ const FLOW_QA_WORD = [
|
||||
'介绍', '是什么', '怎么跑', '怎么做', '需要准备什么输入', '需要准备什么参数',
|
||||
];
|
||||
|
||||
// ---- 生信分析动词 ----
|
||||
const ANALYZE_VERB = ['跑', '跑一下', '分析', '鉴定', '识别', '研究', '做个', '做一个', '做一下', '做', '执行', '运行', 'run', 'identify'];
|
||||
// ---- 生信任务动词(v1.1)----
|
||||
// 可单独成立的生信动作动词:动词 + 家族/对象 即命中(「跑 SPL 家族」)。
|
||||
// 裸「做」「研究」已从 v1.0 动词表移入 CHAIN_VERB(须与对象/前缀组合)——
|
||||
// 避免「做一下」被裸「做」子串误判(漏判安全收窄)。
|
||||
const TASK_VERB = ['跑', '跑一下', '分析', '鉴定', '识别', '做个', '做一个', '做一下', '研究一下', '执行', '运行', 'run', 'identify'];
|
||||
|
||||
// ---- 分析前缀词(v1.1)----
|
||||
// 把宾语动作指回用户本人的引导前缀:前缀自带链动词(「帮我分析」)时仅需
|
||||
// 宾语对象命中;纯前缀(「帮我/请你」)时须紧随链动词 + 宾语对象命中。
|
||||
const VERB_PREFIX = ['帮我', '请帮我', '请你', '帮我分析', '请帮我分析'];
|
||||
|
||||
// ---- 链动词(v1.1)----
|
||||
// 与分析链对象(ANALYSIS_OBJECT)同现时参与前缀分析句式判定,
|
||||
// 且须全词边界匹配(「关联分析」不被「分析」误拆,「做不了」不被「做」误拆)。
|
||||
// 与后端 intent.py 的 CHAIN_VERB 同表(cases_bioflow_intent.md v1.1 §3)。
|
||||
const CHAIN_VERB = ['分析', '研究', '做', '跑'];
|
||||
|
||||
// ---- 疑问引导词(v1.1 拦截表)----
|
||||
// 「什么/怎么/如何/哪些/哪个」等问句一律 qa(复用句式豁免——复用判定在前)。
|
||||
// 断言式词「有哪些」不在此表:不命中下方任何通道,本来就落 qa。
|
||||
const QUESTION_WORD = ['什么', '怎么', '如何', '哪些', '哪个', '什么软件'];
|
||||
|
||||
// ---- 问答主题词(v1.1 拦截表)----
|
||||
// 「研究进展/综述」等问答主题句一律 qa(含「帮我分析水稻 SPL 家族的最新研究进展」)。
|
||||
// 不含「研究」单字——「帮我研究一下水稻 SPL 家族」是分析指令(用例表 #5 保持 bioflow)。
|
||||
const QA_TOPIC = [
|
||||
'研究进展', '研究现状', '研究热点', '最新研究', '最新进展', '最新成果',
|
||||
'研究成果', '研究趋势', '综述', '进展',
|
||||
];
|
||||
|
||||
// 生信分析动词 = 任务动词 + 链动词(与后端 intent.py 的 ANALYZE_VERB 并集语义对齐)
|
||||
const ANALYZE_VERB = [...TASK_VERB, ...CHAIN_VERB];
|
||||
|
||||
// ---- 分析对象词 ----
|
||||
const ANALYSIS_OBJECT = [
|
||||
@@ -85,7 +115,7 @@ export function hasAnyWord(text, words) {
|
||||
}
|
||||
|
||||
/** 家族名命中:词典词 + 语境词(中文)或英文语境;家族名后须跟语境词或位于句尾 */
|
||||
function familyHit(textLower) {
|
||||
export function familyHit(textLower) {
|
||||
for (const name of [...FAMILY_NAME].sort((a, b) => b.length - a.length)) {
|
||||
const idx = textLower.indexOf(name);
|
||||
if (idx < 0) continue;
|
||||
@@ -107,6 +137,85 @@ function hasDocType(textLower) {
|
||||
return hasAnyWord(textLower, DOC_TYPE) || hasAnyWord(textLower, BIO_DOC_TYPE_EXTRA);
|
||||
}
|
||||
|
||||
/** 全词边界查找(v1.1):命中位置的前后字符均非字母数字(「关联分析」中的「分析」不匹配) */
|
||||
function boundaryIndex(textLower, word) {
|
||||
for (let i = 0; i <= textLower.length - word.length; i++) {
|
||||
const idx = textLower.indexOf(word, i);
|
||||
if (idx < 0) return -1;
|
||||
const beforeOk = idx === 0 || !/[A-Za-z0-9一-鿿]/.test(textLower[idx - 1]);
|
||||
const afterIdx = idx + word.length;
|
||||
const afterOk = afterIdx >= textLower.length || !/[A-Za-z0-9一-鿿]/.test(textLower[afterIdx]);
|
||||
if (beforeOk && afterOk) return idx;
|
||||
i = idx + 1;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/** 分析链对象命中(v1.1):链动词 + 链对象(全词边界)。
|
||||
* 「帮我分析水稻叶片 RNA-seq」命中;「做一个全基因组分析」不命中(缺链对象词)。 */
|
||||
function chainObjectHit(textLower) {
|
||||
for (const verb of [...CHAIN_VERB].sort((a, b) => b.length - a.length)) {
|
||||
const idx = boundaryIndex(textLower, verb);
|
||||
if (idx < 0) continue;
|
||||
if (hasAnyWord(textLower.slice(idx + verb.length), ANALYSIS_OBJECT)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** 前缀分析句式命中(v1.1):VERB_PREFIX 引导 + 宾语对象命中。
|
||||
* 「帮我分析水稻 SPL 家族」→ True;「帮我看看水稻 SPL 家族」→ False(「看」非链动词);
|
||||
* 「帮我研究一下水稻 SPL 家族」→ True(补语「一下」+ 链动词);「帮我做水稻全基因组变异检测」
|
||||
* → True(链动词直接邻接宾语,v1.0 金标 #5/#12 形态)。 */
|
||||
function prefixHit(textLower) {
|
||||
for (const p of ['请帮我分析', '帮我分析']) {
|
||||
const idx = textLower.indexOf(p);
|
||||
if (idx < 0) continue;
|
||||
let rest = textLower.slice(idx + p.length).replace(/^\s+/, '');
|
||||
// 前缀动词后容许任务动词(「帮我分析鉴定水稻 SPL 家族」)与补语「一下」
|
||||
for (const v of ['鉴定', '识别', '跑一下', '跑']) {
|
||||
if (rest.startsWith(v) && !(rest.length > v.length && /[A-Za-z0-9一-鿿]/.test(rest[v.length]))) {
|
||||
rest = rest.slice(v.length).replace(/^\s+/, '');
|
||||
}
|
||||
}
|
||||
if (rest.startsWith('一下')) rest = rest.slice(2).replace(/^\s+/, '');
|
||||
if (familyHit(rest) || hasAnyWord(rest, ANALYSIS_OBJECT)) return true;
|
||||
}
|
||||
for (const p of ['请帮我', '帮我', '请你']) {
|
||||
const idx = textLower.indexOf(p);
|
||||
if (idx < 0) continue;
|
||||
let rest = textLower.slice(idx + p.length).replace(/^\s+/, '');
|
||||
let verb = '';
|
||||
// 先试「X一下」形态,再试裸链动词(「做/研究」直接邻接宾语是金标形态)
|
||||
for (const cv of ['研究一下', '做一下', '跑一下']) {
|
||||
if (rest.startsWith(cv)) {
|
||||
verb = cv;
|
||||
rest = rest.slice(cv.length).replace(/^\s+/, '');
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!verb) {
|
||||
for (const cv of [...CHAIN_VERB].sort((a, b) => b.length - a.length)) {
|
||||
if (rest.startsWith(cv)) {
|
||||
verb = cv;
|
||||
rest = rest.slice(cv.length).replace(/^\s+/, '');
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!verb) continue;
|
||||
// 补语「一下」容许
|
||||
if (rest.startsWith('一下')) rest = rest.slice(2).replace(/^\s+/, '');
|
||||
// 前缀动词后容许任务动词(「帮我做全基因组变异检测」)
|
||||
for (const v of ['鉴定', '识别', '跑一下', '跑']) {
|
||||
if (rest.startsWith(v) && !(rest.length > v.length && /[A-Za-z0-9一-鿿]/.test(rest[v.length]))) {
|
||||
rest = rest.slice(v.length).replace(/^\s+/, '');
|
||||
}
|
||||
}
|
||||
if (familyHit(rest) || hasAnyWord(rest, ANALYSIS_OBJECT)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取存档复用句式的「方法名」(REUSE_PHRASE 之前最近的 3-20 字符片段)。
|
||||
* FR-BIO-023:「用 XXX 存档任务的方法帮我跑 xxxx」→ 方法名 = XXX。
|
||||
@@ -176,11 +285,26 @@ export function route(text, hasAttachments = false, hasActiveArtifact = false, h
|
||||
return { intent: 'qa', reuseName: null };
|
||||
}
|
||||
|
||||
const hasAnalyze = hasAnyWord(tl, ANALYZE_VERB);
|
||||
// 5c. 疑问引导词拦截(v1.1):「水稻SPL家族的最新研究进展有哪些?」——
|
||||
// 「有哪些」命中断言词但按漏判安全不拦;「什么/怎么/如何/哪些/哪个」等
|
||||
// 问句优先 qa(复用句式豁免——复用判定在前,词典 v1.1 判定顺序)。
|
||||
if (hasAnyWord(tl, QUESTION_WORD) && !hasAnyWord(tl, REUSE_PHRASE)) {
|
||||
return { intent: 'qa', reuseName: null };
|
||||
}
|
||||
// 5d. 问答主题词拦截(v1.1):「研究进展/综述」等主题句一律 qa,
|
||||
// 含「帮我分析水稻 SPL 家族的最新研究进展」——主题句优先于分析指令。
|
||||
if (hasAnyWord(tl, QA_TOPIC)) {
|
||||
return { intent: 'qa', reuseName: null };
|
||||
}
|
||||
|
||||
const hasTask = hasAnyWord(tl, TASK_VERB);
|
||||
const hasObject = familyHit(tl) || hasAnyWord(tl, ANALYSIS_OBJECT);
|
||||
|
||||
// 6. bioflow(A 分析动词 且 B 家族名/分析对象;C 复用句式同判)
|
||||
if (hasAnalyze && hasObject) {
|
||||
// 6. bioflow(v1.1 三通道):
|
||||
// A. 任务动词 + 对象(「跑 SPL 家族」)
|
||||
// B. 分析链句式(「帮我分析水稻叶片 RNA-seq」)
|
||||
// C. 复用句式(「用 XXX 存档任务的方法帮我跑水稻 SPL 家族鉴定」)
|
||||
if ((hasTask && hasObject) || chainObjectHit(tl) || prefixHit(tl)) {
|
||||
return { intent: 'bioflow', reuseName: extractReuseName(text) };
|
||||
}
|
||||
// 7. 漏判安全:不命中绝不进生信流
|
||||
|
||||
Reference in New Issue
Block a user