feat(v2): T6 CollaborativePipeline 编排(快路径/协作循环/熔断/终审)
This commit is contained in:
@@ -0,0 +1,298 @@
|
||||
"""CollaborativePipeline —— 端云协同协作管线编排(v2 核心调度)。
|
||||
|
||||
流程(对齐《实现方案_v2》第 2/4.3 节):
|
||||
用户 query -> [快路径] Worker 直答+自验证通过即返回
|
||||
-> 否则 Architect.brief 写交流文本 -> 协作循环(Worker 实现/自验证,
|
||||
issue 时 Architect.decide 裁决)-> 全步完成 -> Architect.final_review
|
||||
-> 交付(入人工检验队列)
|
||||
|
||||
护栏(D6):rounds_cap / api_token_cap 任一触顶即熔断;熔断按 breach_policy
|
||||
走 Architect 兜底代做(有 key)或本地降级提示(无 key)。
|
||||
|
||||
测试封闭性(D11):architect 用 httpx.MockTransport,worker 用假 generate;
|
||||
不依赖真实 llama-server 或 API key。
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import time
|
||||
import uuid
|
||||
from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from .architect import ArchitectCircuitBreaker, ArchitectClient, ArchitectError
|
||||
from .worker import WorkerLoop
|
||||
from .workspace import Workspace
|
||||
|
||||
|
||||
@dataclass
|
||||
class PipelineResult:
|
||||
"""v2 协作管线一次运行的结果。"""
|
||||
query: str
|
||||
response: str
|
||||
request_id: str
|
||||
status: str # done | fast_path | escalated | failed
|
||||
fast_path: bool
|
||||
rounds_used: int
|
||||
api_input_tokens: int
|
||||
api_output_tokens: int
|
||||
cost_est: float
|
||||
model_used: str
|
||||
latency_ms: float
|
||||
route: List[str] = field(default_factory=list)
|
||||
workspace_path: Optional[str] = None
|
||||
error: Optional[str] = None
|
||||
|
||||
|
||||
class CollaborativePipeline:
|
||||
"""编排快路径、协作循环、熔断、终审。"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
architect: ArchitectClient,
|
||||
worker: WorkerLoop,
|
||||
fast_path: bool = True,
|
||||
rounds_cap: int = 6,
|
||||
api_token_cap: int = 8000,
|
||||
breach_policy: str = "architect_do", # architect_do | local_only
|
||||
run_dir: str = "runs",
|
||||
):
|
||||
self.architect = architect
|
||||
self.worker = worker
|
||||
self.fast_path = fast_path
|
||||
self.rounds_cap = rounds_cap
|
||||
self.api_token_cap = api_token_cap
|
||||
self.breach_policy = breach_policy
|
||||
self.run_dir = Path(run_dir)
|
||||
|
||||
# ---------------------------------------------------------------
|
||||
# 入口
|
||||
# ---------------------------------------------------------------
|
||||
async def run(self, query: str) -> PipelineResult:
|
||||
request_id = uuid.uuid4().hex[:12]
|
||||
ws = Workspace.new(request_id, query, self.api_token_cap, self.rounds_cap)
|
||||
route: List[str] = ["v2"]
|
||||
t0 = time.perf_counter() * 1000.0
|
||||
|
||||
# ---- 快路径:小模型直答 + 自验证(省 API 钱,D3) ----
|
||||
if self.fast_path:
|
||||
direct = await self.worker.direct_answer(query)
|
||||
dom = self._guess_domain(query)
|
||||
passed, _det = self.worker.verifier.verify(dom, "answer.md", direct, query,
|
||||
kb=self.worker.kb)
|
||||
if passed:
|
||||
route.append(f"fast_path@domain:{dom}")
|
||||
self._save_artifact(request_id, "answer.md", direct)
|
||||
return self._finalize(query, ws, response=direct, status="fast_path",
|
||||
fast_path=True, route=route, model_used=self.worker.model_used,
|
||||
t0=t0, save_ws=False)
|
||||
route.append("fast_path:miss")
|
||||
|
||||
# ---- brief(Architect 一次) ----
|
||||
try:
|
||||
brief = await self.architect.brief(query, ws)
|
||||
ws.apply_brief(brief)
|
||||
route.append("brief")
|
||||
except (ArchitectError, ArchitectCircuitBreaker) as e:
|
||||
return await self._handle_breach(query, ws, route, t0, exc=e)
|
||||
|
||||
# ---- 协作循环 ----
|
||||
route.append("loop")
|
||||
plan = brief.get("plan") or []
|
||||
pending = [p.get("id") for p in plan]
|
||||
while pending and not ws.exhausted():
|
||||
progressed = False
|
||||
for sid in list(pending):
|
||||
step = next((p for p in plan if p.get("id") == sid), {})
|
||||
if not self._deps_done(ws, step.get("deps") or []):
|
||||
continue
|
||||
existing = self._read_artifact(request_id, self._artifact_name(sid, ws))
|
||||
outcome = await self.worker.run_step(ws, sid, existing_artifact=existing,
|
||||
hint=self._last_decision_for(ws, sid))
|
||||
if outcome.status == "done":
|
||||
pending.remove(sid)
|
||||
self._save_artifact(request_id, outcome.artifact_name, outcome.artifact_text)
|
||||
ws.rollup()
|
||||
route.append(f"step:{sid}:done")
|
||||
progressed = True
|
||||
else: # issue -> Architect 裁决
|
||||
route.append(f"step:{sid}:issue")
|
||||
try:
|
||||
dec = await self.architect.decide(ws)
|
||||
ws.add_decision(dec.get("ref") or outcome.issue_id or "",
|
||||
dec.get("reply", ""), dec.get("patch_plan"))
|
||||
self._apply_patch_plan(ws, dec.get("patch_plan"))
|
||||
route.append(f"decide:{sid}")
|
||||
except (ArchitectError, ArchitectCircuitBreaker) as e:
|
||||
return await self._handle_breach(query, ws, route, t0, exc=e)
|
||||
progressed = True
|
||||
ws.mark_round()
|
||||
if not progressed:
|
||||
# 死锁(依赖/裁决都推不动)-> 熔断兜底
|
||||
return await self._handle_breach(query, ws, route, t0,
|
||||
exc=RuntimeError("协作循环死锁:无进度"))
|
||||
|
||||
if ws.exhausted() and pending:
|
||||
return await self._handle_breach(query, ws, route, t0,
|
||||
exc=RuntimeError("预算/回合触顶"))
|
||||
|
||||
# ---- 终审 ----
|
||||
ws.transition("reviewing")
|
||||
route.append("reviewing")
|
||||
try:
|
||||
rev = await self.architect.final_review(ws)
|
||||
if rev.get("verdict") == "done":
|
||||
ws.transition("done")
|
||||
route.append("done")
|
||||
status = "done"
|
||||
else:
|
||||
# reviewing --fail--> in_progress(修正回合);MVP 返回 escalated 标记打回
|
||||
ws.transition("in_progress")
|
||||
route.append("review:fix")
|
||||
status = "escalated"
|
||||
except (ArchitectError, ArchitectCircuitBreaker) as e:
|
||||
return await self._handle_breach(query, ws, route, t0, exc=e)
|
||||
|
||||
response = self._build_response(ws, request_id)
|
||||
return self._finalize(query, ws, response=response, status=status, fast_path=False,
|
||||
route=route, model_used=self.architect.model, t0=t0)
|
||||
|
||||
# ---------------------------------------------------------------
|
||||
# 熔断兜底
|
||||
# ---------------------------------------------------------------
|
||||
async def _handle_breach(self, query: str, ws: Workspace, route: List[str],
|
||||
t0: float, exc: BaseException) -> PipelineResult:
|
||||
route.append("breach")
|
||||
if self.breach_policy == "architect_do" and self.architect.api_key:
|
||||
# 有 key:Architect 兜底代做
|
||||
try:
|
||||
briefish = ws.render_for_architect()
|
||||
answer = await self.architect._chat_once(
|
||||
ws, [{"role": "user",
|
||||
"content": "以下任务自动升级,请直接给出最终可交付答案(非 JSON):" + briefish}])
|
||||
route.append("breach:architect_do")
|
||||
return self._finalize(query, ws, response=answer, status="escalated",
|
||||
fast_path=False, route=route,
|
||||
model_used=self.architect.model, t0=t0, error=str(exc))
|
||||
except Exception as e2:
|
||||
route.append(f"breach:architect_do:fail:{type(e2).__name__}")
|
||||
# 本地降级
|
||||
route.append("breach:local_deg")
|
||||
msg = ("(本地降级)当前请求超出本地可处理范围,且未配置大模型密钥或预算熔断。"
|
||||
f"原因:{exc}")
|
||||
return self._finalize(query, ws, response=msg, status="failed", fast_path=False,
|
||||
route=route, model_used="none", t0=t0, error=str(exc))
|
||||
|
||||
# ---------------------------------------------------------------
|
||||
# 结果组装
|
||||
# ---------------------------------------------------------------
|
||||
def _finalize(self, query: str, ws: Workspace, response: str, status: str,
|
||||
fast_path: bool, route: List[str], model_used: str, t0: float,
|
||||
save_ws: bool = True, error: Optional[str] = None) -> PipelineResult:
|
||||
if save_ws:
|
||||
path = self.run_dir / ws.request_id / "workspace.json"
|
||||
ws.save(path)
|
||||
ws_path = str(path)
|
||||
else:
|
||||
ws_path = None
|
||||
b = ws.budget()
|
||||
return PipelineResult(
|
||||
query=query, response=response, request_id=ws.request_id, status=status,
|
||||
fast_path=fast_path, rounds_used=ws.meta()["round"],
|
||||
api_input_tokens=b["api_input_tokens"], api_output_tokens=b["api_output_tokens"],
|
||||
cost_est=0.0, model_used=model_used,
|
||||
latency_ms=time.perf_counter() * 1000.0 - t0,
|
||||
route=route, workspace_path=ws_path, error=error,
|
||||
)
|
||||
|
||||
# ---------------------------------------------------------------
|
||||
# 辅助
|
||||
# ---------------------------------------------------------------
|
||||
def _guess_domain(self, query: str) -> str:
|
||||
if self.worker.kb is not None:
|
||||
hits = self.worker.kb.match(query)
|
||||
if hits:
|
||||
return hits[0].domain
|
||||
return "general"
|
||||
|
||||
def _artifact_name(self, sid: str, ws: Workspace) -> str:
|
||||
from .worker import artifact_name_for
|
||||
domain = self._guess_domain(ws["query"])
|
||||
# 用 brief.tags 优先
|
||||
tags = (ws.get("brief") or {}).get("tags") or []
|
||||
for t in tags:
|
||||
if t != "safety":
|
||||
domain = t
|
||||
break
|
||||
return artifact_name_for(sid, domain)
|
||||
|
||||
def _deps_done(self, ws: Workspace, deps: List[str]) -> bool:
|
||||
done = {p["step"] for p in ws.get("progress", []) if p.get("status") == "done"}
|
||||
return all(d in done for d in deps)
|
||||
|
||||
def _last_decision_for(self, ws: Workspace, sid: str) -> str:
|
||||
"""取最近一条针对该 step 的决策 reply,作为 worker hint。"""
|
||||
step_issues = {i.get("id") for i in ws.get("issues", []) if i.get("step") == sid}
|
||||
for dec in reversed(ws.get("decisions", []) or []):
|
||||
if dec.get("ref") in step_issues:
|
||||
return dec.get("reply", "")
|
||||
return ""
|
||||
|
||||
def _apply_patch_plan(self, ws: Workspace, patch_plan: Optional[List[Dict[str, Any]]]) -> None:
|
||||
if not patch_plan:
|
||||
return
|
||||
updates = {}
|
||||
for item in patch_plan:
|
||||
if isinstance(item, dict) and item.get("id") and item.get("task"):
|
||||
updates[item["id"]] = item["task"]
|
||||
if updates:
|
||||
ws.revise_plan(updates)
|
||||
|
||||
def _build_response(self, ws: Workspace, request_id: str) -> str:
|
||||
# 汇总 archive 摘要 + 各已完成步骤工件
|
||||
lines = list(ws.get("archive", []) or [])
|
||||
brief = ws.get("brief") or {}
|
||||
parts: List[str] = []
|
||||
if brief.get("goal"):
|
||||
parts.append("任务:" + brief["goal"])
|
||||
if lines:
|
||||
parts.append("完成情况:")
|
||||
parts.extend(f"- {ln}" for ln in lines)
|
||||
# 附上最后一个已完成步骤的工件全文
|
||||
progress = ws.get("progress", []) or []
|
||||
done_steps = [p for p in progress if p.get("status") == "done"]
|
||||
if done_steps:
|
||||
last = done_steps[-1]
|
||||
art = self._read_artifact(request_id, self._artifact_name(last["step"], ws))
|
||||
if art:
|
||||
parts.append("产出:")
|
||||
parts.append(art)
|
||||
return "\n\n".join(parts) if parts else "(协作管线未产出有效内容)"
|
||||
|
||||
def _save_artifact(self, request_id: str, name: str, text: str) -> None:
|
||||
if not text:
|
||||
return
|
||||
d = self.run_dir / request_id / "artifacts"
|
||||
d.mkdir(parents=True, exist_ok=True)
|
||||
(d / name).write_text(text, encoding="utf-8")
|
||||
|
||||
def _read_artifact(self, request_id: str, name: str) -> str:
|
||||
p = self.run_dir / request_id / "artifacts" / name
|
||||
if p.exists():
|
||||
return p.read_text(encoding="utf-8")
|
||||
return ""
|
||||
|
||||
|
||||
def build_pipeline(cfg: Dict[str, Any], architect: ArchitectClient,
|
||||
worker: WorkerLoop) -> CollaborativePipeline:
|
||||
"""cfg 为 config.pipeline 段。"""
|
||||
p = cfg.get("pipeline", {})
|
||||
return CollaborativePipeline(
|
||||
architect=architect,
|
||||
worker=worker,
|
||||
fast_path=bool(p.get("fast_path", True)),
|
||||
rounds_cap=int(p.get("rounds_cap", 6)),
|
||||
api_token_cap=int(p.get("api_token_cap", 8000)),
|
||||
breach_policy=p.get("breach_policy", "architect_do"),
|
||||
run_dir=p.get("run_dir", "runs"),
|
||||
)
|
||||
+12
-4
@@ -68,6 +68,12 @@ class WorkerLoop:
|
||||
self.max_fix_attempts = max_fix_attempts
|
||||
self.model_used = model_used
|
||||
|
||||
async def direct_answer(self, query: str) -> str:
|
||||
"""快路径直答:让 Worker 直接生成用户回答(非 JSON、无围栏)。"""
|
||||
prompt = ("请直接回答下面这个问题,输出对用户有用的正文"
|
||||
"(不要输出 JSON,不要加代码块围栏)。问题:" + query)
|
||||
return await self.generate(prompt)
|
||||
|
||||
def _domain_from(self, ws: Workspace) -> str:
|
||||
tags = (ws.get("brief") or {}).get("tags") or []
|
||||
for t in tags:
|
||||
@@ -76,8 +82,8 @@ class WorkerLoop:
|
||||
return "general"
|
||||
|
||||
async def run_step(self, ws: Workspace, step_id: str,
|
||||
existing_artifact: str = "") -> StepOutcome:
|
||||
"""执行单个 step。existing_artifact 为该步当前已有工件全文(若有)。"""
|
||||
existing_artifact: str = "", hint: str = "") -> StepOutcome:
|
||||
"""执行单个 step。existing_artifact 为该步当前已有工件全文;hint 为 Architect 裁决提示。"""
|
||||
domain = self._domain_from(ws)
|
||||
brief = ws.get("brief") or {}
|
||||
plan = brief.get("plan") or []
|
||||
@@ -89,7 +95,7 @@ class WorkerLoop:
|
||||
details: List[str] = []
|
||||
|
||||
for attempt in range(1, self.max_fix_attempts + 1):
|
||||
prompt = self._build_prompt(ws, step_id, current, attempt, done_criteria)
|
||||
prompt = self._build_prompt(ws, step_id, current, attempt, done_criteria, hint)
|
||||
out = await self.generate(prompt)
|
||||
if domain == "code":
|
||||
candidate = extract_code_block(out)
|
||||
@@ -134,13 +140,15 @@ class WorkerLoop:
|
||||
)
|
||||
|
||||
def _build_prompt(self, ws: Workspace, step_id: str, current: str,
|
||||
attempt: int, done_criteria: str) -> str:
|
||||
attempt: int, done_criteria: str, hint: str = "") -> str:
|
||||
base = ws.render_for_worker(step_id, artifact_text=current or None)
|
||||
if attempt > 1:
|
||||
base += (
|
||||
"\n\n[注意] 上次生成的工件未通过接地验证。请修正以下问题后重新输出"
|
||||
f"完整工件。本次为第 {attempt} 次尝试。"
|
||||
)
|
||||
if hint:
|
||||
base += "\n\n[架构师裁决] " + hint
|
||||
return base
|
||||
|
||||
|
||||
|
||||
@@ -355,6 +355,18 @@ class Workspace:
|
||||
})
|
||||
self._commit(new)
|
||||
|
||||
def revise_plan(self, updates: Dict[str, str]) -> None:
|
||||
"""按 decision.patch_plan 修订既有 step 的 task(不改结构/顺序)。"""
|
||||
if self._data.get("brief") is None:
|
||||
raise ValueError("brief 尚未写入,无法修订 plan")
|
||||
new = copy.deepcopy(self._data)
|
||||
for pid, task in updates.items():
|
||||
for p in new["brief"]["plan"]:
|
||||
if p["id"] == pid:
|
||||
p["task"] = task
|
||||
break
|
||||
self._commit(new)
|
||||
|
||||
def mark_round(self) -> None:
|
||||
self._data["meta"]["round"] += 1
|
||||
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
"""T6 CollaborativePipeline 编排单测(封闭:MockTransport + 假 generate)。"""
|
||||
import json
|
||||
|
||||
import httpx
|
||||
import pytest
|
||||
|
||||
from router_system.architect import ArchitectClient
|
||||
from router_system.pipeline import CollaborativePipeline
|
||||
from router_system.verifier import Verifier
|
||||
from router_system.worker import WorkerLoop
|
||||
|
||||
BRIEF_JSON = json.dumps({
|
||||
"goal": "用 Python 实现一个函数",
|
||||
"constraints": ["标准库"],
|
||||
"tags": ["code"],
|
||||
"acceptance": [{"id": "a1", "check": "可运行", "machine_checkable": True}],
|
||||
"plan": [{"id": "s1", "task": "实现函数", "deps": [], "done_criteria": "函数可运行"}],
|
||||
}, ensure_ascii=False)
|
||||
|
||||
DECIDE_JSON = json.dumps({"reply": "改用更简单的实现", "patch_plan": [{"id": "s1", "task": "简化实现"}]},
|
||||
ensure_ascii=False)
|
||||
REVIEW_DONE = json.dumps({"verdict": "done", "notes": "通过", "fix_issues": []}, ensure_ascii=False)
|
||||
|
||||
|
||||
def _architect_handler(brief=BRIEF_JSON, decide=DECIDE_JSON, review=REVIEW_DONE):
|
||||
def handler(request):
|
||||
body = json.loads(request.content)
|
||||
msg = (body.get("messages") or [{}])[-1].get("content", "")
|
||||
if "任务 brief" in msg or "任务分析" in msg:
|
||||
return _resp(brief)
|
||||
if "裁决" in msg:
|
||||
return _resp(decide)
|
||||
if "终审" in msg:
|
||||
return _resp(review)
|
||||
# 兜底(breach:architect_do)
|
||||
return _resp("(兜底答案)")
|
||||
return handler
|
||||
|
||||
|
||||
def _resp(content):
|
||||
return httpx.Response(200, json={
|
||||
"choices": [{"message": {"content": content}}],
|
||||
"usage": {"prompt_tokens": 100, "completion_tokens": 20},
|
||||
})
|
||||
|
||||
|
||||
def _make_architect(handler):
|
||||
return ArchitectClient(model="deepseek-chat", base_url="https://api.deepseek.com/v1",
|
||||
api_key="sk-test", transport=httpx.MockTransport(handler))
|
||||
|
||||
|
||||
def _fake_generate(texts):
|
||||
it = iter(texts)
|
||||
|
||||
async def _g(prompt):
|
||||
try:
|
||||
return next(it)
|
||||
except StopIteration:
|
||||
return ""
|
||||
|
||||
return _g
|
||||
|
||||
|
||||
def _make_worker(generate):
|
||||
return WorkerLoop(generate=generate,
|
||||
verifier=Verifier(sandbox_runner=lambda c, e, t: (0, "", "")),
|
||||
kb=None, max_fix_attempts=2)
|
||||
|
||||
|
||||
def _pipeline(architect, worker, **kw):
|
||||
return CollaborativePipeline(architect=architect, worker=worker,
|
||||
fast_path=kw.get("fast_path", False),
|
||||
rounds_cap=kw.get("rounds_cap", 6),
|
||||
api_token_cap=kw.get("api_token_cap", 8000),
|
||||
breach_policy=kw.get("breach_policy", "architect_do"),
|
||||
run_dir=kw.get("run_dir", "runs"))
|
||||
|
||||
|
||||
def asyncio_run(coro):
|
||||
import asyncio
|
||||
return asyncio.run(coro)
|
||||
|
||||
|
||||
# ---------- 快路径 ----------
|
||||
def test_fast_path_returns_direct(tmp_path):
|
||||
architect = _make_architect(_architect_handler())
|
||||
worker = _make_worker(_fake_generate(["这是一段足够长的、非占位的直接回答内容,用于快路径。"]))
|
||||
pipe = _pipeline(architect, worker, fast_path=True, run_dir=str(tmp_path / "runs"))
|
||||
res = asyncio_run(pipe.run("讲一下排序"))
|
||||
assert res.status == "fast_path"
|
||||
assert res.fast_path is True
|
||||
assert res.api_input_tokens == 0 # 未调用 Architect
|
||||
assert "快路径" in res.response or "直接" in res.response
|
||||
|
||||
|
||||
# ---------- 协作:一次通过 ----------
|
||||
def test_collab_one_pass(tmp_path):
|
||||
architect = _make_architect(_architect_handler())
|
||||
worker = _make_worker(_fake_generate(["def my_func():\n return 42"]))
|
||||
pipe = _pipeline(architect, worker, fast_path=False, run_dir=str(tmp_path / "runs"))
|
||||
res = asyncio_run(pipe.run("写个函数"))
|
||||
assert res.status == "done"
|
||||
assert res.fast_path is False
|
||||
assert res.api_input_tokens >= 100 # brief + 终审 用量已计量
|
||||
assert res.workspace_path is not None
|
||||
# 交流文本已落盘
|
||||
import os
|
||||
assert os.path.exists(res.workspace_path)
|
||||
assert "done" in res.route
|
||||
|
||||
|
||||
# ---------- 协作:带 issue 修复 ----------
|
||||
def test_collab_issue_fix(tmp_path):
|
||||
architect = _make_architect(_architect_handler())
|
||||
# 前两次失败(占位)-> issue;decide 后重跑成功
|
||||
worker = _make_worker(_fake_generate([
|
||||
"待实现", "待实现", "def ok():\n return 1",
|
||||
]))
|
||||
pipe = _pipeline(architect, worker, fast_path=False, run_dir=str(tmp_path / "runs"))
|
||||
res = asyncio_run(pipe.run("写个函数"))
|
||||
assert res.status == "done"
|
||||
assert any("issue" in r for r in res.route)
|
||||
assert any("decide" in r for r in res.route)
|
||||
# 至少有 1 条 decision
|
||||
import json as _json
|
||||
ws = json.load(open(res.workspace_path, encoding="utf-8"))
|
||||
assert len(ws["decisions"]) >= 1
|
||||
|
||||
|
||||
# ---------- 熔断兜底:本地降级 ----------
|
||||
def test_breach_local_degrade(tmp_path):
|
||||
architect = _make_architect(_architect_handler())
|
||||
worker = _make_worker(_fake_generate(["不通过"]))
|
||||
pipe = _pipeline(architect, worker, fast_path=False, api_token_cap=1,
|
||||
breach_policy="local_only", run_dir=str(tmp_path / "runs"))
|
||||
res = asyncio_run(pipe.run("写个函数"))
|
||||
assert res.status == "failed"
|
||||
assert "本地降级" in res.response
|
||||
assert any("breach" in r for r in res.route)
|
||||
|
||||
|
||||
# ---------- 熔断兜底:Architect 代做 ----------
|
||||
def test_breach_architect_do(tmp_path):
|
||||
# review 一直打回 fix(制造压力),最终走 breach:architect_do
|
||||
def handler(request):
|
||||
body = json.loads(request.content)
|
||||
msg = (body.get("messages") or [{}])[-1].get("content", "")
|
||||
if "任务 brief" in msg or "任务分析" in msg:
|
||||
return _resp(BRIEF_JSON)
|
||||
if "自动升级" in msg:
|
||||
return _resp("(Architect 兜底答案)")
|
||||
return _resp(DECIDE_JSON)
|
||||
architect = _make_architect(handler)
|
||||
worker = _make_worker(_fake_generate(["待实现"]))
|
||||
pipe = _pipeline(architect, worker, fast_path=False, rounds_cap=1,
|
||||
api_token_cap=8000, breach_policy="architect_do",
|
||||
run_dir=str(tmp_path / "runs"))
|
||||
res = asyncio_run(pipe.run("写个函数"))
|
||||
# rounds_cap=1:一轮后预算未满但回合触顶 -> breach -> architect_do
|
||||
assert "breach" in " ".join(res.route)
|
||||
assert res.status in ("escalated", "failed")
|
||||
+1
-1
@@ -72,7 +72,7 @@ P0 完成后的能力:干净的后端抽象 + 可量化的评测 + 可追溯
|
||||
| T3 | ArchitectClient(DeepSeek API,JSON 约束) | ✅ 完成 | T3 |
|
||||
| T4 | Workspace(交流文本 schema/校验/渲染/rollup) | ✅ 完成 | T4 |
|
||||
| T5 | WorkerLoop + 接地验证 | ✅ 完成 | T5 |
|
||||
| T6 | CollaborativePipeline 编排 | ⬜ | |
|
||||
| T6 | CollaborativePipeline 编排 | ✅ 完成 | T6 |
|
||||
| T7 | 网关扩展(/chat 切 v2,/chat/legacy) | ⬜ | |
|
||||
| T8 | 人工检验队列 ReviewQueue | ⬜ | |
|
||||
| T9 | token 计量与账单 | ⬜ | |
|
||||
|
||||
Reference in New Issue
Block a user