三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

Java智能体开发实战:从零构建任务执行与协调系统

Java智能体开发实战:从零构建任务执行与协调系统

最近在整理大三下学期的项目经历时,发现很多同学对“智能体”这个概念既好奇又觉得无从下手,尤其是在Java技术栈下如何落地一个完整的智能体项目。网上资料要么过于理论化,要么是Python生态的专属教程,对于Java开发者来说,缺乏一套从零到一、可复现的实战指南。

本文将分享一个基于Java技术栈独立开发的智能体项目全流程,涵盖从核心概念理解、技术选型、环境搭建、核心模块开发,到最终部署上线的完整闭环。无论你是想为简历增加一个亮眼的项目,还是希望深入理解AI应用开发,都能从本文中找到清晰的路径和可运行的代码。

1. 智能体项目:概念、价值与技术选型

在开始编码之前,我们必须先厘清几个核心问题:什么是智能体?为什么用Java来开发?以及我们这个项目要做什么。

1.1 智能体(Agent)是什么?

在AI领域,智能体通常指一个能够感知环境、进行决策并执行动作以达成目标的自治系统。它不仅仅是调用一个API生成文本,而是具备记忆、规划、工具使用等能力的程序实体。

一个典型的智能体工作流程可以简化为:

  1. 感知:接收用户输入或环境状态。
  2. 思考:基于内部记忆和知识进行推理、规划。
  3. 行动:调用工具(如搜索、计算、写文件)或生成回复。
  4. 学习:根据行动结果更新内部状态。

与我们熟悉的“ChatGPT对话”不同,智能体更强调自主性目标导向性。例如,一个“旅行规划智能体”不仅能回答景点信息,还能主动查询天气、对比机票价格、生成日程表并预订酒店。

1.2 为什么选择Java技术栈?

当前智能体开发的热门工具如LangChain、LlamaIndex确实以Python为主。但选择Java有独特的优势:

  • 工程化优势:Java在大型企业级应用开发中,在并发处理、内存管理、代码结构规范性方面有深厚积累。对于需要高可靠、易维护的智能体应用,Java是绝佳选择。
  • 生态整合:如果你的现有业务系统(如Spring Boot微服务)是Java技术栈,用Java开发智能体可以无缝集成,避免跨语言调用的复杂度。
  • 性能与稳定性:JVM的成熟GC机制和JIT优化,对于需要长时间运行、处理复杂逻辑的智能体服务至关重要。
  • 学习与挑战:用Java探索AI前沿,能让你同时巩固后端工程能力和理解AI应用架构,提升综合竞争力。

1.3 项目目标:一个任务执行与协调智能体

我们的项目将构建一个“多任务协调智能体”。它的核心能力是:

  • 理解复杂指令:将用户模糊的自然语言指令(如“帮我安排下周的学习计划,并查一下杭州的天气”)分解为明确的子任务。
  • 调度与执行:自主调用不同的工具(如日历管理、天气查询、文档总结)来完成子任务。
  • 汇总与回复:整合各工具的执行结果,生成结构清晰、用户友好的最终答复。

技术栈规划如下:

  • 核心框架:Spring Boot 3.x (提供Web服务和依赖注入)
  • AI能力集成:通过HTTP Client调用大模型API(如OpenAI GPT、国内大模型)
  • 工具层:封装各类功能为可被智能体调用的“工具”(Tool)
  • 记忆与状态管理:使用Redis或数据库存储对话历史与任务状态
  • 任务编排引擎:自定义轻量级状态机或使用Flowable等引擎管理任务流

2. 开发环境与项目初始化

工欲善其事,必先利其器。一个清晰的项目结构是成功的第一步。

2.1 环境准备清单

请确保你的开发环境包含以下组件:

  • JDK:17或以上版本(推荐Amazon Corretto 17或OpenJDK 17)
  • 构建工具:Maven 3.6+ 或 Gradle 7.x
  • IDE:IntelliJ IDEA(社区版即可)或 VS Code with Java插件
  • 依赖管理:Maven中央仓库访问正常
  • 可选组件:Docker(用于容器化部署)、Redis(用于状态缓存)

