
访问地址:https://workmind-ai.pages.dev/agent
整个课程做了什么
七章内容,从零搭了一个能实际使用的智能办公平台。回顾一下每章解决了什么问题:
| 章节 | 模块 | 核心技术 | 解决的问题 |
|---|---|---|---|
| 第一章 | 项目脚手架 | Vue3 + Express + SSE | 怎么搭出能跑通的骨架,流式输出怎么实现 |
| 第二章 | RAG 知识库 | Chroma + Embedding | 如何让 AI 回答公司内部文档里的问题 |
| 第三章 | 任务 Agent | LangGraph + Function Call | 如何让 AI 自主规划步骤、调工具完成复杂任务 |
| 第四章 | 内容工作流 | LangGraph 工作流 + HIL | 固定步骤的内容生成,中途暂停等人工确认 |
| 第五章 | ERP 报销请假 | Structured Output + Multi-Agent | 自然语言填单,多角色模拟审批 |
| 第六章 | Prompt 调试 | A/B 测试 + 版本管理 | 不改代码就能调试 Prompt,自动对比效果 |
| 第七章 | 用量看板 | 统计聚合 + P99 延迟 | 实时看 API 费用、缓存命中率、慢请求 |
项目完整目录结构
workmind/
│
├── frontend/ Vue3 前端
│ ├── index.html
│ ├── vite.config.js 配置了 /api 代理到后端
│ ├── package.json
│ └── src/
│ ├── main.js 入口,注册 Pinia + Router
│ ├── App.vue 根布局:侧边栏 + 内容区 + Toast
│ ├── router/
│ │ └── index.js 7个模块的路由(懒加载)
│ ├── styles/
│ │ └── global.css CSS 变量系统(颜色/间距/圆角)
│ ├── utils/
│ │ └── http.js axios 封装 + fetchStream SSE 工具
│ ├── stores/ Pinia 状态管理
│ │ ├── app.js 全局(主题切换、Toast)
│ │ ├── chat.js 对话模块
│ │ ├── knowledge.js RAG 知识库
│ │ ├── agent.js 任务 Agent
│ │ ├── workflow.js 内容工作流
│ │ ├── erp.js ERP 报销请假
│ │ ├── prompt.js Prompt 调试
│ │ └── monitor.js 用量看板
│ ├── views/ 7个页面组件
│ │ ├── ChatView.vue
│ │ ├── KnowledgeView.vue
│ │ ├── AgentView.vue
│ │ ├── WorkflowView.vue
│ │ ├── ErpView.vue
│ │ ├── PromptView.vue
│ │ └── MonitorView.vue
│ └── components/ 可复用组件
│ ├── layout/
│ │ ├── AppSidebar.vue 左侧导航栏
│ │ └── AppHeader.vue 顶部栏
│ ├── common/
│ │ └── ToastList.vue 全局提示
│ ├── chat/ 对话模块组件
│ │ ├── SessionSidebar.vue
│ │ ├── RoleSelector.vue
│ │ ├── MessageBubble.vue
│ │ ├── ChatInput.vue
│ │ └── ProfilePanel.vue
│ ├── rag/ 知识库模块组件
│ │ ├── DocumentUploader.vue
│ │ ├── DocumentList.vue
│ │ └── RagChat.vue
│ ├── agent/
│ │ └── ToolCallCard.vue
│ ├── workflow/
│ │ ├── WorkflowGraph.vue
│ │ └── HumanReviewPanel.vue
│ └── erp/
│ ├── SmartFormParser.vue
│ └── ApprovalTimeline.vue
│
├── server/ Node.js 后端
│ ├── Dockerfile
│ ├── package.json
│ ├── .env.example
│ └── src/
│ ├── index.js 服务入口(注册路由、优雅退出)
│ ├── config/
│ │ └── index.js 统一配置管理
│ ├── middleware/
│ │ └── index.js 限流 + 输入校验 + 安全检查
│ ├── utils/
│ │ ├── logger.js 结构化日志
│ │ └── errors.js 错误分类 + Express 错误中间件
│ ├── routes/ 8个路由文件
│ │ ├── health.js 健康检查(liveness/readiness)
│ │ ├── chat.js 对话(SSE 流式 + 缓存 + 用户画像)
│ │ ├── knowledge.js RAG(上传/列表/删除/问答)
│ │ ├── agent.js Agent(流式执行 + 工具列表)
│ │ ├── workflow.js 工作流(启动/恢复)
│ │ ├── erp.js ERP(解析 + 审批流)
│ │ ├── prompt.js Prompt 调试(测试/A/B/模板)
│ │ └── monitor.js 用量统计
│ └── services/ 业务逻辑层
│ ├── model.js 模型工厂(DeepSeek 配置)
│ ├── cache.js 精确缓存(MD5 + TTL)
│ ├── chat/
│ │ └── memory.js 会话记忆 + 用户画像提取
│ ├── rag/
│ │ ├── ingest.js 文档入库(分片 + 向量化)
│ │ └── query.js RAG 查询(检索 + 生成)
│ ├── agent/
│ │ ├── tools.js 6个工具定义
│ │ └── agent.js LangGraph ReAct 循环
│ ├── workflow/
│ │ └── workflows.js 4个工作流模板
│ ├── erp/
│ │ ├── parser.js 自然语言 → 结构化表单
│ │ └── approval.js Multi-Agent 审批流
│ └── prompt/
│ └── promptService.js 模板管理 + A/B 评分
│
└── docker-compose.yml 一键启动所有服务
从零到运行的完整步骤
第一步:准备环境
确认本机有以下工具:
node --version # 需要 v20 以上
npm --version # v9 以上
docker --version # 用于启动 Chroma 向量数据库
第二步:克隆项目
git clone <你的仓库地址>
cd workmind
第三步:配置环境变量
cd server
cp .env.example .env
用文本编辑器打开 .env,至少要填这两个:
# 必填:DeepSeek 对话模型
# 申请地址:https://platform.deepseek.com
DEEPSEEK_API_KEY=sk-你的key
# 如果要用 RAG 知识库,还需要 OpenAI Embedding
# 申请地址:https://platform.openai.com
OPENAI_API_KEY=sk-你的key
其他配置有默认值,不用改也能跑。
第四步:安装依赖
# 后端
cd server
npm install
# 前端(新开一个终端)
cd ../frontend
npm install
第五步:启动向量数据库(RAG 功能需要)
docker run -d -p 8000:8000 chromadb/chroma
# 验证是否启动成功
curl http://localhost:8000/api/v1/heartbeat
# 返回 {"nanosecond heartbeat": ...} 说明成功
如果不用 RAG 知识库功能,这步可以跳过。
第六步:启动服务
# 终端1:启动后端
cd server
npm run dev
# 看到这个就成功了:
# 🚀 WorkMind Server 已启动
# 地址: http://localhost:3000
# 终端2:启动前端
cd frontend
npm run dev
# 看到这个就成功了:
# ➜ Local: http://localhost:5173/
打开浏览器访问 http://localhost:5173,就能看到界面了。
每个模块的验证清单
✅ 模块一:智能对话
1. 侧边栏点「智能对话」
2. 顶部选择「技术顾问」角色
3. 发送消息:「Vue3 的 ref 和 reactive 有什么区别?」
4. 应该看到:流式输出(字一个个出现),右侧画像面板出现信息
5. 发同一个问题再次:第二次出现「⚡ 缓存」标签,秒回
✅ 模块二:RAG 知识库
前提:已配置 OPENAI_API_KEY + 已启动 Chroma
1. 准备一个 .txt 文件(写几段任意内容)
2. 左侧「上传入库」,拖拽上传文件
3. 看到「共 N 个片段」说明入库成功
4. 右侧问答框提问文档里的内容
5. 应该看到:回答带「📎 参考文档」来源标注
✅ 模块三:任务 Agent
1. 点击左侧示例任务「技术调研」
2. 任务自动填入,点击「▶ 执行任务」
3. 应该看到:步骤卡片依次出现,蓝色边框表示执行中
4. 展开步骤卡片,能看到工具的入参和出参
5. 最终回答在所有步骤完成后出现
✅ 模块四:内容工作流
1. 左侧选「周报生成」模板
2. 填入几条本周工作内容
3. 点「▶ 开始执行」
4. 应该看到:左侧节点图依次变绿,执行到「人工审核」时暂停
5. 右侧出现黄色审核面板,点「✅ 确认并继续」
6. 继续执行生成最终周报
✅ 模块五:ERP 报销请假
报销测试:
1. 点「💰 报销申请」
2. 输入:「上周去北京出差,高铁来回1200,住宿两晚1600,餐费三天共480」
3. 点「🔍 AI 解析」,看到自动填写的表单
4. 如果住宿超标,warnings 里应该有提示
5. 点「🚀 提交审批」
6. 右侧出现审批对话:主管问 → 申请人答 → 财务审核
请假测试:
1. 切换到「📅 请假申请」
2. 输入:「下周一到周三请年假,出去旅游」
3. AI 解析出日期、天数
4. 提交审批后出现:主管 + HR 两个角色
✅ 模块六:Prompt 调试
单次测试:
1. System Prompt 填:「用最简洁的语言回答,不超过两句话」
2. User Message 填:「什么是 JavaScript 的闭包?」
3. 点运行,看输出
4. 把 Temperature 调到 1.5,再运行,输出是否有变化?
A/B 测试:
1. 切到「A/B 对比」
2. A 填简洁版 Prompt,B 填详细版 Prompt
3. 同一个问题:「解释一下 async/await」
4. 点「▶ 开始对比」
5. 等待两边都有输出,看哪边赢了
模板:
1. 在单次测试里调好一个 Prompt
2. 点「💾 另存为模板」
3. 切到「模板库」,能看到刚保存的模板
✅ 模块七:用量看板
1. 做几次对话/RAG/Agent 操作
2. 点「 用量看板」
3. 看指标卡:API 调用次数是否增加
4. 看明细表格:刚才的操作应该在里面
5. 把日预算改成 ¥0.01,进行几次对话
6. 预算进度条应该变黄或红
常见问题汇总
启动相关
Q:后端启动报错**Cannot find package '@langchain/langgraph'**
cd server && npm install
如果已经安装了还报错,删掉 node_modules 重装:
rm -rf node_modules && npm install
Q:前端启动正常,但发请求报 404
检查后端是否启动。Vite 的 proxy 只是把 /api 请求转发到 localhost:3000,如果后端没跑,就 404。
Q:后端启动报**DEEPSEEK_API_KEY 未配置**
.env 文件不存在,或者 Key 没填对。确认 server/.env 存在且内容正确:
cat server/.env | grep DEEPSEEK
# 应该输出:DEEPSEEK_API_KEY=sk-xxxx
功能相关
Q:RAG 提问没有来源引用,或者说"未找到相关内容"
两个可能:
- Chroma 没有启动:
curl http://localhost:8000/api/v1/heartbeat检查 - 没有配置
OPENAI_API_KEY,入库失败了但没报错
Q:Agent 执行卡住不动
DeepSeek 偶尔会慢。等 30 秒,如果还没反应,刷新页面重试。
Agent 是调用真实的 DeepSeek API,网络延迟是正常的。如果一直卡,可以检查 .env 里的 Key 是否有效:
curl https://api.deepseek.com/v1/models \
-H "Authorization: Bearer 你的key"
# 返回模型列表说明 Key 有效
Q:工作流暂停后,刷新页面找不到了
当前用 MemorySaver,状态存在内存里,刷新服务端就丢失了。这是 demo 版本的局限。生产上要换 PostgresSaver,但需要配数据库。
Q:ERP 审批永远都是通过,想让它有时候驳回
在 server/src/services/erp/approval.js 里,finance 角色的 system prompt 加上明确的驳回条件:
finance: `...
必须驳回的情形(遇到立即驳回,不要通过):
- 酒店单晚超过 800 元且无额外说明
- 机票选了商务舱或头等舱
- 餐费单次超过 500 元
满足以上任一,必须输出"驳回"并说明原因。`
二次开发指南
学完整个课程,想在项目基础上做扩展,几个方向:
方向一:接入真实数据
替换模拟数据
Agent 的 searchTool 现在返回模拟数据,可以接真实的搜索 API:
npm install @langchain/community
import { TavilySearchResults } from '@langchain/community/tools/tavily_search'
export const searchTool = new TavilySearchResults({
apiKey: process.env.TAVILY_API_KEY,
maxResults: 3,
})
替换内存存储为数据库
用户画像、会话历史、申请记录现在都在内存里,重启就没了。
接 PostgreSQL:
npm install pg @langchain/langgraph-checkpoint-postgres
// 工作流的 MemorySaver 换成 PostgresSaver
import { PostgresSaver } from '@langchain/langgraph-checkpoint-postgres'
const checkpointer = PostgresSaver.fromConnString(process.env.DATABASE_URL)
await checkpointer.setup()
方向二:加新工具
在 server/src/services/agent/tools.js 里新增工具:
// 例:查询天气
export const weatherTool = tool(
async ({ city }) => {
const res = await fetch(`https://api.weather.com/...?city=${city}`)
const data = await res.json()
return `${city}今天:${data.weather},${data.temp}℃`
},
{
name: 'get_weather',
description: '查询某个城市的天气。当用户问天气相关问题时使用。',
schema: z.object({
city: z.string().describe('城市名,如"北京"'),
}),
}
)
// 加入工具列表
export const allTools = [
searchTool,
readDocTool,
weatherTool, // 新增
// ...
]
方向三:新增工作流模板
在 server/src/services/workflow/workflows.js 里复制一个现有工作流,改改节点逻辑:
// 例:竞品分析工作流
export function buildCompetitorAnalysis() {
const State = Annotation.Root({
target: Annotation({ reducer: (_, n) => n, default: () => '' }),
competitors: Annotation({ reducer: (_, n) => n, default: () => '' }),
analysis: Annotation({ reducer: (_, n) => n, default: () => '' }),
report: Annotation({ reducer: (_, n) => n, default: () => '' }),
humanFeedback: Annotation({ reducer: (_, n) => n, default: () => '' }),
})
// 各节点...
}
别忘了在 WORKFLOW_META 里加元数据(前端展示用)。
方向四:增强 ERP 审批
现在审批角色是固定的几个。可以让用户自定义审批流:
// 用户在前端配置审批链
const customFlow = ['manager', 'finance', 'director']
// 传给 runApprovalFlow
await runApprovalFlow(formData, formType, customFlow, onEvent)
课程技术栈对照表
学完这个项目,你掌握了哪些技术:
LangChain.js 核心用法
| API | 在哪里用的 |
|---|---|
ChatOpenAI |
对话、RAG、Agent、工作流、ERP |
withStructuredOutput(schema) |
用户画像提取、ERP 解析、A/B 评分 |
model.stream() |
所有流式输出场景 |
model.bindTools(tools) |
Agent 的工具调用 |
RecursiveCharacterTextSplitter |
RAG 文档分片 |
OpenAIEmbeddings |
RAG 向量化 |
Chroma |
RAG 向量库 |
LangGraph 核心用法
| API | 在哪里用的 |
|---|---|
StateGraph + Annotation |
Agent + 工作流 |
ToolNode |
Agent 自动执行工具 |
addConditionalEdges |
Agent 路由(有工具调用→继续,无→结束) |
streamEvents |
Agent 步骤可视化 |
MemorySaver + interruptBefore |
工作流 Human-in-Loop |
graph.getState + updateState |
工作流暂停状态检测和注入 |
Vue3 核心用法
| API | 在哪里用的 |
|---|---|
ref / reactive |
所有响应式状态 |
computed |
缓存命中率、过滤列表、派生状态 |
watch |
消息变化时滚底 |
onMounted / onUnmounted |
初始化数据、清理定时器 |
defineEmits |
子组件向父组件传事件 |
<Teleport to="body"> |
Toast 渲染到 body |
<TransitionGroup> |
Toast 进入退出动画 |
<Transition name="slide"> |
工具卡片展开/折叠动画 |
:class动态绑定 |
节点状态颜色、激活态样式 |
v-model.number |
range/number 输入框类型转换 |
Express + Node.js
| 技术 | 在哪里用的 |
|---|---|
| SSE(text/event-stream) | 所有流式接口 |
multer |
文件上传 |
| 令牌桶限流 | middleware/index.js |
| Zod 输入校验 | 所有接口的参数校验 |
| 错误分类中间件 | utils/errors.js |
| 结构化日志 | utils/logger.js |
| 优雅退出 | SIGTERM/SIGINT 处理 |
项目数据流完整图
以「对话」为例,一条消息从前端到后端再回到前端的完整路径:
用户在 ChatInput 按 Enter
↓
chat store.sendMessage(text)
↓
messages 数组推入 userMsg(立即显示)
messages 数组推入 aiMsg(占位,content 为空)
↓
fetchStream('/api/chat/stream', { message, sessionId, role, userId })
↓ HTTP POST(前端)
─────── 网络 ───────
后端 chat.js 路由接收
↓
rateLimiter 检查令牌桶
↓
validateChat 校验 Zod Schema
↓
securityCheck 检查 Prompt 注入
↓
config.js 读取角色 system prompt
memory.js 读取用户画像,转为 profileCtx
拼接 systemPrompt = 角色 + 画像
↓
cache.get(systemPrompt, message) 查精确缓存
├── 命中 → SSE 推 cache_hit + 模拟流式输出
└── 未命中 ↓
↓
memory.getHistory(sessionId) 获取会话历史
trimHistory(history, 2000) Token 感知截取
↓
chatModel.stream([system, ...history, humanMsg])
↓
for await chunk:
SSE 推 event: token, data: { token: chunk }
↓
history.push(humanMsg, aiMsg)
cache.set(systemPrompt, message, { content: fullReply })
extractAndUpdateProfile(userId, ...) ← 异步,不等
SSE 推 event: done, data: { inputTokens, outputTokens }
─────── 网络 ───────
fetchStream 的 onToken 回调
↓
aiMsg.content += token(响应式,Vue 自动重渲染)
↓
watch 检测到消息长度变化
↓
bottomEl.scrollIntoView() 自动滚底
↓
onDone 回调:aiMsg.streaming = false
monitor.recordCall(...)
chat.loadProfile()(刷新画像面板)
最后几点实用建议
关于 DeepSeek 的 API 配额
DeepSeek 新账号有免费额度,做实验足够了。项目上线前,建议去后台看一下当前账户余额,设好预算预警(模块七就是做这个的)。
关于 temperature 的调法
做对话类功能,默认 0.7 就好。做数据提取、结构化输出,用 0。做内容生成,可以试试 0.8 到 1.0。不要急着调,先看默认值的效果。
关于 RAG 的效果调优
如果回答质量不稳定:
- 先检查分片大小(
chunkSize=500是起点,可以试试 300 或 800) - 看相似度分数,如果总是 0.3 左右说明文档和问题匹配度低,考虑换更精确的问法
- System Prompt 一定要写"只根据参考文档回答,文档之外的不回答"
关于生产部署
这个项目的数据都在内存里(Map、MemorySaver),重启就没了。真正上线前,这几个地方要换:
- 会话历史:换 Redis
- 用户画像:换 PostgreSQL
- 工作流状态:换 PostgresSaver
- 向量数据库:Chroma 可以持久化,配
volumes就行
Docker Compose 文件已经写好了,加上这些服务就可以。
感谢
这个项目把课程里学的东西都用上了:SSE 流式输出、RAG 检索增强、Agent 工具调用、LangGraph 工作流、Multi-Agent 协作、结构化输出、缓存策略、成本监控……
每个功能都有对应的教学文档,把"为什么这样写"说清楚,不只是"代码是什么"。
有问题欢迎提 Issue 或者在课程社群里讨论。