news 2026/9/30 20:13:09

Qwen-Code ACP 与 AG-UI 协议深度解析:JSON-RPC 报文对比与 TaoToken 配置实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Qwen-Code ACP 与 AG-UI 协议深度解析:JSON-RPC 报文对比与 TaoToken 配置实战

1. 先搞清楚 Qwen-Code 里 ACP 和 AG-UI 到底谁管谁

如果你最近在折腾 Qwen-Code,大概率会在文档里同时撞见 ACP 和 AG-UI 这两个词,然后陷入一种“这俩是不是一回事”的困惑。我一开始也以为它们是前端视角和后端视角的区别,甚至觉得选一个用就行。实际把 qwen-acp 子进程跑起来、抓了几轮报文之后才发现,它们根本不是替代关系,而是进程内通信和跨网络事件流两层完全不同的东西。

先把结论摆出来:ACP 是 Qwen-Code 自研的 Agent Client Protocol,基于 JSON-RPC 2.0,默认走子进程的 stdin/stdout,只在本地进程之间传消息,不碰网络。AG-UI 是 CopilotKit 那套开源的 Agent-User Interaction Protocol,基于 SSE 或 WebSocket,专门给浏览器前端消费事件流用的。一个管“宿主程序怎么跟本地 Agent 子进程说话”,一个管“Web 前端怎么把 Agent 的流式输出渲染成 UI”。

打个比方,ACP 像主板上的内部总线,AG-UI 像机箱后面的网口。你在本地写个 CLI 脚本调 Qwen-Code,只需要 ACP;你要做个网页版代码助手,浏览器碰不到本地子进程的 stdio,就必须在中间加一层协议桥,把 AG-UI 的网络事件翻译成 ACP 的 JSON-RPC 报文。

这篇文章会带你做三件事:第一,逐字段对比两套协议的请求/响应结构,让你看到报文层面到底差在哪;第二,给出可复制的 settings.json 和 config.toml 骨架,把 TaoToken 的统一 Key 和 API 通道配进去;第三,教你抓报文、切协议、验证请求是否真的通了。适合正在做 Qwen-Code 本地 CLI 接入、或者准备把它嵌到 Web 前端里的开发者。

2. TaoToken 前置配置:统一 Key 与 API 通道怎么接

在动协议之前,得先把模型调用这条链路打通。Qwen-Code 本身是个 Agent 框架,它最终还是要调大模型 API 来完成推理。我实测下来,用 TaoToken 做统一入口比较省事,一个 Key 就能覆盖多个模型通道,不用在 Qwen-Code 里到处塞不同厂商的 endpoint。

TaoToken 的 API 地址是https://taotoken.net/api,注意这个地址不带任何查询参数,直接作为 Base URL 用。官网入口在https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=,注册完在控制台里生成 API Key。

这里有个关键点:Qwen-Code 的 ACP 子进程和 AG-UI 后端服务,理论上可以共用同一个 TaoToken Key,但建议在配置里显式区分环境变量,避免本地调试时把生产 Key 带进去。我一般会在项目根目录建一个.env文件,里面写:

TAOTOKEN_API_KEY=sk-你的实际key TAOTOKEN_BASE_URL=https://taotoken.net/api

然后在 Qwen-Code 的配置里引用这两个变量。如果你用的是 Claude Code 那套配置习惯,~/.claude/settings.json里可以这样写:

{ "env": { "ANTHROPIC_BASE_URL": "https://taotoken.net/api", "ANTHROPIC_API_KEY": "sk-你的实际key" }, "model": "claude-sonnet-4-20250514" }

注意这里的 Base URL 和 Key 是配套的,Model ID 要跟你实际在 TaoToken 控制台里开通的通道对应。如果你走的是 OpenAI 兼容格式,那就换成OPENAI_BASE_URL和OPENAI_API_KEY,地址同样是https://taotoken.net/api。

对于 Qwen-Code 自己的config.toml,我建议把模型通道单独抽出来:

[model] provider = "openai-compatible" base_url = "https://taotoken.net/api" api_key_env = "TAOTOKEN_API_KEY" model_id = "qwen3-coder-plus" [acp] transport = "stdio" command = "qwen-acp" args = ["--config", "./config.toml"] [agui] enabled = true transport = "sse" port = 8787

这个骨架里,[model]段负责模型调用,[acp]段定义子进程怎么启动,[agui]段定义 Web 事件流的监听端口。三块分开写的好处是,你切协议的时候只动[acp]或[agui],模型通道不用改。

如果你用的是 Cline 或者 Roo Code 这类插件,配置项名字会不一样,但核心三件套不变:Base URL 填https://taotoken.net/api,API Key 填 TaoToken 生成的 Key,Model ID 填你开通的模型名。Cline 的 MCP 配置里如果出现local proxy failed,八成是 Base URL 写成了带路径的完整 endpoint,改成纯https://taotoken.net/api就好。

