计划标题纠错与确认流可信输出
Agent:计划标题纠错与确认流可信输出
记录日期:2026-04-02
1. 问题背景
神秘人对话里,create_plan 常在用户先说需求、再说「直接创建」等多轮场景下,把元指令写进 plan_title(例如计划名叫「直接创建」),或与 SSE 混排时出现重复解析正文 JSON、并发 waitForAuthToken 打满 token 接口等问题。目标是:服务端 execute 与 App 确认前双端一致纠错,确认结果优先走结构化 TOOL_CALL_RESULT,并与 Expo Tabs 路由名对齐,减少无意义警告。
2. 问题列表
2.1 问题一:plan_title 被填成「直接创建」等元指令
根因 why1:模型把用户最后一轮口头禅当作计划标题 → why2:最后一轮用户话经常是「直接创建」→ why3:enrich 若只取「最近一条用户话」整条当锚点,会把元指令规范化进 plan_title。
方案
- 短期方案:在后端或前端某一侧写死替换表,命中则改标题。优点:改得快。缺点:与另一客户端/旧包不一致,易漂移。
- 长远方案:在 execute_tools 前
enrich_create_plan_calls_from_messages:按时间倒序收集用户话,跳过_is_meta_plan_instruction,取第一条实质性用户句作 anchor,并与normalize_plan_title_for_create组合;元指令正则覆盖「好的,直接创建」等整句。App 侧enrichCreatePlanArgsFromRecentUserMessages用同一套元指令规则,在确认前修补空参数或元指令型plan_title。优点:双端一致、可测。缺点:规则需与产品话术同步维护。
采纳方案 长远方案:shenmiren-agent/agent/tool_calls_util.py 中扩展 _META_PLAN_TITLE_OR_REPLY 与 enrich_create_plan_calls_from_messages;zhiyuxing-app/src/lib/agent-chat.ts 中 isMetaPlanInstructionUserLine + enrichCreatePlanArgsFromRecentUserMessages。
补充用例
shenmiren-agent/tests/test_plan_title_meta_reply.py:test_is_meta_direct_create、test_enrich_prefers_substantive_over_meta_last_turn、test_enrich_meta_title_empty_args_uses_substantive。- 与既有
test_plan_title_normalize.py同跑:pytest tests/test_plan_title_meta_reply.py tests/test_plan_title_normalize.py -q。
2.2 问题二:工具确认后展示依赖正文里「捞 JSON」,易误解析
根因 why1:确认接口返回的 SSE 同时累积了 resultContent 与结构化 TOOL_CALL_RESULT → why2:UI 只对 content 做 JSON.parse → why3:大段文本里若含 {…} 片段,可能误匹配或解析失败,与真实工具结果不一致。
方案
- 短期方案:继续正则捞第一个 JSON。优点:无协议改动。缺点:脆弱。
- 长远方案:
confirmToolCalls返回structuredToolResults(与ParsedSseResult一致),UI 用formatConfirmedToolResultsForUser优先渲染;无结构化结果时再回退正文 + JSON 解析。优点:与 AG-UI payload 一致。缺点:须保证服务端持续发规范TOOL_CALL_RESULT.payload。
采纳方案 长远方案:ConfirmToolCallsResult、formatConfirmedToolResultsForUser、agent-chat.tsx 确认成功后优先结构化展示。
补充用例
- 手工:创建计划 → 确认 → 检查气泡文案是否含正确
plan_title,且控制台无displayParseFallback(在仅结构化路径下)。
2.3 问题三:多处并发 waitForAuthToken 重复刷新
根因 why1:多个模块在首屏并行调 RDB/云函数 → why2:各自 await waitForAuthToken() → why3:循环内反复 getAccessToken(),放大 SDK 与日志压力。
方案
- 短期方案:调用方串行化。优点:不改公共模块。缺点:易漏、难维护。
- 长远方案:模块级
waitForAuthTokenInFlight,并发合并为单次 Promise,finally清空。优点:一处治理。缺点:需约定「单飞」语义(共享同一次超时/静默重登结果)。
采纳方案 长远方案:zhiyuxing-app/src/lib/cloudbase.ts 中 in-flight 合并。
补充用例
- 日志观察:同帧多次 persistence 调用时,
getAccessToken的轮询次数应接近单次 wait 的 attempts,而非线性叠加(按需加临时计数器验证后可删)。
2.4 问题四:Expo Tabs Tabs.Screen 的 name 与子路由不一致
根因 why1:zhi、xing 为目录,index.tsx 为默认子路由 → why2:部分版本下注册名应为 zhi/index、xing/index → why3:使用 name="zhi" 时出现「路由不存在」类警告。
方案
- 短期方案:忽略警告。优点:零改动。缺点:噪音、潜在导航隐患。
- 长远方案:
app/(tabs)/_layout.tsx中zhi/index、xing/index与文件结构一致;胶囊栏仍使用/zhi、/、/xing。优点:与 Expo Router 一致。缺点:升级 Expo 时需再对照文档。
采纳方案 长远方案:已改 _layout.tsx 中 Tabs.Screen 的 name。
补充用例
- 本地启动 App,切换「知 / 时间 / 行」与深链
/zhi,控制台无缺失路由告警。
3. 小结
计划在后端 enrich + 前端确认前 enrich两层对齐元指令与实质性主题;确认结果优先结构化 payload,减少对正文的启发式 JSON 解析;token 等待单飞降低并发刷 token;Tabs 注册名与目录索引对齐。本地验证:pytest 覆盖计划标题用例;前端对改动文件 read_lints 干净;全仓 tsc 若有历史基线错误,以与应用相关的增量自检为准。