2.2 使用Spring Initializr创建项目

通过 start.spring.io 快速生成项目骨架。

项目元数据

  • Project: Maven
  • Language: Java
  • Spring Boot: 3.2.5 (选择稳定版本)
  • Group: com.example
  • Artifact: java-agent-demo
  • Packaging: Jar
  • Java: 17

依赖选择

  • Spring Web:提供RESTful API接口。
  • Spring Data Redis:用于智能体的记忆存储和会话管理。
  • Lombok:减少样板代码,但需注意IDE插件配置。
  • Validation:参数校验。

点击“GENERATE”下载压缩包并解压,用IDE打开。

2.3 项目结构规划

创建清晰的多模块或分包结构,有助于代码管理。以下是建议的包结构:

src/main/java/com/example/javaagentdemo/ ├── JavaAgentDemoApplication.java # 主启动类 ├── config/ # 配置类 │ ├── RedisConfig.java │ └── WebClientConfig.java # 用于调用外部API ├── controller/ # 控制器层 │ └── AgentController.java ├── service/ # 业务逻辑层 │ ├── AgentCoreService.java # 智能体核心调度逻辑 │ ├── llm/ # 大模型服务封装 │ │ ├── LLMService.java │ │ └── impl/OpenAIServiceImpl.java │ └── tool/ # 工具层 │ ├── Tool.java # 工具接口 │ ├── WeatherTool.java │ ├── CalculatorTool.java │ └── TodoManagerTool.java ├── entity/ # 实体类 │ ├── AgentMessage.java # 消息实体 │ └── Task.java # 任务实体 ├── repository/ # 数据访问层(如需持久化) │ └── ConversationRepository.java └── dto/ # 数据传输对象 ├── AgentRequest.java └── AgentResponse.java

pom.xml中,我们还需要手动添加一些依赖,例如用于HTTP调用的Apache HttpClientSpringWebClient,以及JSON处理库(Spring Boot已包含Jackson)。

<!-- 在Spring Initializr生成的依赖基础上,可选择性添加 --> <dependency> <groupId>org.apache.httpcomponents.client5</groupId> <artifactId>httpclient5</artifactId> <version>5.3</version> </dependency> <!-- 如果使用WebClient,spring-boot-starter-webflux 已包含,但通常Web模块用RestTemplate或第三方客户端 --> <!-- 推荐使用OkHttp --> <dependency> <groupId>com.squareup.okhttp3</groupId> <artifactId>okhttp</artifactId> <version>4.12.0</version> </dependency>

3. 核心模块一:大模型能力集成

智能体的“大脑”依赖于大语言模型。我们将封装一个通用的LLM服务,便于切换不同供应商。

3.1 设计LLM服务接口

首先定义接口,抽象出调用过程。

// file: src/main/java/com/example/javaagentdemo/service/llm/LLMService.java package com.example.javaagentdemo.service.llm; import com.example.javaagentdemo.dto.ChatMessage; import java.util.List; public interface LLMService { /** * 调用大模型聊天补全接口 * @param messages 对话历史消息 * @param temperature 温度参数,控制随机性 * @return 模型返回的文本内容 */ String chatCompletion(List<ChatMessage> messages, Double temperature); /** * 简化调用,使用默认参数 */ String chatCompletion(List<ChatMessage> messages); }

3.2 实现OpenAI API调用

以OpenAI GPT-3.5/4为例,实现上述接口。你需要准备一个有效的API Key。

