图片

上一期对比了 Spring AI、LangChain4J、AgentScope Java 2.0,结论是:默认选 Spring AI。这一期,我用 Spring AI 把它落到代码里——一个可以跑起来的售后智能客服 Agent。


一、开篇:为什么选「智能客服」作为第一个 Demo?

前面三期,我一直在讲售后六站编排:接车 → 历史 → 开单 → 诊断 → 维修 → 交车。理论上每个环节都能做 Agent,但作为一个吃过 4S 店苦头的老 SA,我知道:最痛、最容易先落地、最容易算清 ROI 的,其实是「客服」。

客服场景有三个特点:

高频重复:「我的车保养多少钱?」「保险到期了怎么办?」「胎压灯亮了要紧吗?」这些问题一天能出现几十上百次。

知识边界清晰:答案基本就在车型手册、保养套餐、保险政策、历史工单里,不太需要开放式创造。

容错可控:答错了可以转人工,不会像诊断/维修一样直接造成车辆损坏。

所以第4期,我们先不做全流程,而是把「客服」这个单点打透。当你把客服 Agent 跑通后,再把里面的 RAG、Tool Calling、Memory、MCP Client 复用到诊断、报价、派工等环节,就是水到渠成的事。


二、这个 Demo 要解决什么业务问题?

2.1 业务场景

假设你是一个奥迪 4S 店的售后客服,每天接电话接到手软:

客户 A:「我 2020 款 Q5L,5 万公里,做小保养多少钱?」

客户 B:「我车胎压灯亮了,现在在外地,能继续开吗?」

客户 C:「我上个月在你们这做了喷漆,现在发现有色差,帮我查一下。」

客户 D:「我的保险要到期了,帮我报个价。」

这些问题看起来简单,但客服新人根本答不利索,老人又被大量重复劳动拖垮。

2.2 我们要构建的 Agent 能力

能力 作用 对应技术
意图识别 判断客户想问保养 / 保险 / 故障 / 投诉 / 预约 LLM + Prompt
知识问答 回答车型保养项目、费用、周期等标准问题 RAG
工单/车辆信息查询 查 VIN 对应的历史工单、保养记录、保险到期日 Tool Calling
多轮上下文记忆 连续对话中记住客户是谁、之前说了什么 Memory
跨系统调用 调用 CRM、DMS、保险系统的能力 MCP Client

三、系统架构:四大模块怎么拼?

┌─────────────────────────────────────────────────────────────────────────┐

│                         售后智能客服 Agent                              │

─────────────────────────────────────────────────────────────────────────┤

│                                                                         │

│   用户提问 ──→ ChatController ──→ CustomerServiceAgent                  │

│                                     │                                  │

│                                     ▼                                  │

│                         ┌─────────────────────┐                        │

│                         │   Spring AI ChatClient + ChatMemory        │                        │

│                         └──────────┬──────────┘                        │

│                                    │                                   │

│            ┌───────────────────────┼───────────────────────┐           │

│                                   ▼                       ▼           │

│       ┌─────────           ┌─────────┐           ┌──────────┐        │

│       │   RAG   │           │  Tools  │           │  Memory  │        │

│       │VectorStore│         │ToolCallback│         │ChatMemory │        │

│       └────┬────┘           └────┬────┘           └────┬────┘        │

│            │                     │                     │              │

│            ▼                                          ▼              │

│      车型保养手册          查车辆/工单/保险          会话上下文        │

│      常见问题 FAQ          库存/预约/投诉           多轮对话记忆     │

│                                                                         │

│                              ┌─────────────┐                           │

│                              │  MCP Client │                           │

│                              └──────┬──────┘                           │

│                                     ▼                                  │

│                        ──────────────────────┐                        │

│                        │  外部 MCP Server     │                        │

│                        │  CRM / DMS / 保险系统  │                        │

│                        └──────────────────────┘                        │

│                                                                         │

└─────────────────────────────────────────────────────────────────────────┘

核心思路:LLM 是大脑,RAG 是知识库,Tool Calling 是手,Memory 是上下文,MCP Client 是和外部系统说同一种语言的翻译官。


