简介:本资源是一个基于Spring AI与Langchain4j构建的旅游行程规划智能体完整工程,面向Java后端开发者、AI应用实践者及高校课程设计学习者,解决个性化旅游方案生成、自然语言交互与多阶段行程动态优化等核心问题。压缩包共69个文件,涵盖12个Java服务模块(含Agent编排、LLM调用与工具链集成)、15个Vue前端组件(行程可视化、偏好配置与实时对话界面)、5个JSON配置与示例数据、以及UML类图/用例图/部署图等5张系统设计图,辅以项目文档(PDF/DOCX)与LICENSE说明,整体大小为5.74MB。已有113人学习下载,提供从Prompt工程、RAG增强到天气服务对接的端到端实现,结构清晰、模块解耦,适合快速理解AI智能体在垂直场景中的落地路径与工程组织方式。
1. 项目概述:当大语言模型遇上行程规划
最近在捣鼓一个挺有意思的玩意儿:一个能帮你规划旅游行程的智能体。听起来是不是有点像升级版的“马蜂窝”或者“穷游行程助手”?但底层逻辑完全不同。传统的行程规划工具,要么是基于规则引擎(比如“第一天必须去A景点,因为它在B景点附近”),要么是简单的信息聚合与模板填充,灵活性和个性化程度有限。
而这个项目,核心是让大语言模型(LLM)来担任那个“见多识广的旅行策划师”。我们不再依赖死板的规则,而是利用LLM对自然语言的深刻理解、丰富的世界知识以及一定的推理能力,让它根据用户模糊的、口语化的需求(比如“我想带爸妈去个暖和的地方玩5天,不要太累,预算中等”),生成一份结构清晰、合理可行的行程计划。
为什么用“智能体”这个词?因为它不是一次性的问答。一个完整的智能体(Agent)应该具备感知(理解用户输入)、规划(拆解任务、调用工具)、行动(执行具体操作,如查询信息、计算)和反思(评估结果、调整策略)的能力。我们的旅游行程规划,正是一个典型的智能体应用场景:它需要理解用户意图,规划出“查天气、找景点、算距离、排时间”等一系列子任务,并调用相应的工具(或能力)来逐一完成,最终整合成一份报告。
技术栈上,我选择了Spring AI和Langchain4j这两个目前Java生态里最炙手可热的AI应用框架来搭建。Spring AI提供了与Spring Boot无缝集成的AI能力,让注入一个“ChatClient”就像注入一个“DataSource”一样简单;而Langchain4j则提供了丰富的、用于构建智能体的“乐高积木”,比如工具调用(Tool)、记忆(Memory)、提示词模板(Prompt Template)等。两者的结合,能让开发者更专注于业务逻辑,而非底层API的繁琐调用。
这个项目适合谁呢?如果你是一名Java后端开发者,对AI应用开发感兴趣,想了解如何将LLM能力融入传统业务系统;或者你是一名产品经理、创业者,在构思基于AI的下一代旅行产品,那么通过这个项目的拆解,你能获得从技术选型、架构设计到具体实现的全景视图。即使你对AI底层算法不甚了解,也能跟着一步步搭建出一个可运行、可演示的智能体原型。
2. 核心架构与组件选型解析
2.1 为什么是Spring AI + Langchain4j?
在Java世界里构建AI应用,框架选择不少,比如直接调用各大厂商的SDK,或者使用早期的LangChain Java版。但Spring AI和Langchain4j的组合,在当前时间点显得尤为合理。
Spring AI的核心价值在于“标准化”和“集成”。它定义了一套统一的AI模型访问接口(如ChatClient,EmbeddingClient,ImageClient),无论底层是OpenAI的GPT、Anthropic的Claude,还是阿里云的通义千问、智谱AI的GLM,你都可以通过更换一个spring-ai-*starter依赖和配置项来切换,代码几乎不用改动。这对于需要保障服务稳定性、可能面临模型供应商切换的场景至关重要。此外,它天然融入Spring生态,依赖注入、配置管理、Actuator监控等都开箱即用,大大降低了运维复杂度。
Langchain4j则胜在“工具链”的丰富性。它专为构建复杂的、多步骤的AI智能体而生。虽然Spring AI也提供了基础的Prompt模板和函数调用支持,但Langchain4j在以下方面更加强大:
- 工具(Tools):可以非常方便地将任何Java方法封装成AI可调用的工具,并自动生成描述供LLM理解。我们的行程规划需要查询天气、搜索景点、计算距离,这些都可以封装成独立的Tool。
- 记忆(Memory):支持对话历史、实体记忆等多种记忆方式,能让智能体在多轮对话中记住关键信息(比如用户说过对海鲜过敏)。
- 链(Chains):虽然本项目以智能体为主,但Langchain4j的链式调用对于标准化处理流程(如:提取用户信息 -> 生成搜索关键词 -> 执行搜索 -> 格式化输出)也非常有用。
- 与Spring AI的兼容性:Langchain4j最新版本已经能够直接使用Spring AI提供的
ChatClient等作为其底层LLM,实现了强强联合。
因此,我们的架构基调定为:以Spring Boot为底座,使用Spring AI管理模型连接与核心AI能力,使用Langchain4j构建智能体的高级逻辑(工具、记忆、规划)。这样既能享受Spring生态的便利,又能利用Langchain4j强大的智能体构建能力。
2.2 智能体工作流设计
一个旅游行程规划智能体,不能只是让LLM凭空想象。它需要可靠的数据和计算能力作为支撑。我们设计的工作流如下:
- 需求解析与澄清:用户输入自然语言需求。智能体首先调用LLM,提取结构化信息,如:目的地、出行人数、出行日期、天数、预算范围、兴趣标签(美食、古迹、购物等)、特殊要求(带老人、孩子等)。如果信息不足,智能体会主动发起提问澄清。
- 信息获取与验证:根据解析出的结构化信息,智能体规划并执行一系列工具调用:
- 天气查询工具:获取目标日期、目的地的天气情况,这对户外活动安排至关重要。
- 景点/POI搜索工具:根据兴趣标签,从本地数据库或第三方API(如高德、百度地图的POI接口)获取景点列表、开放时间、门票价格、评分等信息。
- 地理与路线工具:计算景点之间的通勤距离与时间(步行、驾车、公共交通),这是合理排程的基础。
- 预算估算工具:结合酒店、餐饮、门票的均价数据,对总花费进行粗略估算。
- 行程编排与优化:LLM作为“总调度师”,拿到所有工具返回的原始数据后,进行综合编排。它需要考虑:
- 时间约束:每天合理的活动时长(如上午、下午、晚上各1-2个主要活动)。
- 地理邻近:将地理位置靠近的景点安排在同一个半天。
- 体力分配:劳逸结合,避免连续安排高强度活动。
- 兴趣匹配:优先安排用户高兴趣标签的活动。
- 突发情况:预留一定的缓冲时间和备选方案。
- 输出与交互:生成一份格式友好、细节丰富的行程单,通常以Markdown格式输出,包含每日概览、时间点、活动详情、费用预估、注意事项等。并可以支持后续交互,如“把第二天下午的博物馆换成科技馆”、“预算再压缩10%”等调整。
注意:这里的关键是让LLM负责“思考”和“决策”,而让工具负责提供“事实”和“计算”。LLM可能会犯“事实性错误”(比如记错某个景点的开放时间),但工具提供的数据是准确的。LLM也可能不擅长复杂计算(比如最优路径规划),但可以调用专门的路线计算工具。这种“大脑”与“手脚”的分工,是构建可靠AI智能体的核心模式。
3. 环境搭建与核心依赖配置
3.1 初始化Spring Boot项目
使用你熟悉的IDE(如IntelliJ IDEA)或 Spring Initializr 创建项目。核心依赖选择:
- Spring Boot 3.x:建议使用3.2.x或以上版本,对Java 17+有更好的支持。
- Spring AI:选择对应的BOM(Bill of Materials)来管理版本。在
pom.xml中,首先引入Spring AI的BOM。
<dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-bom</artifactId> <version>0.8.1</version> <!-- 请使用最新稳定版 --> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>然后,添加你选定的AI模型供应商的starter。例如,我们使用OpenAI(GPT模型)作为LLM引擎:
<dependencies> <!-- Spring Boot Web (用于提供REST API) --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- Spring AI OpenAI --> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-openai-spring-boot-starter</artifactId> </dependency> <!-- Langchain4j 核心 --> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j</artifactId> <version>0.31.0</version> <!-- 请使用最新稳定版 --> </dependency> <!-- Langchain4j 与 Spring AI 集成 --> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j-spring-ai</artifactId> <version>0.31.0</version> </dependency> <!-- 其他工具类依赖,如JSON处理、HTTP客户端等 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-json</artifactId> </dependency> <!-- 示例中用于调用外部API --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency> </dependencies>如果你想使用阿里云的通义千问,则替换spring-ai-openai-starter为spring-ai-alibaba-ai-spring-boot-starter,并配置相应的AK/SK。
3.2 关键配置详解
在application.yml中,我们需要配置AI模型连接和基础参数。
spring: ai: openai: api-key: ${OPENAI_API_KEY:你的OpenAI-API-KEY} # 强烈建议通过环境变量注入 chat: options: model: gpt-4o-mini # 根据成本和性能选择,gpt-4-turbo, gpt-4o等 temperature: 0.2 # 温度值调低,让行程规划更稳定、可重复 max-tokens: 4000 # 根据输出行程的详细程度调整 # 自定义配置:第三方服务(示例) travel: amap: api-key: ${AMAP_API_KEY} # 高德地图Web服务API Key,用于地理编码和路径规划 weather: api-key: ${WEATHER_API_KEY} # 和风天气等服务的API Key配置要点解析:
- API密钥安全:绝对不要将API密钥硬编码在代码或配置文件中提交到Git。使用环境变量(
${VAR_NAME})或配置中心管理。 - 模型选择:
gpt-4o-mini或gpt-4-turbo在成本、速度和能力上比较平衡,适合此类任务。temperature设置为较低值(如0.1-0.3),可以减少LLM的随机性,让生成的行程更稳定可靠。 - Token限制:行程规划输出可能较长,需要预留足够的
max-tokens。同时,输入(用户需求+工具返回数据)也可能很长,需注意总token数不要超过模型上下文长度限制。
3.3 基础Bean配置与Langchain4j集成
我们需要配置一个Bean,将Spring AI的ChatClient桥接到Langchain4j的ChatLanguageModel接口,以便Langchain4j使用我们配置的模型。
import dev.langchain4j.model.chat.ChatLanguageModel; import dev.langchain4j.model.spring.AiPlatformChatModel; import org.springframework.ai.chat.ChatClient; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class AiConfig { @Bean public ChatLanguageModel chatLanguageModel(ChatClient chatClient) { // 将Spring AI的ChatClient适配为Langchain4j的ChatLanguageModel return new AiPlatformChatModel(chatClient); } }这样,我们就可以在后续的Service中注入ChatLanguageModel,并使用Langchain4j的全套功能了。
4. 核心工具(Tools)的实现与封装
智能体的“手脚”就是各种工具。我们将行程规划中需要的几个关键能力封装成独立的Tool。
4.1 天气查询工具
这个工具调用外部天气API,获取指定城市和日期的天气预报。
import dev.langchain4j.agent.tool.Tool; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import org.springframework.web.reactive.function.client.WebClient; import reactor.core.publisher.Mono; import java.time.LocalDate; @Component public class WeatherTool { private final WebClient webClient; @Value("${travel.weather.api-key}") private String apiKey; public WeatherTool(WebClient.Builder webClientBuilder) { this.webClient = webClientBuilder.baseUrl("https://api.weather.com/v3").build(); } @Tool("根据城市名称和日期查询天气预报信息。返回天气状况、最高最低温度、降水概率等。") public String getWeatherForecast(String cityName, LocalDate date) { // 构建请求逻辑,这里以伪代码和简化的响应处理为例 // 实际应调用和风天气、OpenWeatherMap等服务的API String response = webClient.get() .uri(uriBuilder -> uriBuilder .path("/weather/forecast/daily") .queryParam("city", cityName) .queryParam("date", date.toString()) .queryParam("key", apiKey) .build()) .retrieve() .bodyToMono(String.class) .block(); // 生产环境建议使用非阻塞式,这里简化 // 解析JSON响应,提取关键信息并格式化为自然语言描述 // 例如:解析出 weatherText, tempMax, tempMin, precipProbability // return String.format("%s,%s日天气:%s,气温%d°C到%d°C,降水概率%d%%。", cityName, date, weatherText, tempMin, tempMax, precipProb); // 模拟返回 return String.format("%s在%s的天气预计为晴转多云,最高气温28°C,最低气温20°C,降水概率10%%。", cityName, date); } }关键点:
@Tool注解是Langchain4j的核心,它会把该方法描述(注解中的字符串)注入给LLM,让LLM知道在什么情况下调用这个工具。- 工具方法应尽量设计为纯函数,输入输出明确。返回的字符串应简洁、信息量大,便于LLM理解并用于后续推理。
- 生产环境中,需要考虑API调用的频率限制、失败重试、降级策略(如返回缓存数据或默认值)。
4.2 景点搜索工具
这个工具从数据源(可以是本地数据库、Elasticsearch或第三方POI API)搜索景点。
import dev.langchain4j.agent.tool.Tool; import org.springframework.stereotype.Component; import java.util.List; import java.util.stream.Collectors; @Component public class PoiSearchTool { // 假设我们有一个本地的景点信息服务层 private final PointOfInterestService poiService; public PoiSearchTool(PointOfInterestService poiService) { this.poiService = poiService; } @Tool("根据城市和兴趣标签(如'博物馆'、'自然风光'、'美食'、'亲子')搜索景点。返回景点名称、简介、建议游玩时长、门票价格和评分。") public String searchPointsOfInterest(String city, List<String> tags) { List<PointOfInterest> pois = poiService.searchByCityAndTags(city, tags); if (pois.isEmpty()) { return String.format("在%s未找到标签为%s的景点。", city, tags); } // 将POI列表格式化为LLM易于处理的文本 return pois.stream() .map(poi -> String.format("- 【%s】%s。建议游玩:%s小时。门票:%s。评分:%s/5。", poi.getName(), poi.getDescription(), poi.getSuggestedDuration(), poi.getTicketPrice(), poi.getRating())) .collect(Collectors.joining("\n")); } }实操心得:POI数据的质量直接决定行程的吸引力。初期可以爬取公开数据或使用免费API,但要做好数据清洗(去重、纠正地址、补充开放时间)。后期可以考虑接入商业化的POI数据服务。
4.3 路线规划与时间估算工具
这是行程合理性的核心。我们调用地图服务的路径规划API。
import dev.langchain4j.agent.tool.Tool; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import org.springframework.web.reactive.function.client.WebClient; @Component public class RoutingTool { private final WebClient webClient; @Value("${travel.amap.api-key}") private String apiKey; public RoutingTool(WebClient.Builder webClientBuilder) { this.webClient = webClientBuilder.baseUrl("https://restapi.amap.com/v3").build(); } @Tool("计算两个地点之间的通勤距离和时间。地点可以是具体地址或景点名称。返回驾驶距离(公里)、驾驶时间(分钟)和公共交通时间(分钟,估算)。") public String calculateRoute(String origin, String destination) { // 1. 地理编码:将地址转换为经纬度 (这里省略,假设已有经纬度或直接使用地名搜索) // 2. 路径规划 // 调用高德地图路径规划API String response = webClient.get() .uri(uriBuilder -> uriBuilder .path("/direction/driving") .queryParam("origin", origin) .queryParam("destination", destination) .queryParam("key", apiKey) .build()) .retrieve() .bodyToMono(String.class) .block(); // 解析JSON,提取 distance(米)和 duration(秒) // 简化为模拟数据 int drivingDistanceMeters = 5000; // 5公里 int drivingDurationSeconds = 1200; // 20分钟 // 公共交通时间通常为驾驶时间的1.5-3倍,这里简单估算 int transitDurationSeconds = (int)(drivingDurationSeconds * 2.5); return String.format("从【%s】到【%s】。驾车:距离%.1f公里,约%d分钟。公共交通:约%d分钟。", origin, destination, drivingDistanceMeters / 1000.0, drivingDurationSeconds / 60, transitDurationSeconds / 60); } }注意:地图API通常有每日调用限额。在智能体规划行程时,可能会频繁调用此工具(为多个景点对计算距离)。因此,需要实现一个简单的缓存层,对相同的
(origin, destination)对缓存计算结果,有效期内直接返回,避免不必要的API调用和费用。
5. 智能体(Agent)的组装与提示词工程
有了工具,接下来就是组装大脑——智能体。
5.1 构建智能体并注入工具
我们使用Langchain4j的AiServices来便捷地创建一个智能体。
import dev.langchain4j.service.SystemMessage; import dev.langchain4j.service.UserMessage; import dev.langchain4j.service.V; import dev.langchain4j.service.spring.AiService; // 定义智能体接口 interface TravelPlanningAgent { @SystemMessage(""" 你是一个专业的、经验丰富的旅行规划师。你的任务是根据用户的需求,为他们制定详细、可行、个性化的旅行行程。 在规划时,请务必遵循以下原则: 1. **充分利用工具**:你必须使用提供的工具来获取准确的天气、景点信息和路线时间,不得凭空捏造数据。 2. **合理性与舒适度**:每天安排3-4个主要活动为宜,劳逸结合。景点间移动要预留充足时间。 3. **个性化**:紧密围绕用户的兴趣、预算和特殊要求(如带老人、孩子)进行规划。 4. **输出格式**:最终行程请以清晰的Markdown格式输出,包含每日概览、时间线、活动详情、费用预估和温馨提示。 规划步骤建议: 1. 解析用户需求,明确关键要素(目的地、时间、人数、兴趣、预算、要求)。 2. 查询目的地天气。 3. 根据兴趣搜索景点。 4. 基于景点位置和开放时间,结合路线工具计算的时间,进行每日编排。 5. 生成最终行程。 如果用户需求信息不足,请主动、友好地提问澄清。 """) String planTrip(@UserMessage String userRequest); } @Service public class TravelAgentService { private final TravelPlanningAgent agent; public TravelAgentService(ChatLanguageModel chatLanguageModel, WeatherTool weatherTool, PoiSearchTool poiSearchTool, RoutingTool routingTool) { // 使用AiServices创建智能体实例,并绑定工具和模型 this.agent = AiServices.builder(TravelPlanningAgent.class) .chatLanguageModel(chatLanguageModel) .tools(weatherTool, poiSearchTool, routingTool) // 注入所有工具 .build(); } public String invokePlanning(String userRequest) { return agent.planTrip(userRequest); } }代码解析:
@SystemMessage:定义了智能体的“角色”和“行为准则”。这是提示词工程的关键,直接决定了智能体的输出质量和风格。我们在这里详细规定了它的身份、原则、步骤和输出格式。AiServices.builder(...):这是Langchain4j的核心API,它自动将工具、模型和接口绑定起来。当LLM在执行planTrip方法时,如果认为需要调用工具,它会自动生成相应的工具调用请求,执行工具后,再将结果返回给LLM继续推理。@V注解:如果需要将复杂对象作为参数,可以使用@V注解来指定映射关系,本例中简化使用了字符串。
5.2 设计高效的系统提示词(System Prompt)
系统提示词是智能体的“灵魂”。上面示例中的提示词已经包含了很多要点,这里再深入拆解一下设计技巧:
- 明确角色与目标:“你是一个专业的、经验丰富的旅行规划师”——开门见山,定下基调。
- 规定强制性动作:“你必须使用提供的工具...”——这是确保输出基于事实而非幻觉的关键指令。
- 灌输领域知识:“每天安排3-4个主要活动为宜,劳逸结合。”——将人类专家的经验编码进去。
- 结构化输出要求:“以清晰的Markdown格式输出,包含每日概览、时间线...”——让输出规整,便于前端渲染或用户阅读。
- 提供思维链(Chain-of-Thought)指引:“规划步骤建议:1. 解析... 2. 查询... 3. 搜索... 4. 编排... 5. 生成...”——引导LLM进行有序的、分步的思考,这能显著提升复杂任务的成功率。
- 处理边界情况:“如果用户需求信息不足,请主动、友好地提问澄清。”——让智能体具备交互能力。
实操心得:提示词的优化是一个迭代过程。初期可以简单些,然后通过大量真实用户query测试,观察智能体在哪里“犯傻”(比如同一天安排两个距离很远的景点、忽略了用户的预算限制),然后针对性修改和强化提示词。可以将常见的“错误模式”和“正确示例”以Few-Shot的方式加入到提示词中,效果更佳。
6. 服务层与API暴露
智能体组装好后,我们需要通过一个REST API来暴露它的能力。
import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api/travel") public class TravelPlanningController { private final TravelAgentService travelAgentService; public TravelPlanningController(TravelAgentService travelAgentService) { this.travelAgentService = travelAgentService; } @PostMapping("/plan") public ResponseEntity<TravelPlanResponse> createTravelPlan(@RequestBody TravelPlanRequest request) { // 1. 参数基础校验 if (request == null || StringUtils.isBlank(request.getUserRequest())) { return ResponseEntity.badRequest().body(new TravelPlanResponse("用户请求不能为空")); } try { // 2. 调用智能体服务 String planMarkdown = travelAgentService.invokePlanning(request.getUserRequest()); // 3. 构造响应 TravelPlanResponse response = new TravelPlanResponse(); response.setStatus("success"); response.setPlanMarkdown(planMarkdown); response.setGeneratedAt(LocalDateTime.now()); return ResponseEntity.ok(response); } catch (Exception e) { // 4. 异常处理:可能是LLM API超时、工具调用失败等 log.error("行程规划失败,用户请求:{}", request.getUserRequest(), e); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) .body(new TravelPlanResponse("行程规划服务暂时不可用,请稍后重试。")); } } } // 简单的请求响应对象 @Data class TravelPlanRequest { private String userRequest; // 未来可以扩展更多结构化字段,如userId用于记忆 } @Data class TravelPlanResponse { private String status; // "success", "error" private String planMarkdown; private String errorMessage; private LocalDateTime generatedAt; public TravelPlanResponse(String errorMessage) { this.status = "error"; this.errorMessage = errorMessage; } }注意事项:
- 异步处理:行程规划可能耗时较长(多次LLM调用+多次工具调用)。对于生产环境,应考虑将请求异步化,立即返回一个任务ID,然后通过WebSocket或轮询接口让客户端获取结果。
- 限流与降级:AI服务调用成本较高且可能有速率限制。需要在API网关或Controller层添加限流(如使用Resilience4j或Sentinel),防止恶意或过高频率的调用。
- 上下文管理:目前的实现是无状态的。为了实现多轮对话(例如用户说“把第二天下午的行程改一下”),需要引入对话
Memory。可以为每个会话创建一个独立的智能体实例,并关联一个持久化的ChatMemory(如使用Redis存储),在后续请求中传入ConversationId来恢复上下文。
7. 效果测试、优化与问题排查
7.1 测试用例设计
如何验证智能体是否工作良好?需要设计多维度的测试用例:
- 功能正确性:
- 输入:“帮我规划一个北京3日游,喜欢历史和美食。”
- 验证点:输出是否包含故宫、天坛等历史景点?是否推荐了烤鸭、炸酱面等美食?行程天数是否为3天?
- 工具调用验证:
- 输入:“下周末杭州天气如何?有什么适合孩子的室内景点?”
- 验证点:日志中是否出现了对
WeatherTool和PoiSearchTool(带“亲子”标签)的调用?
- 约束条件满足:
- 输入:“预算5000元,两人上海玩4天。”
- 验证点:生成的行程末尾是否有大致的费用估算?总费用是否在预算附近?
- 异常与边界处理:
- 输入:“明天去火星旅行。”
- 验证点:智能体是生成了一个荒谬的行程,还是合理地回复“无法查询到火星的旅行信息”或要求用户提供有效目的地?
- 输入:(空输入或乱码)
- 验证点:服务是否返回了清晰的错误提示,而非崩溃?
7.2 常见问题与优化策略
在实际开发和测试中,你可能会遇到以下典型问题:
| 问题现象 | 可能原因 | 排查与优化策略 |
|---|---|---|
| 智能体不调用工具,全靠LLM幻想 | 系统提示词中“必须使用工具”的指令不够强;工具描述不清晰。 | 1. 强化系统提示词,例如:“严禁凭空猜测数据,所有天气、景点、距离信息必须通过调用相应工具获得。” 2. 检查工具方法上的 @Tool注解描述,确保清晰、准确,包含关键参数说明。 |
| 工具调用顺序混乱或多余 | LLM的规划能力不足。 | 1. 在系统提示词中提供更详细的“思维链”指引,明确步骤顺序。 2. 考虑使用更高级的“规划智能体”模式,如使用Langchain4j的 PlanAndExecuteAgent,或将复杂规划拆分成多个按顺序执行的小智能体。 |
| 生成的行程时间安排不合理 | LLM对时间、距离的感知能力弱;路线工具返回的数据未被有效利用。 | 1. 在提示词中强调“结合路线工具计算出的具体时间进行安排”。 2. 让工具返回的数据更结构化、更突出(例如“驾车需45分钟”),便于LLM捕捉。 3. 在后处理阶段,可以增加一个“行程合理性校验”环节,用规则简单检查是否存在明显的时间冲突。 |
| 处理长上下文时性能差、费用高 | 用户需求复杂或工具返回数据量大,导致token消耗剧增。 | 1.精简工具输出:让工具只返回最核心的信息,去掉冗余描述。 2.摘要与过滤:在将大量POI数据喂给LLM前,先让另一个LLM调用(或使用简单算法)根据相关性进行筛选和摘要。 3.使用更大上下文窗口的模型,如GPT-4 Turbo(128K)。但成本需权衡。 |
| 多轮对话中忘记之前的信息 | 未启用或正确配置ChatMemory。 | 1. 为智能体配置ChatMemory,如MessageWindowChatMemory,并确保每次对话使用相同的memoryId。2. 在提示词中提醒智能体参考对话历史,例如:“以下是之前的对话历史:[历史]”。 |
7.3 引入记忆(Memory)实现多轮对话
要让智能体记住上下文,修改TravelAgentService:
import dev.langchain4j.memory.ChatMemory; import dev.langchain4j.memory.chat.MessageWindowChatMemory; @Service public class TravelAgentService { private final ChatLanguageModel chatLanguageModel; private final WeatherTool weatherTool; // ... 其他工具 // 用于存储不同会话的记忆 private final Map<String, ChatMemory> memoryStore = new ConcurrentHashMap<>(); public TravelAgentService(ChatLanguageModel chatLanguageModel, WeatherTool weatherTool /*, ... */) { this.chatLanguageModel = chatLanguageModel; this.weatherTool = weatherTool; // ... } public String chat(String sessionId, String userMessage) { // 获取或创建该会话的记忆 ChatMemory memory = memoryStore.computeIfAbsent(sessionId, id -> MessageWindowChatMemory.withMaxMessages(20)); // 保留最近20条消息 // 创建带有记忆的智能体 TravelPlanningAgent agent = AiServices.builder(TravelPlanningAgent.class) .chatLanguageModel(chatLanguageModel) .tools(weatherTool, poiSearchTool, routingTool) .chatMemory(memory) // 关键:注入记忆 .build(); return agent.planTrip(userMessage); // 这次调用会自动包含历史消息 } }这样,当同一sessionId的用户再次提问“那预算能不能再减少一点?”时,智能体就能回忆起之前生成的行程并进行调整了。
8. 项目总结与进阶思考
通过这个项目,我们完成了一个从需求理解、工具调用到行程生成的完整AI智能体闭环。它不再是简单的聊天机器人,而是一个能够执行复杂任务、具备一定自主性的数字助手。
我个人在实现过程中的几点深刻体会:
- 提示词的质量决定智能体的上限:模型和工具是“硬实力”,提示词是“软实力”。花在打磨提示词上的时间,往往比调试代码的回报率更高。多观察、多测试、多迭代。
- 工具的设计要“傻瓜化”:给LLM用的工具,其接口和返回应该尽可能简单、明确、无歧义。复杂的对象结构、嵌套的JSON,很容易让LLM解析出错。返回纯文本的自然语言描述,往往是最可靠的。
- 成本与延迟是工程化的核心挑战:每一次工具调用和LLM调用都意味着时间和金钱。需要精心设计流程,避免不必要的调用;引入缓存;对于非实时性要求高的场景,考虑异步处理和结果推送。
- 评估体系不可或缺:如何判断智能体生成的行程是“好”的?除了人工评审,需要建立自动化的评估基线,比如检查是否包含了用户提到的关键兴趣点、行程时间是否逻辑自洽、费用是否超预算等。这是将原型推进为产品的关键一步。
这个项目还可以从哪些方向扩展?
- 集成更丰富的工具:加入酒店预订查询工具、机票比价工具、当地活动/演出日历工具,让行程规划更全面。
- 实现多模态:利用Spring AI的
ImageClient,让智能体不仅能规划行程,还能根据行程生成分享海报,或者根据用户描述的风景偏好推荐匹配的景点图片。 - 个性化推荐与学习:引入向量数据库,存储用户的历史行程和反馈。当新用户提出需求时,可以寻找相似用户的历史行程作为参考,实现协同过滤式的推荐。
- 部署与监控:将智能体打包成Docker容器,使用Kubernetes部署。利用Spring Boot Actuator和Micrometer监控LLM API的调用延迟、成功率、Token消耗等核心指标,并设置告警。
构建AI智能体的过程,就像在教导一个拥有强大学习潜力但缺乏领域知识的孩子。我们通过清晰的指令(提示词)、可靠的工具和正确的反馈(记忆与评估)来引导它,最终让它成为一个能够独立解决特定领域问题的专业助手。希望这个基于Spring AI和Langchain4j的旅游行程规划智能体项目,能为你打开AI应用开发的大门。
本文还有配套的精品资源,点击获取