完整示例:接入真实后端

完整示例用 Mock 适配器按脚本回放示例数据。换成真实后端时,可以沿用 <Chatkit> 与 SessionList 的结构和多数配置。 业务卡片的 transformOutput、上传服务和 metadata 传递方式仍需按真实协议调整。下面的代码展示适配器和页面骨架,省略了完整示例中的业务配置。

重新生成、编辑等操作按已提供的能力显示;能力函数仍可能因网络或服务端错误而失败,需要接入 onError。

两条原则:

  • 适配器只做协议翻译。每项能力写一个发请求的函数,请求发到哪、带什么凭据、用哪个模型或应用,都在函数里决定。
  • 不在浏览器存储中持久化对话。刷新后通过历史接口恢复后端已保存的内容;没有历史接口时,内存中的对话会丢失。

下面两个例子的请求都发给自己的代理。直连后端的写法见各协议的页面。

OpenAI 兼容接口

接口本身没有会话,也不保存历史。这个例子里接入方的后端另外提供了会话接口和历史接口,并且能作废旧的一轮。

app.tsx
import { Chatkit, SessionList, type SessionListProps, useChatSessions } from '@volcengine/chatkit';
import { createOpenAiAdapter } from '@volcengine/chatkit/adapters/openai';
import '@volcengine/chatkit/styles.css';

// 请求都发给自己的服务:由它校验登录态、补上模型名和密钥,再转发给模型接口
const send = (method: string, path: string, body?: unknown) =>
  fetch(`/api/chat${path}`, {
    method,
    credentials: 'include',
    headers: { 'Content-Type': 'application/json' },
    ...(body === undefined ? {} : { body: JSON.stringify(body) }),
  });

const adapter = createOpenAiAdapter({
  // 对话:把轮次编号带给后端,历史里用它标明消息属于哪一轮
  run: ({ sessionId, runId, body }) => send('POST', `/sessions/${sessionId}/runs`, { run_id: runId, ...body }),
  // 重新生成、编辑后重发:后端作废 replace_from 那一轮和它之后的内容
  regenerate: ({ sessionId, runId, targetRunId, body }) =>
    send('POST', `/sessions/${sessionId}/runs`, { run_id: runId, replace_from: targetRunId, ...body }),
  edit: ({ sessionId, runId, targetRunId, body }) =>
    send('POST', `/sessions/${sessionId}/runs`, { run_id: runId, replace_from: targetRunId, ...body }),
  // 历史:给了它,刷新后对话才能还原
  history: ({ sessionId, cursor }) =>
    send('GET', `/sessions/${sessionId}/messages${cursor ? `?cursor=${encodeURIComponent(cursor)}` : ''}`),
  // 在浏览器里执行的工具
  tools: {
    query_tickets: {
      description: '查询工单',
      parameters: { type: 'object', properties: { status: { type: 'string' } } },
      execute: async (args, { signal }) => {
        const response = await fetch('/api/tickets/query', { method: 'POST', body: JSON.stringify(args), signal });
        return response.json();
      },
    },
  },
});

export function App({
  sessions: items,
  activeSessionId,
  onSelect,
}: Pick<SessionListProps, 'sessions' | 'activeSessionId' | 'onSelect'>) {
  const sessions = useChatSessions({ activeSessionId, adapter });
  return (
    <div style={{ display: 'flex', height: '100vh' }}>
      <SessionList sessions={items} activeSessionId={activeSessionId} onSelect={onSelect} style={{ width: 260 }} />
      <div style={{ flex: 1, minWidth: 0 }}>
        {/* settings、features、slots、renderers、onSend、onEvent 与完整示例相同 */}
        <Chatkit sessions={sessions} />
      </div>
    </div>
  );
}
给了什么界面上后端要做到详见
run对话补上模型名和密钥,转发给模型接口;存下轮次编号OpenAI 兼容接口
regenerate、edit重新生成、编辑后重发作废指定的一轮和它之后的内容后端约定
history刷新后对话还原按约定的格式返回历史会话与历史从哪来
tools模型调用工具时在浏览器里执行—OpenAI 兼容接口

ADK

会话和历史都在 ADK 服务端。这个例子只给标准 ADK 服务有的能力。

app.tsx
import { Chatkit, SessionList, type SessionListProps, useChatSessions } from '@volcengine/chatkit';
import { createAdkAdapter } from '@volcengine/chatkit/adapters/adk';
import '@volcengine/chatkit/styles.css';

// 请求都发给自己的服务:由它按登录态确定用户,补上应用名,再转发到 ADK 对应的接口
const send = (method: string, path: string, body?: unknown) =>
  fetch(`/api/chat${path}`, {
    method,
    credentials: 'include',
    headers: { 'Content-Type': 'application/json' },
    ...(body === undefined ? {} : { body: JSON.stringify(body) }),
  });
const at = (sessionId: string) => `/sessions/${encodeURIComponent(sessionId)}`;

// 只给标准 ADK 服务有的能力:对话、历史、会话的列出、新建、删除
const adapter = createAdkAdapter({
  run: ({ body }) => send('POST', '/run', body),
  history: ({ sessionId }) => send('GET', at(sessionId)),
});

export function App({
  sessions: items,
  activeSessionId,
  onSelect,
}: Pick<SessionListProps, 'sessions' | 'activeSessionId' | 'onSelect'>) {
  // 打开会话时会话层先取历史
  const sessions = useChatSessions({ activeSessionId, adapter });
  return (
    <div style={{ display: 'flex', height: '100vh' }}>
      <SessionList sessions={items} activeSessionId={activeSessionId} onSelect={onSelect} style={{ width: 260 }} />
      <div style={{ flex: 1, minWidth: 0 }}>
        {/* settings、features、slots、renderers、onSend、onEvent 与完整示例相同 */}
        <Chatkit sessions={sessions} />
      </div>
    </div>
  );
}
给了什么界面上代理要做到详见
run对话按登录态确定用户,补上应用名,转发到 run_sseADK
history刷新后对话还原转发到会话详情接口ADK

完整示例里的功能,在各后端上显示哪些

功能完整示例(Mock 适配器)OpenAI 兼容接口(按上面的写法)ADK(按上面的写法)
会话列表:切换、新建显示显示显示
会话改名显示显示不显示(没给 rename)
会话删除显示显示显示
刷新后还原对话回到预置内容还原还原
重新生成、编辑后重发显示显示不显示(没给这两个函数)
附件(配置上传后)图片、PDF、文本、ZIP、Office 等常见文件图片适配器未限制;仍需后端和模型支持
赞踩、@ 提及、划词追问显示显示显示
插槽、渲染器、onSend、onEvent照常照常照常
  • 表中附件、赞踩和提及沿用完整示例的 features;只复制上面的页面骨架不会启用这些配置。
  • 「不显示」的功能不需要在 features 里关掉。
  • 业务卡片需要后端返回对应的数据,用适配器的 transformOutput 从后端的输出里取,见业务卡片。
  • metadata(完整示例里的 knowledgeBase)怎么进请求由适配器决定:ADK 默认只挂在界面上的用户消息上,OpenAI 兼容接口默认不发,都用 transformInput 放进请求内容。
  • 每项能力对应什么功能见能力一览;后端是别的协议时写一个新的适配器。