跳转至

路径输入错误被映射为内部故障,整轮任务终止

2026 年 9 月,goagent 一个真实排课用例失败:模型调用记忆工具时把路径参数传成了 /。路径校验拒绝了这个输入,但整轮任务随即终止,用户看到的就是一次失败。

定位

先确认路径校验这一环。校验存在,也按预期拒绝了 /:它要求路径必须落在记忆目录之下,/ 不满足。拒绝行为本身正常,剩下要查的是这次拒绝被当成了什么。

沿错误流向追到 HTTP 边界的错误出口:这个出口默认把错误写成 500、code 为 internal_error,只有带类型的错误才能拿到别的状态码。路径校验返回的是普通错误,没有带类型,于是落进默认分支,以 500 回到调用方,整轮按系统故障处理,直接结束。

错误分两类。路径不合法、必填参数缺失,是模型可以修正的输入错误,应该作为这次工具调用的失败结果返回给模型,在工具循环里改了重试。存储失败、数据库不可用,是该终止整轮的系统故障。原来两类错误走同一个映射,一个输入问题就变成了整轮失败。

500 和 400 的结局不同,是因为跨进程工具循环按状态码分流。4xx 业务拒绝由工具处理层翻译成工具错误,作为这次调用的失败结果回给模型,模型读到拒绝原因后可以改参数重试;5xx、网络错误这类系统故障直接让整轮失败,模型看不到原因,也就无从修正。invalid_request 这个工具错误类型在协议里本来就有,路径校验只是没有用它;修复后同一个错误出口把带类型的校验错误映射成 400/invalid_request。

模型之所以把路径参数传成 /,是因为工具参数描述原来没有写清合法根,模型只能自己猜。

修复与验证

修复在用户批准后进行,只动错误分类:模型可修正的输入错误改为 invalid_request 工具错误,经 HTTP 400 回到既有工具循环;存储与系统错误保持 500 终止。工具参数描述同时写清楚合法根是记忆目录、重命名的源与目标必须是其后代路径。边界没有动:没有放宽路径权限,没有给 / 加别名,没有另建修补循环。

验证分三层,每层有独立证据。HTTP 前后对照:非法根路径、路径穿越、不存在的文件从 500 变成 400,合法根仍是 200,且没有产生越界写入;存储故障注入确认系统错误仍然返回 500。受控回放:注入此前记录的那个错误调用,真实模型收到拒绝信息后自行改用合法的记忆根路径,最终取得后端签发的排课偏好请求。自然请求:另跑一次完整的"帮我安排本学期课程",模型同样从记忆读取进入后续流程。回放里的首个调用是注入的、不是模型自然生成的,两份证据分开保存,不能混着引用。

回归同时覆盖了几个容易回退的点:错误分类、修正路径、直接返回待输入问题、无写入副作用,以及存储故障保持 500。待输入问题按语义比较,只把可选 options 的省略与空数组视为等价,其余字段和非空选项完整比较。复现命令:

make agent-kernel-test
make ecampus-agent-test
make test
make integration
make runtime-package-test

HTTP 边界前后对照对应 TestEcampusAgentAPIMemoryRoute;修正链路对应跨进程评测用例 TestAgentBenchmarkRunCaseMemoryRootCorrectionNeedsInput,评测需设置 AGENT_BENCHMARK_LIVE=1 并提供测试数据库 DSN,数据库命令必须指向可销毁测试库。全量 Go 测试、产品测试和跨进程评测在该轮均通过。