资讯动态

Next.js + LangGraph.js 构建简历优化 AI Agent 实战

发布时间:2026/10/8 11:11:05 来源:尧图企业网站定制
1. 为什么我要用 Next.js LangGraph.js 做简历工具 Agent先说结论这个项目解决的核心问题是——让简历修改从“凭感觉改”变成“有流程、有状态、可回溯的工程化操作”。我做了三年多前端去年开始密集接触 AI Agent 方向踩了不少坑也看了很多“Demo 很惊艳、落地就翻车”的案例。简历工具这个场景看起来简单实际上是一个非常适合验证 Agent 架构的试验田它有明确的多步骤流程、有状态依赖、有工具调用需求而且用户对输出质量极其敏感。为什么选 Next.js因为简历工具天然需要前后端一体、流式输出、文件上传、实时预览。Next.js 的 App Router Server Actions Route Handlers 能把这些需求收拢在一个工程里不需要额外起一个后端服务。更重要的是Vercel AI SDK 对 Next.js 的支持非常成熟流式传输和工具调用在前端侧的处理成本极低。为什么选 LangGraph.js 而不是直接调 API这是很多人问我的问题。直接调大模型 API 做简历优化你会遇到几个绕不过去的坎第一简历优化不是一步能完成的它需要“解析原始简历 → 识别目标岗位 → 匹配关键词 → 重写经历描述 → 校验格式 → 生成建议”这一串步骤第二每一步的输出会影响下一步的输入你需要一个状态机来管理第三用户可能中途要求“只改第二段”“把技能栏往前挪”这需要可中断、可恢复、可回溯的流程控制。LangGraph.js 的核心价值就在这里——它把 Agent 的流程显式建模成图Graph节点是处理步骤边是流转条件状态在节点间传递和累积。我实测下来用 LangGraph.js 做简历 Agent最大的收益不是“更智能”而是可控。你可以精确知道当前走到哪一步、上一步输出了什么、如果某一步质量不达标该怎么回退。这对于简历这种“改错一个字都可能影响面试机会”的场景太重要了。这个项目适合谁如果你已经会 Next.js 基础想找一个真实场景把 AI Agent 落地简历工具是非常好的切入点。如果你是大模型应用开发者想了解 LangGraph.js 在 Web 端的工程化实践这篇内容也能给你一套可复现的方案。哪怕你只是想做一个小工具给自己改简历跟着走也能跑通。2. 整体架构设计与技术选型拆解2.1 为什么是“图”而不是“链”很多人做 AI 应用的第一反应是 Chain链式调用LangChain.js 的 LCEL 也确实好用。但简历优化这个场景链式结构会很快遇到瓶颈。我举个实际例子用户上传简历后系统需要先解析 PDF 提取文本然后判断这份简历是“应届生版”还是“社招版”两者的优化策略完全不同。应届生侧重项目经历和校园活动社招侧重工作成果和量化指标。如果用链你只能在 Prompt 里写 if-else模型经常不按套路出牌。LangGraph.js 的做法是把“判断简历类型”做成一个条件边Conditional Edge根据解析出的文本特征路由到不同的处理节点。这样每个节点只需要关注自己的职责Prompt 也更干净。我试过两种方案链式方案在复杂简历上的准确率大概 70% 左右图方案能到 90% 以上因为路由逻辑是代码控制的不依赖模型“自觉”。另一个关键点是状态持久化。LangGraph.js 支持 Checkpointer可以把每一步的状态存到数据库或内存。这意味着用户改到一半关掉页面下次回来还能接着改。简历工具的使用场景往往是“改改停停”这个能力直接决定用户体验。2.2 技术栈分层与职责划分整个项目的技术栈我分成四层每层的选型和理由如下层级技术选型核心职责选型理由前端交互层Next.js App Router React Server Components页面渲染、文件上传、流式展示一体化开发SSR 对 SEO 友好流式响应原生支持API 路由层Next.js Route Handlers接收请求、调用 Agent、返回流与前端同工程部署简单Edge Runtime 可选Agent 编排层LangGraph.js状态管理、节点编排、条件路由图结构清晰支持中断恢复社区活跃模型与工具层OpenAI/Claude API 自定义 Tools文本生成、PDF 解析、关键词提取模型可替换工具按需扩展这里有个细节值得展开为什么不用 Server Actions 直接调 Agent我一开始就是这么干的后来发现 Server Actions 在处理长时间流式响应时超时和中断的处理不够灵活。Route Handlers 可以更细粒度地控制 Response Stream配合ReadableStream做 SSEServer-Sent Events前端用EventSource或 fetch 的 reader 接收稳定性好很多。实测下来改简历这种可能跑 30 秒以上的任务Route Handlers 是更稳妥的选择。2.3 简历 Agent 的状态设计LangGraph.js 的核心是 State。我定义的 State 结构大概长这样TypeScript 描述interface ResumeState { rawText: string; // 原始简历文本 resumeType: fresh | experienced | unknown; // 简历类型 targetRole: string; // 目标岗位 jobDescription: string; // 岗位 JD parsedSections: { // 解析后的分段 basic: string; education: string; experience: string; projects: string; skills: string; }; optimizedSections: Recordstring, string; // 优化后的分段 suggestions: string[]; // 改进建议列表 currentStep: string; // 当前步骤标识 errors: string[]; // 错误收集 }这个 State 设计有几个考量第一rawText和parsedSections分开存方便回溯和对比第二resumeType作为路由依据避免在每个节点里重复判断第三errors数组让流程可以“带伤运行”某一步失败不一定要整个中断可以记录后继续最后统一提示。注意State 字段不要设计得太细否则节点间传递的数据量会膨胀。我一开始把每个 bullet point 都单独存字段结果状态对象巨大Checkpointer 序列化很慢。后来改成按 section 存字符串需要细粒度操作时在节点内部临时解析。3. LangGraph.js 核心节点实现与实操要点3.1 节点划分从“解析”到“输出”的六个关键步骤我把整个 Agent 流程拆成六个节点每个节点职责单一便于调试和替换parseResumeNode接收原始文本调用 PDF 解析工具或直接处理文本输出结构化分段。classifyResumeNode根据分段内容判断简历类型应届/社招写入resumeType。extractKeywordsNode从 JD 中提取关键词和技能要求生成匹配清单。optimizeSectionsNode按 section 逐个优化这是最耗时的节点也是流式输出的主要来源。validateNode校验优化后的内容是否符合格式要求如时间格式、量化指标是否保留。generateSuggestionsNode生成整体改进建议汇总输出。这六个节点不是随便切的。我试过把 optimize 和 validate 合并结果 Prompt 太长模型经常顾此失彼。拆开后validate 节点可以用更小的模型甚至规则引擎来做成本和速度都更优。3.2 条件路由的实现细节classifyResumeNode之后需要一个条件边根据resumeType决定走哪条优化路径。LangGraph.js 的条件边写法大概是这样const routeByResumeType (state: ResumeState) { if (state.resumeType fresh) return optimizeFresh; if (state.resumeType experienced) return optimizeExperienced; return optimizeGeneral; }; graph.addConditionalEdges(classifyResume, routeByResumeType, { optimizeFresh: optimizeFreshNode, optimizeExperienced: optimizeExperiencedNode, optimizeGeneral: optimizeGeneralNode, });这里有个坑我踩过条件边的返回值必须是字符串且要和映射表的 key 完全一致。我一开始返回了对象调试了半天才发现类型不对。另外条件边的路由函数尽量保持纯函数不要在里面做异步操作否则图执行器可能行为异常。3.3 流式输出的工程化处理简历优化最怕用户等半天没反应。我的做法是在optimizeSectionsNode里每优化完一个 section 就 yield 一次通过 LangGraph.js 的 stream 模式往外推。Next.js 侧用 Route Handler 接收这个 stream转成 SSE 发给前端。具体实现上LangGraph.js 的graph.stream()返回的是一个异步迭代器每个 chunk 包含当前节点的输出。我在 Route Handler 里这样处理export async function POST(req: Request) { const { resumeText, jobDescription } await req.json(); const encoder new TextEncoder(); const stream new ReadableStream({ async start(controller) { const graphStream await graph.stream({ rawText: resumeText, jobDescription, }); for await (const chunk of graphStream) { const data data: ${JSON.stringify(chunk)}\n\n; controller.enqueue(encoder.encode(data)); } controller.close(); }, }); return new Response(stream, { headers: { Content-Type: text/event-stream, Cache-Control: no-cache, Connection: keep-alive, }, }); }前端用EventSource或 fetch reader 接收每收到一个 section 就更新 UI。实测下来用户感知的“首屏时间”从 20 多秒降到了 2 秒以内体验提升非常明显。提示SSE 连接在有些代理环境下会被缓冲导致流式效果失效。如果你部署后发现前端不是逐段显示而是一次性出来检查一下响应头里有没有X-Accel-Buffering: no这个头对 Nginx 类代理很关键。3.4 工具调用的设计PDF 解析与关键词提取LangGraph.js 的 ToolNode 可以很方便地接入自定义工具。我定义了两个核心工具parsePDFTool接收文件 Buffer返回文本。我用的是pdf-parse这个库轻量且对中文支持还行。如果简历是图片型 PDF需要额外接 OCR这部分我暂时用提示引导用户上传文本版。extractKeywordsTool接收 JD 文本返回关键词数组。这个工具内部其实也是调模型但封装成工具后可以在多个节点复用也方便单独测试。工具定义要注意参数 schema 的严谨性。LangGraph.js 用 Zod 做参数校验我一开始 schema 写得太宽松模型经常传错格式。后来把每个字段的类型、描述、是否必填都写清楚工具调用的成功率明显提升。4. Next.js 侧的工程化落地与避坑实录4.1 项目目录结构与职责边界我的目录结构大概是这样app/ api/ optimize/ route.ts # Agent 入口处理流式响应 resume/ page.tsx # 简历工具主页面 components/ UploadZone.tsx # 文件上传 SectionPreview.tsx # 分段预览 SuggestionPanel.tsx # 建议面板 lib/ agent/ graph.ts # LangGraph 图定义 nodes/ # 各节点实现 tools/ # 自定义工具 state.ts # State 类型定义 utils/ pdf.ts # PDF 处理 text.ts # 文本清洗这个结构的关键是把 Agent 逻辑和 UI 逻辑彻底分开。lib/agent里不引入任何 React 相关代码这样 Agent 可以独立测试也方便以后迁移到其他框架。我见过有人把 Agent 调用写在组件里结果状态管理一团糟改一个节点要动好几个文件。4.2 文件上传与文本提取的实操细节简历上传支持 PDF 和纯文本。PDF 处理在服务端做避免前端解析库体积过大。这里有个细节Next.js 的 Route Handler 默认有 body size 限制大文件上传需要在next.config.js里调整或者用分片上传。我实测一份两页的 PDF 大概 200KB 左右默认限制够用但如果你要支持作品集类的大文件记得提前配置。文本提取后要做清洗去掉多余空行、统一标点、处理乱码。我写了一个cleanText函数大概做了这几件事把连续三个以上的换行合并成两个把全角标点统一成半角中文内容除外去掉页眉页脚的重复内容通过检测高频重复行这些清洗步骤看起来琐碎但对后续模型理解影响很大。我对比过清洗后的简历解析准确率比不清洗高不少。4.3 前端流式渲染的状态管理前端接收 SSE 后需要把每个 section 的优化结果实时更新到 UI。我用的是 React 的useReducer来管理这些分段状态而不是useState因为分段更新是结构化的reducer 更清晰。一个容易忽略的点是滚动位置保持。当某个 section 更新时如果用户正在看下面的内容页面不要跳回顶部。我的做法是给每个 section 加key更新时只重渲染对应部分配合scrollIntoView的block: nearest参数避免跳动。注意流式更新时不要每个 chunk 都触发一次 React 渲染那样性能很差。我的做法是攒 100ms 或攒够一定字符数再批量更新实测流畅度好很多。4.4 错误处理与降级策略Agent 流程中任何一步都可能失败模型超时、工具报错、格式解析失败。我的策略是分级降级模型调用失败重试两次仍失败则返回原始内容并提示“该段暂未优化”PDF 解析失败引导用户手动粘贴文本关键词提取失败跳过匹配步骤直接做通用优化这些降级逻辑写在节点内部通过 State 的errors字段传递。最后在generateSuggestionsNode里统一汇总告诉用户哪些部分没处理好。这样即使部分失败用户也能拿到可用的结果而不是整个流程崩掉。5. 常见问题排查与独家避坑技巧5.1 模型输出格式不稳定的排查思路这是最高频的问题。模型有时候返回 Markdown有时候返回纯文本有时候还带一堆解释性废话。我的排查步骤是检查 Prompt 里的格式约束是否足够具体。不要只说“返回 JSON”要给出完整的 schema 示例。在节点后加一个格式清洗函数。用正则提取 JSON 块去掉代码块标记。如果还不行用结构化输出Structured Output。OpenAI 和 Claude 都支持强制 JSON schemaLangGraph.js 也封装了对应能力。我实测下来加了结构化输出后格式错误率从 15% 降到了 1% 以下。代价是稍微牺牲一点生成灵活性但对简历这种格式敏感的场景完全值得。5.2 流式响应中断的常见原因流式输出中断通常有三个原因现象可能原因排查方法解决方案前端只收到第一段代理缓冲检查响应头加X-Accel-Buffering: no中途断开函数超时查看部署平台日志调整超时时间或改用 Edge Runtime部分 chunk 丢失编码问题检查 TextEncoder 使用确保统一用 UTF-8 编码我遇到过一次很诡异的中断最后发现是部署平台的默认超时是 10 秒而我的 Agent 跑了 15 秒。改成流式后只要首字节在超时前发出后续就能持续传输问题自然解决。5.3 简历优化的质量把控经验技术跑通只是第一步输出质量才是用户真正关心的。我总结了几个提升质量的经验给模型提供“好例子”。在 Prompt 里放一两个优化前后的对比示例模型会模仿这个风格。这比单纯描述“要量化、要突出成果”有效得多。分段优化而不是整篇优化。整篇丢给模型它容易顾此失彼而且 token 消耗大。分段处理后每段可以针对性写 Prompt。保留用户的原始表达习惯。有些人喜欢简洁有些人喜欢详细。我在 Prompt 里加了“保持原文语气”的约束避免优化后风格突变。量化指标不要瞎编。模型有时候会“脑补”数据这是大忌。我在 validate 节点里加了检测如果优化后的内容出现了原文没有的数字标记出来让用户确认。5.4 成本控制的实操技巧简历工具如果免费开放模型调用成本是个现实问题。我的控制策略分级模型解析、分类、校验用便宜的小模型优化和生成建议用大模型。缓存相同 JD 的关键词提取结果缓存起来避免重复调用。限制长度简历文本超过一定长度就截断或提示用户精简避免 token 爆炸。流式 提前终止如果用户中途关闭页面及时 abort 请求避免无效调用。实测下来一份简历的完整优化成本可以控制在几毛钱以内如果用小模型做前置处理还能再降一半。5.5 LangGraph.js 版本升级的注意事项LangGraph.js 还在快速迭代API 偶尔有 breaking change。我踩过一次坑升级后addConditionalEdges的参数顺序变了导致路由全部失效。建议锁定版本号不要用^或latest升级前先跑一遍核心流程的单元测试关注官方 changelog重点看 State 和 Edge 相关的改动如果你在生产环境用建议把 Agent 逻辑封装成独立的 service这样即使库升级影响范围也可控。6. 这个项目后续还能怎么扩展跑通基础流程后我陆续加了一些扩展效果不错分享几个方向多轮对话式修改。用户看完优化结果后可以直接说“第二段再简洁一点”“技能栏加上 Python”Agent 根据指令局部重跑。这需要把 LangGraph 的 Checkpointer 用起来保存对话历史。岗位匹配度评分。在关键词提取后加一个评分节点计算简历与 JD 的匹配度给出百分比和缺失关键词。这个功能对求职者很有吸引力。多版本管理。同一份简历针对不同岗位生成不同版本用数据库存起来用户可以切换查看。这需要把 State 持久化到 Postgres 或 SQLite。导出功能。优化完成后一键导出 PDF 或 Word保持格式整洁。我用的是react-pdf/renderer前端直接生成不需要服务端参与。A/B 对比视图。左边原始简历右边优化后高亮差异部分。这个用 diff 算法就能实现视觉冲击力很强用户很容易感知价值。我个人在实际操作中的体会是简历工具的技术难点不在模型调用而在流程编排和状态管理。LangGraph.js 提供的图结构恰好匹配了这类多步骤、有状态、需回溯的场景。Next.js 则把前后端一体化的开发体验拉满流式响应和文件处理都很顺手。这套组合我用了大半年稳定性没问题扩展性也够。如果你正准备做类似的项目建议先把 State 设计清楚再动手写节点能省很多返工时间。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