@cubos/代理服务器
Cubos代理对话的无头回放钩子 API.
没有DOM. 套件不做,不进口 react-dom,所以一样
钩子驱动一个网络应用程序, React Introduct, 或者其他任何内容 React运行. 测试中
如果一个浏览器全局或一个 <div> 潜入.
npm install @cubos/agent-sdk @cubos/agent-sdk-react
整个事情
import { AgentProvider, useConversation } from "@cubos/agent-sdk-react";
function App() {
return (
<AgentProvider
baseUrl="https://agent.acme.com"
getToken={async () => (await fetch("/api/agent-token").then((r) => r.json())).token}
>
<Chat />
</AgentProvider>
);
}
function Chat() {
const [id, setId] = useState<string | null>(null);
const { messages, send, activity } = useConversation(id, {
agentSlug: "support",
onCreated: (conversation) => setId(conversation.id),
});
return (
<>
{messages.map((m) => (
<div key={m.id} data-role={m.role}>
{m.content}
</div>
))}
{(activity.isProcessing || activity.hasPendingTurn) && <Typing />}
<Composer onSubmit={send} />
</>
);
}
getToken 必须返回 短寿命最终用户代号,在服务器上刻出.
Api key是全租户,绝对不能联系客户端. SDK又问了一遍
forceRefresh 401之后,所以过期 为你处理.
两个钩子,你拿着中间的ID
没有钩子拥有"什么对话是开放的"。 那是你的应用程序
状态 —— 以上示例中的组件状态, 真实的路由参数 ——和
把它从包中排除出去是应该的 /c/:id 工作不同步
任何东西.
const [selectedId, setSelectedId] = useState<string | null>(null);
const list = useConversationList();
const conversation = useConversation(selectedId, {
agentSlug: "support",
onCreated: (created) => setSelectedId(created.id),
});
唯一值得了解的行为是: 第一次对话是创建的
消息,而不是用户点击"新聊天"时. 否则,每一个被抛弃的空白
列表中留下了一行空行 . send 调查两起案子,所以你的UI从来没有
特例"还没有谈话" 刚刚通过 null 直至 onCreated
火灾。 已经在屏幕上的信息 幸存的交接.
双钩相通,相向相通,故名相分:
list.hasMore 编号 : list.loadMore 走开 对话框列表· ;
conversation.hasOlder 编号 : conversation.loadOlder 走开 打开
对话的历史。 。 。 .
无限历史,活现
useConversation 打开一页历史并点出实时流
那页的结尾,所以一年来的对话 需要开一页
并且每次重新连接都要花费一页.
const { messages, hasOlder, isLoadingOlder, loadOlder } = useConversation(id);
// Whatever your list uses to notice the top: a scroll handler, an
// IntersectionObserver on a sentinel, FlatList's onEndReached inverted.
const onReachedTop = () => {
if (hasOlder && !isLoadingOlder) void loadOlder();
};
两个细节 为你处理,一个不是:
- 道口无隙. 溪流从页面光标恢复 服务器在启动前重放所有经过它的东西, 因此发送信件时 页面在飞行中到达 而不是消失.
- 秩序稳定. 页面、活框和你自己的乐观信息 由 id 合并,按顺序排序, 所以一个页面登陆 中转不能洗 记录.
- 螺旋锚是你的. 将列表向上增加,而
滚动偏移保留, 它读作跳跃 。 记录
scrollHeight在此之前 电话loadOlder,然后在布局效果中添加差异——或者,在 网络,请useTranscriptAnchor从@cubos/agent-sdk-react-dom,哪个 正好是这个加上了寻呼触发器.
重新打开用户已经访问过的对话
基础 SDK 缓存已装入对话框( 默认为内存) 和这些钩子
打开它,在留言到达时录音,然后恢复
缓存光标的流出,所以没有重放。 配置或替换
关于客户端的(cache, (中文(简体) ), cacheMessageLimit);见 @cubos/agent-sdk 读取.
loadOlder 决定它实际添加了多少信息。 历史页
以 事件,而不是通过信息,一个答复后面的工具呼叫可以填补一个
所以钩子一直呼唤 直到你没有
已经占领了土地, 一个呼叫总是 一个明显的后退一步。 读取 0
合并 hasOlder数字 : false 是谈话的开始, true
(rare-扫描击中了它的页盖)表示再次呼叫.
代理回复中的组件
给钩子一张地图 渲染器 加减 组件库 此屏幕 可绘制,且该剂可在回复中使用这些组件:
const { messages, renderMessage } = useConversation(id, {
components: {
Choices: (props) => <Choices {...props} onPick={send} />,
},
componentLibraries: ["support-widgets"],
renderMarkdown: (text) => <Markdown>{text}</Markdown>,
});
// In your bubble. Null means the message has no blocks — every user message —
// so that is where your own rendering of `content` goes.
{
renderMessage(message) ?? message.content;
}
这两个部分有不同的主人,这是有意的。 经纪人是什么
告诉 关于一个组件——它的总结,每个道具的含义,JSON Schema,
实例——生活在一个组件库中,操作员的作者在
管理API,因为这些词 进入模型的背景逐字。 什麽
绘图 这是你的。 您选择您拥有的屏幕所应用的库
打开; 钩子可以让他们在对话中, 在创建时聊天
从这里开始,从一个 PUT 当列表更改时.
服务器拒绝任何被启用的库无法解析的组件,并且 在信件被写入前 一个造型师从来没有看到它没有要求的形状. 无渲染器绘图的标签 没有,钩子记录了一个警告 它命名,因为这是一个无声的空白 回复。 反之亦然 保存组件的渲染器无 继续提供图书馆服务,因为这就是画 旧消息在 记录.
renderMarkdown 将散文画入组件周围。 没有它,减分
制作纯文本: 此软件包没有解析器, 您的应用程序已经
一个.
客户端工具: 代理机在您的应用程序中调用功能
useConversation(id, {
clientTools: {
get_browser_context: {
description: "Timezone, locale and local time, as this browser reports them.",
inputSchema: { type: "object", properties: {} },
readOnlyHint: true,
handler: () => ({ timezone: Intl.DateTimeFormat().resolvedOptions().timeZone }),
},
},
onClientToolError: (err) => console.error(err),
});
代理称, 转会暂停你的管家跑来跑去 轮到 带着你回来的回来 处理器会随失败而发出答案 而不是离开代理 等待期限.
这对你来说有两件事 很容易用手做错
- 一道溪来,不是两道. 跑步者已经骑着车钩了
持有 -- -- a
client_tool_call框架推动它,每一次重联,所以 连接下行时发出的电话被接上而不是留到 过期。 在钩子外做这个意味着 第二次SSE 连接每 对话,针对一个服务器,该服务器会限制用户可持有多少个. - 初转正前所宣说. 在空白板块上宣布工具 与创造对话的相同呼吸,所以代理已经可以到达 他们在初次答复时.
租赁 续约 慢处理器运行 重试结果
POST是SDK的基地,是SDK的基地 serveClientTools; 钩子只决定何时开始
并停下来.
水面 水面
| 钩子 | 它给你什么 |
|---|---|
useConversationList(opts) | 现场名单,加 archive, (中文(简体) ), loadMore |
useConversation(id | null, opts) | 信件、 待办事宜、活动、, send, (中文(简体) ), sendAudio, (中文(简体) ), sendImages, (中文(简体) ), loadOlder, (中文(简体) ), renderMessage |
useIdentity() | 何人的行为,以及它可能使用的代理人 |
useAgentClient() | 基础 AgentClient,对于任何没有包裹 |
useConversation(null) 有效且不打开连接——即空白
在谈话被选中之前先排队.
名单和谈话都是 现场直播: 钩子装入一页 历史中,请遵循该页结尾处的SSE流,然后从 准确的一点一滴。 民意调查 无效调查.
当溪水不活
投放是普通的, SDK 处理它: 它与回放重接, 重放 光标不会漏掉任何东西, 如果旧光标过期, 薄荷会重新表示 {\fn华文楷体\fs20\bord1\shad1\1cHE0E0E0\2cH000000}被活活活活的溪流所活活了 {\fn华文楷体\fs20\bord1\shad1\1cHE0E0E0\2cH000000}被活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活 连接 无声 - 一个电话 离开背景, 代理挂断了 由监视器在~30s内重新打开,因为服务器发送一个 每15秒和连续2次失手 都意味着插座不见了.
SDK无法修复的, 它报告. useConversation 返回 :
| 外地 | 含义 |
|---|---|
isStreamLive | 现在连接。 第一次连接之前和一次空投与其再连接之间都是虚假的——谨慎的"重接..."提示,绝不出错 |
streamError | 订阅 死了,不会回来: 令牌被拒绝两次, 对话已去, 或者服务器拒绝流 |
streamError 无法自行推断
因此,特工就再也不回答问题了,所以请表明这一点。 没有恢复
从它自动: 重挂起钩( 或更改) conversationId 转回
重新订阅. useConversationList 自己报告同样的事情
streamError。 。 。 .
信件
interface Message {
id: string;
role: "user" | "agent";
content: string;
attachments: Array<{
id: string;
kind: "image" | "audio";
mimeType: string;
bytes: number;
label: string | null;
}>;
seq: number;
at: string;
}
媒体挂断 attachments,以及 content 是随它而来的文字
图像标题,语音音符的转录. 两者都可以空出这样
提交 attachments 即使当 content 是空白的.
语音信息是一体的 Message 携带剪辑 财务报告和审定财务报表 页:1
复制, 在代理的 STT 模型完成前是空的 —— 您的提示
使玩家具有“trantext”状态.
字节来自 useAgentClient().fetchAttachment(conversationId, message.id, attachment.id)作为 Blob 而不是 URL: 信使在信头中行走, 所以
a 一个 <img src> 指向航道时,会到达未经认证。 把它包起来
平台,并在视图消失时发布.
发送图像是该图像的镜像 :
// Up to 10 as ONE message, so the agent reasons over the set rather than
// spending a turn per picture. `caption` becomes the message's text.
await sendImages(
[
{ image: receipt, label: "receipt" },
{ image: statement, label: "statement" },
],
"which charge is duplicated?",
);
A 类 label 命名一个模型的图像, 它可以回答“ 收据 ”
而不是"第二个图像".
send 附加乐观,并与服务器的回声调和,滚动
如果发送失败, 乐观复制返回 。 一个信息谁 id 开始于
optimistic: 尚未确认.
渲染
这个包裹是故意运送任何部件和CSS的——这就是保持它的原因
运行在 React 运行的地方。 真正通用且确实需要的棋子
dOM,住在隔壁
@cubos/agent-sdk-react-dom数字 : AgentMarkdown 制作一个
代理回复(GFM,栅栏码,TeX)和样式本身.
import { AgentMarkdown } from "@cubos/agent-sdk-react-dom";
useConversation(id, { renderMarkdown: (text) => <AgentMarkdown>{text}</AgentMarkdown> });
Chat UI proper——泡泡,作曲,对话列表——是产品不同之处 大部分, 和一组样式化的组件 对于它要么太僵硬 无法使用或者太 相会可作供养.
renderMessage 这是唯一的例外,它没有使自己的问题:
将散文交给
马克倒置渲染器. renderBlocks 没有钩子的函数是相同的,因为
您已持有信件 .
完整的例子——信息泡沫,一个有图像的作曲家
附件和语音记录、代理服务器组件、客户端工具、
对话列表——参见"仪表盘" packages/web/components/chats/ 输入
寄存器,它建立在这些钩子上.
它写在这些钩子的背面 并打算复制和改变,不是
已安装.
原生反应
取钩入地清净. 在底部SDK有两个警告:
- 流来. 实时更新使用 SSE over
fetch响应流。 反应 本地默认的网络不流响应机构; 您需要抓取 实际的多填充(react-native-fetch-api与react-native-polyfill-globals),作为fetch。 。 。 . - 传媒.
sendAudio财务报告和审定财务报表sendImages带Blobs,和fetchAttachment手一个回来。 录音,取出一幅相片,然后把一个blob变成某种东西 可显示性都是针对特定平台的——使用平台提供的内容.
组件和客户工具不需要额外的设备 : renderMessage 构建元素
与 createElement,而处理器是一个函数。 render 是一个土著
和任何其他组件一样.