在 CloudBase 上实现流式 chatbot:三种实现路径
一句话定义:把"边生成边显示"的 chat 界面接到 CloudBase 后端有三条主路径——官方
cloudbase-agent-ui小程序组件、手写 SSE 云函数、Vercel AI SDK;本篇按"前端是什么、要不要自定义协议"帮你选一条,并给出最小可跑代码。预计耗时:30–60 分钟(取决于选哪条路径) | 难度:进阶
适用场景
- 想做带流式输出体验的 chat 应用(用户边看边读,不是等几秒一次性显示)
- 后端在 CloudBase(云函数 / 云托管)
- 前端可以是微信小程序 / Web / Next.js / Vue / React Native
- LLM 用混元 / DeepSeek / OpenAI 兼容协议都可
三条路径速览
| 路径 | 适合谁 | 优势 | 限制 |
|---|---|---|---|
| A. cloudbase-agent-ui(官方组件) | 微信小程序开发者 | 官方组件,流式/多轮/上传文件/语音/工具调用全封装;混元/DeepSeek/Agent 三种 chatMode | 目前只发布了微信小程序版,Web/React 版暂无 |
| B. 手写 SSE + 云函数 | 已有 Web/Vue/Next 前端,协议要自定义 | 完全可控,前后端协议自由,兼容任何端 | 流式解析、断重连、中断要自己处理 |
| C. Vercel AI SDK | 已有 Next.js / Vue 项目,全栈 React 团队 | useChat hook 体验好,错误/中断 SDK 内置 | 小程序用不了;ai@4 与 ai@5+ 协议不兼容,要锁版本 |
下面三条路径独立成节,按需读其中一条即可。
路径 A:用 cloudbase-agent-ui(小程序推荐主路径)
重要:
cloudbase-agent-ui当前发布形态是微信小程序源码组件(不是 npm 包,也没有 React/Web 版)。本节只覆盖小程序场景;如果你的前端不是小程序,跳到路径 B 或 C。
第一步:在云开发控制台创建 AI 服务
打开微信开发者工具顶部"云开发"开通服务,或前往云开发平台。两种 AI 服务可选:
- 接入大模型(
chatMode: 'model'):直接对接混元 / DeepSeek,开箱即用 - 创建框架型 Agent(
chatMode: 'bot'):在 Agent 平台拖出一个智能体,绑定 prompt / 工具 / 知识库,拿到botId形如agent-xxxxxxx
详见 Agent 开发文档。
第二步:拷贝组件到小程序项目
cloudbase-agent-ui 不发 npm,是源码组件分发:
-
git clone https://github.com/TencentCloudBase/cloudbase-agent-ui或下载 GitHub Release 里的agent-ui.zip -
把仓库里的
components/agent-ui整个目录拷贝到你小程序项目的components/agent-ui/ -
在
miniprogram/app.js里初始化云开发环境:App({onLaunch() {wx.cloud.init({env: 'your-env-id', // CloudBase 环境 IDtraceUser: true,});},});
第三步:在页面注册并使用组件
页面 .json 注册:
{
"usingComponents": {
"agent-ui": "/components/agent-ui/index"
}
}
页面 .wxml 引用:
<view>
<agent-ui
chatMode="{{chatMode}}"
agentConfig="{{agentConfig}}"
modelConfig="{{modelConfig}}"
showBotAvatar="{{showBotAvatar}}"
></agent-ui>
</view>
页面 .js 给配置(两种 chatMode 二选一):
// 方案 1:直接对接大模型(最简单)
Page({
data: {
chatMode: 'model',
showBotAvatar: true,
modelConfig: {
modelProvider: 'cloudbase',
quickResponseModel: 'deepseek-v4-flash',
logo: '',
welcomeMsg: '你好,我是助手',
},
},
});
// 方案 2:对接框架型 Agent(要先在控制台创建 Agent)
Page({
data: {
chatMode: 'bot',
showBotAvatar: true,
agentConfig: {
botId: 'agent-xxxxxxx', // 控制台 Agent 列表里复制
tools: [
// 可选:前端工具,Agent 可以调
{
name: 'get_weather',
description: '获取指定城市的天气',
parameters: {
type: 'object',
properties: { city: { type: 'string' } },
required: ['city'],
},
handler: ({ city }) => `${city} 天气晴,25°C`,
},
],
},
},
});
完整 props(chatMode / agentConfig / modelConfig / showBotAvatar)和取值范围以仓库 README 当前版本为准。流式输出、多轮会话、文件上传、语音输入、工具调用都由组件内部处理,业务代码不用碰 SSE。
第四步:配置小程序服务器域名
公众平台后台 → 开发管理 → 服务器域名 → request 合法域名,加:
https://{your-envid}.api.tcloudbasegateway.com
否则上传文件 / 多会话功能不可用。
路径 B:手写 SSE + 云函数(自定义场景)
适合:协议非 AG-UI,前端不是小程序,或要深度定制前后端通信。
后端 Web 云函数
mkdir chat-backend && cd chat-backend
npm init -y
npm install express
index.js:
const express = require('express');
const app = express();
app.use(express.json());
app.post('/api/chat', async (req, res) => {
const { messages } = req.body;
// 调上游 LLM(这里复用 connect-openai-api-cloud-function 的代理 URL)
const upstream = await fetch(process.env.LLM_PROXY_URL + '/chat/completions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.LLM_PROXY_TOKEN}`,
},
body: JSON.stringify({
model: 'gpt-4o-mini',
messages,
stream: true,
}),
});
if (!upstream.ok) {
res.status(upstream.status).json({ error: await upstream.text() });
return;
}
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
const reader = upstream.body.getReader();
while (true) {
const { done, value } = await reader.read();
if (done) break;
res.write(value); // 上游已经是 SSE 格式,原样透传
}
res.end();
});
app.listen(process.env.PORT || 9000);
部署到 CloudBase Web 云函数:
tcb fn deploy chat-backend --httpFn -e your-env-id
部署后控制台「云函数 → chat-backend」配 LLM_PROXY_URL / LLM_PROXY_TOKEN,HTTP 触发器记下访问 URL,例如 https://your-env.service.tcloudbase.com/chat-backend/api/chat。
前端
Web(普通 React/Vue,fetch + ReadableStream):
const res = await fetch('/api/chat', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ messages: [{ role: 'user', content: '你好' }] }),
});
const reader = res.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
// SSE 以 \n\n 分隔事件
const events = buffer.split('\n\n');
buffer = events.pop(); // 最后一个可能不完整,留到下一轮
for (const event of events) {
for (const line of event.split('\n')) {
if (!line.startsWith('data:')) continue;
const data = line.slice(5).trim();
if (data === '[DONE]') return;
try {
const obj = JSON.parse(data);
const delta = obj.choices?.[0]?.delta?.content;
if (delta) console.log(delta); // 累加到 state 即可
} catch {}
}
}
}
微信小程序(wx.request + enableChunked,不走 cloudbase-agent-ui 时):
const requestTask = wx.request({
url: 'https://your-env.service.tcloudbase.com/chat-backend/api/chat',
method: 'POST',
enableChunked: true,
data: { messages: [{ role: 'user', content: '你好' }] },
responseType: 'arraybuffer',
});
requestTask.onChunkReceived((res) => {
const text = String.fromCharCode.apply(null, new Uint8Array(res.data));
// 同上解析 SSE,把 delta 累加到 page data
});
参考 wx.request enableChunked 文档。
路径 C:Vercel AI SDK(已有 Next.js 项目)
版本说明:本篇代码基于
ai@4.x。ai@5+已发布且useChat/streamText协议有破坏性变更。新建项目请优先参考 Vercel AI SDK 官方文档 的当前版本。
适合:已有 Next.js / Vue 项目正在用 Vercel AI SDK 4.x;或从 Vercel 迁来 CloudBase。
装包
npm install ai@4 @ai-sdk/openai
后端:Next.js Route Handler
app/api/chat/route.ts:
import { createOpenAI } from '@ai-sdk/openai';
import { streamText } from 'ai';
export const runtime = 'nodejs'; // 不要用 edge,云托管跑的是 Node 容器
export const maxDuration = 60;
const llm = createOpenAI({
baseURL: process.env.LLM_PROXY_URL, // https://xxx.service.tcloudbase.com/llm-proxy/v1
apiKey: process.env.LLM_PROXY_TOKEN, // 代理鉴权 token,不是真 OpenAI key
});
export async function POST(req: Request) {
const { messages } = await req.json();
const result = await streamText({
model: llm('gpt-4o-mini'),
system: '你是一个有帮助的助手,用简体中文回答。',
messages,
});
return result.toDataStreamResponse();
}
createOpenAI({ baseURL }) 让请求打到自己的代理;真正的 OpenAI key 在云函数代理层(参考 connect-openai-api-cloud-function),前端摸不到。
后端:Web 云函数版(非 Next.js 前端用这个)
const express = require('express');
const { createOpenAI } = require('@ai-sdk/openai');
const { streamText } = require('ai');
const app = express();
app.use(express.json({ limit: '5mb' }));
const llm = createOpenAI({
baseURL: process.env.LLM_PROXY_URL,
apiKey: process.env.LLM_PROXY_TOKEN,
});
app.post('/api/chat', async (req, res) => {
const { messages } = req.body;
const result = await streamText({
model: llm('gpt-4o-mini'),
messages,
onFinish: async ({ text }) => {
// 可选:流式结束后落库
},
});
result.pipeDataStreamToResponse(res);
});
app.listen(process.env.PORT || 9000);
部署:
tcb fn deploy chat-backend --httpFn -e your-env-id
控制台配 LLM_PROXY_URL / LLM_PROXY_TOKEN。
前端:useChat
app/chat/page.tsx:
'use client';
import { useChat } from 'ai/react';
export default function ChatPage() {
const { messages, input, handleInputChange, handleSubmit, isLoading, error, stop } = useChat({
api: '/api/chat', // 跨服务时填 Web 云函数 URL
});
return (
<div style={{ maxWidth: 720, margin: '0 auto', padding: 24 }}>
{messages.map((m) => (
<div key={m.id} style={{ margin: '12px 0' }}>
<strong>{m.role === 'user' ? '你' : '助手'}:</strong>
<span>{m.content}</span>
</div>
))}
{error && <div style={{ color: 'red' }}>出错了:{error.message}</div>}
<form onSubmit={handleSubmit} style={{ display: 'flex', gap: 8 }}>
<input
value={input}
onChange={handleInputChange}
placeholder="说点什么..."
disabled={isLoading}
style={{ flex: 1, padding: 8 }}
/>
<button type="submit" disabled={isLoading}>发送</button>
{isLoading && <button type="button" onClick={stop}>停止</button>}
</form>
</div>
);
}
Vue 用 @ai-sdk/vue 的 useChat,API 形态相同。
部署
Next.js 一体走云托管:
tcb cloudrun deploy --port 3000
详见 Next.js 部署到云托管。云托管「服务设置 → 环境变量」加 LLM_PROXY_URL(必须以 /v1 结尾,因为 @ai-sdk/openai 会自动拼 /chat/completions)和 LLM_PROXY_TOKEN。
怎么选
| 你的情况 | 选 |
|---|---|
| 微信小程序前端 | A(cloudbase-agent-ui) |
| 想最快做出 chat 界面,不想自己实现协议 | A(小程序 )或 C(Web) |
| 协议必须自定义,或前端是非 Next 的 Web/RN | B |
| 已有 Next.js + Vercel AI SDK 项目 | C |
| 都想试,从哪开始 | 小程序:A;Web:C 起步,需要定制再切 B |
常见错误
| Symptom | 原因 | 修复 |
|---|---|---|
| 前端没有"边出边显示",等几秒一次性出现 | SSE 没真生效 / 中间层把 stream 缓冲了 | 后端确认 Content-Type: text/event-stream + Cache-Control: no-cache;自定义域名/CDN 关闭响应缓冲;先用 *.service.tcloudbase.com 默认域名验证 |
| 路径 A:组件不显示或报"agent-ui 未注册" | 页面 .json 没声明 usingComponents,或 components/agent-ui 没拷贝 | 检查目录结构 + 页面 usingComponents 路径 |
路径 A:botId 不对,提示找不到 Agent | 控制台没创建 Agent 或 ID 拼错 | 云开发控制台 → AI → Agent 列表复制实际 ID(形如 agent-xxxxxxx) |
路径 A:request not in domain list | 服务器域名没加白名单 | 公众平台后台 → 服务器域名 → 加 https://{envid}.api.tcloudbasegateway.com |
路径 B:小程序 wx.request 报 url not in domain list | 同上 | 同上,加 Web 云函数访问域名(*.service.tcloudbase.com) |
| 路径 B:后端流断了前端不知道 | 上游 5xx 让 SSE 中断 | 后端 catch 里写 data: {"error":"..."}\n\n 再 end() 通知前端 |
路径 C:useChat is not exported from 'ai/react' | ai 不是 v4(v5+ import 路径变了) | npm install ai@^4 @ai-sdk/openai@^1;不要混 v5 路径 |
路径 C:useChat 发的是 GET 不是 POST,404 | App Router Route Handler 没导出 POST | 检查 route.ts 里 export async function POST 拼写 |
路径 C:Failed to fetch 跨域 | 前端域名 ≠ 后端域名,CORS 没加 | Express 加 CORS 中间件:Access-Control-Allow-Origin / Methods / Headers,处理 OPTIONS |
路径 C:长会话报 context_length_exceeded | 全量历史消息发了 | 前端只发最近 N 条,或后端做摘要压缩 |
相关文档
- cloudbase-agent-ui GitHub — 官方流式 chatbot 小程序组件
- Cloudbase Agent 开发 — Agent 协议(AG-UI)与开发框架总览
- SSE 协议支持 — Web 云函数 SSE 流式响应底层
- HTTP 云函数 Node.js — Web 云函数 HTTP 触发快速上手
- Next.js 部署到云托管 — Next.js 在 CloudBase Run 的部署
- Vercel AI SDK 官方文档 —
useChat/streamText当前版本(v5+)
下一步
add-rag-with-pgvector-cloudbase— 让 chatbot 基于自己的文档作答(RAG)connect-openai-api-cloud-function— 把 LLM API key 隔离到云函数代理(三条路径都受益)secure-secrets-in-cloud-function—LLM_PROXY_TOKEN/ Bot API Key 在 dev/staging/prod 之间的分层管理