Spring集成DeepSeek方法2:使用Spring AI OpenAI模块

概述

本文介绍如何使用Spring AI的OpenAI模块来集成DeepSeek API。DeepSeek提供了与OpenAI兼容的API接口,因此我们可以直接使用Spring AI的spring-ai-openai模块,只需修改API基础URL即可。这种方法简化了开发,利用了Spring AI的成熟功能。

前置条件

  • Java 17+
  • Spring Boot 3.2+
  • DeepSeek API密钥(从 https://platform.deepseek.com 获取)
  • Maven或Gradle构建工具

项目依赖

Maven (pom.xml)

<dependencies>
    <!-- Spring Boot Starter Web -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    
    <!-- Spring AI OpenAI Starter -->
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-openai-spring-boot-starter</artifactId>
        <version>1.0.0-M4</version>
    </dependency>
    
    <!-- Spring AI BOM (用于管理版本) -->
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.ai</groupId>
                <artifactId>spring-ai-bom</artifactId>
                <version>1.0.0-M4</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    
    <!-- Lombok (可选) -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>

Gradle (build.gradle)

plugins {
    id 'java'
    id 'org.springframework.boot' version '3.2.0'
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.ai:spring-ai-openai-spring-boot-starter:1.0.0-M4'
    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'
}

配置

application.yml

spring:
  ai:
    openai:
      # DeepSeek API基础URL
      base-url: https://api.deepseek.com
      # DeepSeek API密钥
      api-key: ${DEEPSEEK_API_KEY:your-api-key-here}
      # 默认模型
      chat:
        options:
          model: deepseek-chat
          temperature: 0.7
          max-tokens: 4096

application.properties

# DeepSeek API配置
spring.ai.openai.base-url=https://api.deepseek.com
spring.ai.openai.api-key=${DEEPSEEK_API_KEY:your-api-key-here}

# 聊天模型配置
spring.ai.openai.chat.options.model=deepseek-chat
spring.ai.openai.chat.options.temperature=0.7
spring.ai.openai.chat.options.max-tokens=4096

基础使用

简单聊天示例

package com.example.deepseek.controller;

import org.springframework.ai.chat.ChatClient;
import org.springframework.ai.chat.ChatResponse;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/chat")
public class SimpleChatController {
    
    private final ChatClient chatClient;
    
    @Autowired
    public SimpleChatController(ChatClient chatClient) {
        this.chatClient = chatClient;
    }
    
    @GetMapping("/simple")
    public String simpleChat(@RequestParam String message) {
        return chatClient.call(message);
    }
    
    @PostMapping("/prompt")
    public ChatResponse chatWithPrompt(@RequestBody String message) {
        return chatClient.call(new Prompt(new UserMessage(message)));
    }
}

进阶使用

使用ChatModel API

package com.example.deepseek.service;

import org.springframework.ai.chat.ChatModel;
import org.springframework.ai.chat.ChatResponse;
import org.springframework.ai.chat.messages.*;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.chat.prompt.SystemPromptTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Map;

@Service
public class DeepSeekChatService {
    
    private final ChatModel chatModel;
    
    @Value("classpath:prompts/system-prompt.st")
    private Resource systemPromptResource;
    
    @Autowired
    public DeepSeekChatService(ChatModel chatModel) {
        this.chatModel = chatModel;
    }
    
    /**
     * 简单聊天
     */
    public String chat(String userMessage) {
        return chatModel.call(userMessage);
    }
    
    /**
     * 带系统提示的聊天
     */
    public String chatWithSystemPrompt(String userMessage, String systemPrompt) {
        SystemPromptTemplate systemPromptTemplate = new SystemPromptTemplate(systemPrompt);
        Message systemMessage = systemPromptTemplate.createMessage();
        
        UserMessage userMessageObj = new UserMessage(userMessage);
        
        Prompt prompt = new Prompt(List.of(systemMessage, userMessageObj));
        return chatModel.call(prompt).getResult().getOutput().getContent();
    }
    
    /**
     * 使用模板文件
     */
    public String chatWithTemplate(String userMessage, Map<String, Object> model) {
        SystemPromptTemplate systemPromptTemplate = new SystemPromptTemplate(systemPromptResource);
        Message systemMessage = systemPromptTemplate.createMessage(model);
        
        UserMessage userMessageObj = new UserMessage(userMessage);
        
        Prompt prompt = new Prompt(List.of(systemMessage, userMessageObj));
        return chatModel.call(prompt).getResult().getOutput().getContent();
    }
    
    /**
     * 获取完整响应(包含元数据)
     */
    public ChatResponse chatWithMetadata(String userMessage) {
        Prompt prompt = new Prompt(new UserMessage(userMessage));
        return chatModel.call(prompt);
    }
    
    /**
     * 多轮对话
     */
    public String multiTurnChat(List<Message> messages) {
        Prompt prompt = new Prompt(messages);
        return chatModel.call(prompt).getResult().getOutput().getContent();
    }
}

流式响应

package com.example.deepseek.controller;

import org.springframework.ai.chat.ChatModel;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

import java.io.IOException;

@RestController
@RequestMapping("/api/stream")
public class StreamingChatController {
    
    private final ChatModel chatModel;
    
    @Autowired
    public StreamingChatController(ChatModel chatModel) {
        this.chatModel = chatModel;
    }
    
    /**
     * 流式聊天 - SSE方式
     */
    @GetMapping(value = "/chat", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public SseEmitter streamChat(@RequestParam String message) {
        SseEmitter emitter = new SseEmitter(60000L);
        
        Prompt prompt = new Prompt(new UserMessage(message));
        
        chatModel.stream(prompt)
                .subscribe(
                        chunk -> {
                            try {
                                String content = chunk.getResult().getOutput().getContent();
                                if (content != null && !content.isEmpty()) {
                                    emitter.send(SseEmitter.event().data(content));
                                }
                            } catch (IOException e) {
                                emitter.completeWithError(e);
                            }
                        },
                        error -> emitter.completeWithError(error),
                        () -> emitter.complete()
                );
        
        return emitter;
    }
}

自定义选项

package com.example.deepseek.service;

import org.springframework.ai.chat.ChatModel;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.openai.OpenAiChatOptions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class DeepSeekOptionsService {
    
    private final ChatModel chatModel;
    
    @Autowired
    public DeepSeekOptionsService(ChatModel chatModel) {
        this.chatModel = chatModel;
    }
    
    /**
     * 使用自定义选项调用
     */
    public String chatWithOptions(String message, Double temperature, Integer maxTokens) {
        OpenAiChatOptions options = OpenAiChatOptions.builder()
                .withTemperature(temperature)
                .withMaxTokens(maxTokens)
                .withModel("deepseek-chat")
                .build();
        
        Prompt prompt = new Prompt(new UserMessage(message), options);
        return chatModel.call(prompt).getResult().getOutput().getContent();
    }
    
    /**
     * 使用TopP选项
     */
    public String chatWithTopP(String message, Double topP) {
        OpenAiChatOptions options = OpenAiChatOptions.builder()
                .withTopP(topP)
                .withModel("deepseek-chat")
                .build();
        
        Prompt prompt = new Prompt(new UserMessage(message), options);
        return chatModel.call(prompt).getResult().getOutput().getContent();
    }
    
    /**
     * 使用频率惩罚和存在惩罚
     */
    public String chatWithPenalties(String message, Double frequencyPenalty, Double presencePenalty) {
        OpenAiChatOptions options = OpenAiChatOptions.builder()
                .withFrequencyPenalty(frequencyPenalty)
                .withPresencePenalty(presencePenalty)
                .withModel("deepseek-chat")
                .build();
        
        Prompt prompt = new Prompt(new UserMessage(message), options);
        return chatModel.call(prompt).getResult().getOutput().getContent();
    }
}

函数调用(Function Calling)

定义函数

package com.example.deepseek.function;

import com.fasterxml.jackson.annotation.JsonClassDescription;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyDescription;

import java.util.function.Function;

@JsonClassDescription("获取当前天气信息")
public class WeatherFunction implements Function<WeatherFunction.Request, WeatherFunction.Response> {
    
    @JsonInclude(JsonInclude.Include.NON_NULL)
    public record Request(
            @JsonProperty(required = true)
            @JsonPropertyDescription("城市名称")
            String city,
            
            @JsonProperty
            @JsonPropertyDescription("国家名称")
            String country
    ) {}
    
    public record Response(
            String city,
            String country,
            Double temperature,
            String condition,
            String description
    ) {}
    
    @Override
    public Response apply(Request request) {
        // 这里可以调用实际的天气API
        // 示例返回模拟数据
        return new Response(
                request.city,
                request.country != null ? request.country : "China",
                25.5,
                "Sunny",
                "晴朗,气温适宜"
        );
    }
}

配置函数调用

package com.example.deepseek.service;

import com.example.deepseek.function.WeatherFunction;
import org.springframework.ai.chat.ChatModel;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.openai.OpenAiChatOptions;
import org.springframework.ai.openai.api.OpenAiApi;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Description;
import org.springframework.stereotype.Service;

import java.util.function.Function;

@Service
public class FunctionCallingService {
    
    private final ChatModel chatModel;
    
    @Autowired
    public FunctionCallingService(ChatModel chatModel) {
        this.chatModel = chatModel;
    }
    
    /**
     * 使用函数调用
     */
    public String chatWithFunction(String message) {
        OpenAiChatOptions options = OpenAiChatOptions.builder()
                .withFunction("currentWeather")
                .withModel("deepseek-chat")
                .build();
        
        Prompt prompt = new Prompt(new UserMessage(message), options);
        return chatModel.call(prompt).getResult().getOutput().getContent();
    }
    
    /**
     * 配置函数Bean
     */
    @Configuration
    static class FunctionConfig {
        
        @Bean
        @Description("获取当前天气信息")
        public Function<WeatherFunction.Request, WeatherFunction.Response> currentWeather() {
            return new WeatherFunction();
        }
    }
}

提示词模板

创建提示词模板文件

src/main/resources/prompts/system-prompt.st

你是一位专业的{role}。

你的职责是:
{responsibilities}

请用{language}回答用户的问题。

使用提示词模板

package com.example.deepseek.service;

import org.springframework.ai.chat.ChatModel;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.chat.prompt.SystemPromptTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Service;

import java.util.Map;

@Service
public class PromptTemplateService {
    
    private final ChatModel chatModel;
    
    @Value("classpath:prompts/system-prompt.st")
    private Resource systemPromptResource;
    
    @Autowired
    public PromptTemplateService(ChatModel chatModel) {
        this.chatModel = chatModel;
    }
    
    /**
     * 使用模板生成系统提示
     */
    public String chatWithTemplate(String userMessage, Map<String, Object> variables) {
        SystemPromptTemplate systemPromptTemplate = new SystemPromptTemplate(systemPromptResource);
        var systemMessage = systemPromptTemplate.createMessage(variables);
        
        Prompt prompt = new Prompt(List.of(systemMessage, new UserMessage(userMessage)));
        return chatModel.call(prompt).getResult().getOutput().getContent();
    }
    
    /**
     * 内联模板
     */
    public String chatWithInlineTemplate(String userMessage) {
        String template = """
            你是一位Java专家。
            请详细回答以下关于Java的问题:
            
            问题:{question}
            """;
        
        SystemPromptTemplate systemPromptTemplate = new SystemPromptTemplate(template);
        var systemMessage = systemPromptTemplate.createMessage(Map.of("question", userMessage));
        
        Prompt prompt = new Prompt(List.of(systemMessage, new UserMessage("请回答上述问题")));
        return chatModel.call(prompt).getResult().getOutput().getContent();
    }
}

对话记忆管理

使用ChatMemory

package com.example.deepseek.service;

import org.springframework.ai.chat.ChatModel;
import org.springframework.ai.chat.messages.*;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.chat.prompt.PromptTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

@Service
public class ChatMemoryService {
    
    private final ChatModel chatModel;
    private final Map<String, List<Message>> conversationHistory = new ConcurrentHashMap<>();
    
    @Autowired
    public ChatMemoryService(ChatModel chatModel) {
        this.chatModel = chatModel;
    }
    
    /**
     * 创建新对话
     */
    public String createConversation() {
        String conversationId = UUID.randomUUID().toString();
        conversationHistory.put(conversationId, new ArrayList<>());
        return conversationId;
    }
    
    /**
     * 发送消息并保持上下文
     */
    public String chat(String conversationId, String userMessage) {
        List<Message> messages = conversationHistory.get(conversationId);
        
        if (messages == null) {
            throw new IllegalArgumentException("Conversation not found: " + conversationId);
        }
        
        // 添加用户消息
        messages.add(new UserMessage(userMessage));
        
        // 调用模型
        Prompt prompt = new Prompt(messages);
        String response = chatModel.call(prompt).getResult().getOutput().getContent();
        
        // 添加助手回复
        messages.add(new AssistantMessage(response));
        
        return response;
    }
    
    /**
     * 清空对话历史
     */
    public void clearConversation(String conversationId) {
        conversationHistory.remove(conversationId);
    }
    
    /**
     * 获取对话历史
     */
    public List<Message> getConversationHistory(String conversationId) {
        return conversationHistory.get(conversationId);
    }
}

异常处理

package com.example.deepseek.exception;

import org.springframework.ai.chat.ChatMemory;
import org.springframework.ai.chat.prompt.ChatOptions;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.reactive.function.client.WebClientResponseException;

import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;

@RestControllerAdvice
public class GlobalExceptionHandler {
    
    @ExceptionHandler(WebClientResponseException.class)
    public ResponseEntity<Map<String, Object>> handleWebClientException(WebClientResponseException ex) {
        Map<String, Object> error = new HashMap<>();
        error.put("timestamp", LocalDateTime.now());
        error.put("status", ex.getStatusCode().value());
        error.put("error", ex.getStatusText());
        error.put("message", ex.getResponseBodyAsString());
        return ResponseEntity.status(ex.getStatusCode()).body(error);
    }
    
    @ExceptionHandler(IllegalArgumentException.class)
    public ResponseEntity<Map<String, Object>> handleIllegalArgumentException(IllegalArgumentException ex) {
        Map<String, Object> error = new HashMap<>();
        error.put("timestamp", LocalDateTime.now());
        error.put("status", HttpStatus.BAD_REQUEST.value());
        error.put("error", "Bad Request");
        error.put("message", ex.getMessage());
        return ResponseEntity.badRequest().body(error);
    }
    
    @ExceptionHandler(Exception.class)
    public ResponseEntity<Map<String, Object>> handleGenericException(Exception ex) {
        Map<String, Object> error = new HashMap<>();
        error.put("timestamp", LocalDateTime.now());
        error.put("status", HttpStatus.INTERNAL_SERVER_ERROR.value());
        error.put("error", "Internal Server Error");
        error.put("message", ex.getMessage());
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error);
    }
}

完整控制器示例

package com.example.deepseek.controller;

import com.example.deepseek.service.*;
import org.springframework.ai.chat.ChatResponse;
import org.springframework.ai.chat.messages.Message;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

import java.util.List;
import java.util.Map;

@RestController
@RequestMapping("/api/deepseek")
public class DeepSeekController {
    
    private final DeepSeekChatService chatService;
    private final StreamingChatService streamingService;
    private final FunctionCallingService functionService;
    private final PromptTemplateService templateService;
    private final ChatMemoryService memoryService;
    
    @Autowired
    public DeepSeekController(DeepSeekChatService chatService,
                              StreamingChatService streamingService,
                              FunctionCallingService functionService,
                              PromptTemplateService templateService,
                              ChatMemoryService memoryService) {
        this.chatService = chatService;
        this.streamingService = streamingService;
        this.functionService = functionService;
        this.templateService = templateService;
        this.memoryService = memoryService;
    }
    
    // 基础聊天
    @PostMapping("/chat")
    public String chat(@RequestBody String message) {
        return chatService.chat(message);
    }
    
    // 带系统提示的聊天
    @PostMapping("/chat/system")
    public String chatWithSystem(@RequestBody Map<String, String> request) {
        return chatService.chatWithSystemPrompt(
                request.get("message"),
                request.get("systemPrompt")
        );
    }
    
    // 获取完整响应
    @PostMapping("/chat/full")
    public ChatResponse chatFull(@RequestBody String message) {
        return chatService.chatWithMetadata(message);
    }
    
    // 流式聊天
    @GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public SseEmitter stream(@RequestParam String message) {
        return streamingService.streamChat(message);
    }
    
    // 函数调用
    @PostMapping("/function")
    public String chatWithFunction(@RequestBody String message) {
        return functionService.chatWithFunction(message);
    }
    
    // 模板聊天
    @PostMapping("/template")
    public String chatWithTemplate(@RequestBody Map<String, Object> request) {
        return templateService.chatWithTemplate(
                (String) request.get("message"),
                (Map<String, Object>) request.get("variables")
        );
    }
    
    // 创建对话
    @PostMapping("/conversation")
    public ResponseEntity<String> createConversation() {
        String conversationId = memoryService.createConversation();
        return ResponseEntity.ok(conversationId);
    }
    
    // 对话消息
    @PostMapping("/conversation/{id}/message")
    public String chatInConversation(@PathVariable String id, @RequestBody String message) {
        return memoryService.chat(id, message);
    }
    
    // 获取对话历史
    @GetMapping("/conversation/{id}/history")
    public List<Message> getHistory(@PathVariable String id) {
        return memoryService.getConversationHistory(id);
    }
}

测试示例

package com.example.deepseek;

import com.example.deepseek.service.DeepSeekChatService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import static org.junit.jupiter.api.Assertions.*;

@SpringBootTest
class DeepSeekServiceTest {
    
    @Autowired
    private DeepSeekChatService chatService;
    
    @Test
    void testSimpleChat() {
        String response = chatService.chat("你好");
        assertNotNull(response);
        assertFalse(response.isEmpty());
    }
    
    @Test
    void testChatWithSystemPrompt() {
        String response = chatService.chatWithSystemPrompt(
                "解释什么是量子计算",
                "你是一位物理学教授,请用通俗易懂的语言"
        );
        assertNotNull(response);
        assertTrue(response.length() > 50);
    }
}

优点与缺点

优点

  1. 开发效率高:利用Spring AI的成熟功能,减少代码量
  2. 功能丰富:内置函数调用、流式输出、提示词模板等功能
  3. 易于维护:Spring AI团队负责维护API兼容性
  4. 社区支持:活跃的社区和完善的文档
  5. 可扩展性:易于与其他Spring AI模块集成

缺点

  1. 依赖版本:需要关注Spring AI的版本兼容性
  2. 定制限制:某些深度定制可能受限
  3. 学习曲线:需要学习Spring AI的API和概念
  4. 性能开销:抽象层可能带来一定的性能开销

适用场景

  • 快速开发原型和MVP
  • 需要使用Spring AI高级功能(如函数调用、RAG等)
  • 团队熟悉Spring生态系统
  • 需要与Spring AI其他模块集成
  • 对开发效率要求高于极致性能
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