AI 大模型统一集成|Spring AI + DeepSeek 实战接入指南
🌟 在这系列文章中,我们将一起探索如何搭建一个支持大模型集成项目 ChatAI 的开发过程,从 架构设计 到 代码实战,逐步搭建一个支持 多种大模型(GPT-4、DeepSeek 等) 的 一站式大模型集成与管理平台,并集成 认证中心、微服务、流式对话 等核心功能。
本文将结合 Spring AI 与大语言模型,演示如何借助 Tool Calling 实现系统 API 的调用,并支持多轮对话中的上下文记忆处理,从而构建更智能、实用的企业级 AI 应用。并成功打通 DeepSeek 大模型,实现统一的 AI 接入方案。
✨ 为什么选择 Spring AI + DeepSeek?
在多模型百花齐放的时代,OpenAI、DeepSeek、Moonshot、Claude 各有千秋,我们如何实现统一接入、灵活切换、快速调用?
Spring 团队开源的 Spring AI 正是为此而生,它提供了统一的编程模型,支持主流 AI 平台(OpenAI、Azure、HuggingFace、DeepSeek 等)。
而 DeepSeek 则是近年来在中文语义、代码能力方面表现优异的国产大模型,支持 OpenAI 接口风格,适配性强。
⚙️ Spring AI 工具调用
Spring AI 工具调用与不同 AI 模型一起使用的更多信息,请遵循工具调用文档。
工具元数据注入
在发起 Chat Request 时,将工具描述(name/description)、参数结构(input schema)等元数据封装至请求体,建立大模型的工具调用能力基线。每个工具定义都包含输入参数的名称、描述和方案。
模型决策响应
当模型决定调用工具时,它会发送一个响应,其中包含工具名称和输入参数,这些参数在定义的架构之后建模。
大模型根据上下文推理生成工具调用指令(tool_calls字段),返回包含选定工具名称及结构化参数的中间响应。
服务端路由执行
应用程序负责使用工具名称来识别并使用提供的输入参数执行工具。
Spring AI 模块解析工具调用指令,通过服务发现机制定位目标工具实例,注入参数并触发同步/异步执行。
执行结果标准化
工具调用的结果由应用程序处理。
工具返回原始执行结果后,系统进行数据类型校验、异常捕获和JSON序列化处理,生成模型可解析的标准化数据结构。
上下文增强推理
应用程序将工具调用结果发送回模型。
将标准化结果作为新增上下文(tool_outputs)回传大模型,触发基于增强上下文的二次推理流程。
终端响应生成
模型综合初始请求与工具执行结果,生成最终自然语言响应,完成工具增强的对话闭环。
🔧 环境准备
✅ 依赖版本
- Spring Boot:
3.4.1
- Spring AI:
1.0.0-M6
- JDK:
21
✅ Maven 依赖配置
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>3.4.1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
<version>1.0.0-M6</version>
</dependency>
虽然依赖名为 openai
,但因为 DeepSeek 模拟了 OpenAI 接口协议,所以可以通用。
⚙️ DeepSeek 接口说明
DeepSeek 官方接口文档:https://api-docs.deepseek.com/zh-cn/
DeepSeek 官方提供的 API 接口地址:
https://api.deepseek.com
🛠️ Spring AI 配置 DeepSeek
在 application.yml
中添加如下配置:
spring:
ai:
openai:
api-key: sk-xxx # deepseek 密钥
base-url: https://api.deepseek.com
chat:
options:
model: deepseek-reasoner
📦 构建一个 Spring AI 工具调用
SpringAiAiInvoke
代码很简单,由于引入 spring ai 的 stater 之后按照模型配置接入即可。然后注入一个 ChatModel 。我们测试接口可以将实现类继承接口 CommandLineRunner 可在 SpringBoot 启动时自动执行方便查看效果。
import jakarta.annotation.Resource;
import org.springframework.ai.chat.messages.AssistantMessage;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
/**
* Spring AI 框架调用 AI 大模型(Deepseek)
*/
@Component
public class SpringAiAiInvoke implements CommandLineRunner {
@Resource
private ChatModel deepseekChatModel;
@Override
public void run(String... args) throws Exception {
AssistantMessage assistantMessage = deepseekChatModel.call(new Prompt("你好!"))
.getResult()
.getOutput();
System.out.println("DeepSeek大模型调用结果:" + assistantMessage.getText());
}
}
执行测试结果:
📦 Spring AI 上下文记忆
大型语言模型 (LLM) 是无状态的,这意味着它们不会保留有关以前交互的信息。当你希望在多个交互中维护上下文或状态时,这可能是一个限制。为了解决这个问题,Spring AI 提供了聊天内存功能,允许你在与 LLM 的多次交互中存储和检索信息。
可以基于 ChatMemory 抽象实现各种类型的内存以支持不同的使用案例。 消息的底层存储由ChatMemoryRepository,其唯一职责是存储和检索消息。这取决于ChatMemoryimplementation 来决定要保留哪些消息以及何时删除它们。策略示例可能包括保留最后 N 条消息、将消息保留一段时间或将消息保持在某个Tokens限制内。
其中 MessageChatMemoryAdvisor 实现聊天记忆,InMemoryChatMemory 为 SpringAI 自带的实现(基于内存)。可以使用 CHAT_MEMORY_CONVERSATION_ID_KEY 参数指定对话ID,不同的会话ID用于隔离记忆。
@Component
@Slf4j
public class AgentApp {
private final ChatClient chatClient;
private static final String SYSTEM_PROMPT = "你是一个专业的天气助手,必须用中文回答,回答需包含温度、湿度和风力。";
/**
* 初始化 ChatClient
*
* @param deepseekChatModel
*/
public AgentApp(ChatModel deepseekChatModel) {
// 初始化基于内存的对话记忆
ChatMemory chatMemory = new InMemoryChatMemory();
chatClient = ChatClient.builder(deepseekChatModel)
// .defaultSystem(SYSTEM_PROMPT)
.defaultAdvisors(
new MessageChatMemoryAdvisor(chatMemory),
// 自定义日志 Advisor,可按需开启
new LoggerAdvisor()
// // 自定义推理增强 Advisor,可按需开启
// ,new ReReadingAdvisor()
)
.build();
}
/**
* AI 基础对话(支持多轮对话记忆)
*
* @param message
* @param chatId
* @return
*/
public String doChat(String message, String chatId) {
ChatResponse chatResponse = chatClient
.prompt()
.user(message)
.advisors(spec -> spec.param(CHAT_MEMORY_CONVERSATION_ID_KEY, chatId)
.param(CHAT_MEMORY_RETRIEVE_SIZE_KEY, 10))
.call()
.chatResponse();
String content = chatResponse.getResult().getOutput().getText();
log.info("content: {}", content);
return content;
}
/**
* AI 基础对话(支持多轮对话记忆)
*
* DeepSeek Reasoner 严格要求 system 消息必须是第一条 显示声明
*
* @param message
* @param chatId
* @return
*/
public String doChatDeepSeek(String message, String chatId) {
ChatResponse chatResponse = chatClient
.prompt()
// .system(SYSTEM_PROMPT)
.user(message)
.advisors(spec -> spec.param(CHAT_MEMORY_CONVERSATION_ID_KEY, chatId)
.param(CHAT_MEMORY_RETRIEVE_SIZE_KEY, 10))
.call()
.chatResponse();
String content = chatResponse.getResult().getOutput().getText();
log.info("content: {}", content);
return content;
}
}
LoggerAdvisor 可以打印大模型内容。这里补充一下 Advisor 的作用,可以理解为作为模型调用前或者调用后的拦截器。
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.advisor.api.*;
import org.springframework.ai.chat.model.MessageAggregator;
import reactor.core.publisher.Flux;
/**
* 自定义日志 Advisor
* 打印 INFO 级别日志、只输出单次用户提示词和 AI 回复的文本
*/
@Slf4j
public class LoggerAdvisor implements CallAroundAdvisor, StreamAroundAdvisor {
@Override
public String getName() {
return this.getClass().getSimpleName();
}
@Override
public int getOrder() {
return 0;
}
private AdvisedRequest before(AdvisedRequest request) {
log.info("AI Request: {}", request.userText());
return request;
}
private void observeAfter(AdvisedResponse advisedResponse) {
log.info("AI Response: {}", advisedResponse.response().getResult().getOutput().getText());
}
@Override
public AdvisedResponse aroundCall(AdvisedRequest advisedRequest, CallAroundAdvisorChain chain) {
advisedRequest = before(advisedRequest);
AdvisedResponse advisedResponse = chain.nextAroundCall(advisedRequest);
observeAfter(advisedResponse);
return advisedResponse;
}
@Override
public Flux<AdvisedResponse> aroundStream(AdvisedRequest advisedRequest, StreamAroundAdvisorChain chain) {
advisedRequest = before(advisedRequest);
Flux<AdvisedResponse> advisedResponses = chain.nextAroundStream(advisedRequest);
return new MessageAggregator().aggregateAdvisedResponse(advisedResponses, this::observeAfter);
}
}
接下来使用 Junit 测试看看效果。
@SpringBootTest
class AgentAppTest {
@Resource
private AgentApp agentApp;
@Test
void doChat() {
String chatId = UUID.randomUUID().toString();
// 第一轮
String message = "你好,我是小李。";
String answer = agentApp.doChatDeepSeek(message, chatId);
// 第二轮
message = "今天天气如何?";
answer = agentApp.doChatDeepSeek(message, chatId);
Assertions.assertNotNull(answer);
// 第三轮
message = "你知道叫什么来着?刚跟你说过。";
answer = agentApp.doChatDeepSeek(message, chatId);
Assertions.assertNotNull(answer);
}
}
自测效果:
通过连续对话3次,最后一次提问此前给过的信息,可以看到大模型的结果中正常回答,说明聊天记忆功能已生效。
💡 注意事项
DeepSeek 使用 OpenAI 接口兼容层
- 实际上是伪装成 OpenAI,所以你必须手动设置
base-url
和api-key
。 - 模型名称建议使用:
deepseek-chat
或deepseek-coder
(根据模型需求场景)。
🔄 后续如何扩展?
- ✅ 替换为 Claude、Gemini、Moonshot 等其他模型,只需改配置即可;
- ✅ 接入 RAG(支持 Embedding);
- ✅ 搭建前端 Chat UI(如:Cursor、NextChat、ChatUI);
- ✅ 封装为微服务,支持多用户、多模型切换。
📢 你对这个项目感兴趣吗?欢迎 Star & 关注! 📌 GitHub 项目地址 🌟 你的支持是我持续创作的动力,欢迎点赞、收藏、分享!