新闻详情

新闻详情

首页 / 资讯中心 / 详情

Next.js + LangGraph.js 构建简历工具 Agent 实战

发布时间:2026/10/1 9:47:17来源:尧图网络
Next.js + LangGraph.js 构建简历工具 Agent 实战
1. 为什么选择 Next.js LangGraph.js 来做简历工具 Agent1.1 简历工具的真实痛点在哪里做简历工具这件事表面上看是个前端表单加模板渲染的活儿但真正落地过的人都知道麻烦的地方从来不是排版而是“内容”。用户打开一个简历工具脑子里往往是一团浆糊我该写什么、这段经历怎么描述、项目成果怎么量化、技能标签怎么组织。传统简历工具解决的是“排版问题”给你一堆模板让你填空填完导出 PDF 就完事了。但用户真正卡住的地方是“我不知道怎么填”。这就是 AI Agent 切入的价值点。一个简历工具 Agent 要做的不是帮你排版而是像一个有经验的职业顾问一样跟你对话问出你的经历细节帮你把口语化的描述改写成专业的书面表达帮你把模糊的“负责了某个项目”拆解成“主导了 X 项目覆盖 Y 用户提升了 Z 指标”。这个过程本质上是一个多轮对话加结构化信息抽取加文本生成的任务而且中间还涉及状态管理、工具调用、条件分支。为什么不用一个简单的 Prompt 调一次大模型就完事因为简历生成是一个典型的有状态、多步骤、需要外部工具配合的流程。你需要记住用户前面说了什么需要在信息不足时主动追问需要在生成后调用 PDF 渲染工具需要在用户修改某一段时只重新生成那一段而不是全部重来。这些需求叠加在一起普通的单次 API 调用根本扛不住必须上 Agent 架构。1.2 LangGraph.js 相比 LangChain 普通链式调用的优势LangGraph.js 是 LangChain 团队推出的面向 JavaScript/TypeScript 生态的 Agent 编排框架。它和传统的 LangChain Chain 最大的区别在于Chain 是线性的A 做完到 BB 做完到 C一旦中间需要回退、循环、条件跳转Chain 就会变得非常别扭。而 LangGraph 把整个流程建模成一张有向图节点是处理步骤边是流转条件天然支持循环、分支、中断和恢复。放到简历工具这个场景里这个特性太关键了。举个例子用户说“帮我写一下我在某公司的经历”Agent 需要判断用户提供的信息够不够。如果不够它要回到追问节点继续问如果够了它要进入生成节点生成完之后用户可能说“这段再改改”那又要回到生成节点。这种“问-答-生成-修改-再生成”的循环用 LangGraph 的状态图来表达就是几个节点加几条条件边的事用传统 Chain 写会变成一堆 if-else 嵌套维护起来非常痛苦。另一个关键优势是 LangGraph 的 Checkpointer 机制。它可以把每一步的状态持久化下来这意味着用户关掉页面再回来对话还能接着上次的进度继续。对于简历这种可能需要反复打磨好几天的场景这个能力是刚需。你可以把状态存到内存、存到数据库甚至存到 RedisLangGraph 都提供了对应的接口。1.3 Next.js 在这个架构里扮演什么角色Next.js 在这个项目里不只是个前端框架它承担的是全栈胶水层的角色。App Router 的 Route Handlers 可以直接写后端 APIServer Actions 可以让前端组件直接调用服务端逻辑Streaming 能力可以让 Agent 的回复像打字机一样逐字吐出来。这三样东西加在一起刚好覆盖了一个 AI Agent 应用最核心的交互需求。具体来说前端用 React Server Components 渲染简历预览面板用 Client Components 处理对话输入和流式输出。后端用 Route Handler 暴露一个/api/chat接口这个接口内部调用 LangGraph 编译出来的 Agent 实例把用户消息喂进去把 Agent 的输出流式返回给前端。整个链路不需要额外的 Express 或 Fastify 服务一个 Next.js 项目全包了。这样做的好处是部署简单、类型安全、开发体验统一。你不需要在前后端之间维护两套类型定义TypeScript 的类型可以从数据库 Schema 一路推导到前端组件的 Props。对于个人开发者或者小团队来说这种“一个项目搞定所有事”的方案比拆成前后端两个仓库要高效得多。2. 整体架构设计与核心模块拆解2.1 系统分层与数据流向整个系统的分层可以这样理解最上层是 Next.js 的前端界面包含对话面板和简历预览面板中间层是 Next.js 的 API 路由负责接收请求、调用 Agent、返回流式响应核心层是 LangGraph.js 构建的 Agent 图包含状态定义、节点逻辑和条件边最底层是外部服务包括大模型 API、数据库和 PDF 渲染服务。数据流向是这样的用户在对话框输入消息前端通过 fetch 调用/api/chatAPI 路由把消息和当前会话 ID 传给 AgentAgent 从 Checkpointer 加载历史状态根据当前状态决定走哪个节点节点内部可能调用大模型、可能调用工具、可能直接返回追问最终生成的结果通过 Stream 返回给前端前端一边接收一边渲染同时把结构化的简历数据同步到预览面板。这里有个设计决策值得展开说简历数据到底存在哪里。我的做法是双写——Agent 的状态存在 LangGraph 的 Checkpointer 里用于对话上下文的恢复结构化的简历数据单独存一份到数据库用于预览面板的渲染和最终导出。这样做的好处是对话状态和业务数据解耦即使对话历史丢了简历数据还在反过来简历数据被用户手动编辑了也不会影响对话的连贯性。2.2 LangGraph 状态图的设计状态定义是整个 Agent 的地基。在 LangGraph.js 里状态就是一个普通的 TypeScript 对象但你需要用 Annotation 来声明每个字段的更新方式。比如messages字段用messagesStateReducer表示新消息是追加而不是覆盖resumeData字段用默认的覆盖式更新表示每次都是全量替换。我定义的 State 大概长这样包含messages对话历史、resumeData结构化简历数据、currentSection当前正在处理的简历模块比如“工作经历”“教育背景”、missingFields当前模块还缺哪些信息、userIntent用户意图分类。这几个字段组合起来就能支撑起整个对话流程的决策。图的节点设计上我分了六个核心节点router负责意图识别判断用户是想新增内容、修改内容还是查询进度collector负责信息收集根据missingFields生成追问generator负责内容生成调用大模型把口语化描述改写成专业表达updater负责把生成的内容合并到resumeDataresponder负责生成最终回复fallback负责处理异常情况。边的关系上router根据userIntent条件跳转到collector或generatorcollector在信息收集完成后跳转到generatorgenerator跳转到updaterupdater跳转到responderresponder结束。如果任何节点抛出异常条件边会导向fallback。2.3 前后端交互的流式方案流式输出是 AI 应用体验的关键。用户等 10 秒看到一整段回复和看着文字一个个蹦出来感受完全不同。Next.js 的 Route Handler 支持返回ReadableStreamLangGraph.js 的streamEvents方法可以产出事件流两者对接起来就能实现逐字输出。具体实现上我在 API 路由里创建一个TransformStream把 LangGraph 的事件流转成 SSE 格式的文本流。前端用fetch加ReadableStream读取每收到一个 chunk 就更新一次 UI。这里有个细节要注意LangGraph 的事件流里包含多种事件类型比如on_chat_model_stream、on_tool_start、on_tool_end前端需要根据事件类型决定是追加文本、显示加载状态还是更新简历预览。我的做法是在服务端就把事件过滤和转换做好只把纯文本 chunk 和结构化数据更新事件传给前端前端逻辑保持简单。3. 核心细节解析与实操要点3.1 状态定义与 Reducer 的选择LangGraph.js 的状态更新机制和 Redux 很像每个字段可以指定一个 Reducer 来决定新值如何合并到旧值。默认的 Reducer 是覆盖式更新也就是新值直接替换旧值。但对于messages这种需要累积的字段必须用messagesStateReducer否则每次更新都会把历史消息冲掉。我踩过的一个坑是一开始把resumeData也用了追加式 Reducer结果每次生成新内容都会往数组里塞一份导致数据重复。后来改成覆盖式但在updater节点里手动做了深合并才解决了这个问题。所以 Reducer 的选择要根据字段的语义来定对话历史用追加业务数据用覆盖加手动合并。另一个细节是状态的初始化。LangGraph 在第一次调用时会用你传入的初始状态之后每次调用都会从 Checkpointer 加载之前的状态。如果你在初始状态里放了默认值要注意这些默认值只在第一次生效。我建议把默认值的定义抽成一个工厂函数在创建新会话时调用避免硬编码在多个地方。3.2 意图识别的 Prompt 工程router节点的核心是意图识别我用的方案是让大模型输出一个结构化的 JSON包含intent和confidence两个字段。intent的枚举值有add_experience、modify_experience、query_progress、generate_resume、chitchat五种。Prompt 里我会给出每个意图的定义和几个例子让模型做少样本分类。这里的关键技巧是不要让模型自由发挥而是用 JSON Schema 约束输出格式。LangChain.js 提供了withStructuredOutput方法可以传入 Zod Schema模型会按照 Schema 返回结构化对象。这样做的好处是后续代码不需要做字符串解析直接拿对象字段用就行类型安全且不容易出错。置信度字段的作用是兜底。如果模型返回的confidence低于 0.7我会走fallback节点让 Agent 回复一句“我不太确定你的意思你是想新增一段经历还是修改已有的内容”这样即使用户表达很模糊也不会出现答非所问的情况。3.3 信息收集的追问策略collector节点的目标是补全当前模块的必填字段。以工作经历为例必填字段包括公司名称、职位、起止时间、工作内容、成果量化。我会在 State 里维护一个missingFields数组collector每次只追问一个字段而不是一口气问五个。这是基于对话体验的考虑一次问太多问题用户会感到压力回复质量也会下降。追问的 Prompt 里我会带上已经收集到的信息让模型生成自然的追问语句。比如已经知道公司是“某科技公司”职位是“前端工程师”那追问时间时就可以说“好的你在某科技公司做前端工程师这段时间大概是从什么时候到什么时候”而不是干巴巴地问“请提供起止时间”。这种上下文感知的追问能让用户感觉是在和一个真人对话。还有一个策略是“能推断就不追问”。比如用户说“2021 年毕业加入某公司干了三年”那起止时间就可以推断为 2021 到 2024不需要再问。我会在collector里加一个预处理步骤用规则加模型判断哪些字段可以从已有信息推断出来能推断的直接填减少追问轮次。3.4 内容生成的改写技巧generator节点是简历工具的核心价值所在。用户输入的是“我在某公司做了个后台管理系统用了 React 和 Node”生成的目标是“主导某后台管理系统的前端架构设计与开发基于 React 技术栈构建组件化界面配合 Node.js 实现服务端接口系统上线后支撑日均 X 万次访问”。这个改写过程有几个要点第一是动词升级把“做了”改成“主导”“负责”“参与”根据实际贡献程度选择第二是技术栈显性化把用到的技术明确列出来第三是成果量化如果用户没提供数据模型会生成一个占位符提示用户补充而不是编造数据第四是长度控制每段工作经历描述控制在 3 到 5 句话太短显得单薄太长 HR 没耐心看。Prompt 里我会给模型提供几个改写示例让它模仿风格。同时用withStructuredOutput约束输出返回的 JSON 包含rewritten改写后的文本和suggestions给用户的补充建议两个字段。suggestions会展示在对话里提示用户“如果你能提供具体的用户量或性能提升数据这段描述会更有说服力”。4. 实操过程与核心环节实现4.1 项目初始化与依赖安装先创建一个 Next.js 项目用 App Router 和 TypeScript。命令行执行npx create-next-applatest resume-agent --typescript --app --tailwindTailwind 用来快速搭界面不喜欢的可以换成其他方案。进入项目目录后安装 LangGraph 相关依赖npm install langchain/langgraph langchain/openai langchain/core zod。如果你用的是其他模型提供商把langchain/openai换成对应的包就行。环境变量方面在项目根目录创建.env.local写入模型 API 的 Key 和 Base URL。如果你用的是兼容 OpenAI 接口的国内模型服务把 Base URL 指向对应的地址即可。数据库我选的是 SQLite 加 Prisma对于个人项目来说足够用部署时也不用额外买数据库服务。安装 Prismanpm install prisma prisma/client然后npx prisma init初始化。4.2 定义 Agent 状态与图结构在lib/agent/state.ts里定义状态。用Annotation.Root创建状态注解messages字段用AnnotationBaseMessage[]({ reducer: messagesStateReducer, default: () [] })resumeData字段用AnnotationResumeData({ reducer: (x, y) ({ ...x, ...y }), default: () emptyResume })。ResumeData的类型定义包含basics基本信息、experiences工作经历数组、education教育背景数组、skills技能数组几个部分。在lib/agent/graph.ts里构建图。用new StateGraph(StateAnnotation)创建图实例然后.addNode(router, routerNode)逐个添加节点用.addEdge(START, router)设置入口用.addConditionalEdges(router, routeCondition, [collector, generator, fallback])设置条件跳转。最后.compile({ checkpointer })编译成可执行图。Checkpointer 我用的是MemorySaver生产环境可以换成基于 Redis 或 Postgres 的实现。4.3 实现核心节点逻辑router节点的实现从状态里取最后一条用户消息构造 Prompt调用模型的结构化输出方法拿到intent和confidence返回{ userIntent: intent }。如果置信度低返回{ userIntent: fallback }。collector节点的实现根据currentSection确定必填字段列表对比resumeData里已有的值算出missingFields。如果missingFields为空直接返回空对象条件边会导向generator。否则取第一个缺失字段构造追问 Prompt调用模型生成追问语句返回{ messages: [new AIMessage(question)] }。generator节点的实现把resumeData和用户最新输入拼成 Prompt调用模型生成改写后的内容返回{ resumeData: updatedData, messages: [new AIMessage(rewritten)] }。这里要注意生成的内容要合并到resumeData的对应字段里而不是整体替换。updater节点的实现这个节点主要负责数据校验和格式化。检查resumeData里是否有空字段、时间格式是否统一、技能标签是否去重。如果有问题返回修正后的数据如果没问题返回空对象。4.4 前端对话界面与流式渲染前端我用的是useChat类似的思路但自己实现了一个轻量的 hook。核心逻辑是维护一个messages数组和一个isLoading状态发送消息时用fetch调用/api/chat拿到ReadableStream后用getReader()逐块读取每读到一个 chunk 就更新最后一条 AI 消息的内容。简历预览面板用 React 组件渲染resumeData每个模块一个组件数据变化时自动重新渲染。为了让预览更直观我加了一个“高亮更新”的效果当某个字段被 Agent 修改时对应的预览区域会闪一下背景色让用户知道哪里变了。这个效果用 CSS transition 加一个临时的 class 就能实现。导出功能用react-to-print或者服务端 Puppeteer 渲染。我选的是服务端方案因为 Puppeteer 对 CSS 的支持更完整导出的 PDF 和预览效果一致。在 API 路由里创建一个/api/export接口接收resumeData用 Puppeteer 打开一个 HTML 模板页面把数据注入进去然后page.pdf()生成 PDF 返回。5. 常见问题与排查技巧实录5.1 模型输出格式不稳定的处理结构化输出虽然比自由文本稳定但也不是 100% 可靠。我遇到过模型返回的 JSON 里字段名拼错、枚举值超出范围、必填字段缺失的情况。处理方案是加一层校验和重试用 Zod 的safeParse校验模型输出如果失败把错误信息拼回 Prompt 里让模型重新生成最多重试两次。如果两次都失败走fallback节点返回一个友好的错误提示。另一个技巧是在 Prompt 里明确列出枚举值并且用“必须从以下选项中选择”这样的强约束语句。实测下来加了这句话之后枚举值出错的概率从大概 5% 降到了 1% 以下。5.2 对话状态丢失的排查状态丢失通常有几个原因Checkpointer 没配置好、会话 ID 没传对、状态字段的 Reducer 写错了。排查时我会先在graph.invoke的返回值里打印完整状态确认状态是否被正确加载和保存。如果状态是空的检查configurable里的thread_id是否每次请求都一致。如果状态有但字段不对检查对应字段的 Reducer 逻辑。还有一个隐蔽的坑如果你在节点函数里直接修改了状态对象而不是返回新对象LangGraph 可能检测不到变化。记住节点函数应该返回一个部分状态对象由 LangGraph 负责合并而不是原地修改。5.3 流式输出中断的解决流式输出中断最常见的原因是服务端超时。Next.js 的 Route Handler 默认有执行时间限制如果 Agent 处理时间过长连接会被断开。解决方案是在路由配置里设置export const maxDuration 60把超时时间延长到 60 秒。如果还是不够可以考虑把长任务拆成多个短请求或者用后台任务加轮询的方案。另一个原因是前端读取流的方式不对。如果你用response.text()而不是response.body.getReader()会等整个响应结束才拿到内容失去流式效果。确保前端用的是getReader()加TextDecoder的方式逐块读取。5.4 常见问题速查表问题现象可能原因排查方法解决方案Agent 回复答非所问意图识别错误打印userIntent和confidence优化 Prompt增加示例降低置信度阈值状态不保存Checkpointer 未配置检查compile时是否传入 checkpointer配置 MemorySaver 或数据库 Checkpointer流式输出卡顿模型响应慢或网络问题在服务端打时间戳换更快的模型或加 loading 状态提示简历数据重复Reducer 用错检查resumeData的 Reducer改用覆盖式 Reducer 加手动深合并PDF 导出乱码字体未嵌入检查 Puppeteer 的字体配置在 HTML 模板里引入中文字体6. 性能优化与并发处理思路6.1 模型调用的缓存策略简历工具里有很多重复的模型调用比如同样的意图识别 Prompt、同样的改写指令。我加了一层基于内存的 LRU 缓存把 Prompt 的哈希作为 key模型输出作为 value命中缓存直接返回省掉一次 API 调用。实测下来在连续对话场景里缓存命中率能到 30% 左右响应速度明显提升。缓存要注意设置合理的过期时间我设的是 10 分钟。太短了命中率低太长了可能返回过时的结果。另外涉及用户隐私的数据不要缓存比如用户的具体工作经历内容缓存 key 里要排除这些信息。6.2 并发请求的处理如果多个用户同时使用每个用户的 Agent 实例是独立的因为thread_id不同Checkpointer 会隔离状态。但模型 API 的调用是共享的如果并发量大了可能会触发速率限制。我的做法是加一个简单的队列用p-limit控制同时进行的模型调用数量超出的请求排队等待。对于个人项目来说并发量通常不大这个方案够用了。如果要做成多用户产品建议把 Checkpointer 换成 Redis 或 Postgres把模型调用换成支持高并发的服务前端加一个请求队列和重试机制。这些扩展点在设计初期就要预留好接口不然后期改起来很麻烦。6.3 前端渲染的性能简历预览面板如果字段很多频繁更新可能会导致卡顿。我用React.memo包裹了每个模块组件只有对应数据变化时才重新渲染。另外流式输出时不要每个 chunk 都触发一次状态更新可以用requestAnimationFrame做节流把更新频率控制在每秒 30 次左右肉眼看起来依然流畅但渲染压力小很多。7. 我踩过的坑和最后分享几个技巧第一个坑是 LangGraph.js 的版本兼容性。这个库还在快速迭代不同版本之间的 API 有变化。我建议锁定版本号不要用^或~避免自动升级导致代码跑不起来。升级前先看 CHANGELOG确认 breaking change 再动手。第二个坑是 Prompt 里的中文标点。模型对中文标点的处理有时候会出问题比如把逗号识别成句号导致 JSON 解析失败。我的做法是在 Prompt 里明确要求“使用英文标点输出 JSON”生成文本内容时再用中文标点。这个细节很小但能省掉很多调试时间。第三个技巧是关于追问的节奏。一开始我让 Agent 一次性把所有缺失字段都问出来结果用户回复很长但信息不全。后来改成一次只问一个用户回复短而精准整体轮次反而更少。这个经验适用于所有对话式信息收集场景少问多轮比多问少轮体验好。第四个技巧是给 Agent 加一个“总结确认”环节。在生成简历内容后让 Agent 用一句话总结“我帮你把某公司的经历改写成了这样你看可以吗”用户确认后再写入resumeData。这样给了用户一个反悔的机会避免生成的内容直接覆盖了用户原本的输入。这个项目后续还可以扩展的方向很多比如加一个“岗位匹配度分析”功能让 Agent 根据目标岗位的 JD 来调整简历的关键词密度或者加一个“多版本管理”让用户针对不同公司维护不同版本的简历。架构上都是加节点和加状态字段的事LangGraph 的图结构让这些扩展变得很自然。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

