1、前置准备工作

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

springboot3.x以上的版本才包含AI的相关依赖。

在这里插入图片描述

deepseek-r1适用于纯对话功能的场景;deepseek-v3包含向量等功能适用于更复杂的场景。

2、扩展deepseek本地部署

下载Ollama后再去执行安装deepseek大模型

在这里插入图片描述

在这里插入图片描述

上面一是使用官方的阿里巴巴直接封装好的deepseek,如果是本地部署的,就不需要apikey,调用本地deepseek即可。

3、调用示例

在这里插入图片描述

ollama run deepseek-v3

在这里插入图片描述

流式响应:
在这里插入图片描述

4、日志拦截

在这里插入图片描述
在这里插入图片描述

可以用来记录对话信息。

5、实现对话记忆

public OpenAiController(ChatClient.Builder chatClientBuilder, ChatMemory chatMemory) {
    this.chatClient = chatClientBuilder
        .defaultAdvisors(  // 注册 AOP 拦截器(顾问)
            new PromptChatMemoryAdvisor(chatMemory),  // 记忆拦截器(核心)
            new LoggingAdvisor()  // 日志拦截器(辅助)
        )
        .build();
}
  • 关键作用:通过 defaultAdvisors 为 ChatClient 注册拦截器,其中 PromptChatMemoryAdvisor
    负责对话记忆管理。
   @GetMapping("/ai/generateStreamAsString")
   public Flux<String> generateStreamAsString(
       @RequestParam("message") String message,
       @RequestParam("sessionId") String sessionId  // 会话ID(必填)
   ) {
       return chatClient.prompt()
           .user(message)
           .param(CHAT_MEMORY_SESSION_ID_KEY, sessionId)  // 传递会话ID到拦截器
           .stream()
           .content();
   }
// 第42-43行:为当前请求动态配置记忆检索参数
Flux<String> content = chatClient.prompt().user(message)
    .advisors(a -> a.param(CHAT_MEMORY_RETRIEVE_SIZE_KEY, ...))  // 动态设置记忆检索数量
    .stream()
    .content();

这段代码的核心作用是 通过 ChatClient 的拦截器(Advisor)动态配置对话记忆的检索数量,具体是 设置每次从历史对话中提取的消息条数(控制记忆长度)。

6、设定用户的场景角色

在创建 ChatClient 时,通过 defaultSystemMessage 设定全局角色,所有对话都会使用该角色:

// OpenAiController 构造函数中配置
public OpenAiController(ChatClient.Builder chatClientBuilder, ChatMemory chatMemory) {
    this.chatClient = chatClientBuilder
        .defaultAdvisors(
            new PromptChatMemoryAdvisor(chatMemory),  // 记忆拦截器(之前的记忆功能)
            new LoggingAdvisor()
        )
        // === 全局角色设定:系统提示词 ===
        .defaultSystem("你是一名专业的网约车客服助手,只回答与订单、司机、路线相关的问题,语气亲切。")
        .build();
}

7、设置日期到回答模版

chatClient.prompt()
    .user(message)
    // 动态生成 System Prompt,注入当前日期
    .system(s -> s.param("current_date", LocalDate.now()).toString()) 
    .advisors(...)
    .stream()
    .content();

核心逻辑:

  • system(…) 方法:用于设置 系统提示词(System Prompt),定义 AI 模型的角色和行为。
  • s -> s.param(“current_date”, LocalDate.now()):
    • s 是 PromptTemplate 对象,支持通过 param(key, value) 动态注入参数。
    • 这里注入了名为 current_date 的参数,值为 LocalDate.now()(当前日期,如 2025-10-18)。
  • .toString():将填充参数后的 PromptTemplate 转换为最终的字符串提示词。

效果:
在这里插入图片描述

8、通过对话实现取消和确定订单功能

在这里插入图片描述
当前场景的核心是 通过大模型的Function-Call(工具调用)能力,实现对话交互与订单系统的自动化集成,即让AI直接调用后端API完成订单取消/确认操作,无需人工介入。

一、核心逻辑:Function-Call实现订单操作自动化

在这里插入图片描述

二、关键技术点:Function-Call实现步骤

  1. 定义Function-Call接口规范
    首先需告诉大模型“有哪些工具可用”(即订单操作API的参数和格式),例如“取消订单”接口的定义:
{
  "name": "cancel_order",
  "description": "取消用户的航空订单",
  "parameters": {
    "type": "object",
    "properties": {
      "orderId": { 
        "type": "string", 
        "description": "订单ID(必须从用户消息或订单列表中提取)" 
      },
      "userId": { 
        "type": "string", 
        "description": "用户ID(当前登录用户)" 
      }
    },
    "required": ["orderId", "userId"]
  }
}

