轻盈的鱼

← 返回归档

Crispy Admin AI 助手:前端 SSE 流式对话实现解析

日期:2026/07/09分类:AI 技术标签:SSEAI Agent流式传输Payload CMSNext.js

Crispy 3 后台 AI 助手(/admin/ai-agent)通过 POST /api/ai/agent 与浏览器通信。响应类型为 text/event-stream(SSE),而非 WebSocket 或一次性 JSON。本文从协议、客户端解析、服务端推流到上游 LLM 串联,说明「为什么能边生成边显示」。

1. 请求概览

Admin 对话使用一条 POST 请求,携带 Cookie 鉴权与会话上下文:

1POST /api/ai/agent
2Content-Type: application/json
3Cookie: payload-token=...
4
5{
6 "sessionId": 12, // 可选;新会话省略
7 "messages": [
8 { "role": "user", "content": "列出最近 5 篇文章" },
9 { "role": "assistant", "content": "..." },
10 { "role": "user", "content": "把第一篇标题改成..." }
11 ]
12}

服务端校验 Admin 登录与 canUseAiAgent 权限后,创建或追加 ai-chat-sessions,再返回 SSE 流。

2. 为何不用 EventSource

浏览器原生 EventSource 只支持 GET,无法 POST 携带 messages 历史。因此 Crispy 使用 fetch + response.body.getReader() 手动解析 SSE 帧。解析逻辑集中在 consumeAgentStream.ts,前台助手与后台 Agent 共用。

3. 客户端:useAiAgentChat

Hook 位于 src/components/AdminAiAgent/useAiAgentChat.ts,核心流程:

  • 乐观更新 UI:先插入 user 消息与 loading 中的 assistant 占位

  • fetch POST /api/ai/agent,credentials: include 携带 Admin Cookie

  • 调用 consumeAgentStream 逐块读取响应

  • onSession 保存 sessionId;onText 拼接全文;onToolStart/onToolResult 更新工具状态

  • 流结束后 refreshSessions 刷新侧边栏会话列表

1const res = await fetch('/api/ai/agent', {
2 method: 'POST',
3 credentials: 'include',
4 headers: { 'Content-Type': 'application/json' },
5 body: JSON.stringify({ sessionId, messages: history }),
6})
7
8await consumeAgentStream(res, {
9 onSession: (id) => setSessionId(id),
10 onText: (_chunk, fullText) => updateAssistantContent(fullText),
11 onToolStart: (id, name, args) => appendRunningTool(id, name, args),
12 onToolResult: (id, name, result) => markToolDone(id, result),
13})

4. SSE 解析:consumeAgentStream

getReader() 循环 read(),按空行切分 SSE 事件,解析 data: 行中的 JSON:

1while (true) {
2 const { done, value } = await reader.read()
3 if (done) break
4 buffer += decoder.decode(value, { stream: true })
5 // 按 \n\n 切分,解析 data: {...}
6 if (event.type === 'text') {
7 fullText += event.text
8 callbacks?.onText?.(event.text, fullText)
9 }
10}

5. SSE 事件类型(AgentStreamEvent)

  • session — 会话 ID(仅后台 Agent,流开始时推送)

  • tool_start — Function Calling 开始(如 find_documents)

  • tool_result — 工具执行结果或 error

  • text — 模型输出文本增量,客户端拼接为完整回复

  • done — 本轮结束

  • error — 错误信息

示例响应体:

1data: {"type":"session","sessionId":42}
2
3data: {"type":"tool_start","id":"call_1","name":"find_documents","args":{"collection":"posts"}}
4
5data: {"type":"tool_result","id":"call_1","name":"find_documents","result":{"totalDocs":5}}
6
7data: {"type":"text","text":"找到"}
8data: {"type":"text","text":" 5 篇"}
9
10data: {"type":"done"}

6. API 路由:ReadableStream 推流

src/app/(payload)/api/ai/agent/route.ts 创建 ReadableStream,每 yield 一个事件即 enqueue:

1const stream = new ReadableStream({
2 async start(controller) {
3 const send = (data) => {
4 controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}\n\n`))
5 }
6 send({ type: 'session', sessionId })
7 for await (const event of runAiAgentStream(req, messages)) {
8 send(event)
9 if (event.type === 'done' || event.type === 'error') break
10 }
11 controller.close()
12 },
13})
14
15return new Response(stream, {
16 headers: {
17 'Content-Type': 'text/event-stream; charset=utf-8',
18 'Cache-Control': 'no-cache, no-transform',
19 Connection: 'keep-alive',
20 },
21})

连接在 done/error 之前保持打开,因此浏览器可持续收到增量,无需等待整段回复生成完毕。

7. 推理层:runAiAgentStream

src/ai/agent/runAgentStream.ts 负责工具循环(最多 8 轮):

  • 调用 openAiChatCompletionWithToolsStream,body 设 stream: true

  • LLM 每吐出一个 token → yield { type: text, text }

  • 若模型返回 tool_calls → yield tool_start,执行 executeAgentTool,再 yield tool_result

  • 将 tool 结果追加到 conversation,进入下一轮直到模型给出最终文本

8. 上游 LLM 也是 SSE

openaiCompatible.ts 向 OpenAI 兼容端点 POST /v1/chat/completions,stream: true。提供商按 token 推送 SSE,Crispy 服务端解析后再次转发给浏览器——形成 LLM → Crispy API → 浏览器 的双层流式链路。

9. 会话持久化

与前台助手(仅浏览器内存)不同,后台 Agent 在流开始前写入 user 消息,流结束后将 assistant 回复与 tools 活动写入 ai-chat-sessions Collection。session 事件让客户端在首帧即获得 sessionId,后续请求可带上以续聊。

10. 与前台助手对比

  • 前台:GET/POST /api/ai/assistant,无鉴权,无 session 事件,工具只读检索

  • 后台:POST /api/ai/agent,Admin Cookie,含 session,工具可 CRUD

  • 共用:consumeAgentStream、openAiChatCompletionWithToolsStream、AgentStreamEvent 协议

11. 小结

Admin AI 助手的前端流式体验依赖 SSE:服务端 ReadableStream 持续推送 JSON 事件,客户端 fetch + getReader 边读边渲染。相比一次性 JSON,用户能实时看到工具执行状态与打字机式回复,显著降低长任务等待感。相关源码:useAiAgentChat.ts、consumeAgentStream.ts、api/ai/agent/route.ts、runAgentStream.ts。

版权信息

日期2026/07/09
协议CC BY-NC-SA 4.0 转载请注明出处
评论

暂无评论,来抢沙发吧。

返回顶部