feat(tier2): 设计阶段接进服务路径(修服务态不收敛根因)

006 真跑实证:服务态 writer 拿空 design_text → 富游戏三门(tripleLink/economy/latch)全挂、
4 次 resume 不收敛(产物结构合法能渲染,但三系统联动没接对)。CLI 线 run_studio 先跑工作室设计
阶段再写、故更易过门;服务路径此前整段跳过设计(app.py 文件头标的 followup)。

修:bootstrap 加 _run_design_phase——在控制面进程内(有 agentscope+模型)复用 studio._design_stage
(工作室星形多 agent 团队 + 失败 degrade 回单 agent)产出 design_text,喂进 AgentRecord.system_prompt
(roles.writer_system 第二参)。每局建 agent 时跑一次,不随 resume 重跑。best-effort:设计失败返 ""
不阻断生成。do_design 经 drive_generation 透传(control_plane --no-design 可关,对照用)。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
zizi 2026-06-24 10:53:38 +00:00
parent 34b9ce3be0
commit d0b30ddace
2 changed files with 42 additions and 4 deletions

View File

@ -137,6 +137,36 @@ async def _set_permission_bypass(client: Any, base: str, session_id: str, agent_
print(f"[tier2-bootstrap] 会话 {session_id} 权限已设 BYPASS(跳过工具 ASK,无人值守自治)。", flush=True)
async def _run_design_phase(brief: str, model_name: str, *, use_team: bool = True) -> str:
"""阶段 1 设计:在控制面进程内跑「工作室设计团队」产出富游戏 design_text,喂给服务态 writer 系统提示词。
为什么必须有(2026-06-24 cp-smoke-006 真跑实证):服务态此前 writer design_text
(start_new_game ""),M3 单写缺富游戏设计指引 富游戏三门(tripleLink/economy/latch)全挂
4 次有界 resume 不收敛(产物结构合法能渲染,但三系统联动没接对)CLI 线 run_studio 先跑设计阶段
再写故更易过门本函数把同一道设计阶段接进服务路径:复用 studio._design_stage(工作室星形多 agent
团队 + 失败 degrade 回单 agent),在控制面进程内( agentscope + 模型)直接跑,产出 design_text 写进
AgentRecord.system_prompt(roles.writer_system 的第二参)设计阶段每局只跑一次( agent ),不随 resume 重跑
best-effort:任何失败都返回 ""(绝不阻断生成拿不到设计也让 writer ,只是少指引),并记日志
"""
import functools # noqa: PLC0415
from worker import config # noqa: PLC0415
from worker.agent_loop import studio as _studio # noqa: PLC0415 —— 牵出 agentscope,控制面进程内可用
try:
# model_factory:零参偏函数,每次返回新 M3 客户端(team 给 leader/各专家各取一个;degrade 单 agent 也取一个)。
model_factory = functools.partial(config.build_model, model_name)
models: list = [] # 设计阶段造的模型客户端(成本可在此汇总;服务态暂不并入 run 台账,列 followup)
design_text = await _studio._design_stage(brief, model_factory, model_sink=models, use_team=use_team)
print(f"[tier2-bootstrap] 设计阶段产出 design_text {len(design_text or '')} 字(喂服务态 writer)。",
flush=True)
return design_text or ""
except Exception as e: # noqa: BLE001 —— 设计阶段绝不阻断主链(拿不到设计也让 writer 试)
print(f"[tier2-bootstrap] ⚠ 设计阶段失败(writer 将拿空 design,降级继续):"
f"{type(e).__name__}: {e}", flush=True)
return ""
async def start_new_game(
base_url: str,
brief: str,
@ -145,6 +175,7 @@ async def start_new_game(
model_name: str = "MiniMax-M3",
writer_max_iters: int = 40,
credential_id: str | None = None,
do_design: bool = True,
) -> dict:
"""【路①】新建:注册凭据(若需)→ 建 agent → 建 session → 发 kick,启动一款新富游戏的生成。
@ -173,13 +204,17 @@ async def start_new_game(
user_header=user_id)
credential_id = cred.get("credential_id") or cred.get("id")
# ② 建单写 agent:system_prompt=roles.writer_system(题面 + 空 design_text;设计阶段服务态落位见 followup),
# ②a 设计阶段(2026-06-24 接进服务路径修收敛):先在控制面进程内跑工作室设计团队产出 design_text,
# 喂给 writer 系统提示词——否则 writer 拿空 design、富游戏三门全挂不收敛(006 实证)。best-effort。
design_text = await _run_design_phase(brief, model_name) if do_design else ""
# ②b 建单写 agent:system_prompt=roles.writer_system(题面 + 富游戏 design_text),
# react_config 放开轮数,context_config=tier2 富游戏历史压缩(防长程多文件丢约定)。
# context_config / react_config 经 model_dump 转 JSON 传(端点 schema 收 ContextConfig/ReActConfig)。
ctx_cfg = config.build_context_config()
agent_body = {
"name": "tier2-writer",
"system_prompt": roles.writer_system(brief, "", fixture_hint=""),
"system_prompt": roles.writer_system(brief, design_text, fixture_hint=""),
"context_config": ctx_cfg.model_dump(mode="json"),
"react_config": {"max_iters": writer_max_iters},
}

View File

@ -244,6 +244,7 @@ async def drive_generation(
model_name: str = "MiniMax-M3",
writer_max_iters: int | None = None,
user_id: str = "tier2",
do_design: bool = True,
sse_turn_timeout_s: float = 1200.0,
sse_idle_timeout_s: float = 300.0,
) -> dict:
@ -291,7 +292,7 @@ async def drive_generation(
try:
started = await bootstrap.start_new_game(
base_url, brief, user_id=user_id, model_name=model_name,
writer_max_iters=writer_max_iters)
writer_max_iters=writer_max_iters, do_design=do_design)
except Exception as e: # noqa: BLE001 —— 启动失败 best-effort 落结果、记日志,不静默吞
result["stopped_reason"] = f"start_failed:{type(e).__name__}: {e}"
result["wall_s"] = round(time.perf_counter() - t0, 1)
@ -407,12 +408,14 @@ def main(argv: list[str] | None = None) -> int:
help="单写 ReAct 放开轮数(默认从 generation.yaml iteration.writer_max_iters 读)。")
parser.add_argument("--model-name", default="MiniMax-M3", help="模型名(默认 MiniMax-M3)。")
parser.add_argument("--user-id", default="tier2", help="多租户用户标识(默认 tier2)。")
parser.add_argument("--no-design", action="store_true",
help="跳过阶段 1 工作室设计(writer 拿空 design_text;调试/对照用)。默认跑设计。")
args = parser.parse_args(argv)
res = asyncio.run(drive_generation(
args.base_url, args.game_id, args.brief,
max_resumes=args.max_resumes, writer_max_iters=args.writer_max_iters,
model_name=args.model_name, user_id=args.user_id))
model_name=args.model_name, user_id=args.user_id, do_design=not args.no_design))
# 结构化结果打到 stdout(可被上层脚本捕获)。
print(json.dumps(res, ensure_ascii=False, indent=2), flush=True)
# 退出码:门绿落库成功 = 0,否则 1(供编排脚本判成败)。