乐于分享
好东西不私藏

智能机票助手 AI Agent 完整实战:从需求分析到落地实现

智能机票助手 AI Agent 完整实战:从需求分析到落地实现

一、为什么写这篇文章?

上一篇我们讲了业务逻辑层的 6大编排模式。很多人还是不大懂

「模式都懂,但真正落地一个项目还是不会,怎么办?」「Function Calling 怎么注册?Chat Memory 怎么配?RAG 怎么集成到主流程里?」「能给一个完整可跑的案例吗?最好把骨架代码都贴出来。」

这一篇,用一个真实案例完整拆解——智能机票助手。

读完这一篇,你会拿到:


二、需求分析:智能体需要哪三大能力?

任何一个能跑起来的 AI Agent,都绕不开三大核心能力

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() { // 基于内存的 ChatMemory return 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(), nullnullnullnullnull ); } };}

模式完全一致——只需要再写两个 @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_SIZE100)) // 流式响应 .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(系统角色 + 业务规则) ││ ↓ ││ AdvisorsChat Memory + RAG + Logger) ││ ↓ ││ ToolsFunction Calling,调后端 Java) ││ ↓ ││ ChatClient.prompt().stream() │└──────────────────────────────────────────────┘

    任何一个智能体项目,骨架都是:

    系统提示词(角色 + 规则) + 记忆(多轮对话) + RAG(私有知识) + 工具(业务能力) = 智能体

    这五大件,背熟,下次接到任何智能体项目,都能10 分钟出骨架


    八、写在最后

    这一篇,我们用智能机票助手完整跑通了一个 AI Agent 项目:

    • ✅ 需求分析 → 三大能力 + 四大抽象
    • ✅ 架构设计 → 前后端 + 向量库 + 外部工具
    • ✅ 落地实现 → 完整 ChatClient 装配 + RAG + ChatMemory + Function Calling

    接下来要做的,是把这个骨架装进你自己的业务

    • 系统提示词 → 改成你的业务规则
    • RAG → 换成你的私有文档
    • Tools → 换成你的业务方法
    • ChatMemory → 按需选 InMemory / Redis / JDBC

    真正的 AI Agent 落地能力,不是会用某个框架,而是能在任何一个业务场景下,都用这套范式跑通。

    这条路一旦走通,其他项目都是 copy-paste。

    觉得有帮助的话,点赞 + 在看,这是对我持续输出的最大支持。