这次我们来看一个 Spring AI Alibaba Graph 方向的完整实战项目:HR 招聘全流程 Agent。这个项目不是单纯调大模型接口,而是把简历解析、JD 理解、候选人匹配、面试题生成、评估报告串联成一个有状态、可路由、可复用的工作流。核心价值在于:它不绑定某个行业术语,招聘场景只是载体,里面的 Graph 编排方式、Agent 工具调用、结构化输出和批量任务设计,放到客服、研发、运营等场景可以原样复用。
文章会先讲 Spring AI Alibaba Graph 适合谁、不适合谁,再拆 25 个核心技术点,最后给出从环境准备到接口联调、批量任务和问题排查的完整落地路径。如果你是 Java 后端新人,建议先跑通第 5 章的骨架;如果你已经在用 Spring AI,可以直接跳到第 6 章做效果验证。
技术门槛并不高:需要 JDK 17 以上、Spring Boot 3.x、一个可以调用大模型的 API Key(本地实验也可以切换 Ollama 跑开源模型)。不需要自己训练模型,也不需要 GPU,Graph 编排和模型调用都在 JVM 进程里完成。HR 招聘场景天然适合做演示,因为链路足够长:既能体现 Graph 的多步编排能力,也能验证 Function Calling、结构化输出、上下文记忆这些 Agent 关键能力是不是真的可用。
1. Spring AI Alibaba Graph 核心能力速览
先给一张速览表,方便你快速判断这个项目值不值得跟。
| 能力项 | 说明 |
|---|---|
| 项目类型 | Spring AI Alibaba + Spring AI Graph 的 Agent 实战项目 |
| 核心框架 | Spring Boot 3.x、Spring AI Alibaba、Spring AI Graph |
| 模型接入 | 云端大模型 API 为主,可切换本地 Ollama 等 OpenAI 兼容协议 |
| 典型链路 | 简历解析 -> JD 理解 -> 匹配打分 -> 面试题生成 -> 评估报告 |
| Graph 能力 | 有状态多步编排、节点拆分、条件路由、并行处理、循环控制 |
| 运行模式 | JVM Web 服务,常规 Java 进程,不依赖特殊 GPU 服务器 |
| API 能力 | 通过 REST 接口暴露 Agent 调用,Graph 执行器支持程序化调用 |
| 批量任务 | 支持多份简历顺序/并行处理,需要自行设计任务队列与重试 |
| 合规要求 | 简历属于个人信息,必须脱敏、授权、限制访问范围 |
| 适合读者 | 熟悉 Java 但没有完整 Agent 落地经验的开发者 |
从这张表可以看到,这个项目的重点不是模型本身,而是如何把“招聘流程”拆成 Graph 节点,再让大模型在每个节点上做专业任务。这种方式比“丢一大段 Prompt 让模型自由发挥”更可控,也更容易在真实业务里替换规则、增加人工审核。
2. 适用场景与使用边界
先说适合谁。如果你在做 Java 后端,想从“调 API 返回一段文本”升级到“用编排能力完成一条完整业务流”,这个项目非常合适。HR 招聘 Agent 只是示例,实际能够复用的是四件事:节点拆分方式、状态传递方式、工具调用方式、结果落库方式。把这四件事吃透,换到订单客服、IT 工单、内容审核、销售线索筛选,都是一样的架构。
这个项目不适合什么场景?第一,不适合做纯聊天机器人。它的优势在多步任务编排,而不是开放闲聊。第二,不适合对实时性要求极度苛刻的场景。Graph 执行过程中会有多次模型调用,单次可能 2 到 5 秒,如果要求 200 毫秒返回,需要额外做缓存和异步流式设计。第三,不适合完全没有人工兜底的场景。AI 评估候选人、自动生成面试题,只能作为辅助,最终录用决策必须由 HR 人工确认。
这里必须强调安全与合规边界。简历信息属于个人敏感数据,包含姓名、联系方式、教育经历、工作经历。项目演示时,一定要使用脱敏数据,或对构造的假简历进行测试。真正上线时,需要候选人明确授权“简历用于 AI 辅助筛选”,还要限制系统访问范围,不能把简历内容随意传给外部模型做训练。如果模型服务在境外,还要额外评估数据跨境合规问题。合法授权、最小化收集、人工复核这三条底线不能破。
3. 25 个核心技术点一次拆透
标题里说“25 个核心技术点”,这里做一个系统化拆解。我按层次分成 5 个部分,每部分 5 个点,总共 25 个,和你实际编码顺序一致:先搭框架,再接入模型,再做 Agent 能力,再上 Graph 编排,最后做工程化与合规。
3.1 基础设施层
- Spring Boot 3.x 项目搭建。注意 Spring AI 对 Spring Boot 版本有要求,通常需要 3.2 及以上,建议直接用最新稳定版。
- Starter 依赖管理。Spring AI Alibaba 提供了 Spring Boot 风格的 Starter,引入后自动配置模型客户端。
- 配置文件外部化。API Key、模型名、超时时间放在环境变量或 application.yml 中,不写死在代码里。
- 日志链路。每一轮 Agent 调用要有 traceId,方便后面排查问题。
- 环境隔离。开发、测试、生产使用不同的 API Key 和模型配置。
3.2 模型接入层
- ChatClient 统一调用入口。Spring AI 的 ChatClient 屏蔽了不同模型的差异,业务代码不需要关心底层是 qwen-plus 还是 qwen-max。
- PromptTemplate 模板管理。把 HR 角色的系统提示词、JD 分析模板、简历提取模板做成可复用模板。
- 结构化输出。让模型返回 JSON 而不是自由文本,用于简历字段提取、评分表生成。
- 模型参数控制。temperature、maxTokens、topP 这些参数要按节点设置,比如简历解析用低 temperature,面试题生成可以略高。
- 流式输出。对于报告生成这类长文本任务,可以使用流式接口提升体验,后面会演示普通同步调用和流式调用两种方式。
3.3 Agent 能力层
- Function Calling 工具调用。让模型调用 Java 方法完成计算类任务,比如计算匹配分数、查询 JD 库。
- 多轮上下文记忆。候选人追问后,Agent 要记住前面评估的结果,不能每次重新解释。
- 工具结果校验。模型调用 Java 方法后,返回值要先校验再进入下一步节点。
- 错误指令拦截。当模型做出超出招聘范围的操作时,拒绝执行并给出明确提示。
- 人机协同节点。关键决策点插入“人工审核”状态,Graph 停在待审核节点,等 HR 确认后再继续执行。
3.4 Graph 编排层
- StateGraph 状态图核心。把招聘流程建模成节点、边、状态的图结构。
- 节点拆分原则。每个节点只做一件事,简历解析节点不写面试题生成逻辑。
- 条件边路由。匹配分数低于阈值时,直接走到“不通过”节点,而不是继续生成面试题。
- 并行节点。JD 分析、薪资区间分析、简历提取可以并行,减少总耗时。
- 循环与终止条件。面试题追问最多 N 轮,达到次数后强制进入报告节点,防止死循环。
3.5 工程化与合规层
- Actuator 监控。通过 /actuator/health 和自定义 Metrics 观察接口健康状态和调用次数。
- 接口幂等性。同一个候选人重复提交,应该复用已有结果,而不是重复消耗 Token。
- 批量任务队列。多份简历批量处理时,要限制并发,避免打爆 API 配额。
- 数据脱敏与日志清理。日志中不打印完整手机号和邮箱,演示数据使用假信息。
- 效果评估集。准备 10 份典型简历做回归测试,每次修改 Prompt 后跑一遍,确认输出质量没有回退。
这 25 个点并不是全部要在第一个版本里实现,但它们是完整 Agent 项目必须具备的能力。下面的实战部分会按这个顺序逐步落地核心模块。
4. 环境准备与前置条件
在开始写代码之前,先把环境对齐。下面是一个通用检查清单。
- JDK:17 或 21,建议 21。Spring AI 对 JDK 版本要求不高,但 17 是底线。
- 构建工具:Maven 3.9+ 或 Gradle 8.x,本文以 Maven 为例。
- 框架版本:Spring Boot 3.x,建议从 Spring Initializr 生成基础工程后再添加 AI 依赖。
- IDE:IntelliJ IDEA 社区版即可,不需要付费版。
- 模型服务:准备一个可用的大模型 API Key。Spring AI Alibaba 默认对接阿里云百炼平台,也可以配置为 OpenAI 兼容协议,或用 Ollama 跑本地模型。
- 可选工具:Postman 或 curl 用于接口测试;Redis 可选,用于多轮会话状态缓存。
先创建一个空的 Spring Boot 项目,然后添加依赖。下面是一个依赖参考片段。
<!-- 以下坐标为参考,具体版本请以 Spring Initializr 生成结果为准 --> <dependency> <groupId>com.alibaba.cloud.ai</groupId> <artifactId>spring-ai-alibaba-starter</artifactId> <version>${spring-ai-alibaba.version}</version> </dependency> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-graph</artifactId> <version>${spring-ai.version}</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>依赖版本不要手写死,优先用 Spring Initializr 帮你关联的版本。Spring AI 的版本更新比较快,不同版本的配置项存在差异,直接套旧版本写法经常会遇到“属性找不到”或“类不存在”的问题。
接着配置 application.yml。这里以调用云端模型为例, API Key 从环境变量读取。
spring: application: name: hr-agent ai: dashscope: api-key: ${DASHSCOPE_API_KEY} chat: options: model: qwen-plus temperature: 0.3 server: port: 8080 management: endpoints: web: exposure: include: health,metrics注意,Spring AI Alibaba 的配置路径在不同版本中可能有调整,比如有的版本用spring.ai.dashscope.api-key,有的用spring.ai.alibaba.dashscope.api-key。以当前版本官方文档为准。如果启动后日志出现“api-key 未配置”,优先检查这一段。
如果你没有云端 API Key,也可以用 Ollama 跑本地模型。先启动 Ollama 并拉取模型,然后在配置中切换为本地地址。这种方式适合离线学习,但输出质量和速度都不如云端大模型。
5. 从零搭建 HR 招聘全流程 Agent 骨架
这里先把最核心的 Spring AI Graph 执行逻辑跑起来。Graph 的本质是一个有状态的工作流:定义一个状态对象,把流程拆成多个节点,节点之间用边连接,最后通过执行器跑通整条链路。
先定义状态对象。招聘流程状态至少包含:简历路径、JD 信息、解析后的候选人画像、匹配分数、面试题列表、最终报告。
public class RecruitmentState { private String resumePath; private String jdContent; private String candidateProfile; private Double matchScore; private List<String> interviewQuestions; private String report; // 构造函数、getter、setter 省略,实际项目使用 record 或 POJO }再定义节点。每个节点实现统一接口,从状态对象中读取输入,处理后再写回状态对象。这里给一个简历解析节点的示意代码。
@Component public class ResumeParserNode implements Node<RecruitmentState> { private final ChatClient chatClient; public ResumeParserNode(ChatClient chatClient) { this.chatClient = chatClient; } @Override public RecruitmentState apply(RecruitmentState state) { String prompt = """ 你是一名资深 HR 助理。请从下面的简历文本中提取结构化的候选人信息,并以 JSON 返回。 输出字段:name, yearsOfExperience, skills, education, lastCompany, highlights。 简历内容: %s """.formatted(state.getResumePath()); String result = chatClient.call(prompt); state.setCandidateProfile(result); return state; } }上面这份代码的职责很明确:输入简历,输出结构化候选人画像。实际项目中,简历解析不能只传个路径,应该由工具节点读取 PDF 转成文本后再交给模型,这部分可以用 Spring AI 的 Tool 机制实现。
接着配置 Graph。把所有节点按招聘流程串起来。
@Configuration public class RecruitmentGraphConfig { @Bean public Graph<RecruitmentState> recruitmentGraph( ResumeParserNode resumeParserNode, JdMatcherNode jdMatcherNode, InterviewQuestionNode interviewQuestionNode, ReportNode reportNode) { StateGraph<RecruitmentState> graph = new StateGraph<>(RecruitmentState.class) .addNode("resumeParser", resumeParserNode) .addNode("jdMatcher", jdMatcherNode) .addNode("interviewQuestion", interviewQuestionNode) .addNode("report", reportNode) .addEdge("START", "resumeParser") .addEdge("resumeParser", "jdMatcher") .addEdge("jdMatcher", "interviewQuestion") .addEdge("interviewQuestion", "report") .addEdge("report", "END"); return graph.compile(); } }这段代码是教学示意,不同版本的 Spring AI Graph API 命名可能不同,比如StateGraph的构造方式、addEdge的写法、compile()的返回值,都需要按当前版本调整。思路是一致的:图由节点和边组成,执行器给你一个有状态的工作流容器。
最后把 Graph 包成一个 Service,方便 Controller 调用。
@Service public class RecruitmentAgentService { private final Graph<RecruitmentState> graph; public RecruitmentAgentService(Graph<RecruitmentState> graph) { this.graph = graph; } public RecruitmentState run(RecruitmentState initialState) { return graph.execute(initialState); } }到这里,一个最小可运行的 Spring AI Alibaba Graph 骨架就完成了。你可以先用一个最简单的测试调用它,观察状态对象从“简历路径”到“评估报告”的完整流转。这一步跑通后,再逐步加入条件路由和并行节点。
6. 功能测试与效果验证
工程跑通之后,不要急着写复杂业务逻辑,先分功能做验证。这里给出 5 个测试维度,每个维度都写明测试目标、操作步骤、预期结果和排查方向。
6.1 简历解析测试
测试目标是确认 ResumeParser 节点能否从简历文本中提取结构化字段。准备一份脱敏的假简历,例如“张三,5 年 Java 后端经验,熟练使用 Spring Boot、MySQL、Redis”。调用后,预期输出包含 name、yearsOfExperience、skills 等字段的 JSON。
判断成功的标准是字段完整、格式正确、能被 Jackson 正常反序列化。如果模型返回了多余字段或者 JSON 解析失败,优先检查 Prompt 模板是否明确指定了“只返回 JSON”,以及 temperature 是否偏高。
6.2 JD 理解测试
把一份真实 JD 文本传入 JdMatcher 节点,要求输出职位名称、技能要求、经验要求、软素质要求四个结构化字段。测试时要换 2 到 3 份不同行业的 JD,确认模板不是只适配某一个岗位。
如果发现 JD 中的“3 年经验”被模型理解成“9 年经验”,说明 Prompt 缺少“严格按原文提取数字”的约束。这类问题建议在 Prompt 模板中补充“不要推断原文不存在的门槛”。
6.3 匹配打分测试
匹配打分建议不要直接让模型输出一个数字,而是先让模型列出匹配项和不匹配项,再由 Java 方法计算最终分数。这样可以避免模型随意写出一个超出范围的分数。
测试场景:一个 3 年经验的候选人投递“5 年经验”岗位。预期的输出应当是匹配度偏低,并且不匹配项里明确写着“经验不足”。如果模型给出的分数和理由互相矛盾,说明功能调用链路没有生效,需要检查模型是否真的调用了打分方法。
6.4 面试题生成测试
面试题生成节点要同时考虑 JD 和候选人画像,生成技术题、项目题、行为题三类问题。行为题可以要求模型按 STAR 法则设计追问点。
测试时传入一个技能为 Spring Boot 但没有微服务经验的候选人,预期技术题中包含微服务相关的基础问题,而不是直接问“你做过几个微服务项目”。如果生成结果和候选人画像无关,大概率是 Prompt 中没有引用 state 中的 candidateProfile 字段。
6.5 端到端 Graph 执行测试
这是最关键的验证。完整执行一次 Graph,传入简历和 JD,观察整个流程是否按“简历解析 -> 匹配 -> 面试题 -> 报告”顺序执行,并且最后报告内容包含前面节点的结果。
判断成功标准是报告中的候选人画像、匹配分数、面试题相互一致,没有张冠李戴。失败时重点排查两个地方:边是否连接错误,以及某个节点是否因为状态字段为空而提前失败。这里建议在每个节点执行前后打日志,输出状态对象快照。
7. Agent 接口暴露与批量任务编排
Graph 内部跑通后,下一步要对外提供接口。最直接的做法是写一个 REST Controller,接收候选人信息,调用 Graph 执行器,返回结果。
@RestController @RequestMapping("/api/hr-agent") public class RecruitmentAgentController { private final RecruitmentAgentService recruitmentAgentService; public RecruitmentAgentController(RecruitmentAgentService recruitmentAgentService) { this.recruitmentAgentService = recruitmentAgentService; } @PostMapping("/run") public RecruitmentState run(@RequestBody RecruitmentState request) { return recruitmentAgentService.run(request); } }用 curl 测试接口:
curl -X POST http://127.0.0.1:8080/api/hr-agent/run \ -H "Content-Type: application/json" \ -d '{ "resumePath": "./data/resume_zhangsan.txt", "jdContent": "招聘 Java 后端工程师,5 年经验,熟悉 Spring Boot、MySQL、Redis" }'这里返回的是完整状态对象,包含候选人画像、匹配分数、面试题列表、报告内容。注意,接口路径和字段名只是教学示例,实际项目要按照自己的业务结构调整。
批量任务才是真实业务的主角。一个 HR 系统经常要一次处理几十份简历,简单方式是逐个调用接口,但更好的设计是任务队列。核心思路如下:
- 任务入队:把每份简历的路径和对应 JD ID 封装成任务对象。
- 并发控制:用线程池限制并发数,比如 max(2, CPU 核数),避免同时发出太多请求。
- 状态持久化:任务执行进度、中间结果写入数据库,失败任务可以重试。
- 幂等设计:同一个 resumePath 重复提交时,直接返回已有结果。
@Service public class BatchRecruitmentService { private final RecruitmentAgentService agentService; private final ExecutorService executor = Executors.newFixedThreadPool(4); public CompletableFuture<RecruitmentState> submit(String resumePath, String jdContent) { return CompletableFuture.supplyAsync(() -> { RecruitmentState state = new RecruitmentState(); state.setResumePath(resumePath); state.setJdContent(jdContent); return agentService.run(state); }, executor); } }批量任务的关键不是并发越高越好,而是要让每一步都可追踪、可重试、可审计。真实业务中,几十份简历分批处理更合适,每批 5 到 10 份,跑完一批再进下一批,避免单次任务量过大导致 API 超时或 Token 配额耗尽。
8. 资源占用与性能观察
这个项目跑在 JVM 上,本身不涉及显存计算。资源消耗主要看模型服务部署在哪里,以及你如何控制调用频率。
如果你使用云端大模型 API,本机资源占用很低,一个 2 核 4G 的服务器就能稳定运行。此时性能瓶颈在三个地方:网络延迟、模型响应时间、API 并发配额。建议通过 Actuator 暴露自定义指标,统计 Graph 平均执行时间、各节点耗时、Token 消耗量。比如在节点执行前后记录 System.currentTimeMillis(),就能快速找出哪个节点最慢。
如果你切换到本地 Ollama 或其他开源模型,就要考虑显卡资源了。常见 8B 级别量化模型在 6G 到 8G 显存可以运行,Embedding 模型更小,但这是通用经验值,不同的量化方式、上下文长度、并发数都会影响实际占用。更稳妥的判断是:先用云端 API 跑通全部功能,确认架构没有任何问题后,再评估是否值得换成本地模型节省调用费用。
还有一个容易被忽略的性能问题:Graph 中如果存在大量串行模型调用,端到端延迟会非常可观。比如简历解析 2 秒、JD 匹配 3 秒、面试题生成 4 秒、报告生成 5 秒,串行加起来 14 秒。优化方式有两种。第一,把互不依赖的节点改为并行,比如 JD 分析和简历解析同时进行。第二,里程碑报告直接流式输出,用户先看到前部分内容,不用等完整报告生成。
9. 常见问题与排查方法
实际开发中,Spring AI Alibaba Graph 的报错主要集中在依赖版本、配置项、模型返回异常三个方面。下面这张排查表可以帮你快速定位。
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 启动报 api-key 未配置 | 配置项路径不对或环境变量未设置 | 检查日志中的配置加载信息 | 按当前版本文档修正配置路径 |
| 调用模型返回 401 | API Key 无效或已过期 | 用官方控制台验证 Key 是否可用 | 重新生成 API Key,改用环境变量注入 |
| 模型返回内容 JSON 解析失败 | 模型没有严格按 Prompt 返回 JSON | 打印模型原始输出 | 在 Prompt 中强调“只返回 JSON”,降低 temperature |
| StateGraph 节点循环执行不结束 | 缺少循环终止条件 | 查看执行日志判断哪个节点被反复调用 | 在节点中增加轮次计数,达到上限强制终止 |
| 多个并发任务结果互相污染 | 使用了共享的可变状态对象 | 检查状态对象是否被多个线程复用 | 每次请求创建独立状态实例 |
| 批量任务中个别简历失败 | 文件解析失败或模型超时 | 查看任务日志中的异常栈 | 增加失败重试,按 batch 拆小批量 |
| 依赖版本冲突 | Spring Boot 与 Spring AI 版本不匹配 | 执行 mvn dependency:tree 查看依赖树 | 通过 Spring Initializr 重新生成项目统一版本 |
| Actuator 没有暴露自定义指标 | 未引入 micrometer 注册代码 | 检查依赖和配置 | 手动注册 Gauge 或 Counter |
这里要特别提醒一个 Graph 特有的坑:循环节点。如果面试追问节点设计成可以反复执行,一定记得在状态对象里维护一个questionRound计数字段,每轮加一,超过上限就走条件边到报告节点。否则,一个小数点错误或 Prompt 歧义都可能让 Graph 进入死循环,白白消耗 Token。
10. 最佳实践与下一步
最后给几条工程化建议,都是这个项目中已经验证过有价值的做法。
第一,先小步验证,再做大流程。第一次跑 Graph 时只保留“简历解析 -> 报告”两个节点,确认基础链路稳定后,再逐步加入匹配、面试题、条件路由。这样出问题时,定位范围非常小。
第二,Prompt 模板要单独管理。不要把所有提示词都散落在代码里,建议放到 resources 目录下的模板文件中,或统一封装成 PromptTemplate。每次修改后记录版本号,方便对比输出质量变化。
第三,设置一个人工复核节点。HR 招聘 Agent 的最终报告只能作为参考材料,自动化生成的评估结果必须有人工确认环节。在 Graph 中预留一个“PENDING_REVIEW”状态,报告生成后停在人工审核,审核通过再进入下一流程。这不仅解决业务合规问题,也会让你的 Agent 架构更贴近真实生产环境。
第四,准备一个最小回归测试集。用 10 份不同类型的假简历和 3 份 JD,每星期跑一遍端到端测试,确认修改 Prompt 或代码后,简历解析、匹配分数、面试题质量没有明显退步。这是 Agent 项目最难的部分,也是最值得投入的部分。
这个项目的核心收获,不是“我会写一个招聘 Agent”这么简单,而是掌握了一套 Java 生态里的 Agent 编排范式:状态定义、节点拆分、条件路由、工具调用、人工审核、批量任务。这套结构不绑死任何行业业务。你现在理解了 HR 招聘流程的 Graph 怎么画,下一次面对“工单自动分派”“合同智能审阅”“客服话术推荐”这些需求时,只是换一套节点和 Prompt 的问题。
建议先把简历解析和 JD 匹配这条最长的链路跑通,再把面试题生成和报告节点接上。Graph 的价值要在链路完整之后才真正体现出来。收藏这篇文章,按第 4 章到第 6 章的顺序动手跑一遍,25 个核心技术点不用一次全懂,先把主链路跑起来,再逐个点亮其他能力点。