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>
相关推荐
dadaobusi2 小时前
学习:RV lkvm SBI
java·学习·spring
HEJOO94 小时前
深入理解 Java volatile 关键字:原理、用法与常见误区
java·开发语言·spring
曹牧4 小时前
Java:no content to map due to end of input
java·开发语言
子非鱼a4 小时前
【WEB】multiSQL
前端
阿维的博客日记4 小时前
Example 类全场景实战指南
java·mybatis
两点王爷5 小时前
SpringBoot 项目集成 GeoServer 源码,实现地图服务发布
java·spring boot·后端
Javatutouhouduan5 小时前
Java如何速通性能优化难题?
java·java面试·jvm调优·后端开发·java程序员·java八股文·java性能优化
lemon_sjdk5 小时前
DOM 节点
java·前端·javascript
nvd115 小时前
深入现代 Web 鉴权架构:网关统一代理 (Forward-Auth) vs 前端持有 JWT 的终极选型与边缘同域实践
前端·架构·状态模式
leoZ2316 小时前
2026-09-09-springboot-cloud-deploy-pitfalls
java·前端·javascript·vue.js·人工智能·spring boot·后端