文档

预约与到店接待

解析预约请求、识别缺失信息,并自动确认预约。

本指南将一步步教你如何把 预约与到店接待 模板接入你自己的应用。无需任何 AI 经验,你只需要复制代码,而不是编写 AI 逻辑。

它做什么

解析预约请求、识别缺失信息,并自动确认预约。

模板自带 2 个工作流和 2 个响应格式,它们已经可以协同工作:

  • 工作流:booking_intake_workflow、booking_confirmation_workflow
  • 响应格式:booking_intake_response、booking_confirmation_response

想走完全无代码的路径? 直接跳到 第 6 步 - Go no-code。

你将构建什么

  • 一个后端端点,用客户请求启动 预约与到店接待 工作流。
  • 一个实时聊天界面,在生成过程中流式展示 AI 回复。
  • 一个Webhook 接收器,接收流水线事件并把结果发回给客户。

Step 1 - Download and import the template

  1. 打开 预约与到店接待 模板页面,点击下载 JSON。
  2. 在 ModelRiver 项目中点击导入,粘贴或上传文件,确认预览后完成导入。所有内容以原子方式创建,不会覆盖任何已有数据。
  3. 了解更多:导入与导出指南。

Step 2 - Connect the AI providers

该模板运行在 OpenAI 和 Anthropic 上。导入前请先在项目的「提供商」部分完成连接。如果你使用其他提供商,也可以在导入后修改任意工作流。

Step 3 - Add the backend endpoint

你的后端通过调用 ModelRiver API 来启动工作流,需要项目 API 密钥(在「项目设置 → API 密钥」中创建)。响应中包含一次性 ws_token,你的前端用它来流式接收回复。

// Express.js - POST /api/ai/chatimport express from 'express';const app = express();app.use(express.json()); app.post('/api/ai/chat', async (req, res) => {  const response = await fetch('https://api.modelriver.com/v1/ai/async', {    method: 'POST',    headers: {      'Authorization': `Bearer ${process.env.MR_API_KEY}`,      'Content-Type': 'application/json',    },    body: JSON.stringify({      workflow: 'booking_intake_workflow',      messages: [{ role: 'user', content: req.body.message }],      inputs: { customer_id: req.body.customer_id },    }),  });  const data = await response.json();  // Return the one-time ws_token so the frontend can stream the reply.  res.json({ ws_token: data.ws_token, channel_id: data.channel_id });}); app.listen(3000);

也可以使用 CLI 在本地运行这个端点:modelriver listen 会把 ModelRiver 的 Webhook 转发到你的 localhost。

Step 4 - Stream the reply in your frontend

使用 ModelRiver 客户端 SDK 连接数据流。用后端返回的 ws_token 调用 connect() 后,AI 回复会实时到达,无需轮询。

