Spring AI 对接Deepseek ChatModel 聊天对话

解决场景

1. 基础文本生成 (/ai/generate)

  • 场景:最简单的 AI 对话,输入消息返回生成内容

  • 用途:通用问答、闲聊、内容创作等

2. 带参数的模型调用 (/ai/generateWithOptions)

  • 场景:自定义模型参数(如模型版本、温度系数)

  • 用途:需要精细控制生成效果的场景,如创意写作需要较高温度,精确回答需要较低温度

3. 工具调用(Tool Calling) (/ai/generateWithTool/ai/generateWithToolBySelf)

  • 场景:AI 调用外部函数获取实时数据

  • 示例:查询巴黎、东京、纽约的天气

  • 用途:扩展 AI 能力,让模型能获取实时信息、操作外部系统(数据库、API 等)

  • 两种实现方式

    • 方式一 :使用 ChatClient 简化 API

    • 方式二 :手动处理工具调用循环(while 循环处理多轮调用)

4. 代码生成(带前缀提示) (/ai/generatePythonCode)

  • 场景:生成 Python 代码,且自动补全代码块

  • 特点 :通过 stopSequences 控制输出结束位置,使用 prefix 预填充代码块标记

  • 用途:代码助手、自动化编程

5. 推理过程展示(DeepSeek Reasoner) (/ai/deepSeekReasoningExample)

  • 场景 :获取模型的思维链(Chain of Thought) 推理内容

  • 示例:比较 9.11 和 9.8 的大小

  • 用途:需要展示推理过程的教育场景、逻辑分析、复杂问题解答

6. 结构化输出 (/ai/structuredOutput)

  • 场景 :将非结构化文本转换为 结构化 JSON 对象

  • 输出字段:标题、摘要、关键词、情感分数

  • 用途:信息抽取、内容摘要、情感分析、数据清洗

7. 流式输出 (/ai/generateStream)

  • 场景 :使用 Flux 实时返回生成内容

  • 用途:需要实时响应的场景(如 AI 对话打字机效果),提升用户体验

架构图

代码实现

POM

复制代码
<dependency>
     <groupId>org.springframework.ai</groupId>
     <artifactId>spring-ai-starter-model-deepseek</artifactId>
 </dependency>

application.properties

