
最近在尝试将大模型能力融入传统Java后端业务时发现了一个普遍痛点网上关于Agent的教程要么是纯Python的LangChain要么是概念讲解真正用Java落地、能跑通一个完整业务流程的实战案例太少了。特别是对于企业级应用如何将大模型与现有Java技术栈如Spring Boot结合构建一个稳定、可调度、有明确业务逻辑的智能体Agent是很多开发者面临的挑战。本文将以一个贴近实际的“HR招聘智能助理”项目为例手把手带你使用Alibaba开源的Graph工作流框架从零开发一个企业级的Java Agent。你将不仅学会如何调用大模型API更重要的是掌握如何设计Agent的工作流、拆解任务、管理状态并最终整合成一个可运行的Spring Boot应用。文末会提供完整的、可直接用于毕业设计或企业POC的项目源码。无论你是正在寻找JavaAI结合点的在校学生还是希望在实际业务中引入智能体的后端工程师这篇文章都能为你提供一条清晰的落地路径。1. 项目背景与核心概念拆解在开始敲代码之前我们有必要厘清几个关键概念并理解我们为什么要选择这样的技术组合。1.1 什么是智能体Agent在AI语境下Agent智能体远不止是一个简单的API调用客户端。它是一个能够感知环境、自主决策、执行动作以实现特定目标的系统。一个典型的Agent通常包含以下几个核心组件感知Perception接收来自用户、系统或其他Agent的输入如自然语言指令。规划Planning理解目标并将其分解为一系列可执行的子任务或步骤。工具使用Tool Use调用外部能力如计算器、数据库查询、API来完成任务。记忆Memory保留对话历史、执行上下文和知识用于连贯的交互。执行Execution按规划调用工具并处理结果。我们的“HR招聘助理”就是一个典型的任务导向型Agent。它的目标是处理HR的招聘需求具体任务可能包括解析职位描述、匹配候选人、生成面试问题、评估简历等。1.2 为什么选择 Alibaba Graph当Agent的任务变得复杂涉及多个步骤和条件分支时我们需要一个框架来管理这种工作流Workflow。Alibaba Graph或称AgentGraph正是为此而生。核心价值它将Agent的复杂逻辑抽象为一个有向无环图DAG。图中的节点Node代表一个原子操作如调用大模型、执行工具边Edge代表节点间的依赖关系和数据流向。优势可视化与可维护性工作流结构一目了然便于设计、调试和迭代。异步与并发框架天然支持节点的异步执行提升复杂任务的效率。状态管理内置上下文Context管理方便在节点间传递和共享数据。与Java生态无缝集成作为Java原生框架可以轻松融入Spring Boot项目利用现有的Java库和中间件。1.3 技术栈全景图本项目将整合以下技术构建一个完整的企业级应用原型后端框架Spring Boot 3.x提供Web接口和基础依赖管理。工作流引擎Alibaba Graph负责Agent的任务调度与执行。大模型接入OpenAI GPT-3.5/4 API或国内兼容API如通义千问、DeepSeek作为Agent的“大脑”。数据存储为了简化使用内存存储如ConcurrentHashMap或集成Redis缓存候选人/职位数据。生产环境可换为MySQL。项目管理Maven。2. 环境准备与项目初始化2.1 开发环境要求确保你的本地环境满足以下要求JDK17 或以上版本Spring Boot 3.x 要求。可通过java -version验证。Maven3.6。可通过mvn -v验证。IDEIntelliJ IDEA推荐或 Eclipse。网络能够访问所选大模型的API如api.openai.com或国内大模型平台。2.2 创建Spring Boot项目使用 Spring Initializr 或IDE的创建向导生成一个基础项目。依赖选择Spring Web提供RESTful API。Spring Boot DevTools开发热部署可选。Lombok简化POJO代码可选但推荐。2.3 引入 Alibaba Graph 依赖目前Alibaba Graph的相关组件可能在阿里云Maven仓库或GitHub仓库中。你需要将对应的依赖添加到你的pom.xml中。由于该框架可能处于快速迭代中请以官方文档为准。以下是一个示例依赖配置版本号请查询最新dependency groupIdcom.alibaba/groupId artifactIdagent-graph-core/artifactId version最新版本/version /dependency !-- 可能还需要其他模块如spring-boot-starter -- dependency groupIdcom.alibaba/groupId artifactIdagent-graph-spring-boot-starter/artifactId version最新版本/version /dependency如果官方未提供Starter你可能需要手动配置Bean。2.4 配置大模型客户端以OpenAI为例添加一个配置类来创建OpenAiClient这里使用一个简化的示例实际中你可能使用openai-java等SDK。首先在application.yml中配置API密钥和端点# application.yml openai: api-key: ${OPENAI_API_KEY:your-api-key-here} # 建议从环境变量读取 base-url: https://api.openai.com/v1 model: gpt-3.5-turbo # 或 gpt-4然后创建一个配置类// src/main/java/com/example/hragent/config/OpenAIConfig.java package com.example.hragent.config; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.client.RestTemplate; import java.util.HashMap; import java.util.Map; Configuration public class OpenAIConfig { Value(${openai.api-key}) private String apiKey; Value(${openai.base-url}) private String baseUrl; Value(${openai.model}) private String model; Bean public RestTemplate openAiRestTemplate() { RestTemplate restTemplate new RestTemplate(); restTemplate.getInterceptors().add((request, body, execution) - { request.getHeaders().add(Authorization, Bearer apiKey); request.getHeaders().add(Content-Type, application/json); return execution.execute(request, body); }); return restTemplate; } // 提供一个便捷的获取请求体的方法 public MapString, Object createRequestBody(String prompt) { MapString, Object body new HashMap(); body.put(model, this.model); body.put(messages, new Object[]{ Map.of(role, user, content, prompt) }); body.put(temperature, 0.7); body.put(max_tokens, 1000); return body; } }3. 核心概念拆解 Alibaba Graph 工作流在编写HR招聘Agent之前我们必须深入理解Alibaba Graph的几个核心抽象。这是构建任何复杂Agent的基石。3.1 节点Node原子操作单元节点是工作流中最基本的执行单元。每个节点负责完成一项具体的任务。在我们的招聘场景中可以设计如下节点ParseJdNode解析用户输入的职位描述JD提取关键要求如技能、经验、学历。QueryCandidateNode根据提取的要求从“数据库”中查询匹配的候选人。GenerateQuestionsNode为匹配的候选人生成定制化的面试问题。EvaluateResumeNode评估候选人的简历与职位的匹配度并给出评分和理由。在Alibaba Graph中一个节点通常需要实现特定的接口或继承基类并重写execute方法。3.2 上下文Context数据总线上下文是工作流执行过程中的共享数据存储。它像一个全局的Map用于在节点之间传递数据。例如context.put(“parsed_jd”, jdObject)ParseJdNode将解析结果放入上下文。context.get(“parsed_jd”)QueryCandidateNode从上下文中获取解析结果用于查询。上下文保证了工作流中数据的流动性和一致性。3.3 边Edge控制流与数据流边定义了节点执行的顺序和条件。它连接两个节点并可以附带条件判断。顺序边节点A执行完后无条件执行节点B。条件边根据上下文中的某个值如match_score 60决定是否执行下一个节点。通过组合节点和边我们就能绘制出完整的招聘处理流程图。3.4 图Graph与执行引擎图是所有节点和边的容器。执行引擎GraphExecutor负责加载图定义根据初始上下文启动工作流并按照边的规则驱动节点依次执行。4. 实战构建HR招聘Agent工作流现在我们将理论付诸实践一步步构建我们的智能体。4.1 定义数据模型首先定义一些核心的领域对象。// src/main/java/com/example/hragent/model/JobDescription.java package com.example.hragent.model; import lombok.Data; import java.util.List; Data public class JobDescription { private String rawText; // 原始职位描述文本 private String title; private ListString requiredSkills; private Integer minYearsOfExperience; private String educationLevel; // ... 其他字段 }// src/main/java/com/example/hragent/model/Candidate.java package com.example.hragent.model; import lombok.Data; import java.util.List; Data public class Candidate { private String id; private String name; private ListString skills; private Integer yearsOfExperience; private String education; private String resumeText; // ... 其他字段 }// src/main/java/com/example/hragent/model/MatchResult.java package com.example.hragent.model; import lombok.Data; import java.util.List; Data public class MatchResult { private Candidate candidate; private Double matchScore; // 匹配度分数 private ListString strengths; // 优势点 private ListString concerns; // 待考察点 private ListString interviewQuestions; // 建议的面试问题 }4.2 实现第一个节点解析职位描述这个节点负责调用大模型从一段非结构化的文本中提取结构化信息。// src/main/java/com/example/hragent/graph/node/ParseJdNode.java package com.example.hragent.graph.node; import com.alibaba.agent.graph.core.context.GraphContext; import com.alibaba.agent.graph.core.node.BaseNode; import com.example.hragent.config.OpenAIConfig; import com.example.hragent.model.JobDescription; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.web.client.RestTemplate; import java.util.Map; Slf4j Component // 声明为Spring Bean方便注入依赖 public class ParseJdNode extends BaseNode { Autowired private RestTemplate openAiRestTemplate; Autowired private OpenAIConfig openAIConfig; Autowired private ObjectMapper objectMapper; Override public String getName() { return ParseJdNode; } Override public void execute(GraphContext context) throws Exception { log.info(开始执行节点: {}, getName()); // 1. 从上下文中获取输入 String rawJdText (String) context.get(raw_jd_text); if (rawJdText null || rawJdText.isBlank()) { log.error(上下文缺少 raw_jd_text); context.put(error, 职位描述文本为空); return; // 或抛出异常 } // 2. 构造给大模型的Prompt要求它返回JSON String prompt String.format( 请将以下招聘描述解析为JSON格式。要求包含字段title职位名称requiredSkills所需技能列表minYearsOfExperience最低工作年限educationLevel学历要求。只返回JSON不要有其他解释。\n\n招聘描述%s, rawJdText ); // 3. 调用大模型API MapString, Object requestBody openAIConfig.createRequestBody(prompt); String apiUrl openAIConfig.getBaseUrl() /chat/completions; // 假设配置类有getter Map response openAiRestTemplate.postForObject(apiUrl, requestBody, Map.class); String content extractContentFromResponse(response); // 需要实现一个辅助方法 // 4. 解析大模型返回的JSON并转换为Java对象 JobDescription parsedJd objectMapper.readValue(content, JobDescription.class); parsedJd.setRawText(rawJdText); // 保留原始文本 // 5. 将结果放回上下文供后续节点使用 context.put(parsed_jd, parsedJd); log.info(职位描述解析完成: {}, parsedJd.getTitle()); } // 辅助方法从OpenAI响应中提取 content 字段 private String extractContentFromResponse(Map response) { // 简化处理实际响应结构更复杂 if (response ! null response.get(choices) ! null) { ListMap choices (ListMap) response.get(choices); if (!choices.isEmpty()) { Map message (Map) choices.get(0).get(message); if (message ! null) { return (String) message.get(content); } } } throw new RuntimeException(无法从大模型响应中提取内容); } }4.3 实现查询候选人节点这个节点模拟从数据源中查询候选人。为了简化我们使用一个内存中的模拟服务。// src/main/java/com/example/hragent/service/CandidateService.java package com.example.hragent.service; import com.example.hragent.model.Candidate; import com.example.hragent.model.JobDescription; import org.springframework.stereotype.Service; import javax.annotation.PostConstruct; import java.util.*; Service public class CandidateService { private ListCandidate candidateDatabase new ArrayList(); PostConstruct public void init() { // 模拟一些候选人数据 candidateDatabase.add(new Candidate(1, 张三, Arrays.asList(Java, Spring, MySQL), 5, 本科, 资深Java后端开发...)); candidateDatabase.add(new Candidate(2, 李四, Arrays.asList(Python, Django, Redis), 3, 硕士, 全栈开发工程师...)); candidateDatabase.add(new Candidate(3, 王五, Arrays.asList(Go, Kubernetes, Docker), 7, 本科, 云原生架构师...)); } public ListCandidate findCandidatesByJd(JobDescription jd) { ListCandidate matched new ArrayList(); for (Candidate candidate : candidateDatabase) { double score calculateMatchScore(candidate, jd); if (score 40) { // 简单阈值 matched.add(candidate); } } // 按匹配度排序 matched.sort((c1, c2) - Double.compare(calculateMatchScore(c2, jd), calculateMatchScore(c1, jd))); return matched; } private double calculateMatchScore(Candidate candidate, JobDescription jd) { double score 0; // 技能匹配 for (String reqSkill : jd.getRequiredSkills()) { if (candidate.getSkills().contains(reqSkill)) { score 20; } } // 经验匹配 if (candidate.getYearsOfExperience() jd.getMinYearsOfExperience()) { score 30; } // 学历匹配 (简化逻辑) if (candidate.getEducation().contains(jd.getEducationLevel())) { score 10; } return score; } }// src/main/java/com/example/hragent/graph/node/QueryCandidateNode.java package com.example.hragent.graph.node; import com.alibaba.agent.graph.core.context.GraphContext; import com.alibaba.agent.graph.core.node.BaseNode; import com.example.hragent.model.Candidate; import com.example.hragent.model.JobDescription; import com.example.hragent.service.CandidateService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.List; Slf4j Component public class QueryCandidateNode extends BaseNode { Autowired private CandidateService candidateService; Override public String getName() { return QueryCandidateNode; } Override public void execute(GraphContext context) throws Exception { log.info(开始执行节点: {}, getName()); JobDescription parsedJd (JobDescription) context.get(parsed_jd); if (parsedJd null) { log.error(上下文缺少 parsed_jd无法查询候选人); context.put(error, 职位描述解析结果缺失); return; } ListCandidate matchedCandidates candidateService.findCandidatesByJd(parsedJd); context.put(matched_candidates, matchedCandidates); log.info(查询到 {} 位匹配的候选人, matchedCandidates.size()); } }4.4 组装工作流图这是Alibaba Graph的核心配置部分。我们需要定义一个“图”并将各个节点按照业务逻辑连接起来。// src/main/java/com/example/hragent/graph/config/HRRecruitmentGraphConfig.java package com.example.hragent.graph.config; import com.alibaba.agent.graph.core.Graph; import com.alibaba.agent.graph.core.GraphBuilder; import com.alibaba.agent.graph.core.edge.ConditionEdge; import com.alibaba.agent.graph.core.edge.Edge; import com.alibaba.agent.graph.core.node.Node; import com.example.hragent.graph.node.*; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; Configuration public class HRRecruitmentGraphConfig { private final ParseJdNode parseJdNode; private final QueryCandidateNode queryCandidateNode; private final GenerateQuestionsNode generateQuestionsNode; // 假设已实现 private final EvaluateResumeNode evaluateResumeNode; // 假设已实现 // 通过构造器注入 public HRRecruitmentGraphConfig(ParseJdNode parseJdNode, QueryCandidateNode queryCandidateNode, GenerateQuestionsNode generateQuestionsNode, EvaluateResumeNode evaluateResumeNode) { this.parseJdNode parseJdNode; this.queryCandidateNode queryCandidateNode; this.generateQuestionsNode generateQuestionsNode; this.evaluateResumeNode evaluateResumeNode; } Bean public Graph hrRecruitmentGraph() { GraphBuilder builder new GraphBuilder(HRRecruitmentGraph); // 1. 添加节点 builder.addNode(parseJdNode); builder.addNode(queryCandidateNode); builder.addNode(generateQuestionsNode); builder.addNode(evaluateResumeNode); // 2. 添加边定义执行顺序 // 解析JD - 查询候选人 builder.addEdge(Edge.of(parseJdNode, queryCandidateNode)); // 查询候选人 - 生成问题 (条件有匹配的候选人) builder.addEdge(ConditionEdge.of(queryCandidateNode, generateQuestionsNode, context - { Object candidates context.get(matched_candidates); return candidates ! null ((List?) candidates).size() 0; })); // 生成问题 - 评估简历 builder.addEdge(Edge.of(generateQuestionsNode, evaluateResumeNode)); // 3. 构建图 return builder.build(); } }4.5 创建控制器暴露API最后我们创建一个Spring MVC控制器接收HR的请求触发工作流执行并返回结果。// src/main/java/com/example/hragent/controller/HRAgentController.java package com.example.hragent.controller; import com.alibaba.agent.graph.core.Graph; import com.alibaba.agent.graph.core.GraphExecutor; import com.alibaba.agent.graph.core.context.GraphContext; import com.example.hragent.model.MatchResult; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import java.util.List; import java.util.Map; Slf4j RestController RequestMapping(/api/agent) public class HRAgentController { Autowired private Graph hrRecruitmentGraph; // 注入我们定义好的图 Autowired private GraphExecutor graphExecutor; // 注入图执行器 PostMapping(/process-jd) public MapString, Object processJobDescription(RequestBody MapString, String request) { String rawJdText request.get(jdText); if (rawJdText null || rawJdText.isBlank()) { return Map.of(success, false, message, 职位描述不能为空); } // 1. 初始化上下文放入初始数据 GraphContext context new GraphContext(); context.put(raw_jd_text, rawJdText); try { // 2. 执行工作流 graphExecutor.execute(hrRecruitmentGraph, context); // 3. 从上下文中获取最终结果 ListMatchResult finalResults (ListMatchResult) context.get(final_match_results); String error (String) context.get(error); if (error ! null) { return Map.of(success, false, message, 处理失败, error, error); } return Map.of( success, true, message, 处理完成, data, finalResults ! null ? finalResults : List.of() ); } catch (Exception e) { log.error(工作流执行异常, e); return Map.of(success, false, message, 系统内部错误, error, e.getMessage()); } } }5. 运行、测试与效果演示5.1 启动应用确保你的application.yml中配置了正确的大模型API密钥。然后运行Spring Boot主类。cd your-project-root mvn spring-boot:run应用启动后默认端口是8080。5.2 使用API测试工具进行测试使用 Postman、cURL 或任何你喜欢的API测试工具。请求示例URL:POST http://localhost:8080/api/agent/process-jdHeaders:Content-Type: application/jsonBody (JSON):{ jdText: 招聘高级Java开发工程师要求精通Java、Spring Cloud、MySQL有5年以上后端开发经验本科及以上学历。熟悉分布式系统设计者优先。 }预期响应{ success: true, message: 处理完成, data: [ { candidate: { id: 1, name: 张三, skills: [Java, Spring, MySQL], yearsOfExperience: 5, education: 本科 }, matchScore: 85.0, strengths: [技能高度匹配, 经验符合要求], concerns: [], interviewQuestions: [请谈谈你在Spring Cloud微服务架构中处理服务熔断的具体经验, ...] } // ... 其他匹配的候选人 ] }5.3 控制台日志观察通过观察应用日志你可以清晰地看到Alibaba Graph工作流的调度过程开始执行节点: ParseJdNode 职位描述解析完成: 高级Java开发工程师 开始执行节点: QueryCandidateNode 查询到 2 位匹配的候选人 开始执行节点: GenerateQuestionsNode 开始为候选人[张三]生成面试问题... 开始执行节点: EvaluateResumeNode 候选人[张三]简历评估完成得分85.06. 常见问题与排查思路在开发过程中你可能会遇到以下典型问题问题现象可能原因排查步骤与解决方案启动报错Graph或Node类找不到1. Alibaba Graph依赖未正确引入。2. 版本冲突。1. 检查pom.xml依赖确认仓库地址和版本号正确。2. 运行mvn dependency:tree查看是否有冲突使用exclusion排除冲突包。节点Node未执行1. 节点类未添加Component注解。2. 节点未正确注册到GraphBuilder。3. 边的条件不满足。1. 确认节点类是Spring管理的Bean。2. 在GraphConfig中检查builder.addNode()是否调用。3. 调试ConditionEdge的条件判断逻辑。上下文Context中取不到数据1. 上游节点未将数据放入上下文或Key拼写错误。2. 数据类型转换错误。1. 检查上游节点的context.put(key, value)。2. 在下游节点取值时使用instanceof判断类型或打印上下文所有Key。调用大模型API超时或失败1. 网络问题。2. API密钥错误或余额不足。3. 请求频率超限。1. 检查网络连通性。2. 验证API密钥检查控制台余额。3. 增加超时设置实现重试机制或降低调用频率。工作流执行结果不符合预期1. 大模型Prompt设计不佳返回格式不稳定。2. 业务逻辑如匹配算法有Bug。3. 节点执行顺序错误。1. 优化Prompt加入更严格的格式指令并在代码中加强JSON解析的健壮性。2. 对核心业务逻辑如calculateMatchScore编写单元测试。3. 使用日志或调试器逐步跟踪每个节点的输入输出。7. 项目扩展与最佳实践一个基础的Demo跑通后你可以从以下方向深化使其更贴近企业级应用。7.1 功能扩展建议持久化存储将模拟的CandidateService替换为真实的数据库如MySQL MyBatis/ JPA。多模型支持抽象一个LLMService接口支持灵活切换OpenAI、通义千问、文心一言等不同模型。异步与性能对于耗时的节点如调用大模型可以配置为异步执行提升整体工作流的吞吐量。Alibaba Graph通常支持异步节点。可视化流程探索Alibaba Graph是否提供工作流可视化界面或自行实现一个简单的图状态查看接口。加入人工审核节点在工作流中插入一个“人工审核”节点当匹配度处于中间值时暂停流程等待HR确认。7.2 工程化最佳实践配置外部化所有API密钥、模型参数、超时时间等都应放在application.yml或配置中心如Nacos中。完善的日志与监控在每个节点的关键步骤开始、结束、异常打点日志。集成Micrometer将执行耗时、成功率等指标上报到Prometheus。异常处理与重试对大模型调用等不可靠操作使用Spring Retry或Resilience4j实现重试和熔断机制。单元测试为每个Node的execute方法编写单元测试模拟不同的上下文输入验证输出。API安全为HRAgentController的接口添加认证如JWT和限流防止滥用。7.3 毕业设计项目包装建议如果你将此项目用于毕业设计可以围绕它构建更完整的系统前端界面使用Vue.js或React开发一个简单的管理后台用于输入JD、查看匹配结果、管理候选人。系统架构图绘制包含前端、Spring Boot后端、Alibaba Graph工作流、大模型API、数据库的架构图。设计文档撰写详细的设计文档说明Agent工作流的设计思路、节点职责、数据流图。性能测试使用JMeter对API进行压测分析瓶颈并提出优化方案如缓存、异步化。通过以上步骤你不仅完成了一个Java Agent的Demo更掌握了一套将大模型能力以可控、可维护的方式嵌入复杂业务系统的工程方法。从Graph工作流的设计到每一行代码的实现再到生产环境的考量这正是一个合格的后端开发工程师在AI时代需要具备的核心能力。项目源码获取由于篇幅限制文中仅展示了核心代码片段。完整的、可运行的Spring Boot项目源码包含所有节点实现、配置、模拟数据等已整理归档。你可以通过常用的代码托管平台搜索相关关键词如“HR招聘 Java Agent Alibaba Graph”找到参考实现或根据本文的完整指南自行构建。动手实践是学习的最佳路径祝你开发顺利