// file: src/main/java/com/example/javaagentdemo/service/llm/impl/OpenAIServiceImpl.java package com.example.javaagentdemo.service.llm.impl; import com.example.javaagentdemo.dto.ChatMessage; import com.example.javaagentdemo.service.llm.LLMService; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.extern.slf4j.Slf4j; import okhttp3.*; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import java.io.IOException; import java.util.List; @Service @Slf4j public class OpenAIServiceImpl implements LLMService { private static final MediaType JSON = MediaType.get("application/json; charset=utf-8"); @Value("${openai.api.key}") private String apiKey; @Value("${openai.api.url:https://api.openai.com/v1/chat/completions}") private String apiUrl; @Value("${openai.model:gpt-3.5-turbo}") private String model; private final OkHttpClient client = new OkHttpClient(); private final ObjectMapper objectMapper = new ObjectMapper(); @Override public String chatCompletion(List<ChatMessage> messages, Double temperature) { // 1. 构建请求体 OpenAiRequest requestBody = new OpenAiRequest(model, messages, temperature); String jsonBody; try { jsonBody = objectMapper.writeValueAsString(requestBody); } catch (Exception e) { log.error("序列化请求体失败", e); return "请求构造失败"; } // 2. 构建HTTP请求 Request request = new Request.Builder() .url(apiUrl) .addHeader("Authorization", "Bearer " + apiKey) .addHeader("Content-Type", "application/json") .post(RequestBody.create(jsonBody, JSON)) .build(); // 3. 发送请求并处理响应 try (Response response = client.newCall(request).execute()) { if (!response.isSuccessful()) { log.error("OpenAI API调用失败,状态码: {}, 响应体: {}", response.code(), response.body() != null ? response.body().string() : "空"); return "大模型服务调用异常,状态码:" + response.code(); } if (response.body() == null) { return "收到空响应"; } String responseBody = response.body().string(); OpenAiResponse openAiResponse = objectMapper.readValue(responseBody, OpenAiResponse.class); if (openAiResponse.choices != null && !openAiResponse.choices.isEmpty()) { return openAiResponse.choices.get(0).message.content; } else { return "模型未返回有效内容"; } } catch (IOException e) { log.error("调用OpenAI API时发生IO异常", e); return "网络或服务异常"; } } @Override public String chatCompletion(List<ChatMessage> messages) { return chatCompletion(messages, 0.7); // 默认温度值 } // 内部类,用于映射OpenAI请求和响应格式 @lombok.Data private static class OpenAiRequest { private final String model; private final List<ChatMessage> messages; private final Double temperature; } @lombok.Data private static class OpenAiResponse { private List<Choice> choices; @lombok.Data static class Choice { private ChatMessage message; } } }

对应的消息实体和配置:

// file: src/main/java/com/example/javaagentdemo/dto/ChatMessage.java package com.example.javaagentdemo.dto; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @NoArgsConstructor @AllArgsConstructor public class ChatMessage { private String role; // "system", "user", "assistant" private String content; }

application.yml中配置你的API Key:

# file: src/main/resources/application.yml openai: api: key: ${OPENAI_API_KEY:your-api-key-here} # 建议使用环境变量 url: https://api.openai.com/v1/chat/completions model: gpt-3.5-turbo spring: data: redis: host: localhost port: 6379 # password: 如果有密码则配置

关键点说明

  1. 安全性:API Key绝不能硬编码在代码中提交到版本库。使用环境变量(${OPENAI_API_KEY})或配置中心管理。
  2. 容错性:网络请求必须包含超时设置和异常处理。上述示例简化了,生产环境应配置OkHttpClient的超时参数并考虑重试机制。
  3. 模型切换:通过配置项openai.model可以轻松切换不同的模型,如gpt-4

4. 核心模块二:可扩展的工具(Tool)系统

工具是智能体的“手脚”。我们需要设计一个系统,让智能体能够发现、描述并调用各种功能。

4.1 定义工具接口与基类

首先定义一个所有工具都必须实现的接口。

// file: src/main/java/com/example/javaagentdemo/service/tool/Tool.java package com.example.javaagentdemo.service.tool; import com.fasterxml.jackson.databind.JsonNode; /** * 智能体工具接口。 * 每个工具必须提供名称、描述、参数schema,并实现执行方法。 */ public interface Tool { /** * 工具的唯一名称,用于智能体识别和调用。 */ String getName(); /** * 工具的自然语言描述,用于让大模型理解工具的功能。 */ String getDescription(); /** * 工具的输入参数JSON Schema定义。 * 用于让大模型知道调用此工具需要提供哪些参数。 * @return 符合JSON Schema格式的JsonNode */ JsonNode getParametersSchema(); /** * 执行工具的核心方法。 * @param arguments 调用参数,通常是一个JSON字符串或对象。 * @return 工具执行结果的文本描述。 */ String execute(String arguments); }