四、项目初始化与依赖

4.1 项目结构

aftersales-customer-agent/

├── pom.xml

├── src/

│   ├── main/

│   │   ├── java/com/aftersales/agent/

│   │   │   ├── AftersalesCustomerAgentApplication.java

│   │   │   ├── config/

│   │   │   │   ├── AIConfig.java

│   │   │   │   └── VectorStoreConfig.java

│   │   │   ├── controller/

│   │   │   │   └── ChatController.java

│   │   │   ├── service/

│   │   │   │   ├── CustomerServiceAgent.java

│   │   │   │   ├── VehicleService.java

│   │   │   │   ├── OrderService.java

│   │   │   │   └── InsuranceService.java

│   │   │   ├── tools/

│   │   │   │   ├── VehicleTools.java

│   │   │   │   ├── OrderTools.java

│   │   │   │   └── InsuranceTools.java

│   │   │   ├── rag/

│   │   │   │   ├── DocumentLoader.java

│   │   │   │   └── KnowledgeBaseService.java

│   │   │   └── mcp/

│   │   │       └── DMSClient.java

│   │   └── resources/

│   │       ├── application.yml

│   │       ├── knowledge/

│   │       │   ├── q5-maintenance.md

│   │       │   ├── a4l-maintenance.md

│   │       │   └── insurance-policy.md

│   │       └── prompts/

│   │           ├── customer-service-system.st

│   │           └── intent-classifier.st

│   └── test/

│       └── java/com/aftersales/agent/

│           └── CustomerServiceAgentTest.java

└── README.md

4.2 pom.xml 核心依赖

xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation=“http://maven.apache.org/POM/4.0.0

http://maven.apache.org/xsd/maven-4.0.0.xsd">

4.0.0

org.springframework.boot

spring-boot-starter-parent

3.3.0

com.aftersales

aftersales-customer-agent

1.0.0-SNAPSHOT

jar

21

1.0.0-M2

org.springframework.boot

spring-boot-starter-web

org.springframework.ai

spring-ai-openai-spring-boot-starter

org.springframework.ai

spring-ai-transformers-spring-boot-starter

org.springframework.ai

spring-ai-mcp-client-spring-boot-starter

org.springframework.boot

spring-boot-starter-test

test

org.springframework.ai

spring-ai-bom

${spring-ai.version}

pom

import

注意:Spring AI 1.0.0-M2 还在快速演进,建议你阅读本文时到 Spring AI 官方仓库 查看最新版本。

4.3 application.yml

spring:

application:

name: aftersales-customer-agent

ai:

openai:

api-key: ${OPENAI_API_KEY}