json数据的定义需要遵循大模型官方文档去定义,比如你是单组还是多组命令,需要分别对应的接口参数去定义。

deepseek官方定义:
在这里插入图片描述

以下以通义千问来展示其他的json接口对比:

# 定义工具列表
tools = [
    {
        "type": "function",
        "function": {
            "name": "get_current_weather",
            "description": "当你想查询指定城市的天气时非常有用。",
            "parameters": {
                "type": "object",
                "properties": {
                    "location": {
                        "type": "string",
                        "description": "城市或县区,比如北京市、杭州市、余杭区等。",
                    }
                },
                "required": ["location"],
            },
        },
    },
]
  1. 前端:传递订单列表作为Function-Call上下文
    与传统方式类似,前端需将当前订单列表数据(如订单ID、用户ID、状态)传递给大模型,但需额外标记为“可用于工具调用的上下文”:
// 前端发送消息时,携带订单上下文和Function-Call配置
const sendMessage = async (userInput) => {
  const response = await fetch('/api/ai/process', {
    method: 'POST',
    body: JSON.stringify({
      message: userInput,
      // 1. 订单列表上下文(供AI提取orderId)
      orderContext: currentOrders,
      // 2. 告知大模型可调用的工具(Function-Call定义)
      functions: [cancelOrderFunctionDef, confirmOrderFunctionDef]
    })
  });
  const result = await response.json();
  addMessageToChat('assistant', result.content);
  if (result.refresh) refreshOrderList(); // 操作成功后刷新列表
};
  1. 后端:大模型调用Function-Call执行订单操作
    以Spring AI(或OpenAI SDK)为例,后端需处理大模型的工具调用请求,解析参数后调用订单系统API,再将结果返回给大模型:

步骤1:大模型返回Function-Call指令(工具调用请求)
当用户输入“取消2月14日上海到南京的订单”,大模型会识别意图并返回工具调用格式(而非自然语言):

{
  "function_call": {
    "name": "cancel_order",
    "parameters": {
      "orderId": "102", // 从orderContext中提取的订单ID
      "userId": "user_123" // 当前登录用户ID
    }
  }
}

步骤2:后端解析并执行API调用

@Service
public class AiFunctionService {
    @Autowired
    private RestTemplate restTemplate; // 用于调用订单系统API

    // 处理大模型的Function-Call请求
    public String processFunctionCall(String functionName, Map<String, Object> params) {
        // 1. 根据函数名路由到具体操作
        if ("cancel_order".equals(functionName)) {
            return cancelOrder(
                params.get("orderId").toString(),
                params.get("userId").toString()
            );
        } else if ("confirm_order".equals(functionName)) {
            return confirmOrder(params.get("orderId").toString());
        }
        return "未知函数调用";
    }

    // 调用订单系统的取消订单API
    private String cancelOrder(String orderId, String userId) {
        try {
            // 调用后端订单服务(如http://order-system/api/cancel)
            ResponseEntity<String> response = restTemplate.postForEntity(
                "http://order-system/api/orders/" + orderId + "/cancel",
                new HttpEntity<>(userId),
                String.class
            );
            return response.getBody(); // 返回"订单已取消"或错误信息
        } catch (Exception e) {
            return "取消失败:" + e.getMessage();
        }
    }
}

步骤3:将API结果返回给大模型,生成自然语言回复
后端执行API后,将结果(如“订单已取消”)返回给大模型,大模型再用自然语言整理后回复用户:

// 大模型处理流程(伪代码)
String userMessage = "取消2月14日上海到南京的订单";
String orderContext = "..."; // 前端传递的订单列表
List<FunctionDef> functions = Arrays.asList(cancelOrderDef);

// 1. 第一次调用大模型:获取Function-Call指令
FunctionCall call = aiClient.generateFunctionCall(userMessage, orderContext, functions);

// 2. 执行函数调用
String apiResult = functionService.processFunctionCall(call.getName(), call.getParams());

// 3. 第二次调用大模型:将API结果转换为自然语言
String aiReply = aiClient.generateResponse(apiResult, userMessage);

// 最终回复用户:"您2月14日上海到南京的订单(订单号102)已取消,退款将在24小时内到账。"
  1. 前端:自动刷新订单列表
    大模型返回操作结果后,前端无需用户交互,直接刷新订单列表(如将订单102的状态从“已预订”更新为“已取消”):