提供一个抽象基类,简化参数Schema的定义:

// file: src/main/java/com/example/javaagentdemo/service/tool/BaseTool.java package com.example.javaagentdemo.service.tool; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import lombok.RequiredArgsConstructor; @RequiredArgsConstructor public abstract class BaseTool implements Tool { protected final ObjectMapper objectMapper; protected final String name; protected final String description; @Override public String getName() { return name; } @Override public String getDescription() { return description; } /** * 构建一个简单的参数Schema。 * 子类可以重写此方法以提供更复杂的Schema。 * @param properties 参数属性定义 * @return JsonNode格式的Schema */ protected JsonNode buildSimpleSchema(ObjectNode properties) { ObjectNode schema = objectMapper.createObjectNode(); schema.put("type", "object"); schema.set("properties", properties); schema.putArray("required").addAll(() -> properties.fieldNames()); return schema; } }

4.2 实现具体工具:天气查询

让我们实现一个具体的工具,它调用一个免费的天气API。

// file: src/main/java/com/example/javaagentdemo/service/tool/WeatherTool.java package com.example.javaagentdemo.service.tool; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import lombok.extern.slf4j.Slf4j; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import java.io.IOException; @Component @Slf4j public class WeatherTool extends BaseTool { private final OkHttpClient client = new OkHttpClient(); @Value("${weather.api.key:}") // 可从配置读取,示例使用公开API private String apiKey; private static final String API_URL = "https://api.openweathermap.org/data/2.5/weather"; public WeatherTool(ObjectMapper objectMapper) { super(objectMapper, "get_weather", "获取指定城市的当前天气情况。"); } @Override public JsonNode getParametersSchema() { // 定义参数:city,字符串类型,必填 ObjectNode properties = objectMapper.createObjectNode(); ObjectNode cityParam = objectMapper.createObjectNode(); cityParam.put("type", "string"); cityParam.put("description", "城市名称,例如:Beijing, Shanghai"); properties.set("city", cityParam); return buildSimpleSchema(properties); } @Override public String execute(String arguments) { try { JsonNode argsNode = objectMapper.readTree(arguments); String city = argsNode.get("city").asText(); if (city == null || city.trim().isEmpty()) { return "错误:未提供城市参数。"; } // 构建请求URL (使用公开API,无需key,仅作演示) String url = String.format("%s?q=%s&appid=%s&units=metric&lang=zh_cn", API_URL, city, apiKey); Request request = new Request.Builder().url(url).build(); try (Response response = client.newCall(request).execute()) { if (!response.isSuccessful()) { return String.format("获取天气失败,HTTP状态码:%d", response.code()); } if (response.body() == null) { return "天气API返回空响应。"; } String responseBody = response.body().string(); JsonNode weatherData = objectMapper.readTree(responseBody); // 解析并格式化天气信息 String cityName = weatherData.path("name").asText(); String description = weatherData.path("weather").get(0).path("description").asText(); double temp = weatherData.path("main").path("temp").asDouble(); double feelsLike = weatherData.path("main").path("feels_like").asDouble(); int humidity = weatherData.path("main").path("humidity").asInt(); return String.format("城市:%s\n天气状况:%s\n当前温度:%.1f°C\n体感温度:%.1f°C\n湿度:%d%%", cityName, description, temp, feelsLike, humidity); } } catch (IOException e) { log.error("调用天气API异常", e); return "查询天气服务时发生网络错误。"; } catch (Exception e) { log.error("处理天气请求参数异常", e); return "处理请求参数时发生错误。"; } } }

4.3 工具注册与管理

我们需要一个中心化的注册表来管理所有可用工具。