Windows右键添加Markdown新建菜单的注册表方案 2026/10/1 10:34:32

Windows右键添加Markdown新建菜单的注册表方案

1. 项目概述:为什么一个右键菜单值得花半小时认真对待 “一招搞定!Windows 右键秒建 Markdown 文件”——这标题看着像营销号,但实际是我在给团队做效率基建时,被反复问了17次的问题。不是“怎么用Typora写md”,而是“…

阅读更多 →
QPSK蒙特卡洛仿真全解析:从星座图到误码率曲线与工程避坑 2026/10/1 10:34:31

QPSK蒙特卡洛仿真全解析:从星座图到误码率曲线与工程避坑

简介:QPSK正交相移键控在无线通信和卫星通信中应用广泛,其误码率性能是工程设计与课程学习中的关键指标。这份源码包面向通信专业学生、算法研究人员以及需要评估链路性能的工程师,提供一套基于蒙特卡洛仿真的QPSK误码率分析工具。压缩包整体…

阅读更多 →
KNN股市预测源码全解析:从特征工程到回测避坑指南 2026/10/1 10:34:31

KNN股市预测源码全解析:从特征工程到回测避坑指南

简介:利用k近邻算法实现股市预测的Python源代码包,面向已有基础Python语法、正接触机器学习的开发者和量化分析爱好者,通过历史行情数据进行走势预测,直观展示kNN分类与回归思路在金融场景中的应用。压缩包共2个文件,含…

