Agent 对话 API
用一个 API 密钥和 AI SDK,把 Agent 接到你自己的聊天界面上,比如文档站的 Ask AI 面板或客服窗口。
Agent 对话 API 可以把 Agent 接到任何基于 AI SDK useChat 的聊天界面上(UI message stream 协议 v1)。每一轮对话是一次带 API 密钥的 HTTP POST,回复以 server-sent events 流返回。本站的 Ask AI 面板就是这样实现的。
一共三步:
- 给 Agent 添加 API。
- 创建一个仅限 Agent 对话的密钥。
- 用密钥向对话接口发送每一轮对话。
给 Agent 添加 API
Agent 的所有者添加 API 之后,Agent 才接受 API 调用。打开 Agent,进入 集成 → 添加集成,在 工作流 下选 API,选择 AI SDK UI,点 添加 API。

之后 Agent 的集成页会出现一张 API 卡片。它的 快速开始 里有这个 Agent 的对话接口、curl / Node / 浏览器三种示例,以及能访问这个 Agent 的密钥。移除 API 后,Agent 在一分钟内停止接受调用;覆盖它的密钥对其他 Agent 仍然有效。

创建密钥
在快速开始里点 创建密钥;也可以在 设置 → 服务账号 → 密钥 里为服务账号创建,这样它不依赖任何一个人;或者在 个人资料 → API 密钥 里为自己创建。选项都一样:
- 权限:Agent 对话。
- Agent:指定 Agent,然后选中这个 Agent。
Agent 对话密钥只能用于对话接口,而且只对选中并已添加 API 的 Agent 有效;控制平面的 REST API 不接受它。和所有 API 密钥一样,它只显示一次,默认 90 天后过期。吊销后一分钟内失效。
发送一轮对话
从快速开始复制对话接口,向它发送 POST,带上 Authorization: Bearer <密钥> 和 useChat 的请求体({ id, messages, trigger, … }):
curl -N "$CHAT_URL" \
-H "Authorization: Bearer $AGENTCONNECT_API_KEY" \
-H "Content-Type: application/json" \
-d '{"id":"chat-1","messages":[{"id":"m1","role":"user","parts":[{"type":"text","text":"How do I install the daemon?"}]}]}'对话接口是 <relay-url>/ai-sdk/agents/<agent-id>/chat。在 AgentConnect OSS 上,relay URL 是你的 relay 的公开地址。
请求体里的 id(1 到 128 个字符)标识会话:
- 用一个新的
id发出的第一轮会开启会话,之后用同一个id继续。useChat为每个对话生成一个id,每一轮都会带上。 - 会话属于密钥的所有者。用另一位成员的密钥发送同一个
id,是另一个会话。 - Relay 只读取最后一条用户消息里的文本部分。Agent 的会话里已经有历史,所以更早的消息、文件部分和
data-*部分都会被忽略。如果请求的最后一条消息是在回答 Agent 的提问,则会继续那一轮,见提问与审批。
响应是 text/event-stream,带响应头 x-vercel-ai-ui-message-stream: v1。其中的各部分遵循 AI SDK 协议,流以 [DONE] 结束。
- 一轮对话可能持续几分钟,每 15 秒会收到一条保活注释。
- 请求体最大 4 MiB,一轮的文本最大 128 KiB。
- 一个会话同一时间只运行一轮。
拒绝会在流开始之前以状态码返回,JSON 响应体的 reason 在有机器可读原因时给出原因:
| 状态码 | 含义 |
|---|---|
400 | 没有 id,或者没有带文本的用户消息。 |
401 | 密钥未知、已吊销或已过期。 |
403 | 密钥不是 Agent 对话密钥或完全访问密钥,或者 Agent 还没有添加 API(api_disabled)。 |
404 | 密钥选中的 Agent 不包含这个 Agent,或者你看不到它。 |
409 | 这个会话还有一轮在运行(busy),自上次对话以来 Agent 已迁移(agent_moved),或者回答对应的那一轮已经结束(turn_ended)。遇到 agent_moved 请开启新会话。 |
413 | 请求体或这一轮的文本过大。 |
422 | API 的决策门拒绝了这一轮(declined)。 |
503 | Agent 离线、已暂停或正在排空。 |
流里有什么
| 部分 | 内容 |
|---|---|
text-* | Agent 的回复。 |
reasoning-* | Agent 的思考过程。 |
data-tool | 工具活动:只有标题和状态。 |
data-plan | Agent 的计划。 |
data-notice | 状态提示,比如 “Starting sandbox…”。 |
message-metadata | 会话标题。 |
dynamic-tool | Agent 的提问(agentconnect_ask)或工具审批(agentconnect_approval),见提问与审批。 |
只渲染文本的客户端只会显示回复。useChat 的 stop() 只会中止这次请求:这一轮仍会跑完,输出会进入控制台里这个会话的记录。中断的流暂时无法恢复。
提问与审批
一轮对话可能会停在只有调用方能回答的地方:Agent 提了一个问题,或者它要运行的工具需要批准。流会把它作为一次工具调用(dynamic-tool 部分)交给你,并以结束原因 tool-calls 结束。这一轮会等你回答。
| 工具 | 问什么 | 怎么回答 |
|---|---|---|
agentconnect_ask | 一个问题。input.message 是问题本身,input 里还有可选的 options、一个 text 或 number 输入、多个 fields,或者一个要打开的 url。 | 用 addToolOutput 回答:选中的值,多选时是一个列表,fields 时按字段各给一个值,url 则在打开后原样返回。state: 'output-error' 表示跳过这个问题。 |
agentconnect_approval | 批准一次工具调用。input.tool 是工具名,input.detail 是一行摘要,比如要执行的命令。 | addToolApprovalResponse({ id, approved })。 |
把回答作为下一次请求发出。把 sendAutomaticallyWhen 设为 lastAssistantMessageIsCompleteWithToolCalls 或 lastAssistantMessageIsCompleteWithApprovalResponses,useChat 就会自动发出。Relay 会继续同一轮,剩下的回复流进同一条 assistant 消息。每个工具调用的 callProviderMetadata 标明它属于哪一轮,所以要把流生成的 assistant 消息原样发回。
拒绝审批会立即生效,Agent 的编辑者看不到这个请求。批准是否直接生效取决于 Agent 的聊天中可以做什么:为对话并批准工具调用或更高级别时,批准直接生效,因为对话属于密钥的所有者;为和 agent 对话时,只有能编辑这个 Agent 的人持有的完全访问密钥批准才直接生效。用其他密钥批准时,请求会交给 Agent 的编辑者在控制台里处理,这一轮等他们决定。公开站点可以在代码里拒绝所有审批,访客就永远不会打扰到编辑者;本站的 Ask AI 面板就是这样做的。
一轮对话不会一直等下去:
- 10 分钟没有回答,这一轮会被取消。
- 不回答而是发了新消息,这一轮会被取消,新消息开启自己的一轮。
- 回答的那一轮已经结束时,返回
409和turn_ended。
在 Node 中调用
AI SDK 的 DefaultChatTransport 负责发送每一轮并解析流,需要 AI SDK 6 或更高版本(npm i ai):
import { DefaultChatTransport } from 'ai'
const transport = new DefaultChatTransport({
api: process.env.CHAT_URL,
headers: { Authorization: `Bearer ${process.env.AGENTCONNECT_API_KEY}` }
})
const stream = await transport.sendMessages({
trigger: 'submit-message',
chatId: 'chat-1', // the conversation; send the same id again to continue it
messageId: undefined,
abortSignal: undefined,
messages: [{ id: '1', role: 'user', parts: [{ type: 'text', text: 'How do I install the daemon?' }] }]
})
for await (const chunk of stream) if (chunk.type === 'text-delta') process.stdout.write(chunk.delta)在浏览器中调用
浏览器不能拿到密钥,所以要在你自己的服务器上、对话接口前面放一个路由。它加上密钥,把 useChat 的请求原样转发。Next.js 的路由处理器 app/api/chat/route.ts:
export async function POST(req: Request) {
const upstream = await fetch(process.env.CHAT_URL!, {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.AGENTCONNECT_API_KEY}`, 'Content-Type': 'application/json' },
body: await req.text(),
signal: req.signal
})
return new Response(upstream.body, {
status: upstream.status,
headers: { 'Content-Type': 'text/event-stream', 'x-vercel-ai-ui-message-stream': 'v1' }
})
}页面上让 useChat 指向这个路由(ai 和 @ai-sdk/react,AI SDK 6 或更高版本):
'use client'
import { useChat } from '@ai-sdk/react'
import { DefaultChatTransport } from 'ai'
export default function Chat() {
const { messages, sendMessage } = useChat({ transport: new DefaultChatTransport({ api: '/api/chat' }) })
// Render messages, and call sendMessage({ text }) on submit.
}所有访客的会话都属于密钥的所有者,访客只能访问自己的 chat id 所对应的会话;useChat 生成的 id 是随机的。请在你的路由上对访客限流,AgentConnect 只按会话限制并发,不按访客限流。
本站自己的路由在 agentconnect-md/docs 仓库里,做得更多一些:chat id 由它自己生成,按打开的标签页存进 HttpOnly cookie,面板清空后开启新会话。见 lib/ask-ai.ts。
用决策筛选对话
API 卡片上的 添加决策 给 API 加上和频道 按决策 相同的规则:只有当决策匹配调用的消息时 Agent 才回答,否则接口返回 422。决策无法评估的调用按没有决策门处理。规则里的 最近评估 列出决策判断过的每次调用及原因,只有能编辑这个 Agent 的成员可以看到。
会话与安全
这样开启的会话是密钥所有者的一个普通 webchat 会话,来源显示为这个密钥。服务账号的会话对整个组织可见;个人密钥的会话对该用户私有。用量和其他对话一样计入 Agent;见账单与用量。
故障排查
403“this agent does not accept API calls”:给 Agent 添加 API(见上文)。403“this API key cannot chat with agents”:密钥的权限是 只读。创建一个 Agent 对话密钥。404“agent not found”:密钥选中的 Agent 不包含这个 Agent。- 发送一轮时
409:这个会话还有一轮在运行。等它结束,或换一个新的 chatid。 - 回答时
409turn_ended:那一轮等了 10 分钟没等到回答,或者被新消息取代了。请发一条新消息重新提问。 - 部署后立即出现
503:Agent 所在的 Daemon 正在重连。重试即可。