
简历工具这个赛道看起来已经被做烂了但真正用起来顺手的没几个。我自己前前后后试过不下十款在线简历产品要么是模板千篇一律要么是改一个地方格式全乱要么是导出PDF之后排版直接崩掉。后来我干脆自己动手用 Next.js 搭前端、LangGraph.js 编排 AI Agent 逻辑做了一套能对话式改简历、自动优化措辞、按目标岗位定制内容的工具。整套东西跑下来从技术选型到落地踩坑积累了不少值得记录的经验。这篇文章面向的是有一定前端基础、想入门 AI Agent 开发、或者正在做类似简历/文档类工具的朋友。我会把整个项目的设计思路、LangGraph.js 的状态图编排、Next.js 的全栈整合、流式输出的实现细节以及实际部署中遇到的各种问题全部拆开讲清楚。哪怕你之前没接触过 LangGraph.js跟着思路走也能理解它到底解决了什么问题。1. 为什么选 Next.js 加 LangGraph.js 这套组合1.1 简历工具的核心痛点到底是什么先想清楚一个问题简历工具的本质是什么表面上看是排版和模板但真正让人头疼的是内容本身。大部分人写简历的困境不是不知道怎么排版而是不知道该怎么描述自己的经历。比如负责了一个后台系统的开发这种写法放在简历里毫无竞争力但很多人就是写不出来更好的表达。传统简历工具解决的是格式问题提供模板、拖拽排版、一键换肤。但内容质量这件事它们基本不管。你填什么它就渲染什么哪怕你写的是流水账它也照样给你导出一份排版精美的流水账。所以我的核心判断是简历工具的下一个形态一定是内容共创——AI 不只是帮你排版而是帮你把经历翻译成招聘方想看的语言。这就需要一个能理解上下文、能多轮对话、能根据反馈调整输出的 AI Agent而不是简单的输入框加一个调用大模型的按钮。1.2 为什么不用简单的 API 调用而要上 LangGraph.js一开始我也想过不就是调个大模型 API 吗写个函数传 prompt 进去拿结果不就行了但实际做下来发现简历优化这个场景远比想象中复杂。一份简历的优化流程至少包含这些步骤解析用户原始输入、识别目标岗位、分析岗位关键词、逐段优化经历描述、检查整体一致性、生成最终版本。这些步骤之间有依赖关系有些需要循环比如某段经历优化后用户不满意要重新优化有些需要条件分支比如用户没有实习经历就跳过相关模块。如果用一个简单的函数串联代码会变成一坨意大利面而且很难调试。LangGraph.js 的价值就在这里它把整个 Agent 的执行流程建模成一张状态图每个节点是一个处理步骤边定义了步骤之间的流转关系。你可以清晰地看到数据怎么流动、在哪个节点做了什么处理、什么条件下走哪条分支。提示LangGraph.js 不是 LangChain 的替代品它是建立在 LangChain 之上的编排层。你仍然会用 LangChain 的模型接口、工具定义只是用图的方式把它们组织起来。1.3 Next.js 在这个项目里承担了什么角色Next.js 在这个项目里不只是前端框架它承担了全栈的职责。App Router 的 Route Handlers 直接充当了后端 API 层前端组件通过 fetch 调用这些接口接口内部再去调用 LangGraph.js 编排的 Agent。这样做的好处是整个项目只有一个代码仓库、一套构建流程、一次部署。不需要单独维护一个后端服务也不需要处理跨域问题。对于个人项目或者小团队来说这种全栈模式能极大降低运维成本。另外 Next.js 的流式响应能力Streaming和 AI Agent 的场景天然契合。Agent 处理简历优化可能需要十几秒甚至更久如果等全部处理完再返回用户会以为页面卡死了。用流式输出用户可以实时看到 AI 的思考过程和逐步生成的内容体验好很多。2. LangGraph.js 状态图的核心设计2.1 状态定义整个 Agent 的数据骨架LangGraph.js 的核心概念是状态State。整个图在执行过程中所有节点共享一个状态对象每个节点读取状态、处理后返回状态的更新部分。我的简历 Agent 状态定义大概长这样interface ResumeState { rawInput: string; // 用户原始输入 targetRole: string; // 目标岗位 parsedSections: Section[]; // 解析后的简历段落 optimizedSections: Section[]; // 优化后的段落 currentSectionIndex: number; // 当前处理到第几段 feedback: string; // 用户反馈 retryCount: number; // 重试次数 finalResume: string; // 最终输出 }这个状态设计有几个关键考量。第一rawInput和parsedSections分开存因为原始输入需要保留后续如果解析出错可以回溯。第二currentSectionIndex用来支持逐段处理避免一次性把所有内容塞给模型导致输出质量下降。第三retryCount用来防止无限循环用户如果一直不满意达到上限就强制输出当前最优版本。2.2 节点划分每个节点只做一件事我把整个流程拆成了六个核心节点parseNode解析用户输入把一大段文字拆成结构化的段落个人信息、教育背景、工作经历、项目经验、技能等analyzeNode分析目标岗位提取关键词和能力要求optimizeNode对当前段落进行优化结合岗位关键词调整措辞reviewNode检查优化后的内容是否偏离原意、是否有事实性错误feedbackNode处理用户反馈决定是否需要重新优化finalizeNode汇总所有优化后的段落生成最终简历每个节点只负责一件事这样调试的时候很容易定位问题。比如输出质量不好我可以单独测试 optimizeNode 的 prompt而不用跑整个流程。2.3 条件边让 Agent 学会看情况办事LangGraph.js 最有价值的功能之一是条件边Conditional Edge。它允许你根据当前状态决定下一步走哪个节点。在我的设计里reviewNode 之后有一个条件判断如果检查通过就进入下一段的优化如果检查不通过就回到 optimizeNode 重新优化。这个判断逻辑大概是这样function shouldRetry(state: ResumeState): string { if (state.reviewPassed) { return next_section; } if (state.retryCount 3) { return force_accept; } return retry_optimize; }这个设计解决了一个很实际的问题大模型的输出不稳定有时候会把用户的原意改掉有时候会编造不存在的信息。通过 reviewNode 加条件边可以在流程内部做质量把关而不是把所有问题都丢给用户去发现。注意retryCount 的上限一定要设否则遇到模型持续输出不合格内容的情况整个流程会陷入死循环既浪费 token 又让用户等待过久。3. Next.js 全栈整合的实操细节3.1 项目结构怎么组织才清晰我的项目目录结构是这样的/app /api /resume /parse/route.ts /optimize/route.ts /stream/route.ts /editor page.tsx /preview page.tsx /lib /agent graph.ts nodes.ts state.ts /prompts optimize.ts analyze.ts /components ResumeEditor.tsx ChatPanel.tsx把 Agent 相关逻辑全部放在/lib/agent下和 UI 组件完全解耦。这样做的好处是Agent 的逻辑可以独立测试不依赖任何 React 组件。我甚至写了一个纯 Node.js 的测试脚本直接调用 graph 来验证流程是否正确。3.2 流式输出怎么实现流式输出是提升用户体验的关键。Next.js 的 Route Handler 支持返回 ReadableStream我可以把 LangGraph.js 的执行过程通过 stream 推送给前端。实现思路是在 graph 的每个节点执行完成后通过 callback 把当前状态推送到 stream。前端用 EventSource 或者 fetch 的 reader 来接收这些事件实时更新 UI。export async function POST(req: Request) { const encoder new TextEncoder(); const stream new ReadableStream({ async start(controller) { const graph buildGraph(); const eventStream await graph.stream(initialState); for await (const event of eventStream) { controller.enqueue( encoder.encode(data: ${JSON.stringify(event)}\n\n) ); } controller.close(); }, }); return new Response(stream, { headers: { Content-Type: text/event-stream, Cache-Control: no-cache, }, }); }这里有个细节要注意graph.stream()返回的是异步迭代器每个 event 包含当前执行的节点名称和状态更新。前端可以根据节点名称显示不同的进度提示比如正在分析岗位要求、正在优化工作经历等让用户知道系统在干什么。3.3 前端状态管理的一个坑前端这边我踩过一个坑简历编辑器的状态和 Agent 返回的状态容易不同步。用户在编辑器里手动改了一段内容然后触发 AI 优化如果优化结果直接覆盖编辑器状态用户的手动修改就丢了。我的解决方案是维护两份状态一份是用户编辑态一份是AI 建议态。AI 返回的优化结果不直接覆盖而是以 diff 的形式展示用户可以选择接受或拒绝。这个交互设计比直接覆盖要友好得多也避免了很多AI 把我写的东西改没了的抱怨。4. Prompt 工程与输出质量控制4.1 简历优化 Prompt 的写法Prompt 的质量直接决定了输出质量。我试了很多版本最后稳定下来的结构是这样的你是一位资深 HR 和简历顾问正在帮助用户优化简历中的【工作经历】部分。 目标岗位{targetRole} 岗位关键词{keywords} 用户原始描述 {originalText} 优化要求 1. 保持事实不变不得编造任何数据或经历 2. 使用动词开头突出个人贡献而非团队职责 3. 尽量量化成果如果原文没有数据用显著提升等定性描述代替 4. 控制在 3-5 句话每句话不超过 40 字 5. 自然融入岗位关键词但不要生硬堆砌 请直接输出优化后的内容不要添加任何解释。这个 prompt 有几个关键点。第一明确角色定位让模型知道自己在做什么。第二强调不得编造这是简历场景的红线。第三给出具体的格式要求句数、字数避免输出过长或过短。第四要求直接输出结果不要加好的我来帮你优化这种废话。4.2 怎么防止模型编造经历这是简历工具最需要警惕的问题。大模型天生有讨好用户的倾向你让它优化它可能会给你加上一些你根本没做过的事情。我的防御策略分三层。第一层在 prompt 里明确禁止编造。第二层在 reviewNode 里做事实性检查把优化后的内容和原文对比如果出现了原文没有的具体数据比如提升了 30%就标记为可疑。第三层在前端展示 diff让用户自己确认每一处修改。实测下来这三层防护能拦住绝大部分编造问题。但我也遇到过模型把参与了项目改成主导了项目这种微妙的变化reviewNode 很难自动识别。所以最终的用户确认环节不能省。4.3 不同岗位的优化策略差异技术岗和产品岗、运营岗的简历写法差别很大。技术岗看重具体技术栈和项目复杂度产品岗看重数据结果和跨部门协作运营岗看重增长指标和活动策划。我在 analyzeNode 里做了一个岗位分类根据目标岗位自动选择不同的优化策略模板。比如技术岗的 prompt 会强调突出技术难点和解决方案产品岗的 prompt 会强调突出数据结果和用户价值。这个分类不需要很精细粗粒度地分成技术、产品、运营、设计、其他这几类就够了。关键是让模型知道不同岗位的关注点不同而不是用一套通用模板处理所有情况。5. 常见问题与排查实录5.1 流式输出中断怎么办这个问题我遇到过好几次。表现是前端收到一半内容后突然停止控制台报 stream closed 错误。排查下来主要有两个原因。一是 Vercel 的 Serverless Function 有执行时间限制免费版是 10 秒Pro 版是 60 秒。如果 Agent 处理时间超过限制stream 会被强制关闭。解决办法是把长任务拆分成多个短请求或者升级到支持更长执行时间的方案。二是网络波动导致连接断开。这个在前端要做好重连逻辑记录已经接收到的内容重连后从断点继续。5.2 模型输出格式不稳定有时候模型会不按要求的格式输出比如让它直接输出优化内容它偏要加一段以下是我的优化建议。我的处理方式是在代码里做后处理用正则把常见的废话前缀去掉。同时调整 prompt把不要添加任何解释这句话放在最后并且用更强的语气比如直接输出结果禁止任何前缀、后缀或解释性文字。如果还是不稳定可以考虑用结构化输出Structured Output让模型返回 JSON 格式代码解析后再提取需要的字段。LangChain 提供了withStructuredOutput方法可以强制模型按指定 schema 输出。5.3 Token 消耗过快怎么优化简历优化这个场景token 消耗主要来自两个方面一是 prompt 本身比较长包含岗位信息、优化要求等二是多轮重试会重复消耗。优化手段有几个。第一把不变的 prompt 部分做成模板缓存LangChain 支持 prompt caching可以复用已经处理过的前缀。第二控制重试次数不要无限重试。第三对于简单的优化任务用小模型比如 GPT-4o-mini 或者 Claude Haiku只有复杂任务才用大模型。我实测下来合理控制重试次数加上模型分级使用token 成本能降低 60% 左右。5.4 常见问题速查表问题现象可能原因排查方向解决方案流式输出中断执行超时/网络断开查看服务端日志和前端网络面板拆分任务/增加重连逻辑输出包含废话前缀Prompt 约束不够强检查实际返回内容加强 prompt/后处理正则模型编造经历事实性检查缺失对比原文和优化结果三层防护/用户确认Token 消耗过快重试过多/模型选择不当统计每次请求 token 数限制重试/分级用模型前端状态不同步直接覆盖用户编辑检查状态更新逻辑双状态/diff 展示6. 部署与性能优化的实战经验6.1 部署方案怎么选这个项目我试过三种部署方式。第一种是 Vercel最省事push 代码自动部署但 Serverless Function 的执行时间限制是个硬伤。第二种是自建 Node.js 服务器用 Docker 部署灵活度高但需要自己处理 HTTPS、域名、监控这些。第三种是部署到支持长连接的云平台兼顾便利性和灵活性。对于个人项目我建议先用 Vercel 快速验证等用户量上来了再考虑迁移。如果 Agent 处理时间经常超过 10 秒那就直接选自建服务器方案省得后面折腾。6.2 冷启动问题怎么缓解Serverless 的冷启动是个老问题。用户第一次访问时函数需要初始化LangGraph.js 的图构建、模型客户端初始化都需要时间可能导致首屏响应很慢。我的缓解措施是在应用启动时预热用一个定时任务定期调用健康检查接口保持函数活跃。另外把图的构建逻辑做成单例避免每次请求都重新构建。6.3 数据库和持久化简历数据需要持久化用户不可能每次都重新输入。我用的是 Postgres 加 Prisma存用户信息、简历内容、优化历史。这里有个设计决策优化历史要不要存我一开始觉得没必要后来发现很有用。用户可以对比不同版本的优化结果也可以回滚到之前的版本。而且这些历史数据可以用来分析哪些优化策略效果好为后续改进提供依据。6.4 监控和日志AI Agent 应用的监控比普通 Web 应用复杂因为多了模型调用这一层。我主要监控这几个指标每次请求的 token 消耗、模型响应时间、各节点的执行耗时、错误率和错误类型。日志方面我会记录每次 Agent 执行的完整状态变化包括输入、每个节点的输出、最终结果。这些日志在排查问题时非常有用比如用户反馈优化结果不对我可以直接看日志定位是哪个节点出了问题。7. 这套架构还能怎么扩展7.1 多模态简历解析现在只支持文本输入但很多用户的简历是 PDF 或 Word 格式。下一步可以接入文档解析能力把 PDF 转成结构化文本再走 Agent 流程。LangChain 有现成的 Document Loader支持 PDF、Word、Markdown 等多种格式。7.2 岗位匹配度分析除了优化简历内容还可以做一个岗位匹配度分析功能。用户输入目标岗位的 JDAgent 分析简历和 JD 的匹配程度指出哪些要求满足了、哪些还有差距、建议补充什么内容。这个功能对求职者来说价值很大。7.3 面试问题预测基于优化后的简历和目标岗位Agent 可以预测面试官可能问的问题并给出回答建议。这个扩展能把工具从简历优化延伸到求职全流程辅助用户粘性会更强。7.4 多语言支持目前只做了中文简历优化但英文简历的需求也很大。多语言支持需要在 prompt 层面做适配不同语言的简历写法差异很大不能简单翻译。8. 一些踩坑之后的真心话做这个项目最大的感受是AI Agent 的难点不在技术而在产品设计。技术上的东西LangGraph.js 的文档看一遍基本就会了Next.js 的全栈模式也很成熟。但怎么设计交互流程、怎么控制输出质量、怎么让用户信任 AI 的建议这些才是真正花时间的地方。另外一个体会是不要追求一步到位。我一开始想做一个全自动的简历生成器用户输入基本信息AI 直接生成完整简历。但实际做下来发现用户对自己的经历有很强的掌控欲他们不希望 AI 替他们做决定而是希望 AI 给建议、他们来拍板。所以最终的产品形态是AI 建议加用户确认而不是AI 全自动生成。还有一点关于 LangGraph.js 的使用建议不要为了用而用。如果你的流程很简单就是输入到输出一条直线那用普通的函数调用就够了没必要上 LangGraph.js。只有当流程有分支、有循环、有多个步骤需要协调时LangGraph.js 的价值才能体现出来。最后分享一个调试技巧LangGraph.js 支持把执行过程可视化你可以把图的结构导出成图片直观地看到节点和边的连接关系。调试复杂流程时这个功能能帮你快速定位问题出在哪个环节。另外给每个节点加上详细的日志输出记录输入状态和输出状态排查问题时能省很多时间。