// file: src/main/java/com/example/javaagentdemo/service/tool/ToolRegistry.java package com.example.javaagentdemo.service.tool; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; import java.util.HashMap; import java.util.List; import java.util.Map; @Component public class ToolRegistry { private final Map<String, Tool> toolMap = new HashMap<>(); private final List<Tool> tools; // 通过构造器注入所有Tool Bean public ToolRegistry(List<Tool> tools) { this.tools = tools; } @PostConstruct public void init() { for (Tool tool : tools) { toolMap.put(tool.getName(), tool); System.out.println("已注册工具: " + tool.getName() + " - " + tool.getDescription()); } } public Tool getTool(String name) { return toolMap.get(name); } public Map<String, Tool> getAllTools() { return new HashMap<>(toolMap); } /** * 获取所有工具的“描述”,用于构建给大模型的系统提示。 */ public String getToolsDescriptionForPrompt() { StringBuilder sb = new StringBuilder(); sb.append("你可以使用以下工具:\n"); for (Tool tool : tools) { sb.append("- ").append(tool.getName()).append(": ").append(tool.getDescription()) .append(" 参数要求: ").append(tool.getParametersSchema().toString()).append("\n"); } sb.append("当你需要调用工具时,请严格按照工具要求的参数格式,以JSON对象形式提供。"); return sb.toString(); } }

5. 核心模块三:智能体调度引擎

这是项目的“中枢神经系统”,负责协调大模型思考和工具调用。

5.1 设计智能体工作流

一个简化的智能体单次循环流程如下:

  1. 接收用户输入,结合对话历史,形成完整的上下文。
  2. 构建系统提示:包含工具描述、执行格式要求等。
  3. 调用大模型:让模型根据上下文决定是直接回答还是调用工具。
  4. 解析模型响应:判断响应是自然语言还是工具调用指令。
  5. 执行工具:如果是指令,则调用对应工具并获取结果。
  6. 组织回复:将工具结果返回给用户,或作为新的上下文继续循环。

5.2 实现核心调度服务

// file: src/main/java/com/example/javaagentdemo/service/AgentCoreService.java package com.example.javaagentdemo.service; import com.example.javaagentdemo.dto.ChatMessage; import com.example.javaagentdemo.service.llm.LLMService; import com.example.javaagentdemo.service.tool.Tool; import com.example.javaagentdemo.service.tool.ToolRegistry; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.List; @Service @Slf4j public class AgentCoreService { @Autowired private LLMService llmService; @Autowired private ToolRegistry toolRegistry; @Autowired private ObjectMapper objectMapper; // 系统提示词,定义了智能体的角色和能力 private static final String SYSTEM_PROMPT_TEMPLATE = """ 你是一个有帮助的AI助手,可以调用工具来解决问题。 %s 请根据用户的问题,决定是直接回答还是调用工具。 如果你决定调用工具,你的响应必须是严格的JSON格式,且只包含以下两个字段: 1. `action`: 固定为 `tool_call` 2. `tool`: 工具的名称 3. `arguments`: 一个JSON对象,包含调用该工具所需的参数 例如,调用天气工具:{"action": "tool_call", "tool": "get_weather", "arguments": {"city": "北京"}} 如果你不需要调用工具,请直接给出自然语言回答。 """; /** * 处理一轮用户对话 * @param userInput 用户输入 * @param conversationHistory 对话历史(可为空) * @return 智能体的回复 */ public String processUserInput(String userInput, List<ChatMessage> conversationHistory) { // 1. 准备对话历史 List<ChatMessage> messages = new ArrayList<>(); // 添加系统提示 String systemPrompt = String.format(SYSTEM_PROMPT_TEMPLATE, toolRegistry.getToolsDescriptionForPrompt()); messages.add(new ChatMessage("system", systemPrompt)); // 添加上下文历史 if (conversationHistory != null) { messages.addAll(conversationHistory); } // 添加当前用户输入 messages.add(new ChatMessage("user", userInput)); // 2. 调用大模型 String llmResponse = llmService.chatCompletion(messages, 0.2); // 低温度,减少随机性 // 3. 解析响应,判断是否为工具调用 try { JsonNode responseJson = objectMapper.readTree(llmResponse); if (responseJson.has("action") && "tool_call".equals(responseJson.get("action").asText())) { // 是工具调用 String toolName = responseJson.get("tool").asText(); JsonNode arguments = responseJson.get("arguments"); return handleToolCall(toolName, arguments.toString()); } } catch (Exception e) { // 响应不是JSON,或解析失败,视为自然语言回复 log.debug("LLM响应不是工具调用指令,按自然语言处理: {}", llmResponse); } // 4. 如果是自然语言回复,直接返回 return llmResponse; } private String handleToolCall(String toolName, String argumentsJson) { Tool tool = toolRegistry.getTool(toolName); if (tool == null) { return String.format("错误:未知的工具 '%s'。", toolName); } try { log.info("调用工具: {}, 参数: {}", toolName, argumentsJson); String toolResult = tool.execute(argumentsJson); log.info("工具执行结果: {}", toolResult); // 这里可以将工具结果再次喂给LLM,让LLM组织成对用户友好的语言。 // 简化处理:直接返回结果 return "工具执行完成:\n" + toolResult; } catch (Exception e) { log.error("执行工具 {} 时发生异常", toolName, e); return String.format("调用工具 '%s' 时发生错误:%s", toolName, e.getMessage()); } } }

