"""ImpactAgent RAG 上下文注入测试(RAG 迭代 Task 4)。 验证: - use_rag=True 时,run_impact 发送给 LLM 的 prompt 文本包含 RAG 检索命中片段与明确小节标题。 - use_rag=False(或默认)时,prompt 文本不含 RAG 小节标题(向后兼容)。 """ from genesis.impact.impact_agent import ImpactAgent from genesis.rag.embeddings import FakeEmbedder from genesis.rag.impact_rag import ImpactRAG from genesis.rag.store import RagStore _RAG_SECTION_TITLE = "# 既有系统关联上下文(RAG 检索,辅助判断影响范围)" class FakeEngine: """捕获真实 LLM 方法(chat_structured)收到的 prompt 文本。 方法名与签名刻意复用本仓库 InferenceEngine.chat_structured 的形参风格, 以保证 mock 的是真实接口(key=session_id/prompt/variables/schema/retry_count)。 """ def __init__(self) -> None: self.last_prompt: str | None = None self.calls = 0 def chat_structured(self, *, session_id, prompt, variables, schema, retry_count=2): self.last_prompt = prompt self.calls += 1 # 返回结构兼容 ChatResult 的最小占位(测试仅校验 prompt 注入) return {"session_id": session_id, "prompt": prompt} def _make_rag(session_id: str, sources): store = RagStore(":memory:") rag = ImpactRAG(store, FakeEmbedder()) rag.index(session_id, sources) return store, rag def test_run_impact_with_rag_injects_context(): session_id = "sess-rag" store, rag = _make_rag(session_id, [("TradeApplication.java", "订单创建调用 MyBatis")]) try: engine = FakeEngine() agent = ImpactAgent(engine=engine, rag=rag, use_rag=True) agent.run_impact(session_id, requirements_text="创建订单的影响", k=5) prompt = engine.last_prompt assert prompt is not None # 命中片段(含文件名 TradeApplication.java)被注入 assert "TradeApplication" in prompt # 明确小节标题被注入 assert _RAG_SECTION_TITLE in prompt finally: store.close() def test_run_impact_without_rag_no_context(): session_id = "sess-no-rag" store, rag = _make_rag(session_id, [("TradeApplication.java", "订单创建调用 MyBatis")]) try: engine = FakeEngine() agent = ImpactAgent(engine=engine, rag=rag, use_rag=False) agent.run_impact(session_id, requirements_text="创建订单的影响", k=5) prompt = engine.last_prompt # 显式关闭 RAG:不含小节标题,也不含检索片段 assert _RAG_SECTION_TITLE not in prompt assert "TradeApplication" not in prompt finally: store.close() def test_run_impact_default_no_rag_no_context(): # 默认 use_rag 为 False(未显式开启),行为与关闭一致 session_id = "sess-default" store, rag = _make_rag(session_id, [("TradeApplication.java", "订单创建调用 MyBatis")]) try: engine = FakeEngine() agent = ImpactAgent(engine=engine, rag=rag) # 不传 use_rag agent.run_impact(session_id, requirements_text="创建订单的影响", k=5) prompt = engine.last_prompt assert _RAG_SECTION_TITLE not in prompt assert "TradeApplication" not in prompt finally: store.close()