一、为什么写这篇文章?
上一篇我们讲了业务逻辑层的 6大编排模式。很多人还是不大懂
「模式都懂,但真正落地一个项目还是不会,怎么办?」「Function Calling 怎么注册?Chat Memory 怎么配?RAG 怎么集成到主流程里?」「能给一个完整可跑的案例吗?最好把骨架代码都贴出来。」
这一篇,用一个真实案例完整拆解——智能机票助手。
读完这一篇,你会拿到:
二、需求分析:智能体需要哪三大能力?
任何一个能跑起来的 AI Agent,都绕不开三大核心能力:

┌──────────────────────────────────────────────────┐│ AI Agent 三大能力 │├──────────────┬──────────────┬────────────────────┤│ 规划能力 │ 记忆能力 │ 行动能力 ││ Planning │ Memory │ Action │├──────────────┼──────────────┼────────────────────┤│ 拆解任务 │ 多轮对话上下文 │ 调工具改业务 ││ 决策路径 │ 长期/短期记忆 │ 调 API 写数据库 ││ 异常处理 │ 会话隔离 │ 权限校验 │└──────────────┴──────────────┴────────────────────┘
2.1 智能机票助手的功能需求
基于 AI 大模型与用户对话 支持多轮连续对话 理解机票操作相关的术语与规范,并严格遵守 在必要时可调用工具辅助完成任务
2.2 Spring AI Alibaba 提供的四大核心抽象
对应的技术实现:
三、整体架构设计
3.1 技术栈选型
┌─────────────────────────────────────────────────┐│ 智能机票助手整体架构 │├─────────────────────────────────────────────────┤│ 前端 │ Vue3 + Vite ││ 后端 │ Spring Boot + Spring AI Alibaba ││ 向量存储 │ Milvus / PGVector ││ 外部工具 │ REST API ││ 大模型 │ 通义千问 │└─────────────────────────────────────────────────┘
3.2 基础环境版本
Nodejs v20.19.2
NVM 1.2.2
Npm 11.4.1
Pnpm 10.11.0
Yarn 1.22.22
Apache Maven 3.9.9
Java version: 17.0.12
3.3 三大子模块职责
前端
Vue3 + Vite 构建
负责用户交互。
后端
Spring Boot 应用,集成 Spring AI Alibaba。
负责 AI 逻辑、RAG、函数调用等。
AI/向量存储/外部工具
DashScope配置
向量数据库(如 Milvus、PGVector库)
外部 REST API
四、落地实现:完整代码骨架
4.1 第一步:外部化配置(大模型凭证)
application.properties:
# 使用阿里云 DashScope 作为大模型服务spring.ai.dashscope.api-key=sk-xxxxxxxxxxxxspring.ai.dashscope.chat.options.model=qwen-maxspring.ai.dashscope.chat.options.streaming=truespring.ai.dashscope.read-timeout=60spring.ai.dashscope.connect-timeout=60
4.2 第二步:核心 ChatClient 装配(⭐ 模板)
这是整个智能体的总装入口:
@Configurationpublic class CustomerSupportAssistant {private final ChatClient chatClient;public CustomerSupportAssistant(ChatClient.Builder modelBuilder,VectorStore vectorStore,ChatMemory chatMemory) {this.chatClient = modelBuilder// 1. 系统提示词(角色设定 + 业务规则).defaultSystem("""你是”Funnair”航空公司的客户聊天支持代理。请以友好、乐于助人且愉快的方式来回复。你正在通过在线聊天系统与客户互动。你能够支持已有机票的预订详情查询、机票日期改签、机票预订取消等操作,其余功能将在后续版本中添加,如果用户问的问题不支持请告知详情。在提供有关机票预订详情查询、机票日期改签、机票预订取消等操作之前,你需要从用户处获取以下信息:预订号、客户姓名。在询问用户之前,请检查消息历史记录以获取预订号、客户姓名等信息,尽量避免重复询问给用户造成困扰。在更改预订之前,您必须确保条款允许这样做。如果更改需要收费,您必须在继续之前征得用户同意。使用提供的功能获取预订详细信息、更改预订和取消预订。如果需要,您可以调用相应函数辅助完成。请讲中文。今天的日期是 {current_date}.""")// 2. 插件组合:记忆 + RAG + 日志.defaultAdvisors(new PromptChatMemoryAdvisor(chatMemory), // 多轮对话new QuestionAnswerAdvisor(vectorStore), // RAG 知识检索new SimpleLoggerAdvisor() // 日志)// 3. 函数调用:注册工具.defaultTools("getBookingDetails","changeBooking","cancelBooking").build();}}
这段代码念三遍——这就是 Spring AI Alibaba 智能体的核心模式。
4.3 第三步:RAG 知识库(私有文档入库)
应用场景:航司有自己的票价规则、改签政策,这些通用模型不知道,必须让模型查到。
@BeanCommandLineRunner ingestTermOfServiceToVectorStore(EmbeddingModel embeddingModel,VectorStore vectorStore,@Value("classpath:rag/terms-of-service.txt") Resource termsOfServiceDocs) {return args -> {// 1. 文档读取:TextReader 读取文件// 2. TokenTextSplitter:按 token 切分(避免超出模型限制)// 3. 向量化存储:VectorStore.write()vectorStore.write(new TokenTextSplitter().transform(new TextReader(termsOfServiceDocs).read()));// 验证:相似性搜索vectorStore.similaritySearch("Cancelling Bookings").forEach(doc -> {logger.info("Similar Document: {}", doc.getText());});};}
核心配置文件:resources/rag/terms-of-service.txt(航司私有知识库)
4.4 第四步:Chat Memory(多轮对话)
LLM 本身没有记忆——必须有外部会话存储:
@Beanpublic ChatMemory chatMemory() {// 基于内存的 ChatMemoryreturn new InMemoryChatMemory();}
生产环境提示:
4.5 第五步:自定义 HTTP 客户端(调外部 API)
@Bean@ConditionalOnMissingBeanpublic RestClient.Builder restClientBuilder() {return RestClient.builder();}
@ConditionalOnMissingBean 表示允许用户自定义覆盖,默认没有就注册。
4.6 第六步:Function Calling(@Bean + Function 注册工具)
核心原理:LLM 通过 Function Calling 协议,调用后端 @Bean 注册的 Java 方法。
@Bean@Description("获取机票预定详细信息")public Function getBookingDetails() {return request -> {try {return flightBookingService.getBookingDetails(request.bookingNumber(),request.name());} catch (Exception e) {logger.warn("Booking details: {}",NestedExceptionUtils.getMostSpecificCause(e).getMessage());return new BookingDetails(request.bookingNumber(),request.name(),null, null, null, null, null);}};}
模式完全一致——只需要再写两个 @Bean,框架自动注册。
public record BookingDetailsRequest(String bookingNumber,String name) {}public record BookingDetails(String bookingNumber,String name,LocalDate bookingDate,String flightNumber,String departure,String arrival,String status) {}
4.7 第七步:对话入口(流式响应 + 会话隔离)
public Flux chat(String chatId, String userMessageContent) {return this.chatClient.prompt()// 动态参数:当前日期.system(s -> s.param("current_date", LocalDate.now().toString()))// 用户输入.user(userMessageContent)// 会话隔离:每个 chatId 独立的对话历史.advisors(a -> a.param(ChatMemory.CONVERSATION_ID, chatId).param(ChatMemory.RETRIEVE_SIZE, 100))// 流式响应.stream().content();}
三个关键设计:
五、完整代码骨架(直接复用)
把上面的内容串起来,就是一个可运行的最小工程:
src/main/java/com/example/flight/├── config/│ ├── CustomerSupportAssistant.java# 智能体装配(核心)│ └── RAGConfig.java# RAG + ChatMemory + RestClient├── service/│ ├── FlightBookingService.java# 业务逻辑│ └── tools/│ └── BookingTools.java# Function Calling 工具├── controller/│ └── ChatController.java# 对话接口└── dto/├── BookingDetailsRequest.java└── BookingDetails.javasrc/main/resources/├── application.properties└── rag/└── terms-of-service.txt# 私有知识库
复用模板
@Configurationpublic class UniversalAgent {private final ChatClient chatClient;public UniversalAgent(ChatClient.Builder builder,VectorStore vectorStore,ChatMemory chatMemory) {this.chatClient = builder.defaultSystem("""// 在这里写你的系统提示词// 角色 + 业务规则 + 边界条件""").defaultAdvisors(new PromptChatMemoryAdvisor(chatMemory),new QuestionAnswerAdvisor(vectorStore),new SimpleLoggerAdvisor()).defaultTools("tool1", "tool2", "tool3" // 你的工具名).build();}public Flux chat(String chatId, String message) {return chatClient.prompt().user(message).advisors(a -> a.param(ChatMemory.CONVERSATION_ID, chatId).param(ChatMemory.RETRIEVE_SIZE, 100)).stream().content();}}
这就是 Spring AI Alibaba 智能体的标准范式——拿到任何一个项目,往这个骨架里塞业务即可。
六、容易踩的 6 个坑
坑 1:Function Calling 工具名拼写错误
.defaultTools("getBookingDetails", "changeBooking", "cancelBooking") // ✅.defaultTools("getBookingDetail", "changeBooking", "cancelBooking") // ❌ Bean 名要严格一致
最佳实践:用常量。
public static final String TOOL_QUERY = "getBookingDetails";坑 2:请求/响应忘了用 Java 17 record
很多新人用 @Data 加 @AllArgsConstructor,结果 LLM 解析不出字段。
正确做法:必须用 record,JSON 序列化稳定。
坑 3:InMemoryChatMemory 用于多实例
多实例部署下,每个 Tomcat 节点内存独立,用户多轮对话会丢。
正确做法:用 JDBC ChatMemory。
@Beanpublic ChatMemory chatMemory(DataSource dataSource) {return JdbcChatMemory.builder().dataSource(dataSource).build();}
坑 4:RAG 文档没切分
文档一整段塞进去,超过模型上下文窗口就报错。
正确做法:用 TokenTextSplitter 切分。
坑 5:系统提示词没写"边界"
不写"用户问的问题不支持请告知详情",模型会幻觉编造——这是最常见的踩雷。
坑 6:Function Calling 异常吞掉
工具方法里只 logger.warn 不抛错,模型不知道失败了,会继续编造。
正确做法:抛异常 + 框架层处理。
七、回归架构本质:智能体背后的统一范式
虽然我们在做一个机票助手,但底层抽象是所有 AI Agent 的通用范式:
┌──────────────────────────────────────────────┐│ Prompt Template(系统角色 + 业务规则) ││ ↓ ││ Advisors(Chat Memory + RAG + Logger) ││ ↓ ││ Tools(Function Calling,调后端 Java) ││ ↓ ││ ChatClient.prompt().stream() │└──────────────────────────────────────────────┘
任何一个智能体项目,骨架都是:
系统提示词(角色 + 规则)+ 记忆(多轮对话)+ RAG(私有知识)+ 工具(业务能力)= 智能体
这五大件,背熟,下次接到任何智能体项目,都能10 分钟出骨架。
八、写在最后
这一篇,我们用智能机票助手完整跑通了一个 AI Agent 项目:
✅ 需求分析 → 三大能力 + 四大抽象 ✅ 架构设计 → 前后端 + 向量库 + 外部工具 ✅ 落地实现 → 完整 ChatClient 装配 + RAG + ChatMemory + Function Calling
接下来要做的,是把这个骨架装进你自己的业务:
系统提示词 → 改成你的业务规则 RAG → 换成你的私有文档 Tools → 换成你的业务方法 ChatMemory → 按需选 InMemory / Redis / JDBC
真正的 AI Agent 落地能力,不是会用某个框架,而是能在任何一个业务场景下,都用这套范式跑通。
这条路一旦走通,其他项目都是 copy-paste。
觉得有帮助的话,点赞 + 在看,这是对我持续输出的最大支持。
夜雨聆风