通过 API 调用智能体
创建智能体开发服务平台(AgentWorks)的渠道账号和 Token 后,即可通过 HTTP 发送消息、通过 WebSocket 接收智能体回复。本文用 curl 和 websocat 演示;它们不是接入要求,也可以使用你熟悉的客户端或应用中的 HTTP、WebSocket 库。
创建 WebSocket 渠道账号
准备一个已经能在控制台正常对话的测试智能体实例,首次测试使用实例绑定。模板绑定的初始化方式见使用模板绑定。
打开 渠道账号。
也可以从控制台左侧导航进入 渠道账号。
选择 新建账号,在 渠道类型 中选择 WebSocket,并填写 账号名称。
在 绑定模式 中选择 绑定 Agent 实例(默认),再通过 绑定对象 选择测试智能体实例。
按需选择 同步创建 Token(可一次创建多个)。为不同客户端填写不同的 Token 名称和有效期;同一个渠道账号内的 WebSocket Token 名称不能重复。
选择 创建。
信息
“同步创建”指随渠道账号一并签发
新建 WebSocket 渠道账号时,该选项默认选中;不需要立即签发 Token 时,请取消选择。每项配置都会生成一个独立的 wst_ Token,所有 Token 均绑定到新建的渠道账号;平台不会把 Token 自动分发给客户端。
创建成功后,所有 Token 明文会在同一弹窗中一次性显示。请分别复制到对应客户端的密钥存储;关闭弹窗后无法再次查看原值。不要把 Token 写入前端代码、日志或文档。
同一租户内,一个智能体实例或智能体模板只能绑定一个尚未删除的 WebSocket 渠道账号。已经停用的账号仍保留这个绑定目标。如果多个客户端需要连接同一个绑定目标,应在同一个渠道账号下分别创建 Token,而不是为同一个绑定目标创建多个渠道账号。
为客户端创建 Token
为不同客户端分别签发 Token,便于单独轮换和停用;测试服务和生产服务也使用各自的 Token。如果创建渠道账号时已经为当前客户端同步创建并保存了 Token,可以直接准备连接地址和运行环境;否则从渠道账号详情创建:
打开目标 WebSocket 渠道账号,进入 Token。
选择 新建 Token,填写能够识别客户端和环境的 Token 名称,并按需设置 有效期。从账号详情创建时,Token 会自动绑定当前渠道账号。
选择 创建 Token,复制
wst_值并保存到服务端密钥存储。注意
保存 Token 明文
Token 明文只显示一次,关闭弹窗后无法再次查看原值。请保存在服务端密钥存储中,不要写入前端代码、移动应用安装包、日志或版本控制。
需要跨账号查找或创建 Token 时,也可以打开 凭证管理,进入 WebSocket Token,再选择目标渠道账号。
wst_ 绑定到 WebSocket 渠道账号,HTTP 请求和 WebSocket 握手使用同一个 Token。账号的绑定模式和绑定对象决定使用哪个智能体模板或实例;Token 不直接绑定智能体。
同一账号下的不同 Token 具有相同的账号权限,不能代替最终用户授权。你的后端需要根据已认证用户设置 user_id,并检查该用户能否访问目标会话或执行工具。
准备连接地址和运行环境
这组智能体调用接口在平台技术文档中称为 Bridge Channel;HTTP 路径中的 /api/bridge/v1/ 和 WebSocket 子协议 infini.bridge.v2 都使用这个名称。它不是另一种通信协议,调用时请保留路径和子协议标识的原样写法。
本文使用 AgentWorks 生产环境地址,下面的地址可直接使用。如使用其他环境,请替换为对应地址。
如果跟随本文的命令行演示,在受控开发机或服务端准备
curl7.76.0 或更新版本,以及 websocat 1.x,并用curl --version和websocat --version确认版本。使用其他客户端时无需安装这两个工具,按 API 参考配置鉴权和子协议即可。下面的命令行演示需要两个终端。在两个终端中分别执行以下配置;环境变量不会在两个终端之间自动共享。命令适用于 Bash 和 Zsh:
language-bashexport BRIDGE_HTTP_BASE_URL='https://infini-agent.cloud.infini-ai.com' export BRIDGE_WS_URL='wss://infini-agent.cloud.infini-ai.com/ws/v1' read -r -s BRIDGE_TOKEN export BRIDGE_TOKENBRIDGE_HTTP_BASE_URL只填域名地址,不包含/api/bridge/v1/;下面的命令会自动追加/api/bridge/v1/SendMessage。BRIDGE_WS_URL则填写包含/ws/v1的完整 WebSocket 地址。执行
read后粘贴完整的wst_Token 并按回车;输入不会显示,也不会写入命令历史。后面的命令会将 Token 传入进程参数,请使用短期测试 Token,不要在共享终端中操作。
HTTP 和 WebSocket 使用同一个 Token。websocat 可以直接设置 Authorization 请求头,不需要手动编码 Token。其他客户端的鉴权方式见 HTTP 与 WebSocket API 参考。
发送第一条消息并接收回复
以下以 websocat 和 curl 为例:先在终端 1 保持 WebSocket 连接,再从终端 2 提交 HTTP 请求。换用其他客户端时,发送和订阅的顺序相同。WebSocket 用于订阅和接收事件,不能用来发送 SendMessage 请求。
HTTP 返回的 context_key 用于订阅会话,msg_id 用于识别这条输入产生的事件。收到文本后仍需等待同一轮主智能体运行的结束事件,才知道本次调用是否成功。图中展示普通文本调用成功的情况;需要审批或工具结果时,按响应中断处理。
在终端 1 建立连接
执行以下命令,看到 t 为 hello 的 JSON 帧后,保持终端 1 运行:
websocat --text --protocol infini.bridge.v2 \
"$BRIDGE_WS_URL" -H="Authorization: Bearer $BRIDGE_TOKEN"hello 表示连接已就绪,尚未提交消息或订阅会话。后续订阅 JSON 直接输入这个终端,不是在另一个 Shell 中执行。
在终端 2 发送消息
执行以下命令。CHAT_ID 为本次测试生成新的会话标识;下面的 JSON 只将 $CHAT_ID 替换为这个值,不包含 Token。
CHAT_ID="demo-$(date +%s)-$RANDOM"
curl --silent --show-error --fail-with-body --max-time 30 \
"${BRIDGE_HTTP_BASE_URL%/}/api/bridge/v1/SendMessage" \
-H "Authorization: Bearer $BRIDGE_TOKEN" \
-H "Content-Type: application/json" \
--data-binary @- <<JSON
{
"chat_type": "group",
"chat_id": "$CHAT_ID",
"user_id": "demo-user-1",
"content": "请回复接入测试成功,不调用工具。"
}
JSON这里使用 group 会话模式,以便从提交结果直接取得订阅标识,不要求创建实际群聊。本例省略 msg_id,由服务端生成;正式应用可自行提供消息标识,便于追踪请求。
确认响应中的 success_response 为 true,并复制 data.context_key 和 data.msg_id。例如,成功响应包含:
{
"code": 0,
"http_status_code": 200,
"success_response": true,
"data": {
"context_key": "account-1_demo-1234567890-12345",
"msg_id": "6d8c49ab-88fd-411b-9bac-0a6ff9493108"
}
}这些标识以你实际收到的值为准。HTTP 响应只表示消息已接收,不包含最终回答;即使 HTTP 状态为 200,success_response 也可能为 false,此时先按 data.reason 和错误处理排查,不要继续订阅。
回到终端 1 订阅回复
将下面的 session_id 替换为刚收到的 data.context_key,整行粘贴到终端 1 并按回车:
{"t":"sub","d":{"session_id":"<data.context_key>","since_seq":0,"mode":"full"}}since_seq: 0 用于补收提交消息到完成订阅之间的事件,因此不必抢在智能体回复前粘贴。订阅成功后会收到 t 为 subd、d.ok 为 true 的确认,随后收到 t 为 ev 的运行事件。若出现 err、reset 或 d.ok: false,先按 API 参考处理,不要直接重发消息。
验证消息往返
在终端 1 的输出中确认以下结果:
- 已收到
subd.d.ok = true,表示订阅成功。 - 在
t = ev的帧中,sid等于刚才的context_key,mid等于刚才的msg_id,且sub为空或不存在。读取其中d.kind = message_delta、d.message_delta.kind = text的文本,将d.message_delta.text按顺序拼接,应得到“接入测试成功”等预期回复。 - 同一轮主智能体运行收到
d.kind = lifecycle、d.lifecycle.phase = success,表示运行成功结束。不要把子代理的完成事件当成本轮结束。
hello、HTTP 200 或消息已接收都不能单独证明模型回答成功。retry 表示平台正在自动重试,继续等待即可,不要重发消息;error 或 cancelled 才表示失败或取消。pending 事件表示还需要审批或工具结果,处理方法见响应中断。
完成后在终端 1 按 Ctrl+C 关闭连接,再在两个终端分别执行 unset BRIDGE_TOKEN。关闭连接或 HTTP 请求超时并不代表远端运行已取消;需要取消时使用 CancelRun,不要靠重发请求来判断结果。
可选:用 Node.js 自动完成测试
希望自动完成发送、订阅和结果检查时,可使用下面的示例;它不是首次接入的必做步骤。
展开 Node.js 示例
安装 Node.js 24,用 node --version 确认版本。在当前终端完成上面的环境配置后,将以下代码保存为 agentworks-demo.mjs。此示例直接建立 WebSocket 连接,不需要同时运行 websocat。
每次运行都会生成新的测试会话和消息标识,连接成功后只提交一次文本请求。示例中的 callAgent 用 Node.js 自带的 fetch 和 WebSocket 发送消息、收集回复。等智能体这次处理成功结束,它会把回复、消息标识和会话标识交给你的应用。
const { BRIDGE_HTTP_BASE_URL, BRIDGE_WS_URL, BRIDGE_TOKEN } = process.env;
async function callAgent({ content, chatId, userId, msgId }) {
if (!BRIDGE_HTTP_BASE_URL || !BRIDGE_WS_URL || !BRIDGE_TOKEN) {
throw new Error("请设置 BRIDGE_HTTP_BASE_URL、BRIDGE_WS_URL 和 BRIDGE_TOKEN");
}
return new Promise((resolve, reject) => {
const ws = new WebSocket(BRIDGE_WS_URL, [
"infini.bridge.v2", `bearer.${Buffer.from(BRIDGE_TOKEN).toString("base64url")}`,
]);
const controller = new AbortController();
let started = false, finished = false, subscribed = false;
let contextKey = "";
const messages = new Map();
const timer = setTimeout(() => finish(new Error(
"等待超过 90 秒;本地停止等待不代表远端运行已取消。"
)), 90_000);
function finish(error) {
if (finished) return;
finished = true;
clearTimeout(timer);
controller.abort();
ws.close();
if (error) {
reject(Object.assign(error, { chatId, msgId, contextKey }));
} else {
resolve({ contextKey, msgId, messages: [...messages.values()] });
}
}
ws.onerror = () => finish(new Error("连接失败,请检查地址、网络和 Token。"));
ws.onclose = () => finish(new Error("连接提前关闭;示例不会自动重发请求。"));
ws.onmessage = async ({ data }) => {
if (finished) return;
try {
const frame = JSON.parse(data);
if (frame.t === "hello" && !started) {
started = true;
const response = await fetch(
`${BRIDGE_HTTP_BASE_URL.replace(/\/$/, "")}/api/bridge/v1/SendMessage`,
{
method: "POST",
headers: {
Authorization: `Bearer ${BRIDGE_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
chat_type: "group", chat_id: chatId, user_id: userId,
msg_id: msgId, content,
}),
signal: controller.signal,
},
);
const body = await response.json();
if (!response.ok || body.success_response !== true) {
throw new Error(`SendMessage 失败:HTTP ${response.status},${body.data?.reason ?? "请检查响应"}`);
}
if (finished) return;
contextKey = body.data?.context_key;
if (!contextKey || body.data.msg_id !== msgId) {
throw new Error("提交结果缺少会话标识,或消息标识不匹配。");
}
ws.send(JSON.stringify({
t: "sub", d: { session_id: contextKey, since_seq: 0, mode: "full" },
}));
return;
}
if (frame.t === "err") throw new Error(`连接错误:${frame.d?.reason}`);
if (frame.t === "reset" && frame.d?.session_id === contextKey) {
throw new Error("需要恢复会话;请按重连指南处理,勿直接重发消息。");
}
if (frame.t === "subd" && frame.d?.session_id === contextKey) {
if (frame.d?.ok !== true) throw new Error(`订阅失败:${frame.d?.reason}`);
subscribed = true;
return;
}
if (frame.t !== "ev" || frame.sid !== contextKey || frame.mid !== msgId || frame.sub) return;
const event = frame.d;
if (event.kind === "pending" && event.pending_phase === "created") {
throw new Error("本轮需要审批或工具结果;最小示例不自动批准,请按中断指南处理。");
}
if (event.kind === "message_delta" && event.message_delta?.kind === "text") {
const id = event.message_delta.message_id;
if (!id) throw new Error("文本事件缺少 message_id,无法整理回复。");
const message = messages.get(id) ?? { messageId: id, text: "" };
message.text += event.message_delta.text ?? "";
messages.set(id, message);
}
if (event.kind === "lifecycle") {
const phase = event.lifecycle?.phase;
if (phase === "retry") {
const info = event.lifecycle?.error?.retry_info;
// 本例只保留重试后新收到的文本,不把失败尝试的半截输出拼入结果。
messages.clear();
console.log("平台正在自动重试,继续等待原请求。", info ?? {});
}
if (phase === "error" || phase === "cancelled") throw new Error(`运行结束:${phase}`);
if (phase === "success") {
if (!subscribed || ![...messages.values()].some((m) => m.text.trim())) {
throw new Error("运行成功,但尚未确认订阅或收到文本回复。");
}
finish();
}
}
} catch (error) { finish(error); }
};
});
}
try {
const result = await callAgent({
content: "请回复接入测试成功,不调用工具。",
chatId: `demo-${crypto.randomUUID()}`,
userId: "demo-user-1",
msgId: crypto.randomUUID(),
});
for (const message of result.messages) console.log(message.text);
console.log("本轮运行成功结束。");
} catch (error) {
console.error(error.message);
process.exitCode = 1;
}在刚才设置环境变量的终端执行:
node agentworks-demo.mjs示例成功时会显示智能体的文本回复和“本轮运行成功结束。”,进程以状态码 0 退出;出错时打印原因,以状态码 1 退出。运行后可用 unset BRIDGE_TOKEN 清除当前终端中的 Token。
示例用 since_seq: 0 补收事件,再按 sid、mid 和 sub 筛选本轮主智能体输出,并按 message_id 分别拼接文本。callAgent 返回的 messages、msgId 和 contextKey 是本例整理的应用数据;HTTP SendMessage 响应本身只提供消息和会话标识。
收到重试事件时,本例会清空本请求在本地暂存的文本,只采用重试后新收到的文本;这不会清空服务端会话或撤销已执行的操作。它没有完整整理多步任务的重试结果,正式客户端应分别跟踪文本、工具调用及结果,将失败尝试的输出与最终结果分开。
这个单次文本示例不包含重连、持久化或业务结果检查。遇到中断、断线或超时会停止等待,不会自动批准工具、重新发起任务或取消远端运行。正式接入时按会话和中断处理补充这些行为。
接入业务应用
收到第一条回复后,再按应用需要加入审批、工具执行或按用户开通。正式应用应从已认证用户取得 user_id,继续同一会话时复用 chat_id,具体做法见创建和继续会话。
确保客户端库正常响应 WebSocket 协议层心跳;也可主动发送 JSON PING 并检查 PONG。断线后退避重连,并携带该会话最后保存的外层 seq 重新订阅。详细恢复规则见心跳、重连与补偿。
确认结果后再执行下一步
以工单为例:你的应用把问题交给智能体分类,先确认这次处理成功,再检查分类是否为“账号”“账单”或“其他”。确认可用后,才分派工单。如何记住工单和这次请求的对应关系,见保存业务进度和调用记录。
保留上例的 catch,只把 try 中的代码换成下面这段。它会检查分类,但不会调用工单系统的分派接口。callAgent 沿用上面的示例函数;其他语言也可按相同的请求和事件规则实现。
const result = await callAgent({
content: "给这条工单分类:登录时提示账号已停用。仅回复账号、账单或其他中的一个词,不调用工具。",
chatId: `ticket-${crypto.randomUUID()}`,
userId: "demo-user-1",
msgId: crypto.randomUUID(),
});
// 本例只接受一条文本回复,并检查是否为约定的分类。
const category = result.messages.length === 1 ? result.messages[0].text.trim() : "";
if (!["账号", "账单", "其他"].includes(category)) {
throw new Error("分类不符合要求,请转人工检查;尚未分派工单。");
}
console.log("分类符合要求,本例尚未分派工单:", category);真实业务还应检查任务所需的其他字段,例如处理建议是否齐全、目标处理组是否存在。一次运行可能产生多条文本消息,应按业务约定选取输出,具体见整理文本并检查业务结果。不同结果分别按以下方式处理:
| 本次调用的情况 | 工单流程如何继续 |
|---|---|
收到本次运行的 success 事件,分类和所需字段符合要求 | 保存结果,执行分派;记录已经处理过的工单,避免重复分派 |
收到本次运行的 success 事件,但分类或所需字段不符合要求 | 转人工检查或修正结果,不直接分派 |
| 收到审批或工具中断 | 保存等待状态,处理并回传后继续接收事件 |
主智能体 error 或 cancelled | 进入失败或取消分支,核对已完成的业务操作 |
| 等待超时或连接断开 | 保留请求记录,恢复接收并确认原任务结果 |
注意
确认结果后再分派
HTTP 请求被接收时,智能体可能还没完成;连接断开或等待超时后,原任务也可能继续运行。请先确认原任务的结果,并查询分派记录,再决定是否重试,避免重复分派、发通知或写入数据。
接入自己的业务系统前,用测试工单检查以下情况。收到回复后,还要确认工单是否按预期处理:
- 正常完成:只在这次处理成功、分类符合要求后执行下一步。
- 中断等待:等待审批或工具结果时不提前分派;回传后继续接收原任务事件。
- 断线与事件回放:重连后即使再次收到已处理的事件,也不重复显示文本或执行分派、通知。
- 等待超时:保留原请求标识,能够继续确认原任务结果,而不是直接新建任务。
- 结果不符合要求或运行失败:转人工检查或记录失败、取消的原因,不执行下一步。
这些检查使用的状态记录、中断处理和恢复步骤集中在管理会话、运行和中断。
处理审批和用户工具
智能体需要人工批准或业务工具结果时,会暂停并发出 pending 事件。按响应中断实现审批或工具执行,并用 HTTP RespondInterrupt 回传结果。用户工具包提供工具定义,你的后端执行实际业务代码;需要完整业务示例时,参见工单助手实战。
轮换或撤销 Token
先为客户端创建新 Token,建立新连接并验证消息往返,再停止旧连接、停用旧 Token。已建立的 WebSocket 连接可能继续存活,HTTP 请求和新建握手也可能短暂命中鉴权缓存;按分批轮换 WebSocket Token中的步骤确认旧凭证已经失效。该节也说明了需要立即撤销整个账号访问时的处理方式。
停用或删除渠道账号
停用账号会影响该账号下所有客户端。先通知调用方停止发送请求,核对正在运行的任务和业务副作用,再停用并验证消息已停止处理。删除前检查是否仍有业务系统依赖该账号及其绑定目标。
停用不会释放同一租户内绑定目标的账号占用;需要重新规划绑定时,先处理原账号生命周期,不要不断创建重复账号。
使用模板绑定
模板绑定需要用户先发送 /init。通过 SendMessage.content 提交初始化命令,再接收回复;同一用户后续消息使用其已建立的绑定。模板要求参数时使用 /init KEY=VALUE,参数值不能含空格。涉及密码或访问令牌时请谨慎:这些值会经过渠道消息,具体格式和凭证注意事项见通过渠道初始化时提供参数。
首次接入建议先用实例绑定验证通信和工具闭环,再按模板绑定流程验证按用户初始化。