3. 可复制配置:ACP 与 AG-UI 双协议 settings 骨架

这一节直接给能跑的配置。先明确一个原则:ACP 的配置核心是“怎么启动子进程”,AG-UI 的配置核心是“怎么暴露事件流端口”。两者可以同时开,也可以只开一个。

先看 ACP 侧的完整config.toml。这个文件放在项目根目录,qwen-acp 启动时会读:

[agent] name = "qwen-code-local" session_dir = "./.qwen/sessions" max_concurrent_runs = 4 [model] provider = "openai-compatible" base_url = "https://taotoken.net/api" api_key_env = "TAOTOKEN_API_KEY" model_id = "qwen3-coder-plus" temperature = 0.2 max_tokens = 8192 [acp] transport = "stdio" command = "qwen-acp" args = ["--config", "./config.toml", "--log-level", "info"] env = { TAOTOKEN_API_KEY = "${TAOTOKEN_API_KEY}" } startup_timeout_ms = 15000 [acp.tools] enabled = ["code_interpreter", "file_editor", "shell"] sandbox = true

这里[acp]段的transport = "stdio"是关键,它告诉宿主程序通过标准输入输出跟子进程通信。env那行把 TaoToken 的 Key 透传给子进程,避免子进程读不到环境变量。

再看 AG-UI 侧的配置。如果你要在 Web 前端接,需要一个后端服务把 ACP 的 JSON-RPC 转成 AG-UI 的 SSE 事件。这个桥接服务的配置可以写在同一个config.toml里,也可以单独放一个agui-bridge.toml:

[server] host = "127.0.0.1" port = 8787 cors_origins = ["http://localhost:3000", "http://localhost:5173"] [agui] transport = "sse" heartbeat_interval_ms = 15000 event_buffer_size = 256 [bridge] acp_command = "qwen-acp" acp_args = ["--config", "./config.toml"] acp_cwd = "." reconnect_attempts = 3 [bridge.mapping] "agent.stream_chunk" = "TEXT_MESSAGE_CONTENT" "agent.run_complete" = "RUN_FINISHED" "agent.tool_call" = "TOOL_CALL_START"

这个桥接配置里,[bridge.mapping]段定义了 ACP 方法名到 AG-UI 事件名的映射关系。实际项目里映射逻辑会更复杂,但骨架就是这样。

如果你用的是 Claude Code 的 settings.json 风格,可以这样组织:

{ "env": { "ANTHROPIC_BASE_URL": "https://taotoken.net/api", "ANTHROPIC_API_KEY": "sk-你的实际key" }, "model": "claude-sonnet-4-20250514", "acp": { "transport": "stdio", "command": "qwen-acp", "args": ["--config", "./config.toml"] }, "agui": { "enabled": true, "transport": "sse", "port": 8787 } }

注意ANTHROPIC_BASE_URL和ANTHROPIC_API_KEY是给 Claude Code 自身用的,Qwen-Code 的 ACP 子进程走的是config.toml里的[model]段。两套配置可以共存,但 Key 建议用同一个 TaoToken Key,方便统一管理。

配置写完之后,先别急着跑 Web 前端。用命令行验证 ACP 子进程能不能起来:

export TAOTOKEN_API_KEY=sk-你的实际key qwen-acp --config ./config.toml --log-level debug

如果子进程正常启动,你会看到它往 stdout 打出一行 JSON-RPC 的初始化消息,类似:

{"jsonrpc":"2.0","method":"agent.ready","params":{"sessionId":"sess-init","protocolVersion":"1.0"}}

看到这行就说明 ACP 通道通了,模型配置也被正确加载了。

4. 验证请求:抓报文、切协议、看成功结果

配置写完只是第一步,真正要确认的是报文有没有按预期流动。这一节教你抓 ACP 和 AG-UI 两边的报文,并验证协议切换是否生效。

先抓 ACP 的报文。最简单的方式是在宿主程序里把子进程的 stdout 重定向到文件,同时用tee保留终端输出:

qwen-acp --config ./config.toml 2>&1 | tee acp-trace.log

然后在另一个终端里,用一个小脚本往子进程的 stdin 写一条 JSON-RPC 请求。我一般用 Python 快速验证:

