在实际旅行规划场景中,用户常常面临一个割裂的体验:行前,需要花费大量时间在多个App和网页间切换,手动收集景点信息、规划路线、预订票务;抵达目的地后,又要依赖离线地图、纸质攻略或临时搜索来应对实时变化,规划好的行程往往难以灵活执行。这种割裂感正是AI技术可以弥合的关键点。Passage AI作为一个AI旅行规划器,其核心价值在于提供了一个从“规划”到“执行”的无缝衔接体验。它不仅仅是一个行前生成行程单的工具,更是一个在你落地后能实时响应、提供现场指引的“活向导”。本文将深入探讨如何从零开始构建一个具备类似理念的AI旅行助手原型,涵盖从需求分析、技术选型(结合Spring AI与本地模型)、核心功能实现,到将其部署为可交互的智能体(AI Agent)的完整工程实践。无论你是对AI应用开发感兴趣的开发者,还是希望将大模型能力集成到具体业务场景中的产品经理,都能通过本文理解一个完整AI旅行Agent的构建链路与关键技术细节。
1. 理解AI旅行规划器的核心架构与工作流程
一个真正的AI旅行规划器,其智能体现在两个阶段:静态规划与动态引导。静态规划阶段,系统需要理解用户模糊的、非结构化的需求(如“我想去一个温暖的海边城市度过一个放松的周末”),并输出结构化的、包含时间、地点、活动的详细行程。动态引导阶段,系统需要基于用户的实时位置、时间、突发状况(如天气变化、景点关闭、交通延误)以及可能的兴趣转移,对原有规划进行动态调整并提供即时指引。
1.1 核心组件拆解
要实现上述流程,系统至少需要以下几个核心组件:
- 用户意图理解与交互模块:负责与用户进行多轮对话,澄清需求,收集约束条件(如预算、时间、兴趣偏好、饮食禁忌等)。这通常由一个对话管理模块驱动。
- 知识检索与信息整合模块:系统需要访问一个庞大的旅行知识库,包括景点信息、开放时间、门票价格、用户评价、交通方式、餐厅推荐等。这些数据可能来自内部数据库、第三方API(如地图、票务、点评)或网络爬虫。
- 行程规划与推理引擎:这是AI的核心。它需要综合用户意图和检索到的知识,运用空间、时间逻辑进行推理,生成一个合理、高效、个性化的行程计划。例如,它需要知道两个景点间的距离和交通时间,避免将相距甚远的景点安排在相邻时段。
- 实时上下文感知与决策模块:当用户进入“现场模式”后,系统需要接入实时数据源,如GPS定位、实时交通、天气API、景点人流信息等。基于这些实时上下文,它能判断当前行程是否可行,并提供替代方案或下一步行动建议。
- 多模态输出与交互接口:规划结果和现场指引不应只是文本。理想情况下,应结合地图可视化(显示路线)、语音播报(边走边听)、图片/AR(实景导航)等多种形式。对于原型,我们可以从图文混合的聊天界面开始。
1.2 技术栈选型:为什么选择Spring AI与本地模型
构建此类应用,技术选型至关重要。结合当前流行的AI工程实践,我们选择以下技术栈:
- 后端框架:Spring Boot + Spring AI:Spring Boot提供了成熟、高效的Web服务开发能力。Spring AI项目旨在简化Java应用中集成AI功能的过程,它提供了对多种大模型(OpenAI, Azure OpenAI, Ollama, 本地模型等)的统一抽象接口,极大地降低了集成复杂度。
- AI模型:本地部署的大语言模型(LLM):考虑到旅行数据可能涉及用户隐私(如位置、行程),以及需要7x24小时稳定、低延迟的响应,使用云端API可能存在成本、延迟和数据出境风险。因此,选择在本地或私有云部署一个开源模型是更稳妥的方案。例如,可以选择
Qwen2.5-7B-Instruct、Llama 3.2或DeepSeek-Coder等尺寸适中、性能优秀的模型,通过Ollama或vLLM等工具进行部署和管理。 - 向量数据库:用于知识检索:为了高效地从海量旅行知识中检索相关信息,我们需要将非结构化的文本数据(景点描述、游记)转换为向量(Embeddings),并存入向量数据库(如ChromaDB、Milvus、PgVector)。当用户提出需求时,先将问题转换为向量,然后从向量数据库中搜索最相关的知识片段,作为上下文提供给LLM,从而生成更准确、信息量更丰富的回答。这就是检索增强生成(RAG)的核心思想。
- Agent框架:要让AI具备动态规划和实时决策能力,需要将其构建成一个智能体(AI Agent)。Agent可以根据目标、使用工具(如搜索API、计算器、地图服务)、感知环境(用户输入、实时数据)并执行行动。LangChain4j或Spring AI自身的
AiClient和Function Calling功能可以用于构建简单的Agent。
注意:生产环境选择本地模型时,必须充分考虑服务器的GPU资源(显存大小)。一个7B参数的模型通常需要至少8GB显存才能流畅运行,量化版本(如4bit量化)可以降低资源需求,但可能会轻微影响效果。
2. 环境准备与项目初始化
在开始编码前,我们需要搭建好基础的开发环境和项目结构。
2.1 开发环境要求
确保你的开发机器满足以下条件:
| 组件 | 要求 | 说明 |
|---|---|---|
| JDK | 17 或 21 | Spring Boot 3.x 的推荐版本。 |
| Maven | 3.6+ 或Gradle | 项目管理工具。本文使用Maven示例。 |
| Docker & Docker Compose | 最新稳定版 | 用于快速启动Ollama、向量数据库等依赖服务。 |
| IDE | IntelliJ IDEA, VS Code等 | 推荐使用支持Spring Boot和Lombok的IDE。 |
| GPU(可选但推荐) | NVIDIA GPU, 显存 >= 8GB | 用于本地运行7B及以上参数的LLM,显著提升推理速度。CPU也可运行,但速度慢。 |
2.2 初始化Spring Boot项目
使用 Spring Initializr 生成项目骨架,选择以下依赖:
- Spring Web:构建RESTful API。
- Spring AI:核心AI集成依赖。
- Lombok:简化Java Bean代码。
- Spring Boot DevTools:开发热部署。
生成后,pom.xml中应包含类似以下依赖(Spring AI的版本请使用最新稳定版):
<dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-ollama-spring-boot-starter</artifactId> <!-- 请检查Spring AI官方文档获取最新版本 --> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency>2.3 启动基础设施服务(Docker Compose)
创建一个docker-compose.yml文件,用于一键启动Ollama(运行本地模型)和ChromaDB(向量数据库)。
version: '3.8' services: ollama: image: ollama/ollama:latest container_name: passage-ai-ollama ports: - "11434:11434" volumes: - ollama_data:/root/.ollama # 注意:首次启动后,需要进入容器拉取模型,见下文步骤。 restart: unless-stopped chromadb: image: chromadb/chroma:latest container_name: passage-ai-chromadb environment: - IS_PERSISTENT=TRUE - PERSIST_DIRECTORY=/chroma/data ports: - "8000:8000" volumes: - chroma_data:/chroma/data restart: unless-stopped volumes: ollama_data: chroma_data:在终端中,进入该文件所在目录,运行:
docker-compose up -d2.4 下载并运行本地大语言模型
Ollama服务启动后,我们需要为其下载一个模型。以Qwen2.5-7B-Instruct模型为例:
进入Ollama容器:
docker exec -it passage-ai-ollama bash在容器内拉取模型:
ollama pull qwen2.5:7b-instruct这个过程会下载约4.5GB的模型文件,耗时取决于网络。你也可以选择更小的模型,如
llama3.2:1b或qwen2.5:3b用于快速原型验证。退出容器。现在模型已经准备就绪,可以通过
http://localhost:11434访问Ollama的API。
3. 构建核心功能:从静态规划到动态引导
我们的目标是构建一个简单的REST API,它接收用户的旅行需求,返回规划好的行程,并能接受后续的实时交互。我们将分步实现。
3.1 配置Spring AI连接本地Ollama
首先,在application.yml中配置Spring AI连接到我们本地运行的Ollama服务。
spring: ai: ollama: base-url: http://localhost:11434 # Ollama服务地址 chat: options: model: qwen2.5:7b-instruct # 使用的模型名称 temperature: 0.7 # 创造性,0-1之间,越高回答越随机 max-tokens: 2000 # 最大输出token数3.2 实现基础的对话服务
创建一个TripPlanningService,它利用Spring AI的AiClient进行最简单的对话。
import org.springframework.ai.chat.client.ChatClient; import org.springframework.stereotype.Service; import lombok.RequiredArgsConstructor; @Service @RequiredArgsConstructor public class TripPlanningService { private final ChatClient chatClient; public String planTrip(String userRequest) { // 构建一个简单的提示词(Prompt) String prompt = """ 你是一个专业的旅行规划助手。请根据用户的需求,为其规划一份详细的旅行行程。 用户需求:%s 请以清晰的格式(如按天、按时间段)回复,包含景点、活动、餐饮建议和交通提示。 """.formatted(userRequest); // 调用AI模型生成回复 return chatClient.prompt() .user(prompt) .call() .content(); } }创建一个简单的控制器TripPlanningController来暴露API。
import org.springframework.web.bind.annotation.*; import lombok.RequiredArgsConstructor; @RestController @RequestMapping("/api/trip") @RequiredArgsConstructor public class TripPlanningController { private final TripPlanningService tripPlanningService; @PostMapping("/plan") public String plan(@RequestBody PlanRequest request) { return tripPlanningService.planTrip(request.getUserRequest()); } // 简单的请求体 public record PlanRequest(String userRequest) {} }此时,启动应用并调用POST /api/trip/plan,传入{"userRequest": "我想在北京玩3天,喜欢历史和文化"},你应该能得到一段AI生成的行程文本。但这只是一个“聊天机器人”,缺乏真正的知识和实时能力。
3.3 集成知识库(RAG)实现智能规划
要让规划更准确,我们需要为其提供知识。假设我们有一个关于北京旅行的知识文档beijing_travel_guide.txt。步骤如下:
- 加载与分割文档:使用Spring AI的文档加载器和文本分割器。
- 生成向量并存储:将分割后的文本块转换为向量,存入ChromaDB。
- 检索增强生成:用户提问时,先检索相关文本块,再连同问题和文本块一起发给LLM。
首先,添加ChromaDB的依赖和配置。
# application.yml 追加 spring: ai: vectorstore: chroma: host: localhost port: 8000然后,创建文档加载和向量存储的服务。
import org.springframework.ai.document.Document; import org.springframework.ai.reader.TextReader; import org.springframework.ai.transformer.splitter.TokenTextSplitter; import org.springframework.ai.vectorstore.VectorStore; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; import org.springframework.stereotype.Service; import jakarta.annotation.PostConstruct; import lombok.RequiredArgsConstructor; import java.util.List; @Service @RequiredArgsConstructor public class KnowledgeBaseService { private final ResourceLoader resourceLoader; private final VectorStore vectorStore; @PostConstruct // 应用启动时加载知识库 public void initKnowledgeBase() { Resource resource = resourceLoader.getResource("classpath:beijing_travel_guide.txt"); TextReader textReader = new TextReader(resource); List<Document> documents = textReader.get(); TokenTextSplitter splitter = new TokenTextSplitter(); // 按Token分割文本 List<Document> splitDocuments = splitter.apply(documents); vectorStore.add(splitDocuments); // 存入向量数据库 System.out.println("知识库加载完成,文档块数量:" + splitDocuments.size()); } }最后,改造TripPlanningService,在规划时先进行检索。
public String planTripWithKnowledge(String userRequest) { // 1. 检索相关文档 List<Document> similarDocuments = vectorStore.similaritySearch(userRequest); // 2. 构建包含上下文的提示词 String context = similarDocuments.stream() .map(Document::getContent) .collect(Collectors.joining("\n\n")); String prompt = """ 你是一个专业的旅行规划助手,请根据以下提供的旅行指南信息和用户需求,规划行程。 【旅行指南信息】 %s 【用户需求】 %s 请基于以上信息,规划一份详细、可行、个性化的行程。如果信息不足,可以基于常识补充,但请注明。 回复格式:按天规划,每天包含上午、下午、晚上,每个时段注明地点、活动、交通和餐饮建议。 """.formatted(context, userRequest); // 3. 调用AI生成回复 return chatClient.prompt() .user(prompt) .call() .content(); }现在,AI生成的行程将基于我们提供的《北京旅行指南》,推荐的地点、开放时间等信息会更准确。
3.4 实现动态引导与实时交互(Agent雏形)
“动态引导”意味着系统能记住之前的对话(行程),并根据用户的新输入(如“我现在在故宫,接下来去哪?”)或实时数据做出决策。这需要引入“对话记忆”和“工具调用”能力。
对话记忆:Spring AI的
ChatClient可以关联一个ChatMemory对象,自动维护对话历史。我们需要在服务层或控制器层管理不同用户的会话。import org.springframework.ai.chat.memory.InMemoryChatMemory; import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor; public String chatWithMemory(String sessionId, String userMessage) { // 为每个用户会话创建或获取一个ChatMemory ChatMemory chatMemory = sessionMemories.computeIfAbsent(sessionId, id -> new InMemoryChatMemory()); return chatClient.prompt() .user(userMessage) .advisors(new MessageChatMemoryAdvisor(chatMemory)) // 关联记忆 .call() .content(); }工具调用(Function Calling):让AI能调用外部工具获取实时信息。例如,定义一个“获取实时天气”的工具。
import org.springframework.ai.chat.model.ChatResponse; import org.springframework.ai.chat.prompt.Prompt; import org.springframework.ai.model.function.FunctionCallbackWrapper; // 定义一个工具(函数) @Bean public FunctionCallbackWrapper weatherFunction() { return FunctionCallbackWrapper.builder(new WeatherService()) // WeatherService是一个包含getWeather方法的Bean .name("getCurrentWeather") .description("获取指定城市的当前天气情况") .build(); } // 在ChatClient构建时注册工具 private final ChatClient chatClientWithTools; public String getDynamicSuggestion(String location) { String prompt = “用户当前在” + location + “,请根据实时天气,给出接下来的活动建议。如果需要,可以调用工具获取天气。”; ChatResponse response = chatClientWithTools.prompt() .user(prompt) .call() .chatResponse(); // AI可能会在响应中要求调用工具,Spring AI会自动处理调用并将结果返回给AI,最终生成包含工具调用结果的完整回复。 return response.getResult().getOutput().getContent(); }WeatherService示例:@Component public class WeatherService { public String getCurrentWeather(@JsonProperty("city") String city) { // 这里调用真实的天气API,如和风天气、OpenWeatherMap等 // 返回模拟数据 return “城市:” + city + “,天气:晴,温度:25°C,适宜户外活动。”; } }
通过结合记忆和工具,AI旅行助手就具备了初步的Agent能力:它能记住之前的对话(已规划的行程),能根据用户当前位置和实时天气,动态地建议“接下来是去户外景点还是室内博物馆”。
4. 系统集成、测试与常见问题排查
将各个模块组合起来,形成一个可测试的原型系统。
4.1 定义完整的API接口
设计两个核心接口:
POST /api/trip/plan:初始规划,创建新会话。POST /api/trip/chat:实时交互,需携带sessionId。
4.2 编写集成测试
使用@SpringBootTest编写测试,验证从用户输入到AI回复的完整流程,特别是工具调用和记忆功能是否正常工作。
@SpringBootTest @AutoConfigureMockMvc class TripPlanningIntegrationTest { @Autowired private MockMvc mockMvc; @Test void testPlanAndChatFlow() throws Exception { // 1. 测试初始规划 String planRequest = “{"userRequest": "上海两天一夜游"}”; MvcResult planResult = mockMvc.perform(post(“/api/trip/plan”) .contentType(MediaType.APPLICATION_JSON) .content(planRequest)) .andExpect(status().isOk()) .andReturn(); String sessionId = // 从响应头或Cookie中提取会话ID(根据你的实现); // 2. 测试带记忆和工具的实时聊天 String chatRequest = “{"sessionId": "” + sessionId + “", "message": "现在外滩下雨吗?"}”; mockMvc.perform(post(“/api/trip/chat”) .contentType(MediaType.APPLICATION_JSON) .content(chatRequest)) .andExpect(status().isOk()) .andExpect(jsonPath(“$.content”).value(containsString(“天气”))); // 期望回复中包含天气信息 } }4.3 常见问题与排查路径
在开发和运行过程中,你可能会遇到以下典型问题:
| 问题现象 | 可能原因 | 检查方式 | 处理建议 |
|---|---|---|---|
调用/plan接口超时或无响应 | 1. Ollama服务未启动或模型未加载。 2. 模型推理速度过慢(CPU模式)。 3. 网络问题。 | 1.docker ps检查容器状态。2. curl http://localhost:11434/api/tags查看模型列表。3. 查看应用日志和Ollama容器日志。 | 1. 确保docker-compose up -d已执行且模型已拉取。2. 考虑使用更小的模型或启用GPU加速。 3. 增加Spring AI的请求超时配置。 |
| AI回复内容与知识库无关(RAG失效) | 1. 向量数据库未存入数据或数据不相关。 2. 检索返回的文档数量(k值)设置不当。 3. Embedding模型与LLM不匹配。 | 1. 检查KnowledgeBaseService的init方法日志,确认文档已加载。2. 调试 vectorStore.similaritySearch的返回结果。3. 检查使用的Embedding模型(Spring AI默认可能使用Ollama的嵌入模型)。 | 1. 确保知识文档路径正确且内容相关。 2. 调整 similaritySearch的topK参数。3. 考虑使用专门的嵌入模型(如 nomic-embed-text)并统一配置。 |
| 工具调用(Function Calling)不生效 | 1. 模型不支持或未正确识别工具定义。 2. 函数描述(description)不够清晰。 3. 工具未正确注册到 ChatClient。 | 1. 确认模型支持函数调用(如Qwen2.5-Instruct系列支持)。 2. 查看AI的原始响应,看是否发出了工具调用请求。 3. 检查 FunctionCallbackWrapper的Bean是否被创建。 | 1. 使用明确支持函数调用的模型。 2. 完善函数名称和描述,使其易于被AI理解。 3. 确保构建 ChatClient时通过.functions(...)方法注册了工具。 |
| 对话记忆混乱或丢失 | 1.ChatMemory作用域设置错误(如用了单例)。2. 会话ID(sessionId)未正确传递或管理。 | 1. 检查是否为每个用户/会话创建了独立的ChatMemory实例。2. 在客户端调用时打印或日志记录 sessionId。 | 1. 使用ConcurrentHashMap等结构以 sessionId 为键存储ChatMemory。2. 对于Web应用,可将 sessionId 存储在HttpSession或通过Token管理。 |
| 生产环境性能差 | 1. 每次请求都重新检索向量数据库,未缓存。 2. LLM推理无并发控制,拖慢整体响应。 3. 知识库文档过大,Embedding耗时。 | 1. 使用APM工具(如SkyWalking)分析链路耗时。 2. 监控服务器CPU/GPU和内存使用率。 | 1. 对频繁查询的相似问题结果进行缓存(如使用Redis)。 2. 对LLM调用实现限流和队列机制。 3. 对知识库进行预处理和优化,如提取关键信息生成摘要。 |
5. 从原型到产品:最佳实践与扩展方向
构建一个可用的原型只是第一步。要将其发展为可靠的产品,还需要考虑以下方面。
5.1 工程化最佳实践
- 配置外部化:将模型参数(temperature, maxTokens)、API密钥、向量数据库连接信息等全部移至
application-{profile}.yml或配置中心。切勿硬编码。 - 结构化输出:让AI返回JSON而非纯文本,便于前端解析和展示。Spring AI支持通过指定
responseFormat或使用@StructuredOutput注解来实现。@StructuredOutput public record TripPlan(List<DayPlan> days) {} public record DayPlan(String date, List<TimeSlot> schedule) {} // 在Prompt中明确要求AI返回指定格式的JSON - 异步处理:行程规划可能是耗时操作。对于复杂请求,应改为异步接口,立即返回一个任务ID,客户端通过轮询或WebSocket获取结果。
- 监控与日志:记录关键指标:LLM调用耗时、Token使用量、工具调用成功率、用户会话数等。使用MDC(Mapped Diagnostic Context)将请求ID、用户ID贯穿整个调用链路,便于问题追踪。
- 错误处理与降级:LLM服务可能不稳定。设计降级策略,例如,当本地模型不可用时,可暂时切换到备用云端API(需注意成本和数据合规);当RAG检索无结果时,提供基于通用知识的回复并明确告知用户信息有限。
5.2 功能扩展方向
- 多模态输入/输出:
- 输入:支持用户上传旅行照片,AI识别地点并推荐周边活动。
- 输出:集成地图SDK(如高德、百度地图API),将行程自动生成可视化地图路线。结合TTS(Text-to-Speech)服务,提供语音导览。
- 深度Agent化:引入规划(Planning)、反思(Reflection)等更复杂的Agent机制。让AI不仅能调用工具,还能评估当前状态与目标的差距,自主拆解复杂任务(如“规划一个欧洲半月游”),并递归执行子任务。
- 个性化与持续学习:建立用户画像,记录用户的历史行程、评分和反馈。在规划时优先推荐相似用户喜欢的项目,实现越用越懂你的个性化推荐。
- 供应商集成:打通票务(如门票、车票)、住宿(酒店API)、餐饮(预订平台)系统。在规划行程时,直接提供预订链接或比价信息,并在行程中集成订单状态提醒。
- 离线能力:对于国际旅行可能面临的网络问题,可以考虑将核心模型、知识库和地图数据打包,在手机端实现轻量级离线推理和导航。
5.3 安全与合规考量
- 数据隐私:行程数据是敏感的个人信息。必须对用户数据进行加密存储和传输,明确告知用户数据使用范围,并提供数据导出和删除功能。
- 内容安全:对AI生成的内容进行审核过滤,避免产生不当、危险或虚假的旅行建议(如推荐未开发的危险区域)。可以接入内容安全API或在Prompt中设置严格的约束。
- 模型合规:确保所使用的开源模型符合商业使用许可。如果使用微调,需注意训练数据的版权问题。
构建一个像Passage AI这样“规划即服务,落地即向导”的应用,技术核心在于将大语言模型的推理能力、外部知识库的准确性、实时数据的时效性以及智能体的决策能力有机结合起来。从本文的原型出发,你可以沿着工程化、产品化、智能化的方向持续迭代,最终打造出一个真正理解用户、伴随旅程的智能旅行伙伴。在实践过程中,始终牢记以解决用户真实痛点为出发点,平衡技术的先进性与系统的稳定性。