Spring AI 入门之与DeepSeek API融合实战

702 阅读5分钟

Spring AI 作为统一的大模型接入框架,其Model API为开发者提供了多模型适配能力。无论是OpenAIDeepSeekMoonshot AI(月之暗面)Perplexity AIGoogle VertexAI Gemini 等若干主流云服务商模型,还是支持Ollama私有化部署的本地模型,均可通过标准化接口实现无缝集成。

本文将介绍基于Spring AI框架,分别调用云端DeepSeek API的完整对话实现方式,话不多说,搞起来。

引入依赖

第一步,先引入依赖:Springboot 3.4.5Spring AI 1.0.0-M7 。因为deepseek支持open ai的标准接口,所以这里引入 open ai的依赖即可。

	<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.4.5</version>
        <relativePath/> 
    </parent>
    <groupId>site.qxkd</groupId>
    <artifactId>chat-client</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>chat-client</name>
    <description>chat-client</description>
    <properties>
        <java.version>17</java.version>
        <spring-ai.version>1.0.0-M7</spring-ai.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-starter-model-openai</artifactId>
        </dependency>

    </dependencies>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.ai</groupId>
                <artifactId>spring-ai-bom</artifactId>
                <version>${spring-ai.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

配置文件

这只是列举一些简单配置文件,实际上配置还是挺多的,感兴趣可以去官网看一下

spring.application.name=chat-client

# 大模型相关依赖
spring.ai.openai.base-url=https://api.siliconflow.cn
spring.ai.openai.api-key=sk-你的秘钥
#spring.ai.openai.chat.options.model=deepseek-ai/DeepSeek-V3
spring.ai.openai.chat.options.model=deepseek-ai/DeepSeek-R1

测试

上面一切准备好之后,写两个接口测试一下,一个是一次性输出结果的接口,一个是流式输出的接口。

RestController
@RequestMapping("/deepseek")
public class DeepseekController {

    @Autowired
    private OpenAiChatModel chatModel;

    @GetMapping("/ai/generate")
    public Map<String , String> generate(@RequestParam(value = "message") String message) {
        String content = chatModel.call(message);
        return Map.of("generation", content);
    }

    /**
     * 生成流式结果
     * @param message
     * @return
     */
    @GetMapping("/ai/generateStream")
    public Flux<ChatResponse> generateStream(@RequestParam(value = "message") String message) {
        Prompt prompt = new Prompt(new UserMessage(message));
        return this.chatModel.stream(prompt);
    }
}

先测试一次性输出结果的接口,浏览器访问http://localhost:8080/deepseek/ai/generate?message=你是谁 ,会输出如下结果:

下面再来测试一下流式输出到接口,为了实现流式输出的效果,我让deepseek给我写了一个网页,简单修改了一下,达到了如下图中的效果。 流式输出返回的一段JSON是这样的。

{
	"result": {
		"metadata": {
			"finishReason": "",
			"contentFilters": [],
			"empty": true
		},
		"output": {
			"messageType": "ASSISTANT",
			"metadata": {
				"refusal": "",
				"finishReason": "",
				"index": 0,
				"id": "019684b265266dbe29ab56f75eaa75cd",
				"role": "ASSISTANT",
				"messageType": "ASSISTANT"
			},
			"toolCalls": [],
			"media": [],
			"text": "DeepSeek)公司开发的智能助手"
		}
	},
	"metadata": {
		"id": "019684b265266dbe29ab56f75eaa75cd",
		"model": "deepseek-ai/DeepSeek-R1",
		"rateLimit": {
			"requestsRemaining": 0,
			"requestsLimit": 0,
			"tokensRemaining": 0,
			"tokensReset": "PT0S",
			"requestsReset": "PT0S",
			"tokensLimit": 0
		},
		"usage": {
			"promptTokens": 6,
			"completionTokens": 64,
			"totalTokens": 70,
			"nativeUsage": {
				"completion_tokens": 64,
				"prompt_tokens": 6,
				"total_tokens": 70,
				"completion_tokens_details": {
					"reasoning_tokens": 38
				}
			}
		},
		"promptMetadata": [],
		"empty": false
	},
	"results": [{
		"metadata": {
			"finishReason": "",
			"contentFilters": [],
			"empty": true
		},
		"output": {
			"messageType": "ASSISTANT",
			"metadata": {
				"refusal": "",
				"finishReason": "",
				"index": 0,
				"id": "019684b265266dbe29ab56f75eaa75cd",
				"role": "ASSISTANT",
				"messageType": "ASSISTANT"
			},
			"toolCalls": [],
			"media": [],
			"text": "DeepSeek)公司开发的智能助手"
		}
	}]
}

至此,一个简单的Spring AI对话就完成了。细心的同学可能会发现,这也就只能单轮对话,没什么用呀,要连续对话才行。别急,下面就演示一下连续对话的demo。

连续对话

想要连续对话需要3个相关接口和API:

  • ChatMemory : 大模型(LLM)是无状态的,这意味着它们不会保留有关以前交互的信息。在多个交互中维护上下文或状态时,这就变成了一种限制。为了解决这个问题,Spring AI 提供了一个存储和检索与大模型多次对话信息的接口 ChatMemory

  • ChatClient : ChatClient提供了一个流式API(fluent API),用于与AI模型进行通信,它同时支持同步和流式两种编程模型。该流式API提供了一系列方法,用于逐步构建**提示词(Prompt)**的各个组成部分,这些提示词将作为输入传递给AI模型。

  • Advisors API: Advisors API为开发者提供了一种灵活而强大的方式,用于在 Spring 应用程序中拦截、修改和增强 AI 驱动的交互。它的核心优势包括:封装常见的生成式 AI 模式,转换发送给大语言模型(LLMs)的数据及处理其返回结果,以及实现跨不同模型和用例的可移植性。

话不多说,上代码。

  1. 先注入 ChatClientChatMemory 对象 本文使用InMemoryChatMemory将对话记录存放在内存中,如果想实现将对话记录存放在数据库可以考虑实现ChatMemory
@Configuration
public class ChatConfig {

    @Bean
    public ChatClient chatClient(OpenAiChatModel openAiChatModel) {
        ChatClient chatClient = ChatClient.builder(openAiChatModel)
                .defaultAdvisors(new MessageChatMemoryAdvisor(chatMemory()))  //  设置默认的MemoryAdvisor ,将对话记录存放在内存中
                .build();
        return chatClient;
    }


    @Bean
    public ChatMemory chatMemory(){
        return new InMemoryChatMemory();
    }
}
  1. 测试 测试使用流式输出,调用接口时需要传一个chatId ,这样才能根据chatId到内存中查询相关对话信息。
@Autowired
private ChatClient chatClient;

@GetMapping("/ai/chatContext")
public Flux<ChatResponse> chatContext(@RequestParam(value = "message") String message , String chatId) {

    Flux<ChatResponse> chatResponseFlux = chatClient.prompt()
            //连续对话的key
            .advisors(advisor -> advisor.param(AbstractChatMemoryAdvisor.CHAT_MEMORY_CONVERSATION_ID_KEY, chatId)
                    .param(AbstractChatMemoryAdvisor.CHAT_MEMORY_RETRIEVE_SIZE_KEY, 100))
            .user(message).stream().chatResponse();

    return chatResponseFlux;
}

修改一下之前的聊天界面代码进行测试:

至此,Spring AI 使用Deepseek大模型对话的简单示例就完成了,如果使用了Ollama私有化部署了大模型也可以使用上面的流程,或者是引入Ollama的依赖。

下一篇文章将介绍如何利用Spring AI框架使用嵌入模型和向量数据库,敬请关注。