Skip to content

第 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. 关于本项目上线验收,下列哪项做法正确?

🛠️ 动手实践

  1. 为机器人补充"查物流"工具,接入真实快递 API 并在 UI 显示执行状态。
  2. 给审批流增加二级审批:金额超过 2000 元需两名审核人先后批准。
  3. 编写 10 条真实客户问题的评估集接入 CI,把通过率作为上线门禁。

下一章:数据问答 BI Agent