base-url: ${OPENAI_BASE_URL:https://api.openai.com}

chat:

options:

model: gpt-4o-mini

temperature: 0.3

mcp:

client:

enabled: true

stdio:

servers:

dms-server:

command: java

args: -jar, dms-mcp-server.jar

模拟 DMS 系统的 MCP Server

server:

port: 8080

logging:

level:

org.springframework.ai: DEBUG


五、四大核心模块逐个实现

5.1 模块一:Memory —— 让 Agent 记住你是谁

客服最怕的是:客户刚说完「我去年在你们这修过车」,下一句又问「你查一下我的车」。如果没有 Memory,Agent 会原地失忆。

packagecom.aftersales.agent.config;

importorg.springframework.ai.chat.memory.ChatMemory;

importorg.springframework.ai.chat.memory.InMemoryChatMemory;

importorg.springframework.context.annotation.Bean;

importorg.springframework.context.annotation.Configuration;

@Configuration

publicclassAIConfig {

/**

* 会话内存:生产环境建议换 Redis / JDBC 持久化版本

*/

@Bean

publicChatMemorychatMemory() {

returnnewInMemoryChatMemory();

}

}

packagecom.aftersales.agent.service;

importorg.springframework.ai.chat.client.ChatClient;

importorg.springframework.ai.chat.memory.ChatMemory;

importorg.springframework.ai.chat.messages.Message;

importorg.springframework.ai.chat.messages.UserMessage;

importorg.springframework.ai.chat.messages.AssistantMessage;

importorg.springframework.ai.chat.prompt.Prompt;

importorg.springframework.ai.chat.prompt.SystemPromptTemplate;

importorg.springframework.stereotype.Service;

importjava.util.List;

@Service

publicclassCustomerServiceAgent {

privatefinalChatClientchatClient;

privatefinalChatMemorychatMemory;

publicCustomerServiceAgent(ChatClientchatClient, ChatMemorychatMemory) {

this.chatClient=chatClient;

this.chatMemory=chatMemory;

}

publicStringchat(StringconversationId, StringuserInput) {

// 1. 读取历史消息

Listhistory=chatMemory.get(conversationId, 10);

// 2. 构建系统提示词

StringsystemText=””"

你是奥迪4S店售后智能客服Agent,名字叫「奥小迪」。

你的职责是:解答客户关于保养、保险、故障、预约、投诉的咨询。

规则:

1.回答前先判断客户意图;

2.涉及具体车辆信息时,优先调用工具查询;

3.遇到无法处理的问题,礼貌转人工;

4.语气专业、亲切,避免过度承诺。

“”";

Listmessages=newjava.util.ArrayList<>();

messages.add(newSystemPromptTemplate(systemText).createMessage());

messages.addAll(history);

messages.add(newUserMessage(userInput));

// 3. 调用 LLM

Stringresponse=chatClient.prompt(newPrompt(messages))

.call()

.content();

// 4. 更新记忆

chatMemory.add(conversationId, newUserMessage(userInput));

chatMemory.add(conversationId, newAssistantMessage(response));

returnresponse;

}

}

关键设计点:

用 conversationId 区分不同客户会话,可以来自微信公众号 OpenID、手机号、或前端 Session ID。

InMemoryChatMemory 仅适合 Demo,生产请换持久化实现。

每次只取最近 N 条历史,防止上下文窗口爆炸。


5.2 模块二:RAG —— 让 Agent 读懂车型保养手册

客服新人答不出「Q5 5 万公里要做什么保养」,因为答案在厚厚的车型手册里。RAG 的作用就是把手册向量化,问答时检索最相关的片段。

packagecom.aftersales.agent.rag;

importjakarta.annotation.PostConstruct;

importorg.springframework.ai.document.Document;

importorg.springframework.ai.reader.markdown.MarkdownReader;

importorg.springframework.ai.transformer.splitter.TokenTextSplitter;

importorg.springframework.ai.vectorstore.VectorStore;

importorg.springframework.core.io.Resource;

importorg.springframework.core.io.support.PathMatchingResourcePatternResolver;

importorg.springframework.stereotype.Service;

importjava.io.IOException;

importjava.util.Arrays;

importjava.util.List;

@Service

publicclassKnowledgeBaseService {

privatefinalVectorStorevectorStore;

publicKnowledgeBaseService(VectorStorevectorStore) {

this.vectorStore=vectorStore;

}

@PostConstruct

publicvoidinit() throwsIOException {

// 加载 knowledge/ 目录下的所有 md 文件

PathMatchingResourcePatternResolverresolver=newPathMatchingResourcePatternResolver();

Resource[] resources=resolver.getResources(“classpath:knowledge/*.md”);

TokenTextSplittersplitter=newTokenTextSplitter();

for (Resourceresource : resources) {

MarkdownReaderreader=newMarkdownReader(resource);

Listdocuments=reader.get();

// 切分文档并写入向量库

Listchunks=splitter.apply(documents);

vectorStore.add(chunks);

}

System.out.println(“知识库初始化完成,共加载文件:"+resources.length);

}

publicListsearch(Stringquery, inttopK) {

returnvectorStore.similaritySearch(query, topK);

}

}

packagecom.aftersales.agent.service;

importorg.springframework.ai.chat.client.ChatClient;

importorg.springframework.ai.chat.prompt.Prompt;

importorg.springframework.ai.chat.prompt.SystemPromptTemplate;

importorg.springframework.ai.document.Document;

importorg.springframework.stereotype.Service;

importjava.util.List;

importjava.util.Map;

importjava.util.stream.Collectors;

@Service

publicclassRAGCustomerService {

privatefinalChatClientchatClient;

privatefinalKnowledgeBaseServiceknowledgeBaseService;

publicRAGCustomerService(ChatClientchatClient, KnowledgeBaseServiceknowledgeBaseService) {

this.chatClient=chatClient;

this.knowledgeBaseService=knowledgeBaseService;

}

publicStringanswer(Stringquestion) {

// 1. 从知识库检索相关片段

ListrelevantDocs=knowledgeBaseService.search(question, 3);

Stringcontext=relevantDocs.stream()

.map(Document::getContent)

.collect(Collectors.joining(”\n—\n"));

// 2. 构建带上下文的提示词

StringpromptText="""

你是奥迪4S店售后客服。请根据以下「参考知识」回答客户问题。

如果参考知识不足以回答,请明确告知客户需要进一步核实。

【参考知识】

{context}

【客户问题】

{question}

“”";

SystemPromptTemplatetemplate=newSystemPromptTemplate(promptText);

Promptprompt=template.create(Map.of(

“context”, context,

“question”, question

));

returnchatClient.prompt(prompt).call().content();

}

}

一个容易被忽略的坑:

不要把整本手册都丢给 LLM。向量检索 + 片段注入,才是成本可控、效果稳定的正确姿势。Demo 里我用的是内存向量库,生产建议上 PGVector、Milvus 或 Redis Stack。


5.3 模块三:Tool Calling —— 让 Agent 能查车辆、查工单、查保险

只会背手册的客服不是好客服。真正解决问题,需要查询车辆档案、历史工单、保险到期日等业务数据。

package com.aftersales.agent.tools;

import org.springframework.ai.tool.annotation.Tool;

import org.springframework.ai.tool.annotation.ToolParam;

import org.springframework.stereotype.Component;

import java.time.LocalDate;

@Component

public class VehicleTools {

private final VehicleService vehicleService;

public VehicleTools(VehicleService vehicleService) {

this.vehicleService = vehicleService;

}

@Tool(name = “getVehicleInfo”,

description = “根据车牌号或VIN查询车辆基础信息,如车型、排量、购车日期、当前里程”)

public String getVehicleInfo(

@ToolParam(description = “车牌号或VIN,例如 浙A12345 或 LFV…”) String identifier) {

return vehicleService.findByIdentifier(identifier);

}

@Tool(name = “getMaintenanceHistory”,

description = “查询车辆历史保养/维修记录”)

public String getMaintenanceHistory(

@ToolParam(description = “VIN码”) String vin,

@ToolParam(description = “查询条数,默认5”) int limit) {

return vehicleService.getMaintenanceHistory(vin, limit);

}

}

package com.aftersales.agent.tools;

import org.springframework.ai.tool.annotation.Tool;

import org.springframework.ai.tool.annotation.ToolParam;

import org.springframework.stereotype.Component;

@Component

public class InsuranceTools {

private final InsuranceService insuranceService;

public InsuranceTools(InsuranceService insuranceService) {

this.insuranceService = insuranceService;

}

@Tool(name = “getInsuranceQuote”,

description = “根据车型、上年出险次数、险种组合,生成下一年度保险报价”)

public String getInsuranceQuote(

@ToolParam(description = “车型代码,如 Q5L_40TFSI”) String modelCode,

@ToolParam(description = “上年出险次数”) int claimCount,

@ToolParam(description = “险种组合:basic/standard/full”) String packageType) {

return insuranceService.quote(modelCode, claimCount, packageType);

}

@Tool(name = “getInsuranceExpiry”,

description = “查询车辆保险到期日”)

public String getInsuranceExpiry(

@ToolParam(description = “车牌号或VIN”) String identifier) {

return insuranceService.getExpiry(identifier);

}

}

package com.aftersales.agent.service;

import org.springframework.stereotype.Service;

@Service

public class VehicleService {

public String findByIdentifier(String identifier) {

// 实际项目对接 DMS 或 CRM

return """

{“vin”:“LFV3B28R3J3xxxxxx”,“model”:“奥迪Q5L 40 TFSI 时尚型”,

“plate”:“浙A12345”,“mileage”:52000,“purchaseDate”:“2020-06-15”}

“”";

}

public String getMaintenanceHistory(String vin, int limit) {

return """

[2024-05-10] 5万公里保养:机油/机滤/空滤/空调滤,费用 2180 元

[2024-01-08] 索赔更换左后分泵,费用 0 元

[2023-08-22] 4万公里保养:机油/机滤/刹车油,费用 1860 元

“”";

}

}


5.4 模块四:MCP Client —— 让 Agent 和外部系统说同一种语言

Tool Calling 适合调用本地 Java 方法,但 DMS、CRM、保险系统往往是别的团队维护的。MCP 协议的作用,就是让这些系统以标准化的方式暴露能力。

package com.aftersales.agent.mcp;

import org.springframework.ai.mcp.client.McpClient;

import org.springframework.ai.mcp.client.McpSyncClient;

import org.springframework.ai.mcp.spec.McpSchema;

import org.springframework.stereotype.Service;

import java.util.List;

@Service

public class DMSClient {

private final McpSyncClient mcpClient;

public DMSClient(McpSyncClient mcpClient) {

this.mcpClient = mcpClient;

}

public String queryRepairOrder(String vin) {

McpSchema.CallToolRequest request = new McpSchema.CallToolRequest(

“queryRepairOrder”,

new java.util.HashMap<>() {{

put(“vin”, vin);

put(“limit”, 5);

}}

);

McpSchema.CallToolResult result = mcpClient.callTool(request);

return result.content().stream()

.map(Object::toString)

.collect(java.util.stream.Collectors.joining("\n"));

}

}

注意:Spring AI 1.0.0-M2 的 MCP API 可能还会有调整。核心思路不变:MCP Server 定义工具,MCP Client 调用工具,Spring AI 负责把工具注册到 ChatClient 的工具链中。


六、把四大模块串起来:CustomerServiceAgent 完整版

package com.aftersales.agent.service;

import com.aftersales.agent.rag.KnowledgeBaseService;

import org.springframework.ai.chat.client.ChatClient;

import org.springframework.ai.chat.memory.ChatMemory;

import org.springframework.ai.chat.messages.Message;

import org.springframework.ai.chat.messages.UserMessage;

import org.springframework.ai.chat.messages.AssistantMessage;

import org.springframework.ai.chat.prompt.Prompt;

import org.springframework.ai.chat.prompt.SystemPromptTemplate;

import org.springframework.ai.document.Document;

import org.springframework.stereotype.Service;

import java.util.ArrayList;

import java.util.List;

import java.util.Map;

import java.util.stream.Collectors;

@Service

public class CustomerServiceAgent {

private final ChatClient chatClient;

private final ChatMemory chatMemory;

private final KnowledgeBaseService knowledgeBaseService;

public CustomerServiceAgent(ChatClient chatClient,

ChatMemory chatMemory,

KnowledgeBaseService knowledgeBaseService) {

this.chatClient = chatClient;

this.chatMemory = chatMemory;

this.knowledgeBaseService = knowledgeBaseService;

}

public String chat(String conversationId, String userInput) {

// 1. 读取历史

List history = chatMemory.get(conversationId, 10);

// 2. RAG 检索相关知识

List docs = knowledgeBaseService.search(userInput, 3);

String knowledgeContext = docs.stream()

.map(Document::getContent)

.collect(Collectors.joining("\n—\n"));

// 3. 系统提示词

String systemText = """

你是奥迪 4S 店售后智能客服「奥小迪」。

【当前可调用工具】

- getVehicleInfo:查询车辆基础信息

- getMaintenanceHistory:查询保养/维修历史

- getInsuranceQuote:生成保险报价

- getInsuranceExpiry:查询保险到期日

【知识库参考】

{knowledgeContext}

规则:

1. 先判断客户意图(保养/保险/故障/预约/投诉/其他);

2. 涉及车辆/工单/保险信息时,调用对应工具查询,不要编造;

3. 如果知识库和工具都无法回答,礼貌引导转人工或建议到店;

4. 语气专业亲切,不要过度承诺。

“”";

List messages = new ArrayList<>();

messages.add(new SystemPromptTemplate(systemText)

.create(Map.of(“knowledgeContext”, knowledgeContext)));

messages.addAll(history);

messages.add(new UserMessage(userInput));

// 4. 调用 LLM(Spring AI 自动处理 Tool Calling 循环)

String response = chatClient.prompt(new Prompt(messages))

.call()

.content();

// 5. 保存记忆

chatMemory.add(conversationId, new UserMessage(userInput));

chatMemory.add(conversationId, new AssistantMessage(response));

return response;

}

}


七、暴露 REST 接口

package com.aftersales.agent.controller;

import com.aftersales.agent.service.CustomerServiceAgent;

import org.springframework.web.bind.annotation.*;

@RestController

@RequestMapping("/api/customer-service")

public class ChatController {

private final CustomerServiceAgent customerServiceAgent;

public ChatController(CustomerServiceAgent customerServiceAgent) {

this.customerServiceAgent = customerServiceAgent;

}

@PostMapping("/chat")

public ChatResponse chat(@RequestBody ChatRequest request) {

String answer = customerServiceAgent.chat(request.conversationId(), request.message());

return new ChatResponse(answer);

}

public record ChatRequest(String conversationId, String message) {}

public record ChatResponse(String answer) {}

}


八、运行效果演示

请求示例

curl -X POST http://localhost:8080/api/customer-service/chat \

-H “Content-Type: application/json” \

-d ‘{

“conversationId”: “user_001”,

“message”: “我车牌浙A12345,5万公里,做小保养多少钱?”

}’

预期响应

您好,您的爱车是奥迪 Q5L 40 TFSI 时尚型,当前里程 52,000 公里。

根据奥迪保养手册,5 万公里属于常规保养周期,建议项目包括:

- 机油 / 机油滤清器

- 空气滤清器

- 空调滤清器

- 全车 23 项检测

参考费用约 2180 元,具体以到店实际检查为准。

请问需要帮您预约本周末的工位吗?


九、生产落地 checklist

现状 生产建议
向量库 InMemoryVectorStore 换 PGVector / Milvus / Redis Stack
Memory InMemoryChatMemory 换 Redis / JDBC ChatMemory
LLM OpenAI 可按需切换 Qwen / DeepSeek,配置 model 别名
工具权限 无鉴权 工具调用前校验用户身份和数据权限
MCP Server 本地 jar 模拟 对接真实 DMS / CRM / 保险系统的 MCP Server
监控 接入链路追踪、Token 消耗、延迟、Fallback 转人工
安全 Prompt 注入过滤、敏感数据脱敏、审计日志

十、总结 & 下期预告

今天我用 Spring AI 搭建了一个完整的汽车售后智能客服 Agent,覆盖了四大核心模块:

Memory:让 Agent 记住多轮对话上下文

RAG:让 Agent 基于车型手册和 FAQ 回答专业知识

Tool Calling:让 Agent 查询车辆、工单、保险等实时数据

MCP Client:让 Agent 以标准化协议对接外部系统

这个 Demo 虽然叫「客服」,但它的模块完全可以复用到诊断 Agent、报价 Agent、派工 Agent 中。理解了这套结构,你就掌握了把 AI 落到售后业务的核心能力。

第5期预告:ReAct 模式落地——用 Spring AI 手搓一个「诊断 Agent」,让它像老 SA 一样「听→想→查→下结论」。

互动话题:你们店的客服一天被问最多的三个问题是什么?如果是你,会优先把哪些问答做成 RAG?评论区聊聊,下期我可能会用你的真实场景写代码。


关于作者:34 岁,汽车电子技术专业出身,4 年奥迪/奔驰售后服务顾问 + 9 年 Java 架构与 AI Agent 开发经验,深谙汽车售后市场与车险理赔业务。用 Java 的确定性,驾驭 AI 的不确定性。在这里,Agent 不是概念,是车间里正在发生的事。