// 接收后端返回的结果,判断是否需要刷新列表
const handleAiResponse = (result) => {
  addMessageToChat('assistant', result.content);
  if (result.functionCall?.name === 'cancel_order' && result.success) {
    refreshOrderList(); // 自动刷新列表
  }
};

9、问题总结

1、如何通过提示词,来让模型通过对话帮你或者用户信息,航班信息等。

在这里插入图片描述

2、客户输入信息不完整,如何实现客户提示信息用户确认信息

在这里插入图片描述

@Service
public class IntentService {
    // 退订意图所需的关键信息字段(至少需满足一个)
    private static final List<String> REQUIRED_FIELDS = Arrays.asList("orderId", "name", "date");

    public IntentResult detectRefundIntent(String userMessage, List<Order> userOrders) {
        // 1. 识别意图:判断是否为"退订"
        String intent = aiClient.classifyIntent(userMessage, Arrays.asList("refund", "inquiry", "other"));
        if (!"refund".equals(intent)) {
            return new IntentResult("inquiry", "未识别到退订意图");
        }

        // 2. 提取用户输入中的订单信息(通过NLP工具)
        Map<String, String> extractedInfo = nlpTool.extractOrderInfo(userMessage);
        // 示例:userMessage="退订101徐庶" → extractedInfo={"orderId":"101", "name":"徐庶"}

        // 3. 检测信息缺失:判断是否满足退订所需字段
        List<String> missingFields = REQUIRED_FIELDS.stream()
            .filter(field -> !extractedInfo.containsKey(field))
            .collect(Collectors.toList());

        if (!missingFields.isEmpty()) {
            // 信息缺失:生成追问话术(如"请提供姓名或订单号")
            String prompt = generatePrompt(missingFields);
            return new IntentResult("refund", prompt, false); // isComplete=false
        } else {
            // 信息完整:匹配用户订单列表中的唯一订单
            Order matchedOrder = matchOrder(extractedInfo, userOrders);
            if (matchedOrder == null) {
                return new IntentResult("refund", "未找到匹配订单,请确认信息", false);
            }
            return new IntentResult("refund", "确认退订订单吗?", true, matchedOrder.getId());
        }
    }

    // 生成追问话术(如缺失"name"和"orderId",则提示"请提供订单号或姓名")
    private String generatePrompt(List<String> missingFields) {
        if (missingFields.contains("orderId") && missingFields.contains("name")) {
            return "请提供预订号或姓名,以便确认订单";
        } else if (missingFields.contains("date")) {
            return "请提供订单日期,避免操作错误";
        }
        return "请补充订单信息";
    }
}

实现代码如上,让ai通过对话提取信息,如果提取不到信息,则ai回复用户请提供预订号或姓名,以便确认订单这个询问,用户回答后,提取到订单信息后才触发取消订单的方法去取消订单。

10、利用RAG技术增强行业知识支持

1、使用背景

比如客户询问,各种舱取消订单需要多少费用?这个是ai无法回答的,需要通过RAG检索文档来获取答案让ai再回答给客户更精准的回答。

2、实现流程

在这里插入图片描述

3、RAG核心实现步骤

1. 文档预处理(构建知识库)

在这里插入图片描述
预处理代码(Python/Java):

// 文档分割工具(使用LangChain或Spring AI的DocumentSplitter)
List<Document> splitDocuments() {
    // 1. 读取文档内容
    String content = FileUtils.readFileToString(new File("terms-of-service.txt"), StandardCharsets.UTF_8);
    
    // 2. 按段落/标题分割为小文档(避免长文本检索精度低)
    RecursiveCharacterTextSplitter splitter = new RecursiveCharacterTextSplitter(
        300,  // 每个片段最大字符数
        50    // 片段重叠字符数(保持上下文连贯)
    );
    return splitter.splitText(content).stream()
        .map(text -> new Document(text, Map.of("source", "terms-of-service.txt"))) // 添加元数据
        .collect(Collectors.toList());
}

// 向量化存储(使用Embedding模型+向量数据库)
void initVectorStore() {
    List<Document> documents = splitDocuments();
    // 1. 将文档片段向量化(如使用OpenAI Embedding或华为云Embedding)
    List<Embedding> embeddings = embeddingClient.embedAll(documents.stream()
        .map(Document::getContent)
        .collect(Collectors.toList()));
    
    // 2. 存入向量数据库(如Redis/ Pinecone/ 华为云GeminiDB)
    for (int i = 0; i < documents.size(); i++) {
        vectorStore.add(
            documents.get(i).getContent(), 
            embeddings.get(i).getVector(), 
            documents.get(i).getMetadata()
        );
    }
}
2. 检索相关条款(用户提问时触发)

