一、简历项目经验模板
初级版(1-3年,侧重实现)
WorkMind AI — 智能办公 Agent 平台Vue3``Node.js``LangChain.js``LangGraph``DeepSeek``RAG``SSE
基于 Vue3 + Node.js 全栈开发的智能办公平台,集成 DeepSeek 大模型,覆盖 7 个核心办公场景。
- 实现 SSE 流式对话,逐 Token 推送,集成 MD5 精确缓存,相同问题命中率达 40%+ ,API 调用成本降低约 35%
- 基于 LangChain.js + Chroma 构建 RAG 知识库问答,文档分片(chunkSize=500, overlap=50)后向量化入库,检索召回后标注来源片段和相似度评分
- 用 LangGraph 实现 ReAct Agent,集成 6 个工具(搜索/知识库/计算/日期/报告/通知),前端实时展示每一步工具调用的入参和出参
- 开发 ERP 报销请假模块,用 Structured Output 将自然语言解析为结构化表单,Multi-Agent 模拟多角色审批对话流程
中高级版(3-5年,侧重设计决策)
WorkMind AI — 智能办公 Agent 平台Vue3``Pinia``Node.js``Express``LangChain.js``LangGraph``DeepSeek``Chroma``SSE
主导设计并全栈实现的 AI 办公平台,7 个核心模块,从架构设计到生产部署完整落地。
- 流式输出架构:基于 SSE 实现逐 Token 推送,前端用
ReadableStream+ 自定义fetchStream工具解析事件流;设计精确缓存(MD5 哈希 + TTL),缓存命中后模拟流式输出保持体验一致,API 成本下降约 35% - RAG 知识库:
RecursiveCharacterTextSplitter按语义分界符递归分片(chunkSize=500, overlap=50 防止语义截断),余弦相似度阈值 0.3 过滤低相关文档,System Prompt 约束模型不超出文档范围回答,避免幻觉 - LangGraph 双模式:Agent 模式用
StateGraph实现 ReAct 循环,streamEvents拦截on_tool_start/on_tool_end事件推送步骤状态;工作流模式用interruptBefore+MemorySaver实现 Human-in-Loop,graph.getState判断state.next是否为空区分暂停和完成 - ERP Multi-Agent:Zod Schema +
withStructuredOutput将口语化描述解析为结构化表单;多 Agent 共享全局对话历史,根据申请金额/天数动态规划审批角色链,任一角色驳回即终止 - 成本监控:
BaseCallbackHandler回调追踪每次调用的 Token 用量,实时聚合今日费用/P50-P99 延迟/功能分布,支持预算预警
高级版(5年+,侧重系统思考)
WorkMind AI — 智能办公 Agent 平台Vue3``Node.js``LangChain.js``LangGraph``DeepSeek``Chroma``Multi-Agent``RAG
从零设计并实现面向企业内部的 AI Agent 平台,对 LLM 应用的工程化落地有完整实践。
- 核心架构决策:服务端全模块化(routes/services/utils 分层),
config/index.js用 getter 延迟读取环境变量解决 ES Module 并行加载时 dotenv 尚未执行的 race condition;model.js用 Proxy 实现懒加载单例,兼容旧调用方式无需改动消费侧代码 - RAG 质量控制:分片策略上选择语义分隔符优先(段落→句子→字符),overlap=50 保证跨片语义连续性;检索后用相似度阈值二次过滤,System Prompt 明确约束"只根据参考文档回答,文档外明确拒答",从数据层和 Prompt 层双重控制幻觉
- Agent 可观测性:
streamEvents(version: v2)订阅节点级事件,区分工具调用决策阶段(有tool_call_chunks)和最终回答阶段(无tool_call_chunks)的 token 流,前端准确分离两类输出;最大步数兜底防死循环 - HIL 工作流:
interruptBefore配合MemorySaver实现状态检查点,通过state.next.length > 0判断暂停状态,updateState注入人工反馈后传null恢复执行,完整保留中间产物供审核 - Multi-Agent 协作:多 Agent 共享全局
conversationHistory,后序审批人可见前序对话;根据业务规则(金额/天数阈值)动态规划审批链,关键词匹配判断审批结果,任一驳回短路剩余流程
二、核心技术亮点提炼
写简历时按这个框架选:做了什么 → 怎么做的(技术手段)→ 结果是什么(数据或效果)
亮点1:SSE 流式输出 + 缓存体验一致性
做了什么:实现了和 ChatGPT 一样的逐字流式输出体验
技术手段:
- 服务端
res.setHeader('Content-Type', 'text/event-stream')+ 循环res.write('event: token\ndata: ...\n\n') - 前端
fetch+ReadableStream+TextDecoder,手动解析 SSE 协议格式(event:/data:/\n\n分隔) - 缓存命中时用
for...of切片模拟流式推送,保证有无缓存体验一致
结果:缓存命中率稳定在 35-45%,命中时响应延迟从 1-3s 降到 <100ms
亮点2:RAG 知识库检索质量控制
做了什么:上传文档后可以基于内容精准问答,回答带来源标注
技术手段:
RecursiveCharacterTextSplitter按['\n\n', '\n', '。', ',']优先级分片,overlap=50 防止语义在边界截断chunkSize=500平衡语义完整性和向量精度(太长向量不精确,太短上下文不完整)- 相似度阈值 0.3 过滤,System Prompt 双重约束模型边界
结果:对比单一大 Prompt 方式,回答准确率提升明显,幻觉问题基本消除
亮点3:LangGraph Agent 步骤可视化
做了什么:用户输入任务,前端实时展示 AI 的每一步思考和工具调用
技术手段:
- LangGraph
StateGraph实现 ReAct(Reason+Act)循环:agent节点 → 路由函数判断 → tools节点 → 回到agent streamEvents(version: v2)订阅on_tool_start、on_tool_end事件,通过tool_call_chunks字段区分决策 token 和回答 token- 最大步数 8 步兜底,防止模型进入工具调用死循环
结果:工具执行全程可见,入参/出参/耗时都有记录,调试效率大幅提升
亮点4:Human-in-Loop 工作流
做了什么:AI 先生成中间结果,人工确认后再生成最终输出
技术手段:
- LangGraph
interruptBefore: ['human_review']配合MemorySaver实现状态快照 graph.getState(config)检查state.next.length > 0判断是暂停还是完成graph.updateState(config, { humanFeedback })注入反馈,streamEvents(null, config)从断点继续
结果:生成质量明显高于一次性输出,用户能在关键节点介入修正方向
亮点5:ERP 自然语言结构化解析
做了什么:用户说一句话,AI 自动填写报销单/请假单
技术手段:
- Zod 定义精确 Schema,每个字段写
.describe()作为给模型的提示 model.withStructuredOutput(schema)保证输出格式,不需要手动解析 JSON- System Prompt 注入今天日期,让"下周一""上周三"这类相对时间能正确推算
结果:填单时间从平均 5 分钟降到 30 秒,且自动检测超标费用
三、面试官必问问题和标准回答
第一类:项目整体问题
Q:这个项目主要解决什么问题?
A:企业日常办公有几个高频痛点:内部文档查不到、重复性内容整理费时间、报销请假填表麻烦。这个项目把 AI 接入这几个场景,核心是让用户说自然语言,AI 来理解、处理、执行。技术上覆盖了 RAG、Agent、工作流、Multi-Agent 几种 LLM 应用模式,是一个完整的 AI 应用全栈实践。
Q:为什么选 DeepSeek,不用 GPT-4o?
A:两个原因。一是成本,DeepSeek-chat 的输入价格是 $0.27/M token,GPT-4o 是 $2.50/M token,差了将近 10 倍。对于一个办公场景应用,日均几千次调用,成本差异很显著。二是 DeepSeek 的 API 格式完全兼容 OpenAI,LangChain 的 ChatOpenAI 只需要改 baseURL 和 apiKey 就能用,迁移成本几乎为零。中文效果也不错,适合国内办公场景。
Q:整个项目最难的部分是什么?
A:有两个地方花时间最多。一个是 ES Module 加载顺序的问题。Node.js 的静态 import 是并行解析的,model.js 在模块初始化时就调用 new ChatOpenAI(),但这时候 dotenv 还没执行完,process.env.DEEPSEEK_API_KEY 是 undefined,LangChain 直接报错。解决方案是把单例改成懒加载——用 Proxy 代理属性访问,第一次真正调用时才创建实例,同时对已有的消费代码完全透明,不用改任何 import。
另一个是 LangGraph streamEvents 的事件过滤。on_chat_model_stream 事件在模型决定调用工具时和生成最终回答时都会触发,如果不过滤,工具调用的决策 token 也会被推送到前端,前端会显示出奇怪的 JSON 片段。通过判断 chunk.tool_call_chunks?.length 是否有值来区分两种情况,只把没有 tool_call_chunks 的 token 推送给前端。
第二类:技术深挖问题
Q:SSE 和 WebSocket 的区别是什么?你这里为什么选 SSE?
A:SSE 是单向的,只有服务端向客户端推送,基于 HTTP 协议,浏览器原生支持(EventSource 或 fetch + ReadableStream)。WebSocket 是双向的,需要握手升级协议。
选 SSE 的原因:流式对话场景是单向的,用户发一条消息,服务端流式返回,不需要双向通信。SSE 基于 HTTP,穿透代理和防火墙更容易,Nginx 加 proxy_buffering off 就能工作。WebSocket 需要单独处理连接管理、心跳、断线重连,实现复杂很多。对于这个场景,SSE 够用,选更简单的方案。
Q:你的 RAG 分片大小是怎么定的?为什么是 500,不是 200 或 1000?
A:这个值是权衡的结果,不是固定答案。分片太小(比如 100 字),一个知识点可能被拆散,向量化后语义不完整,检索容易找错;分片太大(比如 2000 字),向量平均了太多内容,精确度下降,而且可能超出 Embedding 模型的 token 限制。500 字大约是一个段落的长度,语义相对完整。实际上这个值需要根据文档类型调,技术手册可能 300 更好,法律合同可能 800 更合适。overlap=50 是为了防止关键信息刚好在两片的边界被截断——两片之间有 50 字重叠,边界处的内容两片都有,检索时不会漏掉。
Q:LangGraph 的 StateGraph 和普通代码写工作流有什么区别?
A:普通代码写工作流就是 await step1(); await step2(); await step3(),简单但有局限:没法暂停等人工干预,中间状态要自己管理,出错了要整个重跑,也没法可视化。LangGraph 的 StateGraph 有几个原生能力:一是状态持久化(MemorySaver),每个节点执行后自动存档,可以在任意节点前暂停(interruptBefore),也可以从断点继续(传 null 给 streamEvents);二是内置了 streamEvents,可以订阅每个节点的开始、结束事件,不需要自己埋点;三是图结构支持条件边(addConditionalEdges),Agent 那种"有工具调用就继续,没有就结束"的逻辑很自然。
Q:Human-in-Loop 的暂停是怎么实现的?如果用户关掉页面,状态还在吗?
A:暂停是 LangGraph 的 interruptBefore + MemorySaver 实现的。在 compile 时指定 interruptBefore: ['human_review'],执行到 human_review 节点前会自动中断,状态快照存在 MemorySaver 里。前端收到 paused 事件后展示审核界面,用户填完反馈后调 /resume 接口,服务端用同一个 thread_id 找到对应的快照,注入反馈后继续执行。
关于页面关闭:当前项目用的是 MemorySaver,状态在 Node.js 进程内存里。用户关掉页面、服务重启,状态就丢了,需要重新提交。这是 demo 版本的局限。生产上应该换成 PostgresSaver,把状态持久化到数据库,这样就算重启也能恢复。这个替换很简单,一行代码,接口完全一样。
Q:Multi-Agent 里多个 Agent 怎么协作的?共享什么?不共享什么?
A:多个 Agent 共享一个 conversationHistory 数组,这是一个 HumanMessage 和 AIMessage 的列表。每个审批角色执行完,它的消息(问题+申请人的回答+它的决定)都 push 进这个数组。后面的 Agent 调用时把这个完整历史传进去,它能看到前面所有对话,就不会问重复的问题。
每个 Agent 不共享的是 System Prompt——每个角色有自己独立的人格设定,主管关注业务合理性,财务关注合规性,HR 关注假期政策,这些 system prompt 是隔离的。LangChain 的 model.invoke([system, ...history]) 每次都传完整的上下文,不依赖模型的内部记忆,所以多个 Agent 用同一个模型实例也完全没问题。
Q:你怎么判断报销是否通过审批的?用结构化输出吗?
A:没有用结构化输出,用的是关键词匹配。因为审批对话本身是自然语言,我希望前端能展示真实的对话内容,不想让模型只返回 JSON。判断逻辑是:检查回复里有没有"驳回"、"不批"、"拒绝"这些否定词,没有就认为通过。
这种方式有局限,但在我们控制的场景里够用——因为角色的 System Prompt 里明确要求"给出批准/驳回的意见",模型要表达拒绝一定会用这类词。如果追求更严格,可以在决策阶段额外做一次结构化输出调用,让模型返回 { approved: boolean, reason: string },但这要多一次 API 调用,成本和延迟都增加,权衡下来没必要。
Q:你说 ES Module 有加载顺序问题,具体是怎么回事?
A:Node.js 的 ES Module 在解析阶段,所有 import 是静态的,引擎会先把依赖图分析出来,然后按依赖关系加载。但 dotenv/config 是在 config/index.js 里 import 的,而 model.js 也被其他模块 import,这两个模块谁先加载完毕是不确定的。
更具体的问题是:即使 config/index.js 先加载了,它定义 config 对象的那一行 { deepseekKey: process.env.DEEPSEEK_API_KEY } 在模块求值时立即执行,读取当时的 process.env。如果那一刻 dotenv/config 还没执行(比如在另一个模块的加载链上),读到的就是 undefined,赋值后就固定了。
解决方案两个:一是把 config 里的属性改成 getter,每次访问时才读 process.env,不在模块加载时求值;二是模型的单例改成懒加载,推迟到第一次调用时创建,那时整个模块图已经加载完、dotenv 已执行。两个都用,双重保险。
Q:Prompt 调试工具的 A/B 测试为什么用非流式接口?
A:两个原因。第一,A/B 测试需要等双方都完成才能做评分对比,如果用流式就要等两个流都结束,还要分别收集完整输出,逻辑反而更复杂。第二,用 Promise.all([callA, callB]) 并发执行,两个 Prompt 同时在跑,总耗时是 max(A时间, B时间),而不是 A+B,更高效。评分阶段再额外两次并发调用(分别给 A 和 B 打分),最后一次综合判断。五次 API 调用都是并发的,整体体验还是可接受的。
Q:缓存的 key 为什么用 MD5,直接用字符串拼接行不行?
A:直接用字符串拼接作为 Map 的 key 也能用,但有两个问题。一是 key 可能很长——System Prompt 可能有几百字,User Message 也有几百字,这个 key 字符串就很大,Map 存很多条时内存占用多。MD5 的 key 固定 32 字符,无论原始内容多长都一样。二是碰撞概率:MD5 在这个场景(key 数量在千级别)碰撞概率极低,可以忽略不计。安全性不是 concern,这里只是拿 MD5 当内容哈希用,不是密码学场景。
第三类:设计思路问题
Q:Agent 和工作流都用了 LangGraph,有什么区别?什么情况下选哪个?
A:区别在于谁决定执行路径。Agent 是模型自主决策——每步执行完,路由函数问模型"下一步是继续调工具还是结束",模型的回复里有没有 tool_calls 决定走哪条路,整个流程是动态的。工作流是开发者设计好路径——节点顺序在代码里写死,模型只负责执行当前节点的任务,不参与决策走向哪个节点。
选择标准:任务步骤不确定、需要 AI 自主规划的,用 Agent;步骤固定、要保证质量和稳定性、需要人工介入的,用工作流。周报生成我用工作流——因为每次都是一样的三步,要求质量稳定,还需要人工确认中间结果。技术调研用 Agent——因为不知道要搜哪些关键词、要搜几次,让模型自己决定。
Q:为什么要做用量看板,这是必要的吗?
A:必要。LLM 调用是按量计费的,不监控就不知道在哪花钱。有了看板才能发现问题:比如某个功能的平均 token 用量异常高,说明 System Prompt 写得太长;P99 延迟突然变大,说明某类请求触发了模型的慢路径;缓存命中率低,说明同类问题没有被复用,需要调整缓存策略。对于 AI 应用来说,成本监控和性能监控一样重要,不是锦上添花。
Q:项目有哪些不足,如果要上线还需要做什么?
A:当前版本的主要缺口有三个。
一是存储。所有数据(会话历史、用户画像、工作流状态、申请记录)都在 Node.js 进程内存里,重启就没了。上线要替换:Redis 存会话历史和缓存,PostgreSQL 存用户画像和申请记录,PostgresSaver 替换工作流的 MemorySaver。
二是鉴权。当前没有用户认证系统,任何人都能访问所有功能。上线需要加 JWT 认证,按用户隔离数据,限流也需要从全局粒度细化到用户粒度。
三是 RAG 的 Embedding 依赖 OpenAI,如果 OpenAI 服务有问题或者要降成本,需要支持本地 Embedding 方案(比如 Ollama + nomic-embed-text)。
四、可以主动展示的项目细节
在面试里主动提这些,能体现深度:
1. ES Module 竞态问题和 Proxy 懒加载方案 不只是"我遇到了问题",而是解释了底层原因(模块并行解析)和完整解决方案(getter + Proxy)。
2. streamEvents 的两类 token 区分 解释 tool_call_chunks 字段的作用,说清楚为什么要过滤,不是简单记住 API 用法,而是理解了工具调用阶段和回答阶段的区别。
3. RAG 相似度阈值的权衡 0.3 不是拍脑袋,是在召回率和精确率之间的权衡,能说出太高和太低分别有什么问题。
4. A/B 测试分开打分再综合 不是直接让模型"哪个更好",而是分别打分消除先后顺序偏见,这是一个有思考的设计决策。
5. 工作流暂停的生产局限 主动说出 MemorySaver 在生产环境的问题,以及 PostgresSaver 的替换方案,说明对生产部署有清醒认识。
五、项目数据(回答时可以引用)
| 指标 | 数据 |
|---|---|
| 模块数量 | 7 个核心功能模块 |
| 后端代码文件 | 19 个(路由8 + 服务11) |
| 前端组件 | 16 个 Vue 组件 |
| Agent 工具数量 | 6 个 |
| 工作流模板 | 4 个(周报/纪要/邮件/PRD) |
| ERP 审批角色 | 5 个(申请人/主管/财务/HR/总监) |
| Prompt 模板管理 | 版本历史最多保留 10 个 |
| 缓存容量 | 500 条(LRU 淘汰) |
| Agent 最大步数 | 8 步(防死循环) |
| RAG 分片大小 | 500 字,overlap=50 |
| 相似度阈值 | 0.3 |
六、一句话介绍(面试开场用)
简短版: 用 Vue3 + Node.js + LangChain.js 做了一个企业内部 AI 办公平台,覆盖对话、知识库问答、Agent 任务执行、内容工作流、ERP 报销审批等7个模块,重点在 AI 应用的工程化落地——流式输出、RAG、LangGraph Agent 和 Human-in-Loop 工作流都有完整实践。
展开版: 项目叫 WorkMind AI,是一个智能办公平台。技术栈是 Vue3 前端、Node.js 后端、LangChain.js 对接 DeepSeek 模型。7个模块里有几个比较有意思的:一是 RAG 知识库,上传公司内部文档后可以基于内容回答,我做了分片策略优化和幻觉控制;二是用 LangGraph 实现了 ReAct Agent,任务执行过程前端实时可见;三是 ERP 报销模块,用自然语言填单,Multi-Agent 模拟审批对话。整个项目从架构设计到部署都是自己做的,对 LLM 应用的全栈工程化有比较完整的实践经验。