跳到主要内容

对话 (Chat)

对话 API 让你的程序像网页端一样和已发布的 Agent 聊天:发一条消息会创建(或延续)一个对话并路由给 Agent,回复通过 SSE 流式返回,直到本轮结束。 它和 OpenHex 网页端、小程序走的是同一套协议,所以通过 SDK 发起的对话和在应用里发起的没有区别。

SDK 在同一套协议上提供三层用法,按你需要的控制程度选:

层级入口适合
等整轮结果client.sendMessage() / conversation().send()脚本、后端任务、只关心最终文本
流式消费本轮client.runTurn() / conversation().stream()需要逐字输出、实时展示工具调用
协议原语client.chat.send / stream / messages / history / interrupt自己管理游标、断线续传、历史翻页

浏览器里做聊天界面,直接用 React 组件,下面这些它都替你做好了。

创建客户端​

import { OpenhexClient } from '@openhex-ai/agent-sdk';

const client = new OpenhexClient({
apiKey: token, // 个人 API Key,或工作区签发的会话令牌
agentId: process.env.OPENHEX_AGENT_ID, // 新对话默认路由到的 Agent,见「获取 Agent ID」
});

令牌怎么选、Agent ID 从哪来,见鉴权与获取 Agent ID。

发一条消息,拿到整轮结果​

const turn = await client.sendMessage('帮我总结一下最新的客服工单');

turn.text; // 本轮 Agent 回复的全部文本
turn.toolCalls; // 本轮 Agent 调用过的工具:[{ id, name, input }]
turn.conversationId; // 对话 id,继续聊时带上
turn.records; // 本轮收到的全部原始记录
turn.result; // 本轮结束时的 result 事件
turn.lastEventId; // 最后一条记录的游标

sendMessage 会先发消息,再从这条消息之后开始读流,读到本轮的结束事件为止——所以拿到的只有 Agent 对这条消息的回复,不会混进历史。

对话里的第一条消息会让平台为 Agent 拉起运行环境,首轮可能要十几秒甚至更久才出现第一个字。SDK 默认允许连续 180 秒没有任何事件,超过才判定超时(抛 AbortError);需要时用 idleTimeoutMs 调整。

多轮对话​

conversation() 返回一个记住对话 id 的句柄,后续消息自动延续上下文:

const convo = client.conversation();

await convo.send('记住暗号 ORBIT-7,只回复 OK。');
const reply = await convo.send('暗号是什么?');
console.log(reply.text); // → ORBIT-7
console.log(convo.id); // 第一次 send 之后才有值

接续一段已有的对话:

const convo = client.conversation({ conversationId: '已有的对话 id' });

不用句柄也可以,把 conversationId 放进单次调用的选项里:

await client.sendMessage('继续', { conversationId: turn.conversationId });

新开对话,还是回到上一段?​

用会话令牌(工作区成员)或个人账号和 Agent 聊天时,不带 conversationId 发消息,平台会续上这个用户和该 Agent 最近的那段对话——这样同一个人在网页、小程序之间切换,看到的是同一段聊天。

想明确开一段新对话(例如界面上的「新对话」按钮),在协议层发送时带上 newConversation: true:

const { conversationId } = await client.chat.send({
message: '我们重新开始吧',
targetAgentIds: ['你的-agent-id'],
newConversation: true,
});

// 之后用这个 id 继续
const turn = await client.sendMessage('第一个问题是…', { conversationId });

React 组件的「新对话」按钮就是这么做的。

流式输出​

runTurn 发送消息,并逐条产出本轮的记录,读到结束事件时自动结束:

import { extractText, isTurnComplete } from '@openhex-ai/agent-sdk';

for await (const record of client.runTurn('用一句话介绍你自己')) {
if (record.sender === 'assistant' || record.sender === 'agent') {
process.stdout.write(extractText(record));
}
if (isTurnComplete(record)) console.log('\n[本轮结束]');
}

runTurn 不返回对话 id。需要在流式输出的同时拿到 id,用 conversation().stream():

const convo = client.conversation();
for await (const record of convo.stream('帮我查一下订单')) {
process.stdout.write(extractText(record));
}
console.log(convo.id);

从记录里提取信息用这组辅助函数(完整列表见 API 参考):

函数作用
extractText(record)取出记录里的回复文本
extractToolCalls(record)取出记录里的工具调用
isTurnComplete(record)是否为本轮的结束事件
isInterrupt(record)是否表示本轮被打断

记录的结构​

流里的每一条都是一个 ChatStreamRecord:

{
id: '1789367583782-0', // 游标,断线续传时作为 lastEventId 传回
seq: 12,
sender: 'assistant', // 'user' | 'assistant' | 'agent' | 'system'
event: 'message', // 'message' | 'agent_status' | 'attachment' | …
timestamp: 1789367583782,
sessionId: '…',
raw: { type: 'assistant', message: { content: [{ type: 'text', text: '你好' }] } },
}

一轮对话里常见的记录:

event / raw.type含义
message / user你发出的消息(回显)
agent_status / agent_statusAgent 状态变化,如 phase: 'thinking' / 'responding'
message / assistantAgent 的回复内容
attachmentAgent 产出的图片或文件,见附件
message / result本轮结束

raw 里还可能出现卡片类事件(收款、信息收集、连接器配置),分别见支付宝收款、信息收集卡片、接入外部工具。

协议原语​

需要完全掌控时,client.chat 把每个接口 1:1 暴露出来。

发送​

const sent = await client.chat.send({
message: 'hello',
targetAgentIds: ['你的-agent-id'], // 新对话;延续对话则传 conversationId
});
// → { conversationId, userMessage, userEventId, results: [{ agentId, msgId, streamId }] }

send 立即返回,不等 Agent 回复。userEventId 是你这条消息在事件流里的位置,从它之后开始读,拿到的恰好就是这条消息的回复。

读流​

for await (const record of client.chat.stream(sent.conversationId, {
lastEventId: sent.userEventId,
})) {
console.log(record.sender, record.event, record.raw.type);
if (record.raw.type === 'result') break;
}
选项说明
lastEventId从这个游标之后开始读
turns不带游标时,先回放最近几轮历史(默认 10)
includeThinking是否包含模型的思考过程
reconnect断线后自动重连(默认 true,指数退避)
signal用 AbortController 结束读取

stream 是一个长连接:它会一直读下去,直到你 break、signal 中止或服务端结束。只想读一轮,用 client.chat.resumeTurn(conversationId, { lastEventId }),它读到本轮结束事件就停。

历史​

// 整段对话
const { entries } = await client.chat.messages(conversationId);
// entries: [{ id, data: ChatStreamRecord }]

// 往前翻页:取某个游标之前的若干轮
const page = await client.chat.history(conversationId, {
before: entries[0].id,
turns: 10,
});
// → { entries, hasMore }

打断​

await client.chat.interrupt(conversationId);
// 或者对句柄:await convo.interrupt();

取消与超时​

所有方法都接受 signal:

const ac = new AbortController();
setTimeout(() => ac.abort(), 60_000);

await client.sendMessage('一个很长的任务', { signal: ac.signal, idleTimeoutMs: 300_000 });
  • signal:你主动取消。只取消本地等待;要让 Agent 也停下,另外调用 interrupt。
  • idleTimeoutMs:连续这么久没有任何事件才判定超时,默认 180000。Agent 在委派子 Agent 时可能长时间安静,按需调大。
  • 非流式请求(发送、历史、上传等)默认 30 秒超时,可以用客户端配置 timeoutMs 修改。

下一步​