From bd011650f66bbb28b0bba9f60669e43fbc24f3a1 Mon Sep 17 00:00:00 2001 From: admin Date: Mon, 7 Sep 2026 18:13:52 +0800 Subject: [PATCH] =?UTF-8?q?fix(bioflow):=20=E6=84=8F=E5=9B=BE=E5=88=86?= =?UTF-8?q?=E6=B5=81=E6=94=B6=E7=AA=84=20v1.1=E2=80=94=E2=80=94=E4=BB=85?= =?UTF-8?q?=E6=98=8E=E7=A1=AE=E5=88=86=E6=9E=90=E6=8C=87=E4=BB=A4=E8=BF=9B?= =?UTF-8?q?=E7=94=9F=E4=BF=A1=E6=B5=81,=E9=97=AE=E7=AD=94=E5=8F=A5?= =?UTF-8?q?=E4=B8=80=E5=BE=8B=20qa?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 用户反馈:不是所有对话都走生信编排,只有「用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 --- .../app/services/bioflow/intent.py | 156 +++++++++++++++++- bioflow_service/tests/test_intent.py | 16 ++ docs/PRD.md | 2 +- docs/team/cases_bioflow_intent.md | 53 ++++-- ui/src/utils/intent.js | 136 ++++++++++++++- 5 files changed, 339 insertions(+), 24 deletions(-) diff --git a/bioflow_service/app/services/bioflow/intent.py b/bioflow_service/app/services/bioflow/intent.py index 5b45604..413e1ec 100644 --- a/bioflow_service/app/services/bioflow/intent.py +++ b/bioflow_service/app/services/bioflow/intent.py @@ -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. 漏判安全:不命中绝不进生信流 diff --git a/bioflow_service/tests/test_intent.py b/bioflow_service/tests/test_intent.py index af02f30..0d3093f 100644 --- a/bioflow_service/tests/test_intent.py +++ b/bioflow_service/tests/test_intent.py @@ -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" diff --git a/docs/PRD.md b/docs/PRD.md index 33bbc2f..ca9f6ec 100644 --- a/docs/PRD.md +++ b/docs/PRD.md @@ -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 条候选路径卡,每条含阶段序列与用途) diff --git a/docs/team/cases_bioflow_intent.md b/docs/team/cases_bioflow_intent.md index 02006c6..b1fe880 100644 --- a/docs/team/cases_bioflow_intent.md +++ b/docs/team/cases_bioflow_intent.md @@ -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;三条命中通道(任务动词+对象 / 前缀分析句式 / 分析链句式)必须能明确读出「用户要求执行分析」;问答主题词(研究进展/综述等)与疑问引导词(什么/怎么/如何等)拦截一切生信通道。 diff --git a/ui/src/utils/intent.js b/ui/src/utils/intent.js index a807903..968f33c 100644 --- a/ui/src/utils/intent.js +++ b/ui/src/utils/intent.js @@ -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. 漏判安全:不命中绝不进生信流