RFC-0015:网关 AI 问题事件
| 项目 | 值 |
|---|---|
| 状态 | Superseded(2026-09-16)— P0 曾落地(2026-09-14);/api/agent 对话回路随 S9 之后删除,agent.problem 行不再产生,被拒计划与其代码在浏览器的步进轨迹里(有界步方案) |
| 文档版本 | 0.4.1 |
| 创建/最后修改 | 2026-09-13 / 2026-09-16 |
| 事件 | agent.problem(aira.gateway-log/0.1 里的一种新 event) |
| 上位路线 | 生产运行条件 §4 |
| 发布准入 | 上线计划 G1、G2 |
| 依赖 | 服务说明 |
| 授权边界 | 本文不授权部署、付费模型调用或 Git 提交 |
0. 一句话
Section titled “0. 一句话”网关只在一次 AI 请求里出了能定位的问题时,往已有的结构化日志多写一条 agent.problem:哪个任务、哪个模型、
第几步、哪一类问题、哪个码,再加一个不超过 200 字符的定位线索。成功的请求不多写一个字。没有新表、新绑定、
新路由、新脚本,浏览器不改。够用了就停在这里;不够用的证据出现前不扩。
1. 网关已经看到的、只差记下来的
Section titled “1. 网关已经看到的、只差记下来的”逐项核对 deepseek-agent.ts、http-handler.ts、浏览器 agent-client.ts,定位问题所需的信息网关此刻都在手里:
| 要回答的问题 | 信息在哪 |
|---|---|
| 是哪个任务 | 请求体 id——浏览器用 taskId 构造 chat,chat.id === taskId;已校验,未记录 |
| 用的什么模型 | resolvedModel,已进 gateway.request.completed |
| 宿主拒了什么 | 续传请求 messages 最后一条 assistant 消息里的 tool-aira_compile_exact_plan 部件:output.status = needs-repair 与 output.diagnostic.{operation, code, message};浏览器已原样送回,网关校验条数后原样交给 SDK。SDK 在工具结果续传时沿用同一条 assistant 消息,所以这条消息累积整个任务的每一步,只有最后一个 step-start 之后的部件是新的 |
| 计划格式错在哪 | onError 里已用公开 schema 算出 EXACT_CAD_PLAN_INVALID: … 的字段路径,只回给模型 |
| 是否编造了工具名、参数不合 schema | onStepEnd 的 toolCalls 里 invalid: true 的调用带着 NoSuchToolError / InvalidToolInputError 本身 |
| 是否没调工具就停了 | onStepEnd 的 finishReason 与 toolCalls |
| 查库有没有查到 | 查库工具在服务端执行,toolResults.entries / redirected 在 onStepEnd 可见 |
| 是不是被预算截停 | 三条 stopWhen 与 prepareStep |
| provider 错、流中取消 | 已作为 errorCode 进请求日志 |
看不到的只有“两次请求之间被浏览器取消”,本文不处理。
- 只记问题,不记过程。 不为每一步、每个成功任务落行;请求日志已有的计数就是分母。
- 一条事件回答“问题在哪”,不回答“怎么复现”。 事件里是码、名字、字段路径和一句定位线索;不存计划正文、指令原文、AMIR 上下文、
aira_model_read输出。要复现时用零费用假 provider 栈按事件里的码构造,或临时提高本地日志级别。 - 写进已有的地方。 用现有
structuredLog写 Workers Logs(已启用,head_sampling_rate: 1,最长保留 7 天)。“立刻修”用不到 7 天以外的数据;需要更久时再决定落 DO 表,那是另一份证据触发的另一次决定。 - 不是权威。 探测与写入失败一律吞掉,不改响应、不阻塞流——与
observe现状一致。
3.1 agent.problem
Section titled “3.1 agent.problem”一次请求可产生 0 到少数几条;每条最多 9 个字段:
| 字段 | 值 |
|---|---|
event |
agent.problem |
taskId, requestId |
任务与本次请求 |
model |
resolvedModel |
step |
出问题的模型调用步,从 1 起计(与请求记录的 steps 同一口径);来自续传裁决时为 0 |
kind |
§3.2 五种之一 |
code |
诊断码或错误类名 |
operation |
问题所指的原生操作或工具:plan-rejected 取 diagnostic.operation,区分编译器拒(aira_compile_exact_plan:计划本身无效或没有步骤能对应)、编译器在某一步拒(该步的 kind,如 update-imported-product:对应不完整、歧义、身份冲突等 EXACT_* 拒绝,SDK 从 ExactCadPlanCompileError 取步骤种类)与原生操作拒(model.preview 等);invalid-input 与 library-miss 取工具名 |
detail |
≤200 字符的定位线索,见 §3.2;没有就省略 |
3.2 五种问题及判定
Section titled “3.2 五种问题及判定”kind |
判定 | code |
detail |
通常修什么 |
|---|---|---|---|---|
plan-rejected |
续传最后一条 assistant 消息里、最后一个 step-start 之后的计划工具部件 output.status = needs-repair(更早的部件上一次请求已报过;output-error 是上一次请求已报的 invalid-input,不再报) |
diagnostic.code,缺失时 NEEDS_REPAIR |
diagnostic.message 头 200 字符 |
编译器拒 → 契约与宿主求值;原生操作拒 → 该操作的诊断、守恒检查、名字卡落点说明 |
invalid-input |
本步某个工具调用 invalid: true 且错误为 InvalidToolInputError |
计划工具 EXACT_CAD_PLAN_INVALID,其他工具 TOOL_INPUT_INVALID |
计划工具:公开契约校验给出的第一条诊断(字段路径与 schema 用语,不含值);其他工具省略,因为校验器的消息可能引用参数值 | 计划工具 schema 说明、该步骤的示例 |
unknown-tool |
本步某个工具调用 invalid: true 且错误为 NoSuchToolError |
NO_SUCH_TOOL |
编造的工具名 | 工具描述;某模型反复出现则调整 allowlist |
no-tool-stop |
本请求没有发出任何有效的浏览器执行工具调用(计划或读模型;查库不算)、最后一步 finishReason = stop,且入站历史里没有 status = completed 的计划裁决;step 为本请求步数 |
PREMATURE_COMPLETION(与浏览器同名) |
省略 | 系统提示与工具描述 |
library-miss |
查库结果 redirected 非空;或未命中计划步骤种类且 entries 为空 |
LIBRARY_NO_MATCH / LIBRARY_REDIRECTED |
查库词(模型撰写,可能带用户起的名字) | 名字卡覆盖面、索引别名 |
预算截停、provider 错误和流中取消不另发事件:请求日志已有 errorCode,只需补 §3.3 的 stopReason。
3.3 请求日志补三个字段
Section titled “3.3 请求日志补三个字段”gateway.request.completed 增加 taskId、steps(本请求的模型调用步数)、stopReason
(step-cap / request-token-cap / output-cap,把三条 stopWhen 各包一层记下谁先命中;没有封顶时省略。SDK 只在循环本来会继续时才评估
stopWhen,所以命中的那条就是实际结束本次请求的封顶)。
有了 taskId,按任务归组、算分母、和 agent.problem 对上,都在同一份日志里完成。
3.4 明确不记
Section titled “3.4 明确不记”指令原文、计划 JSON、currentModel、任何 aira_model_read 输出、provider 原始响应、邀请 token、原始 IP。
detail 是唯一的自由文本字段,来源限定为宿主诊断 message、schema 字段路径、工具名、查库词四种,且截断。
4. 写入路径
Section titled “4. 写入路径”service-contracts.ts:AgentProblem、AgentStopReason与AgentExecutionContext.observe(priorPlanCompleted、onProblem、onStop);boundedProblemDetail是自由文本进日志的唯一通道(折行为一行、截到 200 字符)。deepseek-agent.ts:onStepEnd在现有累计逻辑旁分类invalid工具调用、检查查库结果、记录是否发出过浏览器工具调用;三条stopWhen各包一层报告封顶原因;流结束时判定no-tool-stop。onError与invalid-input共用同一个公开契约诊断函数。观察回调抛错一律吞掉。http-handler.ts:/api/agent分支在validateAgentChat之后扫描入站消息:最新 assistant 消息最后一个step-start之后的needs-repair计划部件成为plan-rejected,历史里任何completed计划部件置priorPlanCompleted;消息部件按不可信输入逐字段检查。问题与封顶原因攒在请求 trace 上,随既有的observe一次交给GatewayHttpObserver。http/gateway-log.ts:共享的投影——一条请求记录加每个问题一条agent.problem(带requestId、taskId、model)。Worker 用console.log(object)交给 Workers Logs;Node 宿主server.ts把同一对象写成 stdout 的一行 JSON。
Workers Logs 的查询界面按 event = agent.problem 过滤,再按 kind、code、model 分组;按 taskId 能把同一任务的
请求记录与问题事件串起来看。7 天内按 requestId 排障与现有做法相同。不写脚本。
每次发布后看一次分组计数:哪一类、哪个码在当前版本最多,按 §3.2 最后一列去修产品代码或工具描述,不用 prompt 补丁掩盖;
同一个码按 model 分布明显不均时,调整模型策略。修完后下一版本看该码是否下降;没降再决定是否付费重跑对应的基准格。
7. 阶段与验收
Section titled “7. 阶段与验收”不新建测试文件;用现有 build / typecheck / lint 加实际运行验收。
| 阶段 | 内容 | 验收 |
|---|---|---|
| P0 本地(已完成,2026-09-14) | 观察契约、五种判定、续传裁决扫描、请求日志三字段、共享日志投影、Node 宿主输出 | 已用脚本化的假 DeepSeek fetch 直接驱动构建后的 handler(进程内,无端口、无付费)跑 9 个场景:宿主拒绝后文本放弃(两条事件)、更早步的拒绝不重复、计划 schema 无效再修好、编造工具名、查库未命中与重定向、两步封顶、成功计划、完成后的总结,以及禁止内容检查(指令原文、计划字段、Revision 哈希都不出现在任何日志行)。全部通过;typecheck、lint、build 与 Worker dry-run 打包通过。未跑浏览器整栈 |
| P1 云端 | 无新增改动,随下一次已授权的网关部署上线 | 部署后 Workers Logs 能按 event = agent.problem 过滤 |
8. 只有出现这些证据才扩
Section titled “8. 只有出现这些证据才扩”- 7 天保留不够用(想看跨版本趋势)→ 再决定把
agent.problem落到安全状态 DO 的一张表。 - 事件能定位类别但复现不了 → 再决定保留失败计划正文,且以邀请条款告知为前提。
- 请求日志里成功计划数远少于任务数(说明大量任务在两次请求之间被取消)→ 再决定让浏览器补一条终态。
- 量级让单一 DO 或日志成本成问题 → 再考虑 Analytics Engine。
| 关注点 | 位置 |
|---|---|
| 问题与观察契约 | apps/server/src/service-contracts.ts(AgentProblem、AgentObservation、boundedProblemDetail) |
| 判定与封顶报告 | apps/server/src/model/deepseek-agent.ts(onStepEnd、reportedStop、reportTextOnlyStop) |
| 续传裁决扫描、请求记录字段 | apps/server/src/http/http-handler.ts(carriedPlanJudgements、GatewayHttpObservation) |
| 日志投影 | apps/server/src/http/gateway-log.ts(structuredObserver) |
| 宿主写入 | apps/server/worker/production-worker.ts(structuredLog)、apps/server/src/server.ts(writeJsonLine) |
| 指标定义与阈值 | 生产运行条件 §4(只加数据来源引用) |