ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Next.js + LangGraph.js 实战:构建简历优化 AI Agent 全流程

Next.js + LangGraph.js 实战:构建简历优化 AI Agent 全流程 1. 为什么我选择用 Next.js LangGraph.js 来落地简历工具 AI Agent1.1 从“改简历”这个真实痛点说起简历工具这个赛道看起来已经卷到不能再卷了。市面上有大量在线简历生成器、模板库、AI 润色工具但真正用下来你会发现一个尴尬的事实大部分工具只解决了“排版”和“措辞美化”这两个表层问题而求职者真正的痛点在于——不知道自己的简历跟目标岗位到底差在哪里也不知道该怎么改才能过筛选。我身边做招聘的朋友经常吐槽说每天收到的简历里至少有一半是“自嗨型简历”候选人把自己做过的事情流水账一样列出来但跟岗位 JD 的匹配度极低。而求职者这边呢投了几十份简历石沉大海根本不知道问题出在哪。这个信息差就是 AI Agent 可以发挥价值的地方。我决定做一个简历工具 AI Agent核心目标很明确用户上传简历、粘贴目标岗位 JDAgent 自动完成简历解析、岗位匹配度分析、逐条修改建议、关键词优化、最终生成可下载的定制化简历这一整套流程。这不是一个简单的“调一次大模型 API”就能搞定的事情它需要多步骤推理、工具调用、状态管理和循环迭代这正是 LangGraph.js 擅长的场景。1.2 技术选型背后的真实考量先说为什么用 Next.js。简历工具天然是一个 Web 应用需要前端界面让用户上传文件、查看分析结果、编辑简历内容。Next.js 的 App Router 支持 Server Components 和 Route Handlers我可以把 Agent 的调用逻辑放在服务端前端只负责交互和展示。更重要的是Next.js 的流式响应能力Streaming可以让我把 Agent 的思考过程实时推送给用户而不是让用户对着 loading 转圈等 30 秒。这个体验差异非常大。再说 LangGraph.js。很多人第一反应是为什么不用 LangChain.js 的 AgentExecutor我实际试过AgentExecutor 在处理“需要根据中间结果动态决定下一步”的场景时控制力不够。简历分析这个流程里有很多条件分支如果简历里缺少某个关键技能需要走“补充建议”分支如果匹配度已经很高只需要走“微调优化”分支如果 JD 里有关键词在简历中完全没出现需要走“关键词植入”分支。这些分支用 LangGraph.js 的 StateGraph 来表达清晰得多。提示LangGraph.js 是 LangChain 团队推出的 JavaScript 版本图编排框架核心概念是 StateGraph、Node、Edge 和 Checkpointer。如果你之前只用过 Python 版的 LangGraphJS 版的 API 设计基本一致但要注意异步处理和类型定义上的差异。还有一个现实原因我的技术栈本身是 TypeScript 全栈如果为了用 Python 版的 LangGraph 而单独维护一个后端服务部署和调试成本都会翻倍。LangGraph.js 让我可以用一套语言完成前后端和 Agent 逻辑这对个人开发者和小团队来说非常重要。1.3 这个 Agent 到底解决了什么问题具体来说这个简历工具 AI Agent 解决了四个层面的问题。第一信息提取结构化把 PDF/DOCX 简历解析成结构化的 JSON包括基本信息、教育经历、工作经历、项目经历、技能列表等。第二匹配度量化把简历内容和 JD 进行语义级别的匹配给出一个可解释的匹配分数并指出差距在哪里。第三修改建议可执行不是泛泛地说“建议加强项目描述”而是给出具体的改写示例比如“把‘负责后端开发’改成‘主导 3 人团队完成后端架构设计QPS 从 500 提升到 3000’”。第四一键生成定制简历根据分析结果自动调整简历内容的侧重点和关键词密度输出一份针对该岗位优化的简历。适合谁来参考这篇内容如果你已经了解 Next.js 基础想学习如何把 AI Agent 集成到真实产品里这篇会有帮助。如果你正在纠结 LangChain.js 和 LangGraph.js 怎么选我会用实际代码告诉你我的判断依据。如果你只是想做一个简历工具那可以直接抄作业。2. 整体架构设计与核心模块拆解2.1 系统分层从前端到 Agent 引擎整个系统的架构我分成了四层。最上层是Next.js 前端层负责用户交互简历上传、JD 输入、分析进度展示、结果编辑和导出。第二层是API 路由层用 Next.js 的 Route Handlers 实现负责接收请求、调用 Agent、以流式方式返回结果。第三层是Agent 编排层这是核心用 LangGraph.js 构建 StateGraph定义各个处理节点和条件边。第四层是工具与模型层包括简历解析工具、向量检索工具、大模型调用封装等。这样分层的好处是每一层可以独立测试和替换。比如我一开始用的是 OpenAI 的模型后来想换成其他模型只需要改模型层的封装Agent 编排层完全不用动。简历解析工具也是最开始用正则表达式硬解析后来换成基于大模型的结构化输出替换时只影响工具层。2.2 LangGraph.js 的 StateGraph 设计Agent 的核心是一个 StateGraph我定义的 State 结构大概长这样interface ResumeAgentState { resumeText: string; resumeStructured: ResumeData | null; jobDescription: string; jobRequirements: JobRequirement[]; matchScore: number; gapAnalysis: GapItem[]; suggestions: Suggestion[]; optimizedResume: string | null; currentStep: string; errors: string[]; }这个 State 会随着图的执行不断更新。每个节点接收当前 State返回一个部分更新LangGraph.js 会自动合并。这种设计的好处是任何一个节点都可以访问到之前所有节点的输出不需要通过参数层层传递。图的结构大致是parseResume→parseJD→analyzeMatch→ 条件判断 → 根据匹配度走不同的优化分支 →generateOptimizedResume→validateOutput。其中条件判断那一步如果匹配度低于某个阈值会走“深度优化”分支这个分支里还会有一个循环反复调整直到匹配度达到目标或者达到最大迭代次数。2.3 为什么需要 CheckpointerLangGraph.js 提供了 Checkpointer 机制可以把每一步的状态持久化。我在这个项目里用到了它原因有两个。第一简历分析是一个耗时操作如果中途因为网络问题或者模型超时失败用户不希望从头再来。有了 Checkpointer可以从最后一个成功的节点恢复执行。第二我想让用户看到 Agent 的“思考过程”每一步完成后前端就能收到更新这需要状态在服务端有记录。我用的是基于内存的 MemorySaver 做开发生产环境换成了基于 PostgreSQL 的持久化方案。这里有个坑LangGraph.js 的 Checkpointer 在 Serverless 环境下需要额外注意因为每次请求可能是不同的实例内存 Checkpointer 会失效。如果你的 Next.js 部署在 Vercel 这类 Serverless 平台上一定要用外部存储做 Checkpointer。2.4 前端交互设计的关键决策前端这块我做了几个关键决策。第一用 Server-Sent Events 而不是 WebSocket来推送 Agent 进度。因为简历分析是单向的服务器推送不需要双向通信SSE 更简单也更稳定。第二分析结果分步展示而不是等全部完成再显示。用户先看到简历解析结果再看到匹配度再看到建议这种渐进式的体验比一次性加载好很多。第三允许用户手动编辑中间结果。比如用户觉得 Agent 解析的某段工作经历不准确可以手动修正后再继续后续分析。这个设计让用户有掌控感也提高了最终输出的质量。3. 核心细节解析与实操要点3.1 简历解析从非结构化文本到结构化数据简历解析是整个流程的第一步也是最容易出问题的一步。我试过三种方案。第一种是纯正则表达式针对常见简历格式写规则。优点是快、便宜缺点是鲁棒性极差稍微换个模板就失效。第二种是用专门的简历解析 API效果不错但要花钱而且有调用限制。第三种是用大模型做结构化输出配合 Zod schema 做验证。最终我选了第三种但做了一些优化。首先我会先用简单的文本提取库比如pdf-parse处理 PDFmammoth处理 DOCX把文件转成纯文本。然后把文本切成小块因为有些简历很长一次性塞给模型会超出上下文限制。切块的时候不是简单按字数切而是按语义段落切比如“工作经历”部分作为一个块“项目经历”部分作为另一个块。import { z } from zod; const ResumeSchema z.object({ basicInfo: z.object({ name: z.string(), email: z.string().optional(), phone: z.string().optional(), location: z.string().optional(), }), education: z.array(z.object({ school: z.string(), degree: z.string(), major: z.string(), startDate: z.string(), endDate: z.string(), })), workExperience: z.array(z.object({ company: z.string(), title: z.string(), startDate: z.string(), endDate: z.string(), description: z.string(), achievements: z.array(z.string()), })), skills: z.array(z.string()), projects: z.array(z.object({ name: z.string(), role: z.string(), description: z.string(), technologies: z.array(z.string()), })), });用 Zod 定义 schema 的好处是我可以用withStructuredOutput方法让模型直接输出符合 schema 的 JSON而且 Zod 会自动做类型验证。如果模型输出的格式不对LangChain.js 会自动重试。这里有个实操心得在 prompt 里明确告诉模型“如果某个字段在简历中找不到不要编造留空或者写‘未提供’”。我一开始没加这句话结果模型经常自己脑补出一些不存在的信息比如给没有写邮箱的简历编一个邮箱地址。注意简历解析的准确率直接影响后续所有环节。我的经验是在解析完成后加一个“置信度检查”步骤如果某些关键字段比如工作经历解析结果为空就让用户手动补充而不是硬着头皮往下走。3.2 岗位匹配度分析不只是关键词匹配匹配度分析是简历工具的核心价值所在。我见过很多工具就是简单地把 JD 里的关键词和简历里的词做交集然后算一个百分比。这种做法太粗糙了因为同一个意思可以用完全不同的词表达比如“用户增长”和“拉新获客”其实是一回事。我的做法是分两步。第一步用大模型把 JD 拆解成结构化的要求列表每条要求包含技能名称、重要程度必须/加分、类别技术/业务/软技能。第二步对每条要求在简历中寻找语义匹配的内容而不是字面匹配。这里我用了一个技巧把简历的每个段落和 JD 的每条要求分别做 embedding然后计算余弦相似度。相似度超过阈值的就算匹配低于阈值的算缺口。async function analyzeMatch( resumeStructured: ResumeData, jobRequirements: JobRequirement[] ): PromiseMatchResult { const resumeText flattenResume(resumeStructured); const resumeEmbedding await embeddings.embedQuery(resumeText); const results await Promise.all( jobRequirements.map(async (req) { const reqEmbedding await embeddings.embedQuery(req.description); const similarity cosineSimilarity(resumeEmbedding, reqEmbedding); return { requirement: req, similarity, matched: similarity 0.75, }; }) ); const mustHave results.filter(r r.requirement.priority must); const matchedMustHave mustHave.filter(r r.matched); const score matchedMustHave.length / mustHave.length; return { score, details: results }; }这个方案比纯关键词匹配准确得多但也有代价embedding 调用有成本而且对于长文本单个 embedding 会丢失细节。所以我又加了一层对于相似度在阈值边缘的要求再用大模型做一次精细判断问它“这段简历内容是否满足这个岗位要求请给出是/否和理由”。这样既控制了成本又保证了关键判断的准确性。3.3 修改建议生成从“泛泛而谈”到“可执行”修改建议这块我踩过最大的坑就是模型给出的建议太泛。比如“建议量化工作成果”、“建议突出技术深度”这种话用户看了等于没看。后来我调整了 prompt 策略要求模型必须给出具体的改写前后对比。具体做法是对于每个匹配度低的要求把简历中相关的段落提取出来连同 JD 要求一起发给模型让模型输出一个 JSON包含original原文、revised改写后、reason修改理由。这样用户就能直接看到“把这句话改成那句话”的具体操作。const SuggestionSchema z.object({ original: z.string().describe(简历中的原始表述), revised: z.string().describe(改写后的表述要具体、量化、有冲击力), reason: z.string().describe(修改理由关联到具体的岗位要求), keywords: z.array(z.string()).describe(改写后包含的关键词), });这里有个细节改写后的内容不能脱离事实。我见过一些 AI 工具为了追求“量化”给用户编造数据比如把“参与项目开发”改成“主导项目开发性能提升 300%”这完全是造假。所以我在 prompt 里加了严格约束只能基于原文已有的事实进行重新组织和表达不能添加原文中没有的信息。如果原文确实缺少量化数据建议里会提示用户“这里建议补充具体数据比如处理量、用户数、提升比例等”而不是直接编一个。3.4 关键词优化与 ATS 友好度很多公司用 ATS申请人追踪系统来初筛简历ATS 会扫描简历中的关键词如果关键词匹配度低简历可能根本到不了 HR 手里。所以我在 Agent 里加了一个“关键词优化”节点专门处理这个问题。这个节点的逻辑是从 JD 中提取高频关键词和短语然后检查简历中是否包含这些词。如果不包含但简历中有语义相近的表达就建议替换。比如 JD 里反复出现“微服务架构”而简历里写的是“分布式系统”就会建议在简历中适当加入“微服务”这个术语。但这里要把握一个度。我见过一些工具为了关键词密度把简历改得读都读不通。我的原则是关键词要自然融入不能堆砌。具体做法是只在合适的位置替换同义词不强行插入不相关的词。而且最终生成的简历我会做一个“可读性检查”如果发现某个段落关键词密度过高会提示用户注意。4. 实操过程与核心环节实现4.1 项目初始化与依赖安装先创建一个 Next.js 项目我用的是 App Routernpx create-next-applatest resume-agent --typescript --app --tailwind cd resume-agent然后安装核心依赖npm install langchain/langgraph langchain/core langchain/openai npm install pdf-parse mammoth zod npm install ai eventsource-parser这里解释一下每个依赖的作用。langchain/langgraph是 LangGraph.js 的核心包langchain/core提供了基础抽象langchain/openai是 OpenAI 的集成。pdf-parse和mammoth分别处理 PDF 和 DOCX 文件。zod用于 schema 定义和验证。ai是 Vercel 的 AI SDK用来简化流式响应的处理。提示pdf-parse在 Next.js 的 Serverless 环境里有时会有兼容性问题因为它依赖一些 Node.js 原生模块。如果遇到问题可以换成pdfjs-dist的 Node.js 版本或者把文件解析放在单独的 API 路由里。4.2 构建 LangGraph.js 的 StateGraph这是整个项目最核心的部分。我先定义 State 的 Annotationimport { Annotation } from langchain/langgraph; const ResumeAgentState Annotation.Root({ resumeText: Annotationstring(), resumeStructured: AnnotationResumeData | null(), jobDescription: Annotationstring(), jobRequirements: AnnotationJobRequirement[](), matchScore: Annotationnumber(), gapAnalysis: AnnotationGapItem[](), suggestions: AnnotationSuggestion[](), optimizedResume: Annotationstring | null(), currentStep: Annotationstring(), errors: Annotationstring[]({ reducer: (a, b) [...a, ...b], default: () [], }), });注意errors字段用了自定义 reducer这样多个节点报错时错误会累积而不是覆盖。其他字段用默认的覆盖式更新。然后定义各个节点函数。每个节点就是一个 async 函数接收 state返回部分更新async function parseResumeNode(state: typeof ResumeAgentState.State) { try { const structured await parseResumeWithLLM(state.resumeText); return { resumeStructured: structured, currentStep: resume_parsed, }; } catch (error) { return { errors: [简历解析失败: ${error.message}], currentStep: resume_parse_failed, }; } }接下来是条件边的定义。这是 LangGraph.js 相比普通 Chain 最大的优势function shouldDeepOptimize(state: typeof ResumeAgentState.State) { if (state.matchScore 0.6) { return deep_optimize; } if (state.matchScore 0.85) { return standard_optimize; } return light_optimize; }最后把所有节点和边组装成图const workflow new StateGraph(ResumeAgentState) .addNode(parse_resume, parseResumeNode) .addNode(parse_jd, parseJDNode) .addNode(analyze_match, analyzeMatchNode) .addNode(deep_optimize, deepOptimizeNode) .addNode(standard_optimize, standardOptimizeNode) .addNode(light_optimize, lightOptimizeNode) .addNode(generate_resume, generateResumeNode) .addEdge(__start__, parse_resume) .addEdge(parse_resume, parse_jd) .addEdge(parse_jd, analyze_match) .addConditionalEdges(analyze_match, shouldDeepOptimize, { deep_optimize: deep_optimize, standard_optimize: standard_optimize, light_optimize: light_optimize, }) .addEdge(deep_optimize, generate_resume) .addEdge(standard_optimize, generate_resume) .addEdge(light_optimize, generate_resume) .addEdge(generate_resume, __end__); const app workflow.compile({ checkpointer: new MemorySaver() });这个图的结构很清晰解析简历 → 解析 JD → 分析匹配 → 根据匹配度走不同优化分支 → 生成最终简历。每个节点都可以独立测试整个流程也可以可视化。4.3 流式输出与前端实时展示为了让用户看到 Agent 的执行进度我用 LangGraph.js 的streamEvents方法export async function POST(req: Request) { const { resumeText, jobDescription, threadId } await req.json(); const encoder new TextEncoder(); const stream new ReadableStream({ async start(controller) { const config { configurable: { thread_id: threadId } }; for await (const event of app.streamEvents( { resumeText, jobDescription }, { ...config, version: v2 } )) { if (event.event on_chain_end event.name parse_resume) { controller.enqueue(encoder.encode( data: ${JSON.stringify({ step: resume_parsed, data: event.data })}\n\n )); } // 其他事件类似处理 } controller.close(); }, }); return new Response(stream, { headers: { Content-Type: text/event-stream, Cache-Control: no-cache, Connection: keep-alive, }, }); }前端用EventSource接收const eventSource new EventSource(/api/analyze?threadId${threadId}); eventSource.onmessage (event) { const data JSON.parse(event.data); setProgress(prev [...prev, data]); };这里有个实操心得SSE 连接在 Next.js 的 Serverless 环境里有超时限制。Vercel 的免费版函数执行时间上限是 10 秒Pro 版是 60 秒。如果 Agent 执行时间可能超过这个限制要么升级套餐要么把长任务拆成多个短请求用轮询的方式获取进度。我最后选择了后者把 Agent 的每个节点做成独立的 API 路由前端依次调用这样每个请求都在超时限制内。4.4 最终简历生成与导出最后一步是把优化后的内容重新组装成一份完整的简历。我提供了两种输出格式Markdown 和 PDF。Markdown 方便用户复制到其他平台PDF 方便直接投递。生成 PDF 我用的是react-pdf/renderer它可以在服务端把 React 组件渲染成 PDF。简历模板我做了三套经典单栏、现代双栏、极简风格。用户可以在前端选择模板实时预览效果。import { Document, Page, Text, View, StyleSheet } from react-pdf/renderer; const styles StyleSheet.create({ page: { padding: 40, fontFamily: Helvetica }, section: { marginBottom: 16 }, heading: { fontSize: 14, fontWeight: bold, marginBottom: 8 }, text: { fontSize: 10, lineHeight: 1.5 }, }); export function ResumePDF({ data }: { data: ResumeData }) { return ( Document Page sizeA4 style{styles.page} View style{styles.section} Text style{styles.heading}{data.basicInfo.name}/Text Text style{styles.text}{data.basicInfo.email}/Text /View {/* 其他部分 */} /Page /Document ); }5. 常见问题与排查技巧实录5.1 模型输出格式不稳定怎么办这是最常见的问题。即使你用了withStructuredOutput模型偶尔还是会输出不符合 schema 的内容尤其是当简历内容很复杂的时候。我的解决方案是三层防护。第一层在 prompt 里给出明确的输出示例包括一个完整的 JSON 样例。第二层用 Zod 的.catch()方法给每个字段设置默认值这样即使某个字段解析失败整个对象也不会崩。第三层如果整体解析失败捕获错误后用一个更简单的 prompt 重试一次只提取最关键的信息。const SafeResumeSchema ResumeSchema.catch({ basicInfo: { name: 未知 }, education: [], workExperience: [], skills: [], projects: [], });5.2 长简历超出上下文限制怎么处理有些候选人的简历特别长尤其是工作多年的人可能有好几页。直接塞给模型会超出上下文窗口。我的处理策略是“分而治之”先按章节切分每个章节单独解析最后合并。如果某个章节本身就很长比如工作经历有 10 段再按段落切分每段单独提取最后汇总。这里要注意的是切分的时候不能破坏语义。我用的切分规则是优先按标题行切分比如“工作经历”、“项目经验”如果没有明显的标题就按空行切分再不行才按字数切分。切分后每个块的大小控制在 2000 字以内。5.3 Agent 执行超时或卡住LangGraph.js 的节点如果调用了外部 API可能会因为网络问题卡住。我给每个节点都加了超时控制async function withTimeoutT( promise: PromiseT, ms: number, errorMsg: string ): PromiseT { const timeout new Promisenever((_, reject) setTimeout(() reject(new Error(errorMsg)), ms) ); return Promise.race([promise, timeout]); }然后在每个节点里用withTimeout包裹模型调用设置 30 秒超时。如果超时节点会返回错误信息图会继续执行到下一个节点而不是整个流程挂掉。最终生成简历时如果有节点失败会在简历中标注“此部分分析未完成”而不是给用户一个空白页面。5.4 常见问题速查表问题现象可能原因排查方法解决方案简历解析结果为空PDF 是扫描件没有文本层用pdf-parse检查提取的文本长度提示用户上传文字版简历或集成 OCR匹配度始终为 0embedding 调用失败或返回空检查 API key 和网络连接加 fallback 到关键词匹配流式输出中断Serverless 函数超时查看函数执行日志拆分任务改用轮询生成的简历格式错乱模板组件渲染问题在本地用相同数据测试检查 CSS 和分页逻辑关键词堆砌严重prompt 约束不够检查生成内容的可读性在 prompt 中加“自然融入”约束5.5 几个我踩过的坑第一个坑不要用gpt-3.5-turbo做结构化输出。我一开始为了省钱用了 3.5结果格式错误率极高重试成本反而更高。换成gpt-4o-mini后格式正确率大幅提升总体成本反而更低。第二个坑Checkpointer 的 thread_id 要唯一。我一开始用用户 ID 做 thread_id结果同一个用户多次分析时状态会串。后来改成userId timestamp的组合问题解决。第三个坑前端不要直接暴露 API key。我见过有人在 Next.js 的客户端组件里直接调用 OpenAI这是非常危险的。所有模型调用必须放在服务端的 Route Handler 或 Server Action 里。第四个坑简历文件上传要限制大小和类型。我一开始没做限制结果有人上传了一个 50MB 的 PDF直接把服务打挂了。后来加了 5MB 的大小限制并且只允许 PDF 和 DOCX 两种格式。6. 性能优化与并发处理6.1 并发场景下的 Agent 设计热词里有人问“AI Agent 怎么扛并发”这确实是个关键问题。简历工具的使用场景有明显的波峰波谷招聘季高峰期可能同时有几百个用户在使用。如果每个请求都从头跑一遍完整的 Agent 流程模型调用成本会很高响应时间也会很长。我的优化策略是缓存 队列 降级。缓存方面对于相同的简历和 JD 组合把分析结果缓存起来下次直接返回。用简历内容的 hash 和 JD 的 hash 组合作为缓存 key。队列方面如果同时请求数超过阈值把请求放入队列按顺序处理前端显示排队位置。降级方面如果模型调用失败或超时自动降级到基于规则的简单分析保证用户至少能拿到一个基础结果。6.2 模型调用的成本控制模型调用是这个项目最大的成本项。我做了几个优化。第一分级使用模型简历解析和 JD 解析用便宜的小模型匹配度分析和建议生成用能力强的大模型。第二批量处理把多个独立的分析请求合并成一个请求发给模型减少 API 调用次数。第三缓存 embedding简历和 JD 的 embedding 结果缓存起来避免重复计算。实测下来这些优化能把单次分析的成本降低 60% 左右响应时间也从平均 25 秒降到了 12 秒左右。6.3 用户体验的细节打磨技术之外用户体验的细节也很重要。我在前端加了几个小功能分析过程中显示“正在解析简历”、“正在匹配岗位要求”这样的具体状态而不是一个笼统的 loading分析完成后用颜色标注匹配度绿色高、黄色中、红色低让用户一眼看到重点修改建议支持一键采纳用户点击“应用”按钮就能把建议的改写内容替换到简历里。这些细节看起来不起眼但实际使用中用户反馈这些功能让整个工具“感觉更靠谱”。做 AI 产品技术能力是基础但最终留住用户的是体验。6.4 后续可以扩展的方向这个项目还有不少可以继续做的方向。比如加入多轮对话能力用户可以对某条建议追问“为什么这么改”Agent 给出更详细的解释。比如支持多版本简历管理用户针对不同岗位保存不同的简历版本。再比如接入真实招聘数据让匹配度分析不仅基于 JD 文本还基于该岗位历史成功候选人的画像。我个人觉得最有价值的方向是“面试准备”基于简历和 JD 的差距分析自动生成可能的面试问题和准备建议。这能把工具的价值从“改简历”延伸到“拿 offer”用户粘性会更强。最后分享一个小技巧如果你也在做类似的 AI Agent 项目建议先把整个流程用最简单的代码跑通哪怕全是硬编码的假数据。先验证流程和交互再逐步替换成真实的模型调用。这样你能更快地看到问题所在而不是在调试模型的同时还要调试流程。
返回列表