Spring AI集成DeepSeek大模型

发布时间:2026/8/2 3:14:22
Spring AI集成DeepSeek大模型 Spring AI 是spring官方提供的大模型集成框架采用统一的抽象接口对接各类大模型DeepSeek 是国内热门的开源大模型本文介绍如何通过Spring AI集成DeepSeek实现基础对话、流式响应、推理模型思维链以及Function Calling等功能。具体的代码参照 示例项目 https://github.com/qihaiyan/springcamp/tree/main/spring-ai-deepseek一、概述Spring AI 遵循了spring生态一贯的设计理念通过starter自动装配的方式把不同厂商的大模型统一封装到 ChatClient 和 ChatModel 这套抽象接口之下开发者只需要引入对应的starter、配置api-key就可以像调用普通bean一样调用大模型无需关心底层的http请求和协议细节。DeepSeek 提供了与OpenAI兼容的接口同时具备普通对话模型和推理模型思维链两种能力。本示例项目基于Spring Boot 4.1.0 和 Spring AI 2.0.0用极简的代码演示了四种典型用法基础同步对话流式响应SSE推理模型的思维链输出Function Calling工具调用二、项目依赖与配置首先引入spring-ai的deepseek starter整个模块只需要两个依赖ext{set(springAiVersion,2.0.0)}dependencies{implementationorg.springframework.ai:spring-ai-starter-model-deepseekimplementationorg.springframework.boot:spring-boot-starter-web}dependencyManagement{imports{mavenBomorg.springframework.ai:spring-ai-bom:${springAiVersion}}}通过 spring-ai-bom 统一管理spring-ai相关依赖的版本starter会自动装配好 DeepSeekChatModel 和 ChatClient.Builder我们直接注入即可使用。接下来在 application.properties 中配置DeepSeek的api-key和模型参数server.port8080 spring.main.banner-modeoff logging.level.rootINFO # DeepSeek configuration spring.ai.deepseek.api-keyyour-api-key spring.ai.deepseek.chat.modeldeepseek-chat spring.ai.deepseek.chat.temperature0.8api-key 需要在DeepSeek开放平台https://platform.deepseek.com申请示例中写死在配置文件里只是为了演示生产环境应该通过环境变量或配置中心注入避免泄露。三、配置ChatClientspring-ai-deepseek starter 会自动提供一个 ChatClient.Builder我们只需要在启动类中构建出 ChatClient 这个beanSpringBootApplicationpublicclassApplication{publicstaticvoidmain(String[]args){SpringApplication.run(Application.class,args);}BeanpublicChatClientchatClient(ChatClient.Builderbuilder){returnbuilder.build();}}ChatClient 是spring-ai提供的模型无关的高层门面后续的对话、流式响应、工具调用都基于它完成。四、基础对话与流式响应基础对话是调用大模型最简单的方式通过 ChatClient 的链式api一行代码就能完成GetMapping(value/chat)publicStringchat(RequestParam(valuemessage,defaultValueTell me a joke)Stringmessage){returnchatClient.prompt().user(message).call().content();}调用/ai/chat?message讲个笑话接口会同步等待大模型返回完整结果后再响应。实际应用中为了提升用户体验我们通常会采用流式响应让前端实现类似打字机的逐字输出效果GetMapping(value/chat/stream,producesMediaType.TEXT_PLAIN_VALUE;charsetUTF-8)publicFluxStringstream(RequestParam(valuemessage,defaultValueTell me a joke)Stringmessage){returnchatClient.prompt().user(message).stream().content();}只需要把call()换成stream()返回值由 String 变成FluxStringspring会以SSEServer-Sent Events的方式把大模型逐个生成的token推送给客户端这里显式设置了text/plain;charsetUTF-8避免中文出现乱码。五、推理模型思维链DeepSeek 的推理在给出答案之前会先输出一段思考过程也就是思维链Chain of Thought这是推理模型区别于普通对话模型的关键能力。ChatClient 是模型无关的通用接口无法直接获取DeepSeek专属的思考过程因此这里我们绕过ChatClient直接注入底层的 DeepSeekChatModelGetMapping(/reasoning)publicMapString,Stringreasoning(RequestParam(valuemessage,defaultValue9.11 和 9.8 哪个大)Stringmessage){ChatResponseresponsechatModel.call(newPrompt(message));DeepSeekAssistantMessageoutput(DeepSeekAssistantMessage)Objects.requireNonNull(response.getResult()).getOutput();returnMap.of(reasoning,output.getReasoningContent()!null?output.getReasoningContent():,answer,output.getText()!null?output.getText():);}把返回结果强转为 DeepSeekAssistantMessage 后就可以分别通过getReasoningContent()拿到思考过程通过getText()拿到最终答案。代码里对这两个字段都做了非空判断因为只有推理模型才会返回reasoningContent普通对话模型这个字段为null。需要注意的是要真正拿到思维链需要模型具备思维能力DeepSeek当前的两个模型deepseek-v4-flash和deepseek-v4-pro都具备思维能力普通对话模型不会返回思考过程。默认示例问题9.11 和 9.8 哪个大正是检验模型推理能力的经典测试题。六、Function CallingFunction Calling 让大模型能够调用外部的java方法从而获取实时数据或执行具体操作。spring-ai通过Tool注解把一个普通的java方法声明为可被模型调用的工具无需手写json schema。定义一个模拟的天气查询服务ServicepublicclassWeatherService{privatestaticfinalMapInteger,StringCONDITIONSMap.of(0,晴,1,多云,2,小雨,3,小雪);Tool(description查询指定城市的当前天气情况返回温度和天气状况)publicStringgetCurrentWeather(Stringcity){inttempThreadLocalRandom.current().nextInt(-5,35);StringconditionCONDITIONS.get(ThreadLocalRandom.current().nextInt(CONDITIONS.size()));returnString.format(%s 当前天气%s气温 %d°C,city,condition,temp);}}在调用ChatClient时通过tools()方法把这个服务传给模型GetMapping(value/tool)publicStringtool(RequestParam(valuemessage,defaultValue北京和上海今天天气怎么样)Stringmessage){returnchatClient.prompt().user(message).tools(weatherService).call().content();}当用户询问北京和上海今天天气怎么样时模型会自主判断需要查询天气自动调用getCurrentWeather方法两次分别查询北京和上海的天气再把结果组织成自然语言返回。整个调用过程由模型决定是否调用、调用几次这就是Function Calling的核心能力。七、查看Function Calling的调用过程上一节的/ai/tool接口只返回了模型的最终答案但模型在背后到底调用了几次工具、每次传了什么参数、工具返回了什么从结果里是看不出来的。实际开发和调试中把这些调用过程暴露出来能帮助我们观察模型的行为判断它是否按预期调用了工具。我们可以定义一个请求作用域的 ToolCallRecorder 来记录单次请求内的工具调用ComponentRequestScopepublicclassToolCallRecorder{privatefinalListToolInvocationinvocationsnewArrayList();publicrecordToolInvocation(Stringtool,Stringarguments,Stringresult){}publicvoidrecord(Stringtool,Stringarguments,Stringresult){invocations.add(newToolInvocation(tool,arguments,result));}publicListToolInvocationget(){returnCollections.unmodifiableList(invocations);}}这里用RequestScope把作用域限定在单个HTTP请求内每个请求都会创建一个独立的实例请求结束后随之销毁。借助这种请求级别的隔离无需 ThreadLocal 就能让不同请求的调用记录互不干扰对虚拟线程也很友好。每条调用过程用 record 封装了工具名、入参和返回值三个字段。接下来在 WeatherService 中注入 ToolCallRecorder在工具方法内部把每次调用记录下来ServicepublicclassWeatherService{privatestaticfinalMapInteger,StringCONDITIONSMap.of(0,晴,1,多云,2,小雨,3,小雪);privatefinalToolCallRecorderrecorder;publicWeatherService(ToolCallRecorderrecorder){this.recorderrecorder;}Tool(description查询指定城市的当前天气情况返回温度和天气状况)publicStringgetCurrentWeather(Stringcity){inttempThreadLocalRandom.current().nextInt(-5,35);StringconditionCONDITIONS.get(ThreadLocalRandom.current().nextInt(CONDITIONS.size()));StringresultString.format(%s 当前天气%s气温 %d°C,city,condition,temp);recorder.record(getCurrentWeather,city,result);returnresult;}}相比上一节只是在方法返回前增加了一行recorder.record(...)把工具名、入参 city 和返回值记录下来。最后改造/ai/tool接口把返回值从 String 改为 Map同时返回模型的最终答案和完整的工具调用过程GetMapping(value/tool)publicMapString,Objecttool(RequestParam(valuemessage,defaultValue北京和上海今天天气怎么样)Stringmessage){StringanswerchatClient.prompt().user(message).tools(weatherService).call().content();returnMap.of(answer,answer!null?answer:,toolCalls,toolCallRecorder.get());}再次调用/ai/tool?message北京和上海今天天气怎么样返回结果里除了 answer还多了一个 toolCalls 数组里面记录了模型调用工具的完整过程{answer:北京当前多云气温20°C上海当前小雨气温25°C。,toolCalls:[{tool:getCurrentWeather,arguments:北京,result:北京 当前天气多云气温 20°C},{tool:getCurrentWeather,arguments:上海,result:上海 当前天气小雨气温 25°C}]}从 toolCalls 可以清楚看到模型针对北京和上海分别调用了一次getCurrentWeather每次的入参和返回值都被完整记录下来这样我们就能直观地观察模型是如何使用工具的。