当用户提问时,先将问题向量化,然后从向量数据库中 检索最相似的文档片段(如前3条)。

检索代码示例:

@Service
public class RAGService {
    @Autowired
    private VectorStore vectorStore; // 向量数据库
    @Autowired
    private EmbeddingClient embeddingClient; // Embedding模型客户端

    // 检索与用户问题相关的条款
    public List<Document> retrieveRelevantTerms(String userQuestion) {
        // 1. 将用户问题向量化
        Embedding questionEmbedding = embeddingClient.embed(userQuestion);
        
        // 2. 向量数据库相似度检索(返回Top 3最相关片段)
        return vectorStore.similaritySearch(
            questionEmbedding.getVector(), 
            3,  // 检索数量
            Map.of("source", "terms-of-service.txt") // 仅检索条款文档
        );
    }
}

在这里插入图片描述

3. 生成回答(结合检索结果)

将用户问题、检索到的条款片段拼接为 带上下文的Prompt,送入大模型生成回答,确保回答引用条款内容。

大模型调用代码(Spring AI):

@Service
public class AiService {
    @Autowired
    private ChatClient chatClient;
    @Autowired
    private RAGService ragService;

    public String generateAnswerWithRAG(String userQuestion) {
        // 1. 检索相关条款
        List<Document> relevantTerms = ragService.retrieveRelevantTerms(userQuestion);
        String context = relevantTerms.stream()
            .map(Document::getContent)
            .collect(Collectors.joining("\n")); // 拼接条款片段
        
        // 2. 构建带RAG上下文的Prompt
        String prompt = String.format("""
            你是航空客服助手,请根据以下条款回答用户问题。
            条款内容:
            %s
            
            用户问题:%s
            回答必须引用条款中的具体内容,如"根据条款X:..."。
            """, context, userQuestion);
        
        // 3. 调用大模型生成回答
        return chatClient.prompt()
            .system("严格按照条款内容回答,不编造信息。")
            .user(prompt)
            .call()
            .getResult()
            .getOutput()
            .getContent();
    }
}

在这里插入图片描述

完整调用:
在这里插入图片描述

问题扩展:
如果用户回答的很模糊,没有指定什么舱位,那这个就可以根据上面提到过的,信息缺失的话,可以通过两种方式处理:

  • 1、ai引导客户回答,提示您指的是什么舱位?经济舱还是豪华舱?
  • 2、检索上下文,通过对话提取客户的订单信息,来判断用户的舱位信息

11、项目整体流程解析

在这里插入图片描述

用户问题→Spring-AI应用→RAG检索资料→拼接Prompt→大模型生成回答→返回用户。

其中,RAG模块(资料→Embedding→向量数据库→检索) 是“知识增强”的关键,确保AI回答“有据可查”,尤其适用于规则密集型场景(如航空客服、金融咨询、法律问答等)。你的项目正是通过这个流程实现了“AI客服精准回答改期/取消费用”的功能。

Embedding→向量数据库:

// 向量化存储(使用Embedding模型+向量数据库)
void initVectorStore() {
    List<Document> documents = splitDocuments();
    // 1. 文档向量化(Embedding)
    List<Embedding> embeddings = embeddingClient.embedAll(documents.stream()
        .map(Document::getContent)
        .collect(Collectors.toList()));
    
    // 2. 存入向量数据库(vectorStore是向量数据库的抽象)
    for (int i = 0; i < documents.size(); i++) {
        vectorStore.add(
            documents.get(i).getContent(), 
            embeddings.get(i).getVector(), 
            documents.get(i).getMetadata()
        );
    }
}

// 检索相关条款(向量数据库相似度搜索)
public List<Document> retrieveRelevantTerms(String userQuestion) {
    // 1. 问题向量化
    Embedding questionEmbedding = embeddingClient.embed(userQuestion);
    
    // 2. 向量数据库检索(返回Top 3相似片段)
    return vectorStore.similaritySearch(
        questionEmbedding.getVector(), 
        3, 
        Map.of("source", "terms-of-service.txt")
    );
}

向量数据库:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 构建RedisVectorStoreConfig配置对象
  • 设置向量索引名称为"my-vector-index"
  • 使用RedisVectorStore作为向量存储实现

在这里插入图片描述
在这里插入图片描述

阿里百炼官方文档:
https://help.aliyun.com/zh/model-studio/what-is-model-studio?spm=a2c4g.11174283.0.i0
DeepSeek官方文档:
https://api-docs.deepseek.com/zh-cn/guides/function_calling

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