Chaoyu Fan bio photo

PhD Student in Artificial Intelligence

Tongji University

PhD student in Artificial Intelligence at Tongji University, working across AI systems, research tooling, and computational workflows.

Shanghai, China

Email

LinkedIn

Instagram

Github

LLM Agent Harness 的协议适配问题:从一次 GLM-5.1 接入失败说起

更新时间:2026/05/02
文章定位:一次真实 agent harness 接入排障的技术复盘。
关键词:Agent harness, OpenAI Responses API, Chat Completions, SiliconFlow, Wecode, protocol adapter

这篇文章记录一个很小但很有代表性的工程问题:我尝试把硅基流动上的 Pro/zai-org/GLM-5.1 接到自己的类 Codex/Wecode agent harness,最初以为只是 config.tomlauth.json 没写对,最后发现根因并不在配置,而在模型服务和 agent harness 之间的 wire protocol 不兼容。

这个问题值得单独写下来,是因为它揭示了一个容易被忽略的事实:模型 API 能聊,不等于它能被 agent harness 使用。Coding agent 不是普通聊天客户端。它依赖流式事件、工具调用、状态标识、错误恢复和上下文压缩策略。只要这些协议层不匹配,模型本身再强也接不进去。

1. 症状:一个看起来像配置错误的 404

最初的目标很直接:在远程机器的 ~/.wecode/config.toml 里增加一个硅基流动 provider,让 Wecode 使用 GLM-5.1:

model_provider = "siliconflow"
model = "Pro/zai-org/GLM-5.1"
disable_response_storage = true

[model_providers.siliconflow]
name = "SiliconFlow CN"
base_url = "https://api.siliconflow.cn/v1"
wire_api = "responses"
requires_openai_auth = true

auth.json 的思路也很自然:把硅基流动 API key 放到 OpenAI-compatible harness 常见的 OPENAI_API_KEY 字段中。

但启动后直接报错:

Unexpected status 404 Not Found: Not Found,
url: https://api.siliconflow.cn/v1/responses

第一眼看上去,这很像下面几类常见问题:

  • API key 没有写对,或者 auth.json 的字段名不对。
  • 模型名写错,例如 Pro/zai-org/GLM-5.1zai-org/GLM-5.1 混淆。
  • 域名选错,例如 api.siliconflow.cnapi.siliconflow.com
  • provider 的 base_url 多写或少写了 /v1

这些都值得检查,但这次都不是根因。真正有用的线索在错误 URL 本身:程序请求的是 /v1/responses

2. 快速定位:服务端没有这个 endpoint

硅基流动的 OpenAI-compatible 接口主要是 Chat Completions 风格,也就是:

POST https://api.siliconflow.cn/v1/chat/completions

可以用一个最小 curl 验证服务本身是否可用:

curl https://api.siliconflow.cn/v1/chat/completions \
  -H "Authorization: Bearer $SILICONFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "Pro/zai-org/GLM-5.1",
    "messages": [{"role": "user", "content": "hello"}],
    "stream": false
  }'

这一步的意义是把问题分层。如果 /chat/completions 成功,而 harness 请求 /responses 失败,那么问题就不是 key,也不是模型不可用,而是客户端协议选错了。

3. 第二个错误:wire_api = "chat" 已经被移除

直觉上的修复是把配置改成:

wire_api = "chat"

但新版 Codex/Wecode 类 harness 会直接拒绝这个配置:

Error loading config.toml:
/home/fcy/.wecode/config.toml:11:12: `wire_api = "chat"` is no longer supported.
How to fix: set `wire_api = "responses"` in your provider config.
More info: https://github.com/openai/codex/discussions/7782
   |
11 | wire_api = "chat"

这条错误把问题彻底钉住了:

组件支持的协议实际 endpoint结论
新版 Codex/Wecode harnessResponses API/v1/responses只会按 Responses 协议发请求
SiliconFlow GLM-5.1Chat Completions API/v1/chat/completions不提供 /v1/responses

所以这是一个协议不兼容问题。config.toml 可以选择 provider,但不能让服务商凭空支持一个不存在的 HTTP endpoint。

4. 为什么这不是“改个 URL”就能解决

很多模型 API 都自称 OpenAI-compatible。但在 agent harness 里,OpenAI-compatible 这句话必须拆开问:兼容的是哪一个 API?

Chat Completions

经典聊天接口。核心输入是 messages,输出是 assistant message 或 streaming delta。很多第三方模型服务兼容的是这一层。

Responses API

更适合 agent 的统一接口。除了文本,还要承载 response id、工具调用、推理内容、结构化 output item 和更细粒度的事件流。

普通聊天客户端可能只需要:

messages -> assistant text

但 coding agent harness 通常需要:

conversation state
  -> model request
  -> streaming output events
  -> tool call events
  -> shell/apply_patch execution
  -> tool result injection
  -> model continuation
  -> final answer
  -> trace and persistence

这意味着协议层至少包含:

  • 消息结构:Responses 的 input item 与 Chat Completions 的 role messages 不完全同构。
  • 流式事件:Chat 的 delta chunk 需要翻译成 harness 期待的 response events。
  • 工具调用:tool call 的 id、name、arguments、增量拼接和完成事件都要保持一致。
  • 状态管理:Responses API 常带有 response id、previous response、output item 等状态语义。
  • 错误归一化:供应商错误码、限流、模型不存在、鉴权失败都要转成 harness 能理解的形态。
  • 审计与压缩:长程任务依赖 trace、rollout、上下文压缩摘要,协议事件如果丢失,后续排障会很困难。

