Skip to content

通过 API 调用智能体 ​

连接 AI 助手

创建智能体开发服务平台(AgentWorks)的渠道账号和 Token 后,即可通过 HTTP 发送消息、通过 WebSocket 接收智能体回复。本文用 curl 和 websocat 演示;它们不是接入要求,也可以使用你熟悉的客户端或应用中的 HTTP、WebSocket 库。

创建 WebSocket 渠道账号 ​

准备一个已经能在控制台正常对话的测试智能体实例,首次测试使用实例绑定。模板绑定的初始化方式见使用模板绑定。

  1. 打开 渠道账号。

    也可以从控制台左侧导航进入 渠道账号。

  2. 选择 新建账号,在 渠道类型 中选择 WebSocket,并填写 账号名称。

  3. 在 绑定模式 中选择 绑定 Agent 实例(默认),再通过 绑定对象 选择测试智能体实例。

  4. 按需选择 同步创建 Token(可一次创建多个)。为不同客户端填写不同的 Token 名称和有效期;同一个渠道账号内的 WebSocket Token 名称不能重复。

  5. 选择 创建。

信息

“同步创建”指随渠道账号一并签发

新建 WebSocket 渠道账号时,该选项默认选中;不需要立即签发 Token 时,请取消选择。每项配置都会生成一个独立的 wst_ Token,所有 Token 均绑定到新建的渠道账号;平台不会把 Token 自动分发给客户端。

创建成功后,所有 Token 明文会在同一弹窗中一次性显示。请分别复制到对应客户端的密钥存储;关闭弹窗后无法再次查看原值。不要把 Token 写入前端代码、日志或文档。

同一租户内,一个智能体实例或智能体模板只能绑定一个尚未删除的 WebSocket 渠道账号。已经停用的账号仍保留这个绑定目标。如果多个客户端需要连接同一个绑定目标,应在同一个渠道账号下分别创建 Token,而不是为同一个绑定目标创建多个渠道账号。

为客户端创建 Token ​

为不同客户端分别签发 Token,便于单独轮换和停用;测试服务和生产服务也使用各自的 Token。如果创建渠道账号时已经为当前客户端同步创建并保存了 Token,可以直接准备连接地址和运行环境;否则从渠道账号详情创建:

  1. 打开目标 WebSocket 渠道账号,进入 Token。

  2. 选择 新建 Token,填写能够识别客户端和环境的 Token 名称,并按需设置 有效期。从账号详情创建时,Token 会自动绑定当前渠道账号。

  3. 选择 创建 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 都使用这个名称。它不是另一种通信协议,调用时请保留路径和子协议标识的原样写法。

  1. 本文使用 AgentWorks 生产环境地址,下面的地址可直接使用。如使用其他环境,请替换为对应地址。

  2. 如果跟随本文的命令行演示,在受控开发机或服务端准备 curl 7.76.0 或更新版本,以及 websocat 1.x,并用 curl --version 和 websocat --version 确认版本。使用其他客户端时无需安装这两个工具,按 API 参考配置鉴权和子协议即可。

  3. 下面的命令行演示需要两个终端。在两个终端中分别执行以下配置;环境变量不会在两个终端之间自动共享。命令适用于 Bash 和 Zsh:

    language-bash
    export 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_TOKEN

    BRIDGE_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 运行:

language-bash
websocat --text --protocol infini.bridge.v2 \
  "$BRIDGE_WS_URL" -H="Authorization: Bearer $BRIDGE_TOKEN"

hello 表示连接已就绪,尚未提交消息或订阅会话。后续订阅 JSON 直接输入这个终端,不是在另一个 Shell 中执行。

在终端 2 发送消息 ​

执行以下命令。CHAT_ID 为本次测试生成新的会话标识;下面的 JSON 只将 $CHAT_ID 替换为这个值,不包含 Token。

language-bash
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。例如,成功响应包含:

language-json
{
  "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 并按回车:

language-json
{"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 的输出中确认以下结果:

  1. 已收到 subd.d.ok = true,表示订阅成功。
  2. 在 t = ev 的帧中,sid 等于刚才的 context_key,mid 等于刚才的 msg_id,且 sub 为空或不存在。读取其中 d.kind = message_delta、d.message_delta.kind = text 的文本,将 d.message_delta.text 按顺序拼接,应得到“接入测试成功”等预期回复。
  3. 同一轮主智能体运行收到 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 发送消息、收集回复。等智能体这次处理成功结束,它会把回复、消息标识和会话标识交给你的应用。

language-javascript
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;
}

在刚才设置环境变量的终端执行:

language-bash
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 沿用上面的示例函数;其他语言也可按相同的请求和事件规则实现。

language-javascript
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,参数值不能含空格。涉及密码或访问令牌时请谨慎:这些值会经过渠道消息,具体格式和凭证注意事项见通过渠道初始化时提供参数。

首次接入建议先用实例绑定验证通信和工具闭环,再按模板绑定流程验证按用户初始化。

已有客户端升级时,对照 API 更新日志检查影响;旧协议的改造步骤集中在迁移旧版智能体接入。