会话界面与流式状态
围绕任务身份、消息版本、内容安全和终态建立可靠的聊天与工具反馈界面。
消息是有身份与版本的内容
会话保存的是用户消息、模型结果、工具请求与工具回执。模型生成中的文本是当前消息的部分内容;消息完成后才能作为最终回答进入后续流程。用户编辑旧问题时会形成新的上下文分支,需要记录父消息或所用上下文版本。
界面可使用 idle → submitting → streaming → completed 的正常路径,以及 failed、cancelled 终态。网络断开只说明客户端失去连接,服务端任务可能仍在运行。用户恢复连接后用任务 ID 查询状态,避免再次点击提交制造第二次调用。重新生成回答应产生新消息版本,让用户知道比较的是哪个结果。
后端无关的组件库和绑定特定流协议的 hooks 处在不同层次。选择组件前先确认消息类型、工具事件和流协议是否适配已有服务。assistant-ui 概览 可作为组件与 runtime 的候选入口;使用 AI SDK UI 时按其消息协议接入后端。组件展示能力不能替代服务端任务治理。
流事件需要关联和终态
SSE、WebSocket 或 HTTP 流都可以承载模型事件。每个事件至少关联任务、消息及顺序;文本增量、工具调用增量、工具结果、用量和完成事件有各自类型。供应商事件可能按内容块或索引拆分,适配层先归并,再输出应用协议。
type StreamEvent =
| { type: 'text'; taskId: string; messageId: string; seq: number; delta: string }
| { type: 'tool-result'; taskId: string; callId: string; seq: number; result: unknown }
| { type: 'done'; taskId: string; messageId: string; seq: number }
| { type: 'error'; taskId: string; seq: number; code: string };这个类型表达事件契约。恢复传输时,还需要服务端定义事件 ID 的保留期限、是否可以续传和重复事件去重方式。浏览器收到旧任务的晚到事件时,按任务 ID 更新对应消息;不要把它附加到用户刚开始的新回答中。
结构化输出在流中可能是部分 JSON。可以提供预览,最终消费以完成后的验证结果为准。工具参数也应在完整接收、解析与授权后执行。视觉上“参数已出现”不能触发写业务系统。
AI SDK 的服务端与 React 接线
以下为 AI SDK transport 接线示例,依赖 ai、@ai-sdk/react 与 React。服务端 route 函数适用于采用标准 Request/Response 的框架;鉴权、限流和消息大小校验在调用模型前完成。模型标识从服务端环境读取。
import { convertToModelMessages, streamText } from 'ai';
export async function POST(request: Request) {
const { messages } = await request.json();
// 此处应核对会话身份、消息形状、附件与总输入预算。
const model = process.env.AI_MODEL;
if (!model) throw new Error('AI_MODEL is required');
const result = streamText({
model,
messages: await convertToModelMessages(messages),
abortSignal: request.signal,
onError: ({ error }) => {
// 写入带 runId 的服务端错误记录,正文与凭据需脱敏。
console.error(error instanceof Error ? error.name : 'stream_error');
},
});
return result.toUIMessageStreamResponse();
}'use client';
import { useState } from 'react';
import { useChat } from '@ai-sdk/react';
import { DefaultChatTransport } from 'ai';
export function Chat() {
const [input, setInput] = useState('');
const { messages, sendMessage, status, stop, error } = useChat({
transport: new DefaultChatTransport({ api: '/api/chat' }),
});
const busy = status === 'submitted' || status === 'streaming';
return (
<section>
{messages.map((message) => (
<p key={message.id}>
{message.parts.map((part) => part.type === 'text' ? part.text : '').join('')}
</p>
))}
<form onSubmit={(event) => {
event.preventDefault();
if (busy || !input.trim()) return;
sendMessage({ text: input });
setInput('');
}}>
<input value={input} onChange={(event) => setInput(event.target.value)} />
<button disabled={busy || !input.trim()}>发送</button>
{busy && <button type="button" onClick={() => stop()}>停止</button>}
</form>
{error && <p role="alert">请求未完成,请查看已保存的结果后重试。</p>}
</section>
);
}这是文字路径的教学示例,未运行页面。真实应用需渲染工具和文件 parts,并处理审批、引用和失败。useChat 的 transport 与输入状态约定在不同大版本间有迁移成本;React 组件不能直接搬到其他框架。AI SDK useChat 参考。
显示内容保持清晰与受控
Markdown 渲染需要控制原始 HTML、链接协议与外部图片。来源链接带来源名称和定位,让用户能核对断言。工具结果中过长的原始数据可折叠,关键状态与业务回执放在可见位置。读取中、等待审批、执行中和结果未知分别表达;“完成”应对应外部效果已确认的任务。
自动滚动只在用户仍接近列表底部时进行。用户向上阅读时显示有新消息的提示,避免反复抢位置。重试按钮对应失败消息,停止按钮对应运行中的任务;界面无需同时堆叠含义相近的多个操作。
上传文件先显示解析状态、文件身份和可用范围。多模态预览可与模型实际收到的文件版本不同,必须绑定同一个资源 ID。删除会话、删除上传文件和撤销外部动作是不同操作,由各自的存储和权限边界处理。
取消和审批的结果落到状态里
“停止生成”要将取消信号传到服务端和供应商适配器。服务端仍可能已产生费用,已完成的工具动作也会保留。取消后查询尚未确认的动作回执,界面据此展示任务实际结果。
审批界面应呈现操作对象、具体参数、影响范围和审批有效期。审批通过后若参数改变,应用重新核对授权范围。让用户确认一句抽象的“允许 Agent 执行”无法表达具体业务授权。
验收可以覆盖同一任务重复提交、旧事件晚到、流中断、用户切换会话、编辑历史、工具审批超时和取消后的外部回执。本页列出需要验证的场景,本轮没有进行浏览器或实际应用观察。
工具审批进入同一消息流
审批事件携带工具调用 ID、工具名、经过验证的参数摘要、目标资源和有效期限。用户确认后,服务端重新核对当前身份、参数和资源版本,再恢复该调用。修改参数、换用户或资源状态发生变化时,原审批是否仍有效需由策略判断。
AI SDK 7 在调用或 agent 配置上用 toolApproval 声明审批策略,客户端通过审批响应恢复;v6 的工具级 needsApproval 在 v7 已弃用。assistant-ui 的工具界面和 CopilotKit 的人在环路能力提供呈现与传输原语。实际授权在服务器工具入口执行。MCP Apps 的工具 UI 还需 iframe 隔离和来源核对;AG-UI 描述 agent 与用户界面的事件,MCP 描述工具接入,两者不互相替代。AI SDK 7 审批迁移、AG-UI。
最后更新于