山东大学创新实训(六)--基于Multi-Agent的剧本杀平台博客
这段时间我们组线下讨论一下觉得作为一个Agent系统项目虽然可以跑但我们事前的功能都是相当于在部分主文件不断的加功能功能越来越多但主链路越来越模糊。我们考虑收口主链路。因为一个Agent最怕的不是暂时缺少某个 feature而是同一类逻辑在不同地方各写一份。就比如我们这个项目一个重心是NPC Agent文字的输出我们会考虑它到底在做什么决策它输出的文本是什么它有没有 tool intent它有没有越权泄漏它要不要转成语音它要不要进 trace它会不会更新 belief 或房间状态如果这些事情在项目里没有统一链路而是散落在多个局部函数里我们后续不管是修改功能还是测试都会愈发困难。我们要先统一主链路继续做升级。Agent“决策”和“输出”拆开我先做的第一件事是新建 backend/app/core/agent_protocol.py把几个核心协议对象单独定义出来AgentDecisionMessageRenderAudioRenderTaskBeliefUpdateGuardrailResult这一步看起来像是“加了几个 dataclass”但它其实是这次收口的起点。因为以前很多逻辑默认把“NPC 想做什么”和“NPC 最后说了什么”混在一起。一旦混在一起后面所有层都会痛苦文本、语音、字幕、复盘、工具调用、状态写入都会互相打架。所以我把这几个对象先抽出来目的很明确AgentDecision 负责表达“它打算做什么”MessageRender 负责表达“它最终向玩家展示什么”AudioRenderTask 负责表达“这条文本是否要异步转成语音”BeliefUpdate 负责表达“这一轮输出对内部判断产生了什么更新”这一步做完后后面的 Structured Outputs、Voice、Replay、Behavior Evals 才有了统一落点。公共输出收口到统一链路接下来我把公共输出尽量统一收口到 backend/app/core/agent_output_pipeline.py。这条 pipeline 现在负责的事情包括output mode metadata 规范化guardrail 检查decision/render 结构补齐trace 打点tool intent 分发belief update 写入房间消息落盘SSE 发布语音异步任务调度也就是说以前那些“消息发出去就算完”的逻辑现在不再只是简单 publish而是走完整的主链路。我这样做的原因很现实如果以后我要继续做 BeliefState、voice-safe rewrite、tool dispatch side effect、replay explainability它们都必须挂在同一条链路上否则每加一层都要反复补分支。所以这一步不是“抽象得更漂亮”而是把系统未来所有能力的落点先定死。阶段推进迁到workflow如果说 service 层是在拆“职责”那 workflow 层就在拆“流程”。我这几天做的另一条主线是把阶段推进继续迁进 backend/app/core/workflows/game_workflow.py。例如advance_discussion_turn这段逻辑原来要同时处理当前轮到谁发言NPC 是陈述还是提问NPC 对 NPC 的即时问答human turn ready 提示discussion round 结束后的收尾它本质上就是 orchestration而不是 route 应该承担的职责。不应该存在于game.py应该迁徙到独立的workflowBelief、Voice、RecoveryBeliefState 不是只存字段而是真正写进 room state而且 belief 已经开始影响vote targetdiscussion question targetprivate reply 策略也就是说Belief 已经不只是一个结构化字段而开始参与 Agent 后续行为。而对于Voice-safe rewrite voice_persona 和 voice_safe_rewrite并且把它们挂回主链路。现在文本仍然是业务事实语音是渲染层但在 TTS 之前系统会先尝试做 voice-safe rewrite而不是直接拿原始文本朗读。Durable recovery不只是能落盘我不满足于仅仅把任务存到 durable_tasks.json我把他们推进到了回复失败记录最大回复次数等使得方便后续测试。