测试:轻量拓展分析 NL 触发两轮测试交付(单测 79 + 集成 23 + 前端契约 20)

This commit is contained in:
potaser
2026-08-27 00:34:19 +08:00
parent afdb1614a2
commit 214eb11490
5 changed files with 1707 additions and 0 deletions
+98
View File
@@ -0,0 +1,98 @@
# 轻量版拓展分析自然语言触发 — 第一轮测试报告
- 时间:2026-08-27
- 被测提交:a60f45754(轻量拓展分析自然语言触发,4 文件 +813/-8,未 push)
- 测试环境:/home/xzq/miniconda3/envs/plantai-bff/bin/python(Python 3.11.15 / pytest 9.1.1 / pytest-cov 7.1.0;本轮补充安装 pyyaml/openai/pandas/requests/openpyxl 后方可跑通 main.py 相关测试)
## 测试文件清单
| 文件 | 新增/追加 | 用例数 | 结果 |
|---|---|---|---|
| tests/unit/test_extension_stage_lightweight_nl_intent.py | 新增 | 58 | 全绿 |
| tests/unit/test_extension_stage_lightweight_nl_backend.py | 新增 | 18 | 全绿 |
| tests/unit/test_extension_stage_lightweight_frontend_contract.py | 追加 5 个测试(未删任何现有断言,原 15 → 现 20) | 20 | 全绿 |
## 通过/失败数
| 组 | 结果 |
|---|---|
| A 组(nl_intent 单元) | 58 passed |
| B 组(main.py 后端集成) | 18 passed |
| C 组(前端契约) | 20 passed |
| 新增三件套合计 | 96 passed |
| 回归:Phase A 三件套(agent/input_binding/golden_runner_semantic) | 6 passed |
| 回归:test_agent_chat_actions + deepseek_semantics | 46 passed |
| 回归:全套 test_extension_stage_lightweight*.py | 312 passed + **6 既有失败** + 1 skip |
**6 个既有失败**(已在父提交 90f51ed60 干净 worktree 上逐一复现,与本改动无关,不拦路):
1. test_extension_stage_lightweight_fact_contract.py::test_build_execution_facts_keeps_unmatched_outputs_without_guessing
2. test_extension_stage_lightweight_runtime_binding.py::test_shell_patch_targets_record_hash_literal_kind_and_exact_count
3. test_extension_stage_lightweight_runtime_binding.py::test_same_uploaded_path_in_two_steps_forms_one_safe_path_binding
4. test_extension_stage_lightweight_runtime_binding.py::test_repeated_parameter_literal_across_facts_is_not_patchable
5. test_extension_stage_lightweight_runtime_package.py::test_unrelated_implicit_output_remains_blocked
6. test_extension_stage_lightweight_target_launchers.py::test_target_launchers_share_lightweight_extension_runtime_roots
## 覆盖率
`python3 -m pytest --cov=gfa_core.extension_stage_lightweight.nl_intent --cov-report=term-missing tests/unit/test_extension_stage_lightweight_nl_intent.py`
- gfa_core/extension_stage_lightweight/nl_intent.py:**187 stmts / 0 miss / 100%**
- 无未覆盖行(补测过程:93% → 98% → 99% → 100%,最终 0 遗漏)
## 失败用例+关键报错
本轮无功能缺陷导致的测试失败(测试自身两处断言问题已修复:集合类型断言、reason 前缀断言)。
## 重点验证结论(对应 developer 标记风险 R1-R4 + 测试要求 1-10)
### R1 目录截断(P0 缺陷确认)
真实 store 实测:61 个 published 记录,`_build_catalog` 只放前 11 个即达 11276 字符,truncated=True。
「拓展分析」强触发词下 `_rough_candidates` 返回全部 61 个,但目录按目录名排序截断后 **12_/13_/14_/15_/16_/17_/18_ 共 50 个扩展(含 manifestqc、tfbs、kaks_cis、motif、pav_cnv 等 18 案例中的后 7 个)LLM 完全不可见**,任何消息都无法命中这些扩展。
测试固化:test_real_store_catalog_truncation_smoke、test_build_catalog_truncates_when_over_max_chars。
### R2 中文关键词粗筛(缺陷确认)
真实 store 实测:「帮我做样本表校验」→ 0 候选、不调 LLM(function_summary 全英文,纯中文功能词无法命中 `_summary_keywords` 英文 token 与中文子串)。只有强触发词(拓展分析/扩展分析/extension)或工具词(hmmsearch/hmmer/blast/blastp)才能进 LLM。**注意:工具词路径会把全部 61 个记录都当候选(无法按工具区分),再叠加 R1 截断,实际能解析的只有前 11 个。**
测试固化:test_real_store_pure_chinese_function_words_miss_rough_candidates。
### R3 prefill 字段位置(实现一致)
已验证:`LightweightIntentResolution.prefill` 在 dataclass 顶层;`_match_lightweight_extension_intent` 返回 dict 中 `params.prefill`(顶层副本)与 `params.llm_slots`;前端 `openLightweightNlRunDialog` 读 `intentParams.llm_slots` 的 input_overrides/parameter_overrides/explicit_prefix,不依赖顶层。C 组断言按实际实现写,无错位。
### R4 extension_name 英文(产品缺陷候选,记录待验收)
已确认:`extension_name` 取 function_summary 英文首句(≤60 字符),后端 message 与前端向导标题会显示英文(如 "This tool searches sequences with hmmsearch.")。测试固化:test_resolution_uses_first_sentence_as_extension_name。建议后续加中文 display_name 映射。
### 测试要求 1-10 覆盖情况
1. 无触发词 → extension_id None 且 client_factory 计数为 0(参数化 3 条消息)✅
2. 触发词 + 无 records → 不调 LLM(reason=no_published_records)✅
3. _build_catalog:只含 replaceable inputs 的 {path,role,description}、剔 locked input、parameter 无 value、含 function_summary、超长截断置 truncated ✅
4. _sanitize_resolution:编造 path/fact_id/extension_id/非 str value/confidence 非法 → None+原因(参数化 7 种非法输入 + 4 种非对象);合法 → 正确映射 ✅
5. LLM 抛异常/坏 JSON/缺 choices → None resolution 不抛(reason=llm_unavailable_or_invalid 或 invalid_resolution: 前缀)✅
6. happy path:「使用 HMM 拓展分析对刚上传的文件进行分析,参数 e-value=1e-5」→ input_overrides/parameter_overrides/confidence/prefill 全对 ✅
7. load_published_lightweight_records:published/confirmation_pending/baseline_failed/坏 JSON/非对象 → 只返回 published;坏 store 根返回 [] ✅
8. job_file_hints 进入 LLM user 消息 JSON(并测 MAX_JOB_FILE_HINTS 截断 200)✅
9. 参数值约束:sanitize 只做结构校验(fact_id 合法即收),不校验"参数是否在用户消息中出现"(prompt 层约束),测试固化该行为 ✅
10. 覆盖率 100%(见上)✅
### B 组关键用例
- _match_lightweight_extension_intent:命中返回 lightweight:True/llm_slots/extension_store/prefill/confidence/parse_reason;空 store、resolution None、extension_id None → 均返回 {} ✅
- 回退三处:Phase A 命中不标 lightweight;Phase A 空列表 → 回退轻量(三个空返回点中 candidates 为空与 trigger 为空两处直接测到;rnaseq 短路点静态确认回退存在,未单独构造 rnaseq 消息,因为该消息本身也不命中轻量粗筛)✅
- _lightweight_job_file_hints:input+workspace 相对路径、空 job_id → []、200 上限 ✅
- import-paths 端点:合法 /home/xzq/ 下文件复制进 _nl_imports、同名冲突加 _1 后缀、前缀外路径 400、不存在路径 400、空 job_id 400 ✅(注:source 必须在 /home/xzq/ 下构造,tmp_path 在 /tmp 会被前缀校验拒绝)
- _run_agent_chat 短路:预闸命中 + 轻量 intent → action="propose_extension_stage"、model_used="rule",且 monkeypatch 证明未触碰 LLMManager/RouterAgent(会抛异常);预闸不命中 → 走正常 MainAgent 流程 ✅
- /api/agent/plan 守卫端到端:轻量 intent 不调 _extension_stage_plan_response;Phase A 命中正常调用 ✅
## 遗留问题清单
| 编号 | 位置 | 现象 | 建议修复 |
|---|---|---|---|
| 1(P0) | gfa_core/extension_stage_lightweight/nl_intent.py `_build_catalog`(约 128-173 行) | 真实 store 61 个 published 记录只进前 11 个即截断(11276/12000 字符),目录按目录名排序,12_ 及以后的 50 个扩展(含 18 案例中 12-18 的 7 类)LLM 不可见,「拓展分析」强触发词下也永远无法命中 | 截断策略需改为"全部进目录、按相关性排序"或"先粗筛再目录化"(按关键词把候选缩到相关子集),至少保证 18 案例的功能词能进目录;12_ 以后的扩展在截断前应有兜底(如只放 extension_id+summary 的轻量条目) |
| 2(P1) | 同文件 `_rough_candidates`(约 108-122 行) | 纯中文功能词(如"样本表校验")0 候选不调 LLM,只能靠强触发词或英文工具词进入;工具词 hmmsearch/hmmer/blast 路径会把全部 61 个记录都当候选(不按工具区分) | 中文 summary 子串匹配对 18 案例 prompt 影响需产品确认;工具词路径建议按工具名映射到对应扩展(如 hmmsearch → 03_ 系列),或至少接受"全候选+目录排序优化" |
| 3(P1) | gfa_core/extension_stage_lightweight/nl_intent.py `_rough_candidates`(约 113-114 行) | `str(record.get("extension_id"))` 对缺失 id 的记录产出 "None" 字符串候选 | 改为 `record.get("extension_id") and str(...)` 过滤,或直接 `[r["extension_id"] for r in records if r.get("extension_id")]` |
| 4(P2) | 同文件 `resolve_lightweight_extension_intent`(约 265-346 行) | `extension_name` 取 function_summary 英文首句(≤60),后端 message 与前端向导标题显示英文 | 产品验收时确认是否需中文 display_name 映射;若需,在 record 或 catalog 层加中文名字段 |
| 5(P2) | apps/web/vue3/main.py `nl_import_paths`(约 4449-4512 行) | 目录导入用 `shutil.copytree(dirs_exist_ok=True)` 合并进目标目录,且同名目录无冲突后缀(与文件路径的 _1 后缀策略不一致);多路径循环中前一个成功、后一个 400 时已复制的文件不回滚 | 目录同名冲突建议同样加后缀;整体事务性按需评估(低风险,向导场景可接受部分成功) |
| 6(P3/信息) | 全套 lightweight 回归 | 6 个既有失败(见上清单),父提交 90f51ed60 复现,与本改动无关 | 不拦路;建议另开任务修复 |
| 7(P3/信息) | 测试环境 | plantai-bff 环境缺 pyyaml/openai/pandas/requests/openpyxl,main.py 相关测试此前跑不通(test_agent_chat_actions 修复前 27 failed) | 已补充安装;建议把依赖清单写进仓库 requirements 或测试文档 |
## 测试环境备注
- 本轮在 plantai-bff conda 环境补充安装了:pyyaml、openai、pandas、requests、openpyxl(均为项目既有依赖,纯 pip 安装,未动仓库文件)。
- 验证既有失败时在父提交 90f51ed60 建了临时 worktree,验证后已删除(git worktree remove --force)。
- 工作区改动仅限测试文件:tests/unit/test_extension_stage_lightweight_nl_intent.py(新增)、tests/unit/test_extension_stage_lightweight_nl_backend.py(新增)、tests/unit/test_extension_stage_lightweight_frontend_contract.py(追加 5 个测试,现有断言未删)。开发代码未触碰,未 push。
+87
View File
@@ -0,0 +1,87 @@
# 轻量版拓展分析自然语言触发 — 第二轮测试报告
- 时间:2026-08-27
- 被测提交:db610031c + afdb1614a(修复第一轮 7 项反馈 + PM 裁断修正,均未 push)
- 测试环境:/home/xzq/miniconda3/envs/plantai-bff/bin/python(Python 3.11.15 / pytest 9.1.1 / pytest-cov 7.1.0)
## 测试文件清单
| 文件 | 状态 | 用例数 | 结果 |
|---|---|---|---|
| tests/unit/test_extension_stage_lightweight_nl_intent.py | 更新+新增 | 79(上轮 58) | 全绿 |
| tests/unit/test_extension_stage_lightweight_nl_backend.py | 更新+新增 | 23(上轮 18) | 全绿 |
| tests/unit/test_extension_stage_lightweight_frontend_contract.py | 不变(与新实现一致,未删断言) | 20 | 全绿 |
## 通过/失败数
| 组 | 结果 |
|---|---|
| A 组(nl_intent 单元) | 79 passed,覆盖率 100%(223 stmts / 0 miss) |
| B 组(main.py 后端集成) | 23 passed |
| C 组(前端契约) | 20 passed |
| 相关四文件回归(A+B+C+deepseek_semantics) | 131 passed |
| Phase A 三件套(agent/input_binding/golden_runner_semantic) | 6 passed |
| test_agent_chat_actions | 36 passed |
| 全套 test_extension_stage_lightweight*.py | 337 passed + 6 既有失败(与第一轮相同 6 个,父提交复现,不拦路)+ 1 skip |
## 覆盖率
`nl_intent.py`:**223 stmts / 0 miss / 100%**。第一轮后新增代码(union 粗筛、_extension_id_tokens、_parameter_flag_tokens、_record_keyword_sources、compact_extension_name、catalog 24000、truncated reason、resolve 排序)全部覆盖。补测过程中 99%→100% 的一次补齐为 `_parameter_flag_tokens` 非 Mapping 元素跳过分支。
## 失败用例+关键报错
本轮无功能缺陷导致的失败;5 个上轮用例因行为有意变更而更新(工具词不再全量、extension_name 改紧凑形式、blast/hmmer 用例 fixture 隔离)。
## 第二轮修复验证结论(对应 main 要求的 5 项)
### 1. union 粗筛策略(afdb1614a)— 通过
- 带工具词+强触发词 → 关键词子集(不因强触发词全量):test_rough_candidates_union_strategy_with_strong_trigger_and_keyword ✅
- 纯强触发词无关键词命中 → 全量兜底:test_rough_candidates_pure_strong_trigger_falls_back_to_all ✅
- 纯工具词无强触发词 → 子集:参数化用例 "用 hmmsearch 搜索结构域" → ["ext_hmm"] ✅
- 无任何命中 → []:test_rough_candidates_no_match_returns_empty ✅
- 命中数降序(同分按 id):test_rough_candidates_sorted_by_hits_desc ✅
- 缺失 extension_id 过滤不产出 None:test_rough_candidates_skips_missing_extension_id ✅
- resolve 按候选分排序目录:test_resolve_orders_records_by_candidate_score ✅
### 2. compact_extension_name — 通过
参数化 6 例:去尾缀时间戳(`03___hmm_1787130993548`→`03___hmm`)、超长截 30、空串、无时间戳原样 ✅
### 3. catalog 24000 上限与 truncated reason — 通过
- MAX_CATALOG_CHARS == 24000 断言 ✅
- 60 条长 summary 记录截断、总长 ≤ 上限、截断点后首条追加超限 ✅
- 去重 seen_ids ✅
- 截断时 reason="ok; 候选过多已截断,请补充工具名" ✅
### 4. _extension_id_tokens / _parameter_flag_tokens — 通过
- id token:`03___hmm_1787130993548` → 含 hmm、剔 len<3 ✅
- flag token:`--samples` 保留、单字母剔除、停用词剔除、无 contract 返回 []、非 Mapping 元素跳过 ✅
- _record_keyword_sources 三源合并(summary + id token + flag)✅
### 5. B 组 message 中文模板 — 通过
- extension_id 为主、summary 首句截 60 字括号补充:"已识别为轻量拓展分析:ext_hmm(This tool searches sequences with hmmsearch)。请按引导上传或选择输入文件并核对参数后执行。" ✅
- 无 summary 时无括号补充 ✅
- params 新增 function_summary 字段 ✅
### 6. import-paths 第二轮修复 — 通过
- 目录同名冲突 _1 后缀(原目录不被覆盖)✅
- 多路径先整体校验后复制:合法+非法混合 → 400 且合法文件未复制 ✅
- 不存在路径混入 → 400 且未复制 ✅
- 多合法路径全部复制 ✅
### 7. PM 实测对齐(真实 store 固化)— 通过
test_real_store_pm_alignment_counts:HMM→9、DIAMOND→8、OrthoFinder→3、KaKs→2、ManifestQC→5、纯拓展分析→61,与 PM 实测完全一致;且 HMM 子集(9 条)构建 catalog 不截断(truncated=False),P0 截断问题在带工具词场景已解决。
### 8. C 组一致性 — 通过
index.html 两提交间零改动,现有 20 个契约测试(含上轮追加 5 个)全部保持绿色,无需调整。
## 遗留问题清单
| 编号 | 位置 | 现象 | 建议修复 |
|---|---|---|---|
| 1(P2) | nl_intent.py `_rough_candidates` / `_summary_keywords` | 纯中文功能词(如「样本表校验」)仍 0 候选不调 LLM——manifestqc 等中文场景只能靠「拓展分析」强触发词全量兜底(61 候选 + 24000 截断后 13 条进 catalog,排序靠目录名,manifestqc 18_ 系列仍在截断线外)。实测「用 ManifestQC 拓展分析」走 id token 命中可解,但用户不知工具英文名的场景仍受限 | 产品层面确认是否需要中文功能词→工具映射表;或截断时按 18 案例优先级排序 |
| 2(P3) | nl_intent.py `_extension_id_tokens` | extension_id 的通用英文 token(如 "ext"、数字时间戳)也进关键词源,实测 "ext" 已在 _ENGLISH_STOPWORDS 之外(是 "extension" 的子串场景会误命中);上轮实测 blast 消息误命中 "against" 属 summary 普通词干扰,union 策略下仍可能存在这类弱命中把无关记录顶进子集 | 低风险;可考虑给关键词源加权重(id token > summary > flag),弱命中不参与排序竞争 |
| 3(P3) | 全套 lightweight 回归 | 6 个既有失败(与第一轮相同清单),父提交 90f51ed60 复现,与本改动无关 | 不拦路;建议另开任务 |
## 测试环境备注
- 环境同第一轮(plantai-bff + 已补装的 pyyaml/openai/pandas/requests/openpyxl)。
- 工作区改动仅测试文件:tests/unit/test_extension_stage_lightweight_nl_intent.py(更新+新增)、tests/unit/test_extension_stage_lightweight_nl_backend.py(更新+新增)。开发代码未触碰,未 push。