"""tier_fn 钩子测试(T-G6):None 零回归 / T1 / T2 单次直答 / T3 完整管线。""" import sys from pathlib import Path import pytest sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) from router_system.pipeline import CollaborativePipeline # noqa: E402 from router_system.architect import ArchitectClient # noqa: E402 from router_system.worker import WorkerLoop # noqa: E402 def asyncio_run(coro): import asyncio return asyncio.run(coro) class FakeArchitect(ArchitectClient): """可脚本化的假架构师:记录 brief/直答调用。""" def __init__(self, calls): super().__init__(model="fake", api_key="k") self.calls = calls async def brief(self, query, ws): self.calls.append("brief") return {"goal": "g", "constraints": [], "tags": ["code"], "acceptance": [], "plan": [{"id": "s1", "task": "实现加法", "deps": [], "done_criteria": "c"}]} async def decide(self, ws): self.calls.append("decide") return {"reply": "hint", "patch_plan": []} async def final_review(self, ws): self.calls.append("review") return {"verdict": "done", "notes": "", "fix_issues": []} async def _chat_once(self, ws, messages): self.calls.append("chat_once") return "直接答案" class ScriptedWorker(WorkerLoop): """脚本化小模型:先给坏答案(触发自修/升级),再给好答案。""" def __init__(self, calls): import asyncio calls_worker = calls async def gen(prompt): calls_worker.append("gen") if len([c for c in calls_worker if c == "gen"]) == 1: return "不够长的占位" return ("```python\ndef add(a, b):\n" " if not isinstance(a, int) or not isinstance(b, int):\n" " raise ValueError\n" " return a + b\n\n" "def test_add():\n" " assert add(2, 3) == 5\n```") super().__init__(generate=gen, max_fix_attempts=2, model_used="fake-worker") def _pipeline(calls, tier_fn=None, fast_path=True): return CollaborativePipeline(architect=FakeArchitect(calls), worker=ScriptedWorker(calls), fast_path=fast_path, tier_fn=tier_fn) def test_tier_fn_none_unchanged(tmp_path): """D-G5 零回归:tier_fn=None 现行为(快路径 miss -> 完整管线)。""" calls = [] pipe = _pipeline(calls) r = asyncio_run(pipe.run("讲讲排序", request_id="tf-none")) assert "brief" in r.route and "reviewing" in r.route # 完整管线 assert r.status == "done" def test_tier_fn_t1_fast_path(tmp_path): """T1 -> 入口为 fast path(本地直答命中)。""" calls = [] class GoodWorker(WorkerLoop): def __init__(self): async def gen(prompt): return "排序算法是把一组数据按照特定顺序重新排列的过程,广泛用于数据处理。" super().__init__(generate=gen, max_fix_attempts=2, model_used="fake-worker") async def tier_fn(query): return "T1" pipe = CollaborativePipeline(architect=FakeArchitect(calls), worker=GoodWorker(), fast_path=True, tier_fn=tier_fn) r = asyncio_run(pipe.run("讲讲排序", request_id="tf-t1")) assert r.status == "fast_path" assert any("fast_path@domain" in x for x in r.route) def test_tier_fn_t2_single_cloud_direct(tmp_path): """T2 新档:单次云端直答(chat_once),跳过 brief/循环/review。""" calls = [] class DirectArch(FakeArchitect): async def _chat_once(self, ws, messages): self.calls.append("chat_once") return "T2 直答结果" async def tier_fn(query): return "T2" pipe = CollaborativePipeline(architect=DirectArch(calls), worker=ScriptedWorker(calls), fast_path=True, tier_fn=tier_fn) r = asyncio_run(pipe.run("实现一个函数", request_id="tf-t2")) assert r.status == "fast_path" # T2 复用 fast_path 状态位 assert "t2:direct" in r.route assert "brief" not in r.route # 跳过 brief assert "loop" not in r.route # 跳过协作循环 assert r.model_used == "fake" # 云端模型 def test_tier_fn_t3_full_pipeline(tmp_path): """T3 -> 强制完整管线(跳过 fast path,走 brief/循环/review)。""" calls = [] class DirectArch(FakeArchitect): async def _chat_once(self, ws, messages): self.calls.append("chat_once") return "直接答案" async def tier_fn(query): return "T3" pipe = CollaborativePipeline(architect=DirectArch(calls), worker=ScriptedWorker(calls), fast_path=True, tier_fn=tier_fn) r = asyncio_run(pipe.run("实现加法函数", request_id="tf-t3")) assert r.status in ("done", "escalated") assert "brief" in r.route and "reviewing" in r.route assert "fast_path@" not in " ".join(r.route) # 不走快路径 def test_tier_fn_t2_direct_miss_falls_back(tmp_path): """T2 直答失败(上游异常)-> 落回完整管线(D-G1 阶梯)。""" calls = [] class FailArch(FakeArchitect): async def _chat_once(self, ws, messages): raise RuntimeError("云端不可用") async def tier_fn(query): return "T2" pipe = CollaborativePipeline(architect=FailArch(calls), worker=ScriptedWorker(calls), fast_path=False, tier_fn=tier_fn) r = asyncio_run(pipe.run("实现加法函数", request_id="tf-fb")) # 直答失败 -> 落回完整管线(brief/loop/review) assert "brief" in r.route or r.status == "failed"