5.3 提供REST API接口

最后,我们通过一个简单的Controller暴露服务。

// file: src/main/java/com/example/javaagentdemo/controller/AgentController.java package com.example.javaagentdemo.controller; import com.example.javaagentdemo.dto.AgentRequest; import com.example.javaagentdemo.dto.AgentResponse; import com.example.javaagentdemo.service.AgentCoreService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api/agent") @Slf4j public class AgentController { @Autowired private AgentCoreService agentCoreService; @PostMapping("/chat") public AgentResponse chat(@RequestBody AgentRequest request) { log.info("收到用户请求: {}", request.getMessage()); // 简化处理:每次对话独立,不维护历史。实际项目应使用session或userId维护对话历史。 String response = agentCoreService.processUserInput(request.getMessage(), null); log.info("智能体回复: {}", response); return new AgentResponse(response); } } // file: src/main/java/com/example/javaagentdemo/dto/AgentRequest.java package com.example.javaagentdemo.dto; import lombok.Data; @Data public class AgentRequest { private String message; private String sessionId; // 可用于关联对话历史 } // file: src/main/java/com/example/javaagentdemo/dto/AgentResponse.java package com.example.javaagentdemo.dto; import lombok.AllArgsConstructor; import lombok.Data; @Data @AllArgsConstructor public class AgentResponse { private String reply; }

6. 运行、测试与效果演示

6.1 启动项目

确保Redis服务已启动(如果使用了Redis存储会话)。在项目根目录下运行:

mvn spring-boot:run # 或 java -jar target/java-agent-demo-0.0.1-SNAPSHOT.jar

应用默认会在http://localhost:8080启动。

6.2 使用API测试工具进行测试

使用curl、Postman 或任何你喜欢的HTTP客户端进行测试。

请求示例

curl -X POST http://localhost:8080/api/agent/chat \ -H "Content-Type: application/json" \ -d '{"message": "今天北京天气怎么样?"}'

预期响应

{ "reply": "工具执行完成:\n城市:Beijing\n天气状况:晴\n当前温度:22.5°C\n体感温度:21.0°C\n湿度:45%" }

请求示例(无需工具)

curl -X POST http://localhost:8080/api/agent/chat \ -H "Content-Type: application/json" \ -d '{"message": "你好,请介绍一下你自己。"}'

预期响应

{ "reply": "你好!我是一个AI助手,可以通过调用工具来帮助你查询天气、进行计算或管理任务等。有什么我可以帮你的吗?" }

6.3 项目效果与扩展方向

至此,一个具备基础能力的Java智能体项目就完成了。它能够:

  1. 理解用户意图。
  2. 在需要时自主调用预定义的工具(如天气查询)。
  3. 返回工具执行结果。

