feat(v3): T26 两级智能体(大模型规划/审查 + 本地小模型执行,D7)

- run_dual 编排:规划者两阶段 JSON(plan/review,失败回喂重试一次再降级),
  redo 时裁决意见回喂执行者,交接上限 agent.max_handoffs(默认 2)
- 交接文档 agent_runs/{id}/handoff.json(智能体版交流文本:instructions/acceptance/exchanges)
- /agent 新增 executor_pool_id;规划者==执行者条目拒绝;整体 token_cap 覆盖两级调用
- ToolLoop 增 emit_final 开关(内层循环不发终态,防前端 SSE 提前收口)
- 前端:执行者选择器 + phase/message 事件渲染(阶段徽标 + 双色消息卡)
- 测试 +3(done/redo/执行者故障),全量 277 passed
- fix(tests): test_config_get_put_reset 增加设置备份/恢复隔离,防止清掉用户真实配置
This commit is contained in:
tzt
2026-09-01 11:45:33 +08:00
parent 8e2123343c
commit 43e2bceae7
20 changed files with 568 additions and 59 deletions
+36 -2
View File
@@ -530,13 +530,44 @@ try:
workspace_dir = agent_cfg.get("workspace_dir", "agent_workspace")
chat, model, used_pool_id = _resolve_agent_chat(pool_id)
# 两级模式(D7):显式指定执行者(本地小模型)时,规划=chat、执行=executor_chat
executor_pool_id = str((req or {}).get("executor_pool_id") or "").strip()
executor_chat = None
executor_model = ""
if executor_pool_id:
entry = get_pool().get(executor_pool_id)
if entry is None:
raise HTTPException(status_code=400,
detail=f"执行者条目不存在: {executor_pool_id}")
if entry["backend"] == "mock":
raise HTTPException(status_code=400,
detail="mock 模型不能担任执行者,请选择 llama_server 或 openai 条目")
from gateway.agent import OpenAICompatChat
executor_chat = OpenAICompatChat(
base_url=entry["base_url"] or "http://127.0.0.1:8901/v1",
api_key=entry.get("api_key") or None,
model=entry["model"],
temperature=float(entry.get("temperature", 0.3)),
max_tokens=int(entry.get("max_tokens", 4096)))
executor_model = f"{entry['name']}{entry['model']}"
if executor_chat is not None and executor_pool_id == used_pool_id:
raise HTTPException(status_code=400,
detail="规划者与执行者是同一个模型,两级模式无意义;请更换执行者条目")
service = get_agent_service()
request_id = new_request_id()
mode = "dual" if executor_chat is not None else "single"
info = service.register(request_id, task, model, used_pool_id,
workspace=workspace_dir)
workspace=workspace_dir,
executor_model=executor_model, mode=mode)
if info is None:
raise HTTPException(status_code=503, detail="智能体同时运行任务已达上限")
s = settings_store().to_dict()
agent_cfg = s.get("agent", {})
async def _run():
try:
await service.run(
@@ -546,6 +577,8 @@ try:
token_cap=int(agent_cfg.get("token_cap", 20000)),
allow_shell=bool(agent_cfg.get("allow_shell", False)),
shell_timeout_s=int(agent_cfg.get("shell_timeout_s", 20)),
executor_chat=executor_chat,
max_handoffs=int(agent_cfg.get("max_handoffs", 2)),
)
except Exception as exc:
import traceback
@@ -557,7 +590,8 @@ try:
info.asyncio_task = asyncio.create_task(_run())
return {"request_id": request_id, "status": "running", "model": model,
"workspace": workspace_dir}
"workspace": workspace_dir, "mode": mode,
"executor_model": executor_model}
@app.get("/agent/fs", tags=["agent"])
async def agent_fs_browse(path: str = ""):