# RAG 影响调查接入(Task 5)实现报告 日期:2026-08-29 范式步骤:Agent 实现(RAG 迭代 Task 5,含评审修订 D1/D3) ## 真实代码要点(先读后改) - `src/genesis/server/service.py` - `GenesisService.__init__`:新增 `rag: ImpactRAG | None = None`、`use_rag: bool = False`、`rag_db_path: str | None = None`、`embedder=None`(默认 `get_embedder("fake")`)。保存 `self.rag`/`self.use_rag`;`rag=None` 为不启用(向后兼容默认关闭)。 - `upload_file`:当 `file_type == "existing_system"` 且 `self.rag is not None`,解压后 `self.rag.index_dir(session_id, path)`;异常 `try/except` + `_LOGGER.warning` 不阻断上传(D1 上传即索引,scope=session_id)。 - `run_impact` 改为 `async def run_impact(self, session_id, use_rag=None)`:`eff = self.use_rag if use_rag is None else use_rag`。`eff 且 self.rag is not None` 时走 RAG 路径——`engine` 为空则 `build_inference_engine()`;取 `requirements_text = rec.files["requirements"]["name"] or "要件定義"`;`await ImpactAgent(engine=..., rag=..., use_rag=True).run_impact(...)`;写 `status=awaiting_impact_confirm` + `impact_summary={"rag_enabled": True, "impact_llm": ...}`,并尽量保留 `impact_report_path`。否则走原确定性 `ImpactAgent().run(...)` 路径(行为不变)。 - `src/genesis/server/app.py` - `create_app` 新增 `rag=None`;为 None 时内部构造 `RagStore(data_root/rag.db) + ImpactRAG(..., get_embedder("fake"))`(先 `mkdir(parents=True)` 防止 `unable to open database file`),以 `rag=rag, use_rag=False` 注入 `GenesisService`。 - **Critical 修复**:`_FakeEngine.chat_structured` 改为 `async def`(与真实 `InferenceEngine` 一致)。 - `start_impact` 改为 `async def start_impact(sid, use_rag=False)`,`await service.run_impact(sid, use_rag=use_rag)`。 - `src/genesis/chat/agent.py`:`_parse_and_confirm` 与 `_run_impact` 中两处 `self.service.run_impact(...)` 以 `asyncio.run(...)` 包裹(兼容同步消息处理,避免协程未执行)。 - `tests/test_server_service.py`:两处 `svc.run_impact(...)` 改为 `asyncio.run(svc.run_impact(...))`,保持全绿。 - `tests/test_impact_rag_e2e.py`(新增,D3 强验证):真实 `GenesisService` + 临时 `SessionStore` + 临时 `data_root` + 异步 `FakeEngine`(捕获 prompt)+ `ImpactRAG(RagStore(tmp/rag.db), FakeEmbedder())`,`use_rag=True`。 - 辅助:`zipfile` 构造含 `src/KnownOrder.java`(内容含「订单创建调用 MyBatis」)的 existing_system zip;requirements/template 用 `glob` 定位 `sample/` 下真实文件。 - 流程:create_session → upload requirements → upload template → upload existing_system → run_parse → confirm_parse(→ impact_running)→ `asyncio.run(svc.run_impact(sid, use_rag=True))`。 - 核心断言:`FakeEngine.captured` 含「KnownOrder」(上传源码被 RAG 检索并注入 LLM prompt);上传后 `rag.retrieve(sid, "订单创建", k=1)` 命中非空(D1 验证);另起会话 `rag=None` 且默认 `use_rag` 时走确定性路径、`captured is None` 且不含 RAG 小节标题(向后兼容)。 ## 验证状态 - `python -m pytest tests/test_impact_rag_e2e.py -q`:**2 passed** - `python -m pytest tests/test_server_service.py -q`:**20 passed** - `python -m pytest tests/ -k "server or impact or chat" -q`:**228 passed**(无回归;最初 `test_server_api` 因 `rag.db` 目录不存在报 `OperationalError`,已通过 `Path(rag_db).parent.mkdir(...)` 修复) ## 自我审查(顾虑) - `create_app` 现在总会创建 `data_root/rag.db`(即使 `use_rag=False`);默认 `app = create_app()` 会在工作区生成 `data/server/rag.db` 文件,属轻微副作用,可接受(向后兼容且关闭时不参与影响调查)。 - `chat/agent.py` 用 `asyncio.run` 包裹 `run_impact` 是最小侵入方案;若未来聊天层整体异步化,可改为直接 `await`。 - 覆盖配置 `fail_under=99%`,单文件运行 e2e 会因采样不足触发覆盖率告警(非测试失败);以 `-o addopts=""` 运行可获干净结果。 - RAG 路径不依赖 `_rebuild_source`(仅用 requirements 文件名 + RAG 检索),与 Brief 一致;确定性路径仍保留 `_rebuild_source`。 ## 修复后验证(复审 Minor 3 项最小化修复) 日期:2026-08-30 范式步骤:反馈迭代(RAG 迭代 Task 5 复审 Minor 修复) ### 修复项 1. **死存储(service.py)**:移除 `GenesisService.__init__` 的 `rag_db_path` 与 `embedder` 两个未使用参数及其赋值(`rag`/`use_rag` 保留)。已确认全仓无调用方传入这两个参数(仅 `app.create_app` 传 `rag=`)。 2. **引擎构建竞态(service.py)**:`run_impact` 中引擎懒构建改为双重检查锁:`__init__` 新增 `self._engine_lock = threading.Lock()`,构建处 `with self._engine_lock: if self.engine is None: self.engine = build_inference_engine()`(新增 `import threading`)。 3. **文档措辞(docs/design.md)**:将"端点同步改为 `async def`"改为"端点改为 `async def`"(原端点本就是同步 def,去掉"同步"歧义)。 ### 验证命令 `python -m pytest tests/test_impact_rag_e2e.py tests/test_server_service.py -q` ### 结果摘要 - **22 passed**(test_impact_rag_e2e 2 + test_server_service 20),无回归。 - 注:覆盖率门禁 `fail_under=99%` 在仅运行这两个文件时会触发(项目级全量门禁,与本次修复无关,非测试失败)。按报告既有建议以 `-o addopts=""` 运行可获干净结果。 - 默认路径语义与 `use_rag=False` 行为保持不变。