你可以在此基础上进行大量扩展:

  • 增加更多工具:如计算器、日历管理、邮件发送、数据库查询等。
  • 实现对话记忆:利用Redis存储ChatMessage历史,实现多轮对话。
  • 复杂任务分解:让大模型将复杂问题拆解成多个工具调用步骤,并循环执行。
  • 前端界面:使用Vue/React构建一个简单的聊天界面。
  • 部署上线:使用Docker容器化,部署到云服务器。

7. 常见问题与排查思路

在开发过程中,你可能会遇到以下典型问题:

问题现象可能原因排查步骤与解决方案
启动报错:Failed to configure a DataSource引入了数据库依赖但未配置数据源。检查pom.xml中是否有多余的spring-boot-starter-data-jpa等依赖。如果不需要,可排除或添加spring.datasource.url配置。
调用OpenAI API超时或连接失败网络问题、API Key无效、服务地址错误。1. 检查网络连通性。
2. 验证API Key是否正确且有余额。
3. 检查application.yml中的openai.api.url配置。
4. 在OkHttpClient中配置合理的超时时间。
智能体不调用工具,总是直接回复系统提示词(Prompt)设计不佳,或模型温度参数过高。1. 检查SYSTEM_PROMPT_TEMPLATE,确保指令清晰,要求模型以JSON格式回复。
2. 降低llmService.chatCompletion调用时的temperature值(如设为0.1)。
3. 在Prompt中提供更详细、更强制性的工具调用示例。
工具调用参数解析失败大模型返回的JSON格式不正确,或参数类型不匹配。1. 打印出大模型的原始响应llmResponse,检查其格式。
2. 在handleToolCall方法中增加更健壮的JSON解析和校验。
3. 在工具Schema中更精确地定义参数类型和描述。
Lombok注解不生效IDE未安装Lombok插件,或编译环境不支持。1. 在IntelliJ IDEA中安装Lombok插件并启用注解处理(Settings -> Build -> Compiler -> Annotation Processors)。
2. 确保pom.xml中Lombok依赖范围是providedcompile
3. 如果使用其他IDE或构建工具,请参考Lombok官方文档进行配置。
Redis连接失败Redis服务未启动,或配置的主机/端口/密码错误。1. 运行redis-cli ping确认Redis服务是否运行。
2. 检查application.yml中的spring.data.redis配置项。
3. 如果不需要会话记忆功能,可以先注释掉相关代码和依赖。

8. 项目总结与进阶思考

通过这个项目,我们完成了一个Java智能体从零到一的搭建。它不仅是一个简单的API调用封装,而是初步具备了感知-思考-行动的智能体核心循环。

回顾核心收获

  1. 架构清晰:分离了LLM集成层、工具层和核心调度层,符合单一职责原则,易于维护和扩展。
  2. 可扩展性强:新的工具只需实现Tool接口并注册到ToolRegistry即可被智能体使用。
  3. 工程化实践:使用了Spring Boot的依赖注入、配置管理,以及面向接口的编程,代码质量更高。

下一步可以深入的方向

  • 记忆与状态管理:实现真正的多轮对话,需要将会话历史、工具调用结果等状态持久化。可以考虑使用向量数据库存储历史,实现长期记忆。
  • 流式响应:当前是同步阻塞调用,可以改造为SSE或WebSocket,实现打字机式的流式输出,体验更佳。
  • 工具编排与工作流:当任务需要多个工具按顺序或条件执行时,需要引入工作流引擎(如Flowable、Camunda)或自定义状态机来管理复杂的任务流。
  • 评估与监控:为智能体的回答质量、工具调用成功率添加监控和评估指标,这对于迭代优化至关重要。
  • 对接国内大模型:将LLMService的实现替换为文心一言、通义千问、智谱GLM等国内模型的API调用,注意调整请求和响应的数据格式。

这个项目作为学习起点,已经涵盖了智能体开发的核心模式。你可以将其作为毕业设计、个人作品集的一部分,或者在此基础上不断迭代,探索更复杂的AI应用场景。

← 返回列表