import subprocess, json, time proc = subprocess.Popen( ["qwen-acp", "--config", "./config.toml"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, bufsize=1 ) request = { "jsonrpc": "2.0", "id": 1, "method": "agent.run", "params": { "sessionId": "sess-verify-001", "prompt": "写一个Python快速排序算法", "tools": ["code_interpreter"] } } proc.stdin.write(json.dumps(request) + "\n") proc.stdin.flush() for _ in range(20): line = proc.stdout.readline() if not line: break print(line.strip()) if "run_complete" in line: break proc.terminate()

跑起来之后,你应该能看到类似这样的流式输出:

{"jsonrpc":"2.0","method":"agent.stream_chunk","params":{"sessionId":"sess-verify-001","content":"def quick_sort(arr):\n if len(arr) <= 1:\n return arr"}} {"jsonrpc":"2.0","method":"agent.stream_chunk","params":{"sessionId":"sess-verify-001","content":"\n pivot = arr[len(arr) // 2]"}} {"jsonrpc":"2.0","method":"agent.run_complete","params":{"sessionId":"sess-verify-001"}}

每条stream_chunk都带sessionId,run_complete没有id字段,因为它是通知类消息。这就是 ACP 的典型报文结构:请求有id,流式推送和完成通知没有id。

再验证 AG-UI 侧。启动桥接服务:

agui-bridge --config ./agui-bridge.toml

然后用 curl 订阅 SSE 流:

curl -N -H "Accept: text/event-stream" \ "http://127.0.0.1:8787/agui/stream?sessionId=sess-verify-002"

在另一个终端触发一次 Agent 运行:

curl -X POST http://127.0.0.1:8787/agui/run \ -H "Content-Type: application/json" \ -d '{"sessionId":"sess-verify-002","prompt":"写一个Python快速排序算法"}'

你应该在 SSE 终端看到这样的事件序列:

event: RUN_STARTED data: {"runId":"run-abc123"} event: TEXT_MESSAGE_START data: {"messageId":"msg-001","role":"assistant"} event: TEXT_MESSAGE_CONTENT data: {"content":"def quick_sort(arr):","messageId":"msg-001"} event: TOOL_CALL_START data: {"toolName":"code_interpreter","runId":"run-abc123","toolCallId":"tool-001"} event: TEXT_MESSAGE_END data: {"messageId":"msg-001","stopReason":"finish"} event: RUN_FINISHED data: {"runId":"run-abc123"}

对比一下就能看出差异:ACP 的报文是 JSON-RPC 格式,有jsonrpc、method、params字段;AG-UI 是 SSE 事件格式,有event和data两行,事件名是RUN_STARTED、TEXT_MESSAGE_CONTENT这种生命周期语义。

验证协议切换是否生效,最简单的办法是改config.toml里的[agui] enabled字段,从true改成false,重启桥接服务,再 curl 一次 SSE 端点。如果返回 404 或者连接被拒绝,说明 AG-UI 通道确实被关掉了,ACP 子进程不受影响。反过来,把[acp] transport从stdio改成tcp(如果版本支持),再跑一次 Python 验证脚本,看报文是不是从 socket 而不是 stdin/stdout 出来。

我踩过的一个坑是:AG-UI 的 SSE 事件里TEXT_MESSAGE_CONTENT的content字段是增量文本,不是完整消息。前端渲染的时候要自己拼接,不能每次覆盖。ACP 的stream_chunk也是增量,但字段名是content,语义一样。如果你在桥接层做映射,记得把增量语义保留,别在中间做聚合。

5. 常见报错排查:401、local proxy failed、reading choices、OAuth

这一节列几个我实际遇到过的报错,以及对应的排查路径。每个报错都跟协议配置或 TaoToken 通道有关,不是泛泛的“网络问题”。

401 Unauthorized:这个最常见。ACP 子进程报 401,说明[model]段里的api_key_env指向的环境变量没读到。检查两点:第一,export TAOTOKEN_API_KEY=sk-xxx有没有在启动子进程的同一个 shell 里执行;第二,config.toml里env = { TAOTOKEN_API_KEY = "${TAOTOKEN_API_KEY}" }这行有没有写对,${}是 TOML 的变量插值语法,不是 shell 的。如果用的是 settings.json,检查ANTHROPIC_API_KEY或OPENAI_API_KEY有没有拼错。

local proxy failed:这个报错通常出现在 Cline 或 Roo Code 的 MCP 配置里。原因是 Base URL 填成了带路径的完整 endpoint,比如https://taotoken.net/api/v1/chat/completions。正确做法是只填https://taotoken.net/api,让客户端自己拼路径。另外检查一下有没有多余的尾部斜杠,https://taotoken.net/api/和https://taotoken.net/api在某些客户端里行为不一样。

reading choices 报错:这个一般出现在 OpenAI 兼容通道的响应解析阶段。报错信息类似cannot read property 'choices' of undefined。原因是模型返回的 JSON 结构跟客户端预期的不一致。排查步骤:先用 curl 直接打 TaoToken 的 API,看返回体里有没有choices字段:

curl -s https://taotoken.net/api/v1/chat/completions \ -H "Authorization: Bearer sk-你的实际key" \ -H "Content-Type: application/json" \ -d '{"model":"qwen3-coder-plus","messages":[{"role":"user","content":"hi"}]}' | jq '.choices'

如果 curl 能拿到choices,但 Qwen-Code 里报错,那就是客户端把响应包了一层,或者 Model ID 写错了导致路由到了不兼容的通道。检查config.toml里的model_id是否跟 TaoToken 控制台里开通的通道名完全一致。

OAuth 相关报错:如果你在 Claude Code 里看到 OAuth 报错,比如OAuth token expired或invalid_grant,说明客户端在尝试走 OAuth 流程而不是 API Key。解决办法是在 settings.json 里显式配置ANTHROPIC_API_KEY,并且确保没有同时存在 OAuth 相关的配置项。Claude Code 的auth.json里如果残留了旧的 OAuth token,可以删掉或者重命名为auth.json.bak,让它重新走 API Key 认证。

还有一个容易忽略的点:ACP 子进程的startup_timeout_ms设得太短,会导致子进程还没初始化完就被宿主杀掉,报错信息可能是subprocess exited with code 1但没有具体原因。把startup_timeout_ms从 5000 调到 15000,再跑一次,通常就能看到真正的错误输出了。

6. 选型建议与接入入口

回到最初的问题:到底该用 ACP 还是 AG-UI?我的判断逻辑是这样的。

如果你做的是本地 CLI 工具、VS Code 插件、或者桌面客户端,Agent 逻辑跑在本地子进程里,不需要浏览器渲染,那就只用 ACP。配置简单,没有网络开销,子进程崩溃也不影响宿主。config.toml里[agui] enabled = false,桥接服务都不用起。

如果你做的是 Web 前端,Agent 逻辑跟 HTTP 服务同进程,没有子进程隔离需求,那就只用 AG-UI。后端直接吐 SSE 事件,前端监听TEXT_MESSAGE_CONTENT做打字机效果。这种场景下不需要 qwen-acp 子进程,也不需要协议桥。

如果你既要 Qwen-Code 的子进程隔离能力,又要 Web 前端可视化,那就必须双协议配合。链路是:浏览器 → AG-UI SSE → 桥接服务 → ACP stdio → qwen-acp 子进程。桥接层负责把agent.stream_chunk映射成TEXT_MESSAGE_CONTENT,把agent.run_complete映射成RUN_FINISHED。

配置层面,TaoToken 的 Key 和 Base URL 在两套协议里是共用的。ACP 侧通过config.toml的[model]段读取,AG-UI 侧如果桥接服务也需要调模型,就在桥接配置里再引一次环境变量。统一用https://taotoken.net/api作为 Base URL,Key 从控制台生成。

如果你还没生成 Key,可以去控制台创建一个:https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=。创建完之后在 API Keys 页面复制:https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=。接入文档在https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=,里面有各客户端的配置示例。

想先验证模型通道通不通,可以用模型对话页面直接发一条消息:https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=。如果对话正常返回,说明 Key 和 Base URL 没问题,再去配 Qwen-Code 的 ACP 或 AG-UI 就不会卡在认证上。

长期做编码 Agent 的话,Coding Plan 比按量计费更划算:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=。Claude Code 用户如果走 Anthropic 兼容通道,配置参考:https://taotoken.net/claude-code-anthropic?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=。

最后提醒一句:ACP 和 AG-UI 的报文抓取最好在开发阶段就打开日志,--log-level debug会往 stderr 打完整的 JSON-RPC 收发记录。上线前记得把日志级别调回info,不然流式输出量大时日志文件会涨得很快。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/30 20:05:18

ToolTrain 实战:用 LLM 做资源库深度搜索与问题定位的配置指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/30 20:02:30

目标检测数据集格式转换实战:VOC、COCO、YOLO互转全攻略

做目标检测的&#xff0c;早晚都会撞上这么一堵墙&#xff1a;模型结构和训练代码都准备好了&#xff0c;结果手里的标注数据格式对不上。别人交付的是VOC格式的xml&#xff0c;你的训练脚本只认YOLO格式的txt&#xff1b;从开源项目里扒下来的是COCO格式的json&#xff0c;你的…

作者头像 李华
网站建设 2026/9/30 20:02:07

Trae AI 编程工具配 TaoToken:settings.json 骨架与报错排查

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/30 20:01:56

WorkBuddy 实战指南:从安装到本地部署,AI Agent 工作台避坑全攻略

1. 为什么我要认真写这篇 WorkBuddy 实战指南 第一次接触 WorkBuddy 是在一个赶项目的深夜。当时手里压着三份文档要整理、一个数据清洗脚本要调、还有一堆重复性的表格要合并&#xff0c;人已经麻了。同事甩过来一句“你试试 WorkBuddy&#xff0c;腾讯那个 AI 工作台”&#…

作者头像 李华