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>
相关推荐
aixingpan6 分钟前
aixingpan.cn API开发文档:api_docs_trichart_natal_solararc_transit2接口指南
前端·php
Ayayoyo11 分钟前
公平随机转盘的前端实现:Web Crypto API、拒绝采样与加权抽取
前端
程序员黑豆1 小时前
鸿蒙应用开发:@Link 装饰器实现父子组件双向同步
前端·后端·harmonyos
前端炒粉1 小时前
简易实现ssr
开发语言·前端·javascript
huahailing10241 小时前
Spring Boot 集成 XXL-Job 完整实现方案(支持动态CRUD)
java·spring boot·后端
顶级自由人1 小时前
【前端菜鸟的补课01】Zod 与 PostgreSQL 全栈数据工程教学
前端·后端·程序员
swipe1 小时前
11|(前端转全栈)购物车不能只存在前端:用户维度数据如何在后端落库
前端·后端·全栈
薛定谔的猫-菜鸟程序员1 小时前
基于 Electron 的本地短视频解析与下载工具:架构设计与工程实践
java·electron·音视频
音符犹如代码2 小时前
Arthas Profiler 火焰图实战:CPU 热点在哪一目了然
java·jvm·spring boot
小园子的小菜2 小时前
Java 并发编程:线程安全队列全解 —— 阻塞与非阻塞实现原理及源码深度剖析
java·开发语言