1. 概述
現代 Web 應用程序越來越多地與大型語言模型 (LLM) 集成,以構建解決方案。
DeepSeek 是一家中國 AI 研究公司,致力於開發強大的 LLM,並最近憑藉其 DeepSeek-V3 和 DeepSeek-R1 模型,在 AI 領域掀起了波瀾。 該模型及其響應,展示了其思維鏈 (CoT),這讓我們瞭解 AI 模型如何解釋和處理給定的提示。
在本教程中,我們將探索將 DeepSeek 模型與 Spring AI 集成。 我們將構建一個簡單的聊天機器人,能夠進行多輪文本對話。
2. 依賴項和配置
有多種方法可以將 DeepSeek 模型集成到我們的應用程序中,並在本節中,我們將討論一些流行的選項。我們可以選擇最適合我們需求的那個。
2.1. 使用 OpenAI API
DeepSeek 模型與 OpenAI API 兼容,可通過任何 OpenAI 客户端或庫進行訪問。
我們首先將 Spring AI 的 OpenAI starter 依賴項 添加到項目的 pom.xml 文件中:
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
<version>1.0.0-M6</version>
</dependency>由於當前版本 1.0.0-M6 是里程碑發佈,我們需要在 pom.xml 中添加 Spring Milestones 倉庫:
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>此倉庫用於發佈里程碑版本,與標準 Maven Central 倉庫不同。 無論我們選擇哪種配置選項,都需要添加此里程碑倉庫。
接下來,讓我們在 DeepSeek API 密鑰 和 聊天模型 中配置 application.yaml 文件:
spring:
ai:
openai:
api-key: ${DEEPSEEK_API_KEY}
chat:
options:
model: deepseek-reasoner
base-url: https://api.deepseek.com
embedding:
enabled: false此外,我們還指定了 DeepSeek API 的基本 URL 以及禁用嵌入式向量,因為 DeepSeek 目前沒有提供與嵌入式向量兼容的模型。
在配置上述屬性時,Spring AI 會自動創建一個類型為 ChatModel 的 Bean,從而使我們能夠與指定的模型進行交互。 我們將在教程後續使用它來定義幾個額外的 Bean,用於我們的聊天機器人。
2.2. 使用 Amazon Bedrock Converse API
或者,我們可以使用 Amazon Bedrock Converse API 將 DeepSeek R1 模型集成到我們的應用程序中。
為了配合這一配置步驟,我們需要一個 活躍的 AWS 賬户。 DeepSeek-R1 模型可通過 Amazon Bedrock Marketplace 提供,並可使用 Amazon SageMaker 進行託管。 此 部署指南 可供參考以進行設置。
讓我們從添加 Bedrock Converse starter 依賴項 到我們的 pom.xml 文件中開始:
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bedrock-converse-spring-boot-starter</artifactId>
<version>1.0.0-M6</version>
</dependency>接下來,為了與 Amazon Bedrock 交互,我們需要在 application.yaml文件中配置我們的 AWS 憑據,用於身份驗證,以及 DeepSeek 模型託管的區域:
spring:
ai:
bedrock:
aws:
region: ${AWS_REGION}
access-key: ${AWS_ACCESS_KEY}
secret-key: ${AWS_SECRET_KEY}
converse:
chat:
options:
model: arn:aws:sagemaker:REGION:ACCOUNT_ID:endpoint/ENDPOINT_NAME我們使用 ${} 屬性佔位符從環境變量中加載我們的屬性值。
此外,我們指定了 DeepSeek 模型託管的 SageMaker 端點 URL ARN。我們應該記住用實際值替換 REGION、ACCOUNT_ID 和 ENDPOINT_NAME 這些佔位符。
最後,為了與模型交互,我們需要將以下 IAM 策略分配給我們在應用程序中配置的 IAM 用户。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "bedrock:InvokeModel",
"Resource": "arn:aws:bedrock:REGION:ACCOUNT_ID:marketplace/model-endpoint/all-access"
}
]
}再次提醒,我們應該將 REGION 和 ACCOUNT_ID 佔位符替換為實際的值,在 Resource 的 ARN 中。
2.3. 本地設置與 Ollama
對於本地開發和測試,我們可以通過 Ollama 運行 DeepSeek 模型,Ollama 是一款開源工具,允許我們在本地機器上運行 LLM。
請在項目中的 pom.xml 文件中導入必要的依賴項:
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-ollama-spring-boot-starter</artifactId>
<version>1.0.0-M6</version>
</dependency>Ollama starter 依賴項有助於我們與 Ollama 服務建立連接。
接下來,讓我們在 application.yaml 文件中配置我們的聊天模型:
spring:
ai:
ollama:
chat:
options:
model: deepseek-r1
init:
pull-model-strategy: when_missing
embedding:
enabled: false這裏,我們指定了 deepseek-r1 模型,但我們也可以嘗試使用不同的可用模型,如通過 搜索 找到的。
此外,我們將 pull-model-strategy 設置為 when_missing。 這確保 Spring AI 在本地沒有模型時,會拉取指定的模型。
Spring AI 默認情況下,當在 localhost 上運行時,會自動連接到 Ollama,端口為 11434。但是,我們可以使用 spring.ai.ollama.base-url 屬性來覆蓋連接 URL。 另一種方法是使用 Testcontainers 設置 Ollama 服務。
這裏,Spring AI 將會自動為我們創建 ChatModel Bean。 如果我們同時擁有 OpenAI API、Bedrock Converse 和 Ollama 依賴項,我們可以使用 qualifier openAiChatModel, bedrockProxyChatModel, 或 ollamaChatModel 來引用我們想要使用的特定 Bean。
3. 構建聊天機器人
現在我們已經討論了各種配置選項,讓我們使用配置好的 DeepSeek 模型構建一個簡單的聊天機器人。
3.1. 定義聊天機器人 Bean
讓我們首先定義我們聊天機器人所需的 Bean:
@Bean
ChatMemory chatMemory() {
return new InMemoryChatMemory();
}
@Bean
ChatClient chatClient(ChatModel chatModel, ChatMemory chatMemory) {
return ChatClient
.builder(chatModel)
.defaultAdvisors(new MessageChatMemoryAdvisor(chatMemory))
.build();
}首先,我們使用 InMemoryChatMemory 實現定義一個 ChatMemory Bean,它將聊天曆史記錄存儲在內存中,以保持對話上下文。
接下來,我們使用 ChatModel 和 ChatMemory Bean 創建一個 ChatClient Bean。 ChatClient 類作為我們與配置好的 DeepSeek 模型交互的主要入口點。
3.2. 創建自定義 StructuredOutputConverter
如前所述,DeepSeek-R1 模型的響應包括其 CoT,並且我們得到響應的格式如下:
<think>
Chain of Thought
</think>
Answer不幸的是,由於這種獨特的格式,當前版本的 Spring AI 中所有的結構化輸出轉換器在嘗試將響應解析為 Java 類時都會失敗並拋出異常。
因此,讓我們創建一個自定義的 StructuredOutputConverter 實現,以分別解析 AI 模型的答案和 CoT:
record DeepSeekModelResponse(String chainOfThought, String answer) {
}
class DeepSeekModelOutputConverter implements StructuredOutputConverter<DeepSeekModelResponse> {
private static final String OPENING_THINK_TAG = "<think>";
private static final String CLOSING_THINK_TAG = "</think>";
@Override
public DeepSeekModelResponse convert(@NonNull String text) {
if (!StringUtils.hasText(text)) {
throw new IllegalArgumentException("Text cannot be blank");
}
int openingThinkTagIndex = text.indexOf(OPENING_THINK_TAG);
int closingThinkTagIndex = text.indexOf(CLOSING_THINK_TAG);
if (openingThinkTagIndex != -1 && closingThinkTagIndex != -1 && closingThinkTagIndex > openingThinkTagIndex) {
String chainOfThought = text.substring(openingThinkTagIndex + OPENING_THINK_TAG.length(), closingThinkTagIndex);
String answer = text.substring(closingThinkTagIndex + CLOSING_THINK_TAG.length());
return new DeepSeekModelResponse(chainOfThought, answer);
} else {
logger.debug("No <think> tags found in the response. Treating entire text as answer.");
return new DeepSeekModelResponse(null, text);
}
}
}在這裏,我們的轉換器從 AI 模型的響應中提取 chainOfThought 和 answer,並將其返回為 DeepSeekModelResponse 記錄。
如果 AI 響應不包含 <think> 標籤,則將整個響應視為答案。 這確保了與不包含 CoT 在響應中的其他 DeepSeek 模型兼容。
3.3. 實現服務層
有了我們的配置就緒,讓我們創建一個 ChatbotService 類。我們將注入我們之前定義的 ChatClient Bean,以便與指定的 DeepSeek 模型進行交互。
不過,首先讓我們定義兩個簡單的記錄來表示聊天請求和響應:
record ChatRequest(@Nullable UUID chatId, String question) {}
record ChatResponse(UUID chatId, String chainOfThought, String answer) {}ChatRequest 包含用户的 問題 以及可選的 chatId,用於標識持續進行的對話。
同樣,ChatResponse 包含 chatId,以及聊天機器人生成的 ChainOfThought 和 答案。
現在,讓我們來實現預期的功能:
ChatResponse chat(ChatRequest chatRequest) {
UUID chatId = Optional
.ofNullable(chatRequest.chatId())
.orElse(UUID.randomUUID());
DeepSeekModelResponse response = chatClient
.prompt()
.user(chatRequest.question())
.advisors(advisorSpec ->
advisorSpec
.param("chat_memory_conversation_id", chatId))
.call()
.entity(new DeepSeekModelOutputConverter());
return new ChatResponse(chatId, response.chainOfThought(), response.answer());
}如果傳入的請求中不包含 chatId, 我們會生成一個新的。 這允許用户開始新的對話或繼續之前的對話。
我們將用户的 question傳遞給 chatClient Bean,並將 chat_memory_conversation_id參數設置為已解析的 chatId ,以保持對話歷史。
最後,我們創建自定義的 DeepSeekModelOutputConverter 類實例並將其傳遞給 entity() 方法,以將 AI 模型響應解析為 DeepSeekModelResponse 記錄。然後,我們從中提取 chainOfThought 和 answer 並將其與 chatId 一起返回。
3.4. 與我們的聊天機器人交互
現在我們已經實現了服務層,讓我們在其之上暴露一個 REST API:
@PostMapping("/chat")
ResponseEntity<ChatResponse> chat(@RequestBody ChatRequest chatRequest) {
ChatResponse chatResponse = chatbotService.chat(chatRequest);
return ResponseEntity.ok(chatResponse);
}讓我們使用 HTTPie CLI 調用上述 API 端點並啓動新的對話:
http POST :8080/chat question="What was the name of Superman's adoptive mother?"在這裏,我們向聊天機器人發送一個簡單的 問題,讓我們看看我們收到什麼樣的回覆:
響應包含一個唯一的 chatId,以及聊天機器人的 chainOfThought 和 answer,用於回答我們的問題。 我們可以通過 chainOfThought 屬性看到 AI 模型如何推理和處理給定的提示。
接下來,我們使用上一次響應中的 chatId 發送一個後續 問題:
http POST :8080/chat question="Which bald billionaire hates him?" chatId="1e3c151f-cded-4f10-a5fc-c52c5952411c"讓我們看看聊天機器人是否能保持我們的對話上下文並提供相關的回覆:
正如我們所見,聊天機器人確實保持了對話上下文。 chatId 保持不變,表明後續 answer 是同一對話的延續。
4. 結論
在本文中,我們探討了使用 DeepSeek 模型與 Spring AI 的結合。
我們討論了將 DeepSeek 模型集成到應用程序中的各種選項,包括直接使用 OpenAI API 的方案(因為 DeepSeek 與其兼容)以及與 Amazon Bedrock Converse API 的協作方案。此外,我們還設置了一個本地測試環境,使用了 Ollama。
然後,我們構建了一個簡單的聊天機器人,能夠進行多輪文本對話,並使用自定義的 StructuredOutputConverter 實現來提取 AI 模型響應中的思維鏈和答案。