阅读更多 →
仓库托盘检测数据集全解析:YOLO/VOC标注校验到训练避坑 2026/10/1 10:34:31

仓库托盘检测数据集全解析:YOLO/VOC标注校验到训练避坑

简介:一套面向仓库内托盘检测任务的目标检测数据集,专为需要训练YOLO、Faster R-CNN等算法的研究者与开发者准备。压缩包共2000个文件,包含1182个XML标注文件和818个TXT标签文件,整体约192.54MB。数据采用VOC与YOLO双格式存储&…

阅读更多 →
Java SSM人事OA系统拆解:框架整合、数据库设计与部署指南 2026/10/1 10:34:31

Java SSM人事OA系统拆解:框架整合、数据库设计与部署指南

简介:这是一套基于Java与SSM框架(Spring、SpringMVC、MyBatis)实现的人事管理OA办公系统毕业设计项目,面向计算机相关专业的学生、老师及初期开发者,适用于毕业设计、课程设计、项目立项演示或进阶学习。源码已通过导师…

阅读更多 →
阜阳本地AI内容创作实战:AI培训、短剧、漫剧与婚礼视频落地指南 2026/10/1 10:34:12

阜阳本地AI内容创作实战:AI培训、短剧、漫剧与婚礼视频落地指南

1. 阜阳本地AI内容创作的机会与切入点这两年我一直在阜阳做本地的短视频和视觉内容服务,从最早帮婚庆公司剪片子,到后来接一些本地商家的产品视频,再到现在带着几个人的小团队专门做AI内容创作。说实话,AI这波浪潮刚起来的时候&am…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