此示例包含什么
这是一个由 React、Express 和 ModelRiver 构建的全栈聊天机器人示例:
| 功能 | 工作方式 |
|---|---|
| 会话记忆 | 第一个请求创建 session_id;后续轮次将其传回,使 ModelRiver 能注入相关对话历史。 |
| 异步 AI 请求 | Node.js 后端调用 /v1/ai/async,并立即向浏览器返回连接信息。 |
| 实时交付 | 浏览器使用一次性的 ws_token 加入该请求对应的 Phoenix WebSocket 通道。 |
| 结构化回复 | 回复包含 reply、summary、sentiment、confidence、topics 和 action_items。 |
| 后端回调 | ModelRiver 将 webhook_received 事件发送到后端;后端可保存或丰富结果,再调用回调地址。 |
示例为了演示而将消息保存在内存中。用于生产环境之前,请改为使用你的数据库。
请求流程
1React 前端2 │ POST /chat(message + 可选 session_id)3 ▼4Node.js 后端5 │ POST /v1/ai/async6 ▼7ModelRiver → AI 提供商8 │9 ├─ 返回 channel_id、ws_token、websocket_url、10 │ websocket_channel 和 session_id11 │12 └─ 将 webhook_received 发送到 Node.js 后端13 │14 └─ 后端调用 callback_url15 │16 ▼17 最终结构化回复通过该请求的18 WebSocket 通道发送到 React前置条件
- Node.js 18 或更高版本
- 一个 ModelRiver 项目
- 一个项目 API 密钥
- 至少一个受支持 AI 提供商的凭据
第 1 步 — 获取应用
git clone https://github.com/modelriver/chatbot-async-app.gitcd chatbot-async-app cd backendnpm install cd ../frontendnpm install第 2 步 — 导入聊天机器人模板
- 打开会话记忆聊天机器人模板。
- 点击下载 JSON。
- 打开你的 ModelRiver 项目,使用导入上传文件。
- 检查预览并确认导入。
模板会创建:
- 工作流:
mr_chatbot_workflow - 结构化输出:
chatbot_response - 会话记忆:已启用
- 后端流水线事件:
webhook_received - 主模型:
openai / gpt-5.6-luna - 备用模型:
anthropic / claude-haiku-4-5-20251001
发送真实请求之前,请在项目中连接所选提供商。你也可以先在模板页面更换模型,再下载模板。
会话记忆的必要条件: 项目必须保持启用请求正文日志。禁用请求正文后,ModelRiver 无法构建对话记忆。
第 3 步 — 创建 API 密钥和 Webhook
API 密钥
创建项目 API 密钥,并将其写入 backend/.env 的 MODELRIVER_API_KEY。
Webhook
为项目创建一个已启用的 HTTP Webhook,并将密钥写入 WEBHOOK_SECRET。
如果 ModelRiver 在本机 4000 端口运行,请使用:
1http://localhost:4001/webhook/modelriver如果使用托管版 ModelRiver,Webhook 必须能从公网访问。本地开发时,可以使用 ModelRiver CLI forward 命令或 HTTPS 隧道。
项目中也可以存在 CLI Webhook,但未运行 CLI 监听器时会显示“No CLI client connected”。这不表示 HTTP Webhook 失败;如果不使用 CLI Webhook,建议将其禁用,使请求日志更清晰。
第 4 步 — 配置环境变量
先复制示例文件:
cd backendcp .env.example .env cd ../frontendcp .env.example .env使用托管版 ModelRiver
backend/.env:
PORT=4000MODELRIVER_API_KEY=mr_live_your_project_keyMODELRIVER_API_URL=https://api.modelriver.comBACKEND_PUBLIC_URL=https://your-backend-or-tunnel.example.comWEBHOOK_SECRET=your_webhook_secretfrontend/.env:
VITE_API_URL=http://localhost:4000ModelRiver 在本机运行
ModelRiver 已使用 4000 端口,因此聊天机器人后端应使用 4001。
backend/.env:
PORT=4001MODELRIVER_API_KEY=mr_live_your_local_project_keyMODELRIVER_API_URL=http://127.0.0.1:4000/apiBACKEND_PUBLIC_URL=http://localhost:4001WEBHOOK_SECRET=your_local_webhook_secretfrontend/.env:
VITE_API_URL=http://localhost:4001前端开发端口固定为 frontend/vite.config.js 中的 3006,不会从前端 .env 文件读取。BACKEND_PUBLIC_URL 必须指向接收 /webhook/modelriver 的同一个后端。应用不使用 EVENT_NAME 环境变量;默认事件固定为 webhook_received。
第 5 步 — 运行聊天机器人
在克隆的仓库中打开两个终端。
终端 1 — 后端:
cd backendnpm start终端 2 — 前端:
cd frontendnpm run dev如果你使用托管版 ModelRiver 和 CLI Webhook,请在第三个终端运行转发命令,端口应与 modelriver login 中配置的端口一致:
modelriver forward --port 4000本机 ModelRiver 使用直接 HTTP Webhook 时,不需要运行 CLI。
第 6 步 — 测试会话记忆
先发送:
1My name is Vishal. I have a bakery named Sunrise Bakes in Bangalore. Please remember this.然后在同一会话中发送:
1What is my name, business name, and city?回复应能记住 Vishal、Sunrise Bakes 和 Bangalore,并且两轮消息的 UI 会话标识应保持一致。
点击新建会话,然后再次询问第二个问题。此时应创建新的 session_id,聊天机器人也不应声称知道上一会话中的信息。
session_id 如何工作
浏览器不会自行生成 ModelRiver 会话 ID:
- 第一次
/chat请求不发送session_id。 - ModelRiver 创建会话,并在异步响应正文中返回
session_id。 - 后端将该 ID 返回给 React。
- React 在该会话的每一条后续消息中发送同一个 ID。
- 新建会话会清除它,使下一个请求创建新会话。
客户端生成的 conversationId 只是应用元数据,不能替代 ModelRiver 的 session_id。
后端请求示例:
1const payload = {2 workflow: 'mr_chatbot_workflow',3 messages: [{ role: 'user', content: message }],4 delivery_method: 'websocket',5 webhook_url: `${BACKEND_PUBLIC_URL}/webhook/modelriver`,6 events: ['webhook_received'],7 metadata: {8 conversation_id: conversationId,9 message_id: messageId,10 original_prompt: message11 }12};13 14if (sessionId) payload.session_id = sessionId;15 16const { data } = await axios.post(17 `${MODELRIVER_API_URL}/v1/ai/async`,18 payload,19 { headers: { Authorization: `Bearer ${MODELRIVER_API_KEY}` } }20);WebSocket 交付如何工作
应用直接使用 Phoenix 客户端,因为每个 ws_token 只能使用一次。收到最终回复后,应用会主动断开连接,避免客户端使用已消耗的令牌进行重试。
1import { Socket } from 'phoenix';2 3const socket = new Socket(websocket_url, {4 params: { token: ws_token },5 reconnectAfterMs: () => 60_0006});7 8const channel = socket.channel(websocket_channel);9 10socket.onOpen(() => {11 channel.join();12});13 14channel.on('response', payload => {15 const status = payload.meta?.status || payload.status;16 renderResponse(payload);17 18 if (status === 'completed' || status === 'success') {19 channel.leave();20 socket.disconnect();21 }22});23 24socket.connect();必须使用该请求返回的准确 websocket_url 和 websocket_channel。不要存储或重复使用 ws_token。
后端回调如何工作
ModelRiver 为 webhook_received 事件发送 task.ai_generated Webhook。后端会:
- 使用
WEBHOOK_SECRET验证X-ModelRiver-Signature和X-ModelRiver-Timestamp。 - 从
ai_response.data读取结构化结果。 - 添加应用自己的消息 ID。
- 使用项目 API 密钥将结果 POST 到
callback_url。
1const callbackPayload = {2 data: {3 ...webhook.ai_response.data,4 id: messageId5 },6 task_id: messageId,7 metadata: webhook.ai_response.meta || {}8};9 10await axios.post(webhook.callback_url, callbackPayload, {11 headers: {12 Authorization: `Bearer ${MODELRIVER_API_KEY}`,13 'Content-Type': 'application/json'14 }15});最终回调负载随后会发送到浏览器的 WebSocket 通道。
结构化回复
导入的 chatbot_response 包含:
| 字段 | 类型 | 用途 |
|---|---|---|
reply | 字符串 | 向用户展示的主要回答 |
summary | 字符串 | 本轮对话的简短描述 |
sentiment | positive、neutral、negative 或 mixed | 情感标签 |
confidence | high、medium 或 low | 置信度指示器 |
topics | 字符串数组 | 主题标签 |
action_items | { task, priority } 数组 | 存在时显示后续操作 |
前端同时支持分类值和数值型置信度,但可下载模板统一使用 high、medium 或 low。
后端端点
| 端点 | 方法 | 说明 |
|---|---|---|
/chat | POST | 启动异步请求并返回 WebSocket 与会话信息 |
/webhook/modelriver | POST | 验证并处理 ModelRiver Webhook 事件 |
/conversations/:id | GET | 返回示例保存在内存中的会话记录 |
/health | GET | 显示后端与 ModelRiver 配置状态 |
POST /chat 接受:
1{2 "message": "What did I tell you about my business?",3 "workflow": "mr_chatbot_workflow",4 "conversationId": "optional-application-conversation-id",5 "session_id": "optional-modelriver-session-uuid",6 "events": ["webhook_received"]7}只有 message 是必填项;React 客户端会自动管理 session_id。
生产环境检查清单
部署此示例之前:
- 将内存 Map 替换为持久化存储。
- 强制配置
WEBHOOK_SECRET;生产环境绝不能接受未签名 Webhook。 - 将 CORS 限制为已部署前端的来源。
- 后端与 WebSocket 端点都使用 HTTPS。
- 在服务端将会话 ID 与已认证用户绑定。
- 为数据库写入和回调添加幂等与重试处理。
- API 密钥和 Webhook 密钥只能保存在后端。
- 监控请求日志、失败的 Webhook 交付、回调和会话摘要。
故障排除
WebSocket connection error 或 HTTP 403
- 确认前端使用最新
ws_token,且不会用它重新连接。 - 确认前端使用返回的
websocket_channel,而不是在浏览器中自行拼接通道名。 - ModelRiver 在本机运行时,WebSocket 主机优先使用
127.0.0.1,避免 macOS 的 IPv6 解析问题。 - 修改前端
.env后重新启动前端。
聊天机器人没有记忆
- 确认
mr_chatbot_workflow已启用会话记忆。 - 确认项目已启用请求正文日志。
- 确认第二次
/chat请求发送了第一次返回的同一个session_id。 - 确认两轮之间没有点击新建会话。
HTTP Webhook 成功,但 CLI Webhook 失败
没有 CLI 客户端连接时,已启用的 cli://localhost Webhook 会失败。请将其禁用或运行 CLI;成功的 HTTP Webhook 仍可正常完成请求。