因此,简单把 /responses 字符串替换成 /chat/completions 往往只能让第一跳 HTTP 成功,不能保证 agent 真的能完成任务。

5. 一个更准确的心理模型:模型不是后端,协议适配器才是后端

在 agent 系统里,模型服务最好不要被当成“直接可替换后端”。更稳的抽象是:

Agent Harness
/v1/responses
->
Protocol Adapter
schema + stream + tools
->
Model Provider
/v1/chat/completions

这层 adapter 不是网关转发器,而是语义翻译器。它需要知道 harness 的事件模型,也要知道供应商的能力边界。

如果没有 adapter,那么 base_url 的配置只能覆盖网络地址,不能覆盖协议语义。今天这个 404 就是最直接的例子:网络到了,协议没到。

6. 三种解决方案

这次问题的实际可选方案并不多:

方案工程成本稳定性适合场景
换支持 Responses API 的供应商或网关想快速让新版 Codex/Wecode 跑起来
换支持 Chat Completions 的旧版 harness明确想继续用硅基流动模型,且可以接受旧协议限制
写 Responses-to-Chat adapter取决于实现正在构建自己的 agent platform,需要统一接多家模型服务

如果只是个人临时使用,最省事的是前两种。如果目标是长期维护自己的 agent harness,第三种才是正确的工程方向。

7. 适配层应该怎么设计

一个最小可用 adapter 可以从非流式文本开始,但真正用于 coding agent,至少要逐步覆盖下面这些模块:

Request Translator

把 Responses request 中的 input、instructions、tools 和模型参数转换成 Chat Completions 的 messages、tools、tool_choice 和 stream 参数。

Stream Translator

把 Chat Completions 的 SSE chunks 转成 harness 期待的 response events,尤其是文本增量、工具参数增量和 completed 事件。

Tool Semantics

确保 tool call id 稳定,arguments 可以增量拼接,工具结果能回灌给下一轮模型请求,而不是只在文本里描述。

Failure Contract

把 401、404、429、5xx、模型不存在、上下文过长等错误归一成 harness 可诊断的错误,避免只暴露低信息量 HTTP 异常。

更完整的 adapter 还要处理模型能力声明:

  • 是否支持 tool calling。
  • 是否支持 parallel tool calls。
  • 是否支持 reasoning content。
  • 最大上下文长度和输出长度。
  • streaming 下 tool arguments 是否稳定。
  • 供应商对 system/developer/user 角色的支持差异。

这些能力不应该散落在 prompt 里,而应该成为 provider capability metadata。否则 agent 在某个模型上能跑,在另一个模型上随机失败,最后很难定位。

8. 配置文件应该更早失败

这次排障里最浪费时间的地方,是错误看起来像鉴权或模型名问题。一个更好的 harness 应该在启动时做 provider preflight:

1. 读取 model_provider 和 wire_api
2. 检查 binary 是否支持这个 wire_api
3. 检查 provider 是否声明支持目标 endpoint
4. 用最小请求验证 endpoint 和模型名
5. 输出协议层诊断,而不是等到任务中途 404

理想错误信息应该像这样:

Provider protocol mismatch:
  harness wire_api: responses
  requested endpoint: /v1/responses
  provider appears to support: /v1/chat/completions

Config-only fix is not available. Use a Chat-compatible harness,
a Responses-compatible provider, or a protocol adapter.

这比裸露的 Unexpected status 404 Not Found 更能帮助用户做正确决策。

9. 从这次问题里得到的结论

这次真正的修复不是某一行配置,而是把问题从“怎么写 config.toml”重新表述成“agent harness 与模型服务之间的协议契约是否一致”。

我的几个判断:

  • 模型兼容性不是 API key 兼容性。API key 能通过鉴权,只说明网络和账户层没问题,不说明 agent 协议可用。
  • OpenAI-compatible 不是一个单一标准。很多服务兼容的是 Chat Completions,不是 Responses API。
  • Agent harness 应该把 protocol adapter 作为一等组件。模型路由不只是选 base URL,还要选择 schema、stream、tool 和 failure contract。
  • 配置文件不能弥补协议缺失。当 binary 移除了 wire_api = "chat",继续调 config 只会绕圈。
  • 长程 agent 任务更需要协议清晰。一次聊天可以容忍轻微格式差异,但 coding agent 的工具调用和上下文状态会把协议差异放大成系统性失败。

10. 后续如果要继续接 GLM-5.1

如果我后面继续把 GLM-5.1 接到自己的 agent harness,我会走 adapter 路线,而不是继续试图用新版 Codex 的 responses provider 直连硅基流动。

优先级大概是:

Phase 1: non-streaming text completion
Phase 2: streaming text delta
Phase 3: single tool call
Phase 4: incremental tool arguments
Phase 5: multi-turn tool result injection
Phase 6: failure normalization and trace persistence
Phase 7: benchmark against existing Responses-native provider

只有 Phase 5 之后,它才算是一个真正能跑 coding agent 的适配层。Phase 1 的“能回复 hello”只能证明模型 API 可用,不能证明 agent harness 可用。

参考