import { useState } from 'react';import { useModelRiver } from '@modelriver/client/react'; export function TemplateChat() {  const [input, setInput] = useState('');  const { connect, response, error, status } = useModelRiver({    baseUrl: 'wss://api.modelriver.com/socket',    persist: true,  });   const handleSend = async () => {    // 1. Your backend starts the booking_intake_workflow workflow and returns a one-time ws_token.    const res = await fetch('/api/ai/chat', {      method: 'POST',      headers: { 'Content-Type': 'application/json' },      body: JSON.stringify({ message: input }),    });    const { ws_token, channel_id } = await res.json();     // 2. Connect to ModelRiver's stream. The reply arrives in real time.    connect({ wsToken: ws_token, channelId: channel_id });    setInput('');  };   return (    <div>      <input value={input} onChange={(e) => setInput(e.target.value)}             disabled={status === 'loading'} placeholder="Type your request..." />      <button onClick={handleSend} disabled={!input || status === 'loading'}>Send</button>      {status === 'loading' && <p>Working on it...</p>}      {response && (        <div>          <p>{response.content}</p>          {response.data && <pre>{JSON.stringify(response.data, null, 2)}</pre>}        </div>      )}      {error && <p>{error}</p>}    </div>  );}

用 npm install @modelriver/client 安装 SDK。同一个 ws_token 可用于 React、Vue、原生 JS 和 Svelte。

Step 5 - Receive the pipeline event in your backend

AI 步骤完成后,ModelRiver 会把 task.ai_generated 事件投递到你项目中配置的 Webhook 地址。你的后端可以补充自己的数据(你的政策、订单详情或数据库查询),然后调用事件中的 callback_url,最终回复就会流式发送给客户。

每次投递都是一个 JSON 信封(callback_url、channel_id、data、timestamp),事件本身(type、event、ai_response、callback_url、customer_data 等)位于 data 中。请使用 x-modelriver-signature 和 x-modelriver-timestamp 请求头进行校验:签名是以你的 Webhook 密钥对 <x-modelriver-timestamp>.<data> 计算的 HMAC-SHA256(十六进制),其中 <data> 是从原始请求体中截取的 data 值的原始 JSON 文本(第一个 "data": 之后、最后一个 ,"timestamp": 之前)。切勿对解析后的 JSON 重新序列化;请使用常量时间比较,并拒绝早于约 5 分钟的时间戳。

// Express.js - POST /webhooks/modelriverimport express from 'express';import crypto from 'node:crypto'; const app = express(); // Keep the raw body: the signature covers the exact text ModelRiver sent.app.post('/webhooks/modelriver', express.raw({ type: 'application/json' }), async (req, res) => {  // 1. Verify the HMAC signature: hex HMAC-SHA256 of "<timestamp>.<data>", where  // <data> is the exact JSON text of the body's "data" value (never re-serialize it).  const raw = req.body.toString('utf8');  const timestamp = req.get('x-modelriver-timestamp') || '';  const signature = Buffer.from(req.get('x-modelriver-signature') || '');  const start = raw.indexOf('"data":') + '"data":'.length;  const end = raw.lastIndexOf(',"timestamp":');  const expected = Buffer.from(crypto    .createHmac('sha256', process.env.MR_WEBHOOK_SECRET)    .update(`${timestamp}.${raw.slice(start, end)}`)    .digest('hex'));  if (    start < 7 || end < start ||    signature.length !== expected.length ||    !crypto.timingSafeEqual(signature, expected)  ) {    return res.status(401).send('Invalid signature');  }   const event = JSON.parse(raw).data; // the event payload is under "data"   // 2. The AI step finished. Enrich it with your own data (order, policy, ...).  const enriched = { ...event.ai_response, checked_at: new Date().toISOString() };  await saveEvent(event.channel_id, enriched); // your database   // 3. Send the enriched result back so the reply streams to the customer.  if (event.callback_url && event.callback_required) {    await fetch(event.callback_url, {      method: 'POST',      headers: {        'Authorization': `Bearer ${process.env.MR_API_KEY}`,        'Content-Type': 'application/json',      },      body: JSON.stringify({ data: enriched }),    });  }   res.status(200).send('ok');}); app.listen(3000);

Step 6 - Go no-code

不想自己运行服务器?这些平台可以直接接收流水线事件、存储数据并回调,无需管理任何基础设施。

// Supabase Edge Function - webhooks/modelriver// Deploy with: supabase functions deploy webhooks-modelriverimport { serve } from 'https://deno.land/[email protected]/http/server.ts';import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'; const supabase = createClient(  Deno.env.get('SUPABASE_URL')!,  Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!); // Signed string: "<x-modelriver-timestamp>.<exact JSON text of the body's data value>".async function verifySignature(raw: string, timestamp: string | null, signature: string | null): Promise<boolean> {  const start = raw.indexOf('"data":') + 7;  const end = raw.lastIndexOf(',"timestamp":');  if (!timestamp || !signature || start < 7 || end < start) return false;  const key = await crypto.subtle.importKey(    'raw',    new TextEncoder().encode(Deno.env.get('MR_WEBHOOK_SECRET')!),    { name: 'HMAC', hash: 'SHA-256' },    false,    ['sign']  );  const digest = new Uint8Array(await crypto.subtle.sign(    'HMAC', key, new TextEncoder().encode(`${timestamp}.${raw.slice(start, end)}`)  ));  const expected = [...digest]    .map((byte) => byte.toString(16).padStart(2, '0'))    .join('');  if (signature.length !== expected.length) return false;  let diff = 0;  for (let i = 0; i < expected.length; i++) diff |= signature.charCodeAt(i) ^ expected.charCodeAt(i);  return diff === 0;} serve(async (req) => {  // 1. Verify the HMAC signature over the raw body before parsing the event.  const raw = await req.text();  const valid = await verifySignature(    raw, req.headers.get('x-modelriver-timestamp'), req.headers.get('x-modelriver-signature'));  if (!valid) return new Response('Invalid signature', { status: 401 });  const event = JSON.parse(raw).data; // the event payload is under "data"   // 2. Store the AI step result in Postgres.  const { error } = await supabase.from('ai_content').insert({    channel_id: event.channel_id,    payload: event.ai_response,    status: 'ai_generated',  });  if (error) return new Response(error.message, { status: 500 });   // 3. Send the result back so the reply streams to the customer.  if (event.callback_url && event.callback_required) {    await fetch(event.callback_url, {      method: 'POST',      headers: {        Authorization: `Bearer ${Deno.env.get('MR_API_KEY')}`,        'Content-Type': 'application/json',      },      body: JSON.stringify({ data: event.ai_response }),    });  }   return new Response('ok');});

Test it end to end

  1. 启动你的后端(或部署无代码函数),并在项目中配置 MR_API_KEY、MR_WEBHOOK_SECRET 和 Webhook 地址。
  2. 打开前端,输入一条测试请求并点击发送。
  3. 确认回复实时到达,并且后端收到了 task.ai_generated 事件。

Next steps