HireMind:从 0 到 1,用 LangGraph 打造 7 Agent 协作的智能招聘平台

HireMind:从 0 到 1,用 LangGraph 打造 7 Agent 协作的智能招聘平台 一、引言招聘场景中HR 每天要面对大量简历手工评估不仅耗时而且标准不一。能不能让 AI 来完成这件事从简历解析、技能匹配到面试题生成、综合评分全流程自动化——听起来简单但每一步都需要不同的能力解析需要抽取结构化信息匹配需要检索知识库评分需要综合判断。单次 LLM 调用无法覆盖这么长的链路。HireMind 是我独立设计实现的一个开源项目基于 DeepSeek LangGraph 构建了 7 个专业 Agent 协作的智能招聘评估平台。如果要用一句话概括它的核心思路那就是把复杂任务拆成多个小步骤每个步骤交给专门的 Agent 处理用状态图编排它们之间的流转逻辑。在线演示 http://36.151.144.50Git 仓库 https://gitee.com/monan1122/hire-mind二、架构总览项目采用 Vue 3 Java Spring Boot 3 Python FastAPI 三层架构。前端负责交互Java 层管理业务数据用户、岗位、简历、报告Python 层承载所有 AI 能力。Java 通过 WebClient 异步调用 Python 的/evaluate接口拿到评估报告后存入 MySQL前端轮询展示结果。用户上传 PDF 简历 ↓ Java 业务层 (Spring Boot) ← MySQL / Redis ↓ WebClient POST /evaluate Python AI 层 (FastAPI) ↓ LangGraph StateGraph 编排 ↓ Resume → Desensitize → Skill → Router → Interview → Score → Report三、7 Agent 协作 PipelineLangGraph 的StateGraph是整个 AI 层的骨架。每个 Agent 接收一个共享的AgentContext对象处理完后更新状态传递给下一个节点。关键设计在于条件边Conditional Edge——每个节点执行完后根据结果走不同的分支。builder.add_node(resume, resume_node) builder.add_node(resume_fallback, resume_fallback) builder.add_node(desensitization, desensitize_node) builder.add_node(skill, skill_node) builder.add_node(condition_router, condition_router_node) builder.add_node(interview, interview_node) builder.add_node(score, score_node) builder.add_node(score_fallback, score_fallback) builder.add_node(report, report_node) # Resume 节点成功 → 脱敏重试 → 再试降级 → 正则兜底失败 → 终止 builder.add_conditional_edges( resume, lambda ctx: route_after_agent(ctx, resume), { success: desensitization, retry: resume, fallback: resume_fallback, fail: error_handler, } )真正有意思的是Condition Router。如果候选人的技能匹配度不到 50%说明他很可能不适合这个岗位这时候再花 token 去生成面试题纯粹是浪费。于是我们在 Skill Agent 之后插入了一个纯规则节点async def condition_router_node(ctx: AgentContext) - AgentContext: match_score ctx.skill_result.match_score if ctx.skill_result else 0 ctx.skip_interview match_score 50 return ctx def route_after_condition(ctx: AgentContext) - str: return score if ctx.skip_interview else interview别看这只是个简单的 if-else在每天 100 份简历的场景下假设 30% 匹配度不达标就省下了 30 次 LLM 调用。四、异常处理让 Pipeline 不会一碰就碎Agent Pipeline 是串行的任何一个节点挂了都可能让整个任务失败。我给每个 Agent 设计了三级异常处理Tier 1 可重试API 超时、网络抖动 → 指数退避重试最多 3 次Tier 2 可降级JSON 解析失败、字段缺失 → 规则兜底不中断流程Tier 3 终止核心服务不可用 → 返回 PartialResult 错误原因async def call_deepseek_safe(prompt: str, node: str, ctx: AgentContext) - str: max_retries MAX_RETRIES.get(node, 1) for attempt in range(max_retries): try: content await deepseek.chat(prompt, modelmodel) return content except Exception as e: ctx.retry_counts[node] ctx.retry_counts.get(node, 0) 1 if attempt max_retries - 1: await asyncio.sleep(2 ** attempt) continue raiseScore Agent 的降级策略是一个典型的纯规则兜底async def score_fallback(ctx: AgentContext) - AgentContext: match ctx.skill_result.match_score if ctx.skill_result else 0 project_count len(ctx.resume_desensitized.projects) if ctx.resume_desensitized else 0 raw_score match * 0.7 project_count * 5 ctx.score_result { final_score: min(raw_score, 100), recommendation: 进入技术面试 if match 60 else 建议淘汰, strengths: [], weaknesses: [评分系统暂时不可用], } ctx.error None return ctx实践验证即使 DeepSeek API 完全不可用系统仍能基于关键词匹配和规则评分返回可用的评估结果——matchScore × 0.7 项目数 × 5简单但有效。五、混合检索 RAG岗位知识库和面试题库的查询有两种截然不同的需求有时是精确技能标签匹配Java、Redis有时是语义描述匹配有分布式系统经验。纯 ES BM25 解决不了语义问题纯 Milvus 向量对精确标签效果差。于是两边都查然后 RRF 融合RRF_K 60 def _rrf_fusion(es_results, milvus_results, kRRF_K): scores {} for rank, doc in enumerate(es_results): scores[hash(str(doc.get(id, )))] 1 / (k rank 1) for rank, doc in enumerate(milvus_results): key hash(str(doc.get(id, ))) scores[key] scores.get(key, 0) 1 / (k rank 1) merged {} for doc in es_results milvus_results: did hash(str(doc.get(id, ))) if did in scores: merged[did] {**doc, _fusion_score: scores[did]} return sorted(merged.values(), keylambda x: x[_fusion_score], reverseTrue)同时 Milvus 检索做了 3 秒超时保护不可用时自动降级为纯 ESasync def _safe_milvus_search(collection: str, query: str, top_k: int): try: return await asyncio.wait_for( milvus_client.search(collection, query, top_k), timeout3.0 ) except (asyncio.TimeoutError, Exception): return []六、数据脱敏与合规所有发往 DeepSeek API 的数据在离开本地网络前必须脱敏。但教育和技能信息又必须保留——删掉学历和专业就没法正确匹配了。这是一个「选择性脱敏」的问题。实现方式很直接正则匹配 占位符替换零外部依赖单次 5ms。关键是在 Pipeline 中找准插入点——脱敏节点必须在 Resume Agent 之后、任何数据流向外部 API 之前async def desensitize_node(ctx): if ctx.resume_raw: ctx.resume_desensitized desensitize_resume(ctx.resume_raw) return ctx脱敏只针对 PII个人身份信息教育/技能/项目描述完整保留不影响下游的匹配和评分准确度。七、写在最后这个项目从最初的单文件 Python 脚本到后来加入 LangGraph 编排、Java 业务层、Vue 前端经历了好几轮迭代。几个关键体会拆解比堆 prompt 更可靠把大任务拆成 7 个小 Agent每个只做一件事调试和优化都更可控降级比完美更重要API 会超时、JSON 会解析失败、服务会不可用——每个节点都有 Plan B 比追求 100% 准确率更实际脱敏不是可选项从第一版就把脱敏做进 Pipeline比后期补救省心得多欢迎试用和提 Issue。在线演示 http://36.151.144.50管理员账号admin/admin123候选人账号user1/user123。