第 20 章 · 实战二:全栈 AI 应用
本章目标:综合运用全书知识,实现一个客服知识库机器人——RAG 检索 + Workflow 审批流 + 记忆 + 前端集成,并完成部署。
20.1 需求与架构
功能:用户提问 → 检索知识库回答;涉及退款时走审批工作流(金额 > 500 元需人工批准);记住用户历史诉求。
text
用户 ─▶ Next.js UI ─▶ API 路由 ─▶ Agent(RAG 工具 + Memory)
│ 退款意图
▼
refundFlow(suspend 等审批)项目结构:
text
src/
mastra/
index.ts # Mastra 实例:存储/追踪汇总
tools/kb.ts # RAG 检索工具
workflows/refund.ts
agents/support.ts
app/
api/chat/route.ts # 流式接口
page.tsx # 聊天 UI
scripts/ingest.ts # 知识库导入脚本20.2 知识库导入与检索工具
typescript
// scripts/ingest.ts —— 把 docs/ 目录灌入向量库
import { MDocument } from '@mastra/rag'
import { embedMany } from 'ai'
import { openai } from '@ai-sdk/openai'
import { LibSQLVector } from '@mastra/libsql'
import { readdirSync, readFileSync } from 'node:fs'
const vector = new LibSQLVector({ url: process.env.LIBSQL_URL! })
await vector.createIndex({ indexName: 'kb', dimension: 1536 })
for (const file of readdirSync('./docs')) {
const doc = MDocument.fromText(readFileSync(`./docs/${file}`, 'utf8'))
const chunks = await doc.chunk({ strategy: 'markdown', size: 512, overlap: 50 })
const { embeddings } = await embedMany({
model: openai.embedding('text-embedding-3-small'),
values: chunks.map((c) => c.text),
})
await vector.upsert({
indexName: 'kb',
vectors: embeddings,
metadata: chunks.map((c) => ({ text: c.text, source: file })),
})
}typescript
// src/mastra/tools/kb.ts —— 暴露给 Agent 的检索工具
import { createTool } from '@mastra/core/tools'
import { z } from 'zod'
export const searchKb = createTool({
id: 'search-kb',
description: '在公司知识库中搜索政策与流程信息',
inputSchema: z.object({ query: z.string().describe('搜索关键词') }),
execute: async ({ context }) => {
const results = await retrieve(context.query) // 第 12 章的检索函数
return { passages: results.map((t, i) => ({ id: i, text: t })) }
},
})20.3 审批工作流
typescript
// src/mastra/workflows/refund.ts
import { createWorkflow, createStep } from '@mastra/core/workflows'
import { z } from 'zod'
const review = createStep({
id: 'review',
inputSchema: z.object({ orderId: z.string(), amount: z.number() }),
outputSchema: z.object({ approved: z.boolean() }),
resumeSchema: z.object({ approved: z.boolean() }), // 审批人回传
execute: async ({ inputData, suspend, resumeData }) => {
if (resumeData) return resumeData // 已有审批结果
// 大额退款挂起等人工审批;状态已自动持久化
await suspend()
return { approved: false }
},
})
export const refundFlow = createWorkflow({
id: 'refund-flow',
inputSchema: z.object({ orderId: z.string(), amount: z.number() }),
outputSchema: z.object({ result: z.string() }),
})
.then(review)
.map(({ inputData }) => ({
result: inputData.approved ? '退款已批准' : '退款被拒绝',
}))
.commit()20.4 组装 Agent 与服务端
typescript
// src/mastra/agents/support.ts
import { Agent } from '@mastra/core/agent'
import { Memory } from '@mastra/memory'
import { LibSQLStore } from '@mastra/libsql'
import { searchKb } from '../tools/kb'
import { mastra } from '../index'
export const support = new Agent({
id: 'support',
name: '客服助手',
instructions: `你是公司客服。先调用 search-kb 查询资料再回答;
引用来源编号。退款请求请确认订单号与金额后执行退款流程。`,
model: 'openai/gpt-5-mini',
tools: { searchKb },
memory: new Memory({
storage: new LibSQLStore({ url: process.env.LIBSQL_URL! }),
options: { observationalMemory: true }, // 长期记忆(第 11 章)
}),
workflows: { refundFlow }, // Agent 可触发审批流
})
export default support前端直接复用第 17 章的 useChat 页面;审批动作由运营后台调用 run.resume() 完成。
20.5 部署与验收
bash
npm run build && docker build -t support-bot .
docker run -p 4111:4111 \
-e LIBSQL_URL=libsql://prod.turso.io -e LIBSQL_AUTH_TOKEN=$TOKEN \
-e OPENAI_API_KEY=$KEY support-bot验收清单:
- [ ] 纯知识问答命中知识库且引用来源编号
- [ ] 501 元退款触发挂起,后台批准后对话内收到结果
- [ ] 新会话仍记得"上次买过耳机"(OM 生效)
- [ ] Studio 中能看到完整 trace 与评估分数
本章小结
- 全栈应用 = RAG 工具化 + Workflow 审批 + Memory 长期记忆 + 前端流式 UI;
- 检索能力封装成 tool 让模型自主决定何时查询;
- 大额敏感操作用 suspend/resume 引入人工关卡;
- 部署后按四条验收项逐条打勾,缺一不可。
🧪 随堂测验
点击你认为正确的选项。答错时会展示正确答案与原因解析。
1. 综合实战中把 RAG 能力交给 Agent 的方式是?
2. 退款审批步骤中调用 suspend() 后,执行状态发生了什么?
3. 验证长期记忆生效的正确测试方法是?
4. 关于本项目上线验收,下列哪项做法正确?
🛠️ 动手实践
- 为机器人补充"查物流"工具,接入真实快递 API 并在 UI 显示执行状态。
- 给审批流增加二级审批:金额超过 2000 元需两名审核人先后批准。
- 编写 10 条真实客户问题的评估集接入 CI,把通过率作为上线门禁。