复制代码
spring.ai.deepseek.api-key=${DEEPSEEK_API_KEY:}
spring.ai.deepseek.base-url=${DEEPSEEK_API_BASE_URL:https://api.deepseek.com}

ChatController

java 复制代码
import com.haiwei.javaai.entities.ArticleSummary;
import com.haiwei.javaai.entities.WeatherRequest;
import com.haiwei.javaai.service.impl.WeatherService;
import com.haiwei.javaai.service.impl.WeatherService1;
import com.haiwei.javaai.utils.JsonUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.ChatOptions;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.deepseek.DeepSeekAssistantMessage;
import org.springframework.ai.deepseek.DeepSeekChatModel;
import org.springframework.ai.deepseek.DeepSeekChatOptions;
import org.springframework.ai.deepseek.api.DeepSeekApi;
import org.springframework.ai.model.tool.ToolCallingManager;
import org.springframework.ai.model.tool.ToolExecutionResult;
import org.springframework.ai.support.ToolCallbacks;
import org.springframework.ai.tool.ToolCallback;
import org.springframework.ai.tool.function.FunctionToolCallback;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;

import java.util.List;
import java.util.Map;
import java.util.function.Consumer;

@Slf4j
@RestController
public class ChatController {

    private final DeepSeekChatModel chatModel;

    @Autowired
    public ChatController(DeepSeekChatModel chatModel) {
        this.chatModel = chatModel;
    }

    @GetMapping("/ai/generate")
    public Map generate(@RequestParam(value = "message", defaultValue = "Tell me a joke") String message) {
        return Map.of("generation", chatModel.call(message));
    }

    @GetMapping("/ai/generateWithOptions")
    public Map generateWithOptions(@RequestParam(value = "message", defaultValue = "Tell me a joke") String message) {
        DeepSeekChatOptions options = DeepSeekChatOptions.builder()
                .model(DeepSeekApi.ChatModel.DEEPSEEK_V4_PRO.getValue())
                .temperature(0.8)
                .build();
        Prompt prompt = new Prompt(message, options);
        ChatResponse response = chatModel.call(prompt);
        return Map.of("generation", response.toString());
    }

    @GetMapping("/ai/generateWithTool")
    public Map generateWithTool(@RequestParam(value = "message", defaultValue = "Tell me a joke") String message) {
        ToolCallback weatherCallback = FunctionToolCallback.builder("getCurrentWeather", new WeatherService())
                .description("Get the weather in location")
                .inputType(WeatherRequest.class)
                .build();

        // Synchronous
        String response = ChatClient.create(chatModel)
                .prompt()
                .user("What's the weather in Paris, Tokyo, and New York?")
                .tools(weatherCallback)
                .call()
                .content();

        return Map.of("generation", response);
    }

    @GetMapping("/ai/generateWithToolBySelf")
    public Map generateWithToolBySelf() {
        ToolCallingManager toolCallingManager = ToolCallingManager.builder().build();

        DeepSeekChatOptions options = DeepSeekChatOptions.builder()
                .toolCallbacks(ToolCallbacks.from(new WeatherService1()))
                .build();

        Prompt prompt = new Prompt("What's the weather in Paris, Tokyo, and New York?", options);
        ChatResponse response = chatModel.call(prompt);
        log.info("response:{}", JsonUtil.toJson(response));

        while (response.hasToolCalls()) {
            ToolExecutionResult result = toolCallingManager.executeToolCalls(prompt, response);
            log.info("result:{}", JsonUtil.toJson(result));
            prompt = new Prompt(result.conversationHistory(), options);
            response = chatModel.call(prompt);
            log.info("response:{}", JsonUtil.toJson(response));
        }

        return Map.of("generation", response);
    }

    @GetMapping("/ai/generatePythonCode")
    public String generatePythonCode(@RequestParam(value = "message", defaultValue = "Please write quick sort code") String message) {
        UserMessage userMessage = new UserMessage(message);
        Message assistantMessage = DeepSeekAssistantMessage
                .builder()
                .content("```python\\n")
                .prefix(true)
                .build();
        Prompt prompt = new Prompt(List.of(userMessage, assistantMessage)
                , DeepSeekChatOptions.builder().stopSequences(List.of("```")).build()
        );
        ChatResponse response = chatModel.call(prompt);
        return response.getResult().getOutput().getText();
    }

    @GetMapping("/ai/deepSeekReasoningExample")
    public Map<String, String> deepSeekReasoningExample() {
        DeepSeekChatOptions promptOptions = DeepSeekChatOptions.builder()
                .build();
        Prompt prompt = new Prompt("9.11 and 9.8, which is greater?", promptOptions);
        ChatResponse response = chatModel.call(prompt);

        // Get the CoT content generated by the model
        DeepSeekAssistantMessage deepSeekAssistantMessage = (DeepSeekAssistantMessage) response.getResult().getOutput();
        String reasoningContent = deepSeekAssistantMessage.getReasoningContent();
        String text = deepSeekAssistantMessage.getText();
        return Map.of(reasoningContent, text);
    }

    @GetMapping("/ai/structuredOutput")
    public ArticleSummary structuredOutput(
            @RequestParam(value = "message",
                    defaultValue = "Spring AI 是一个用于构建 AI 应用的 Java 框架,支持 RAG、Tool Calling 和结构化输出。") String message) {

        log.info("[StructuredOutput] request message={}", message);

        Consumer<ChatClient.PromptUserSpec> text = u -> u.text("""
                请对以下文本做结构化摘要,严格按 JSON 格式返回:
                - title: 标题(10字以内)
                - summary: 摘要(50字以内)
                - keywords: 3-5个关键词
                - sentimentScore: 情感分数 1-10
                
                文本:{text}
                """).param("text", message);
        ArticleSummary result = ChatClient.create(chatModel)
                .prompt()
                .user(text)
                .call()
                .entity(ArticleSummary.class);

        log.info("[StructuredOutput] result={}", JsonUtil.toJson(result));
        return result;
    }

    @GetMapping("/ai/generateStream")
    public Flux<ChatResponse> generateStream(@RequestParam(value = "message", defaultValue = "Tell me a joke") String message) {
        var prompt = new Prompt(new UserMessage(message));
        return chatModel.stream(prompt);
    }
}

ArticleSummary

java 复制代码
import java.util.List;

public record ArticleSummary(
        String title,
        String summary,
        List<String> keywords,
        int sentimentScore
) {
}

WeatherRequest

java 复制代码
import lombok.Data;

@Data
public class WeatherRequest {
    private String local;
}

WeatherService

java 复制代码
import com.haiwei.javaai.entities.WeatherRequest;
import com.haiwei.javaai.entities.WeatherResponse;
import lombok.extern.slf4j.Slf4j;

import java.util.function.Function;

@Slf4j
public class WeatherService implements Function<WeatherRequest, WeatherResponse> {
    public WeatherResponse apply(WeatherRequest request) {
        log.info("WeatherService apply ....." + request.getLocal());
        return new WeatherResponse(30.0);
    }
}

WeatherService1

java 复制代码
import com.haiwei.javaai.entities.WeatherRequest;
import com.haiwei.javaai.entities.WeatherResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;

import java.util.function.Function;

@Slf4j
public class WeatherService1 {

    @Tool(description = "Get the weather in location")
    WeatherResponse getWeather(@ToolParam(required = false) WeatherRequest request) {
        log.info("getWeather apply ..... {}" , request.getLocal());
        return new WeatherResponse(30.0);
    }
}

JsonUtil

java 复制代码
import com.google.gson.*;
import com.google.gson.reflect.TypeToken;

import java.lang.reflect.Type;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class JsonUtil {

    public static Gson gson = null;

    static {
        builder();
    }

    private JsonUtil() {
        // no-op, just to avoid new instance.
    }

    private static void builder() {
        LongSerializer serializer = new LongSerializer();
        gson = new GsonBuilder().setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")
            // Serialize BigInteger/Long/long as String for frontend
            .registerTypeAdapter(BigInteger.class, serializer)
            .registerTypeAdapter(long.class, serializer)
            .registerTypeAdapter(Long.class, serializer)
            .create();
    }

    public static synchronized Gson newInstance() {
        if (gson == null) {
            builder();
        }
        return gson;
    }

    public static String toJson(Object obj) {
        return gson.toJson(obj);
    }

    public static <T> T toBean(String json, Class<T> clz) {
        return gson.fromJson(json, clz);
    }

    public static <T> T toBean(Object sourceObject, Class<T> targetClass) {
        return gson.fromJson(toJson(sourceObject), targetClass);
    }

    public static <T> Map<String, T> toMap(String json, Class<T> clz) {
        @SuppressWarnings("serial")
		Map<String, JsonObject> map = gson.fromJson(json, new TypeToken<Map<String, JsonObject>>() {}.getType());
        Map<String, T> result = new HashMap<>(8);
        for(Map.Entry<String, JsonObject> entry : map.entrySet()) {
            result.put(entry.getKey(), gson.fromJson(entry.getValue(), clz));
        }
        return result;
    }

    @SuppressWarnings("serial")
	public static Map<String, Object> toMap(String json) {
        return gson.fromJson(json, new TypeToken<Map<String, Object>>() {}.getType());
    }

    public static <T> List<T> toList(String json, Class<T> targetClass) {
        JsonArray array = new JsonParser().parse(json).getAsJsonArray();
        List<T> list = new ArrayList<>();
        for (final JsonElement elem : array) {
            list.add(gson.fromJson(elem, targetClass));
        }
        return list;
    }

    public static <T> List<T> toList(Object sourceObject, Class<T> clz) {
        return toList(toJson(sourceObject), clz);
    }

    private static class LongSerializer implements JsonSerializer<Long> {
        @Override
        public JsonElement serialize(Long src, Type typeOfSrc, JsonSerializationContext context) {
            if (src != null) {
                String strSrc = src.toString();
                if (strSrc.length() > 15) {
                    return new JsonPrimitive(strSrc);
                }
            }
            return new JsonPrimitive(src);
        }
    }
}

总结对照表

场景 端点 核心技术点
基础对话 /ai/generate 简单 call
参数调优 /ai/generateWithOptions ChatOptionsPrompt
工具调用 /ai/generateWithTool FunctionToolCallback
手动工具循环 /ai/generateWithToolBySelf ToolCallingManager
代码生成 /ai/generatePythonCode 前缀提示、停止序列
推理过程 /ai/deepSeekReasoningExample reasoningContent
结构化输出 /ai/structuredOutput .entity() 反序列化
流式响应 /ai/generateStream Flux<ChatResponse>
相关推荐
自信的未来3 小时前
JSON 工具|Web Worker 工程化打包 + 语法自动修复 + 多语言代码生成实战
java·前端·json
Brookty3 小时前
【JavaEE】线程安全(一).4:写块串行保安全、CAS
java·开发语言·java-ee·多线程·线程安全
全栈前端老曹3 小时前
【MongoDB】安全与权限管理 —— 用户认证、角色权限、SSL 加密
前端·javascript·数据库·安全·mongodb·nosql·ssl
怕孤单的草丛3 小时前
缓存管理面临的主要问题
java·数据库·缓存
zhangxingchao4 小时前
AI大模型核心八:从 Agent Skill、长文档 RAG 到知识库更新与训练策略
前端·人工智能·后端
zhangxingchao4 小时前
AI大模型核心七:从 Workflow、RAG、记忆治理到幂等性
前端·人工智能·后端
gyx_这个杀手不太冷静5 小时前
Agent开发进阶指南(第 1 章):AI Agent 到底是什么?
前端·agent·ai编程
小小小小宇6 小时前
模型相关知识
前端