当前位置: 首页 > news >正文

Spring AI实战:5分钟接入DeepSeek实现Java AI应用

1. 为什么“5分钟跑通”不是营销话术,而是Spring AI设计哲学的直接体现

Java开发者看到“5分钟跑通第一个AI应用”这种标题,第一反应往往是皱眉——毕竟我们刚被Spring Boot的自动配置惊艳过一次,又被Lombok的编译期魔法震撼过一回,但AI?模型加载、依赖冲突、API密钥管理、流式响应处理、错误重试机制……光是列个待办清单都得花三分钟。可这次不一样。Spring AI不是在封装OpenAI SDK,它是在重新定义Java与大模型交互的基础设施层。它的核心设计目标,就是让一个熟悉Spring生态的工程师,在不打开任何官方文档PDF、不查Stack Overflow、不翻GitHub Issues的前提下,仅凭直觉和已有知识就能把ChatClient跑起来。

这背后有三层硬核支撑:第一,零模型绑定。Spring AI不强制你用哪个模型提供商,OpenAI、Anthropic、DeepSeek、阿里千问、本地Ollama,甚至自建HTTP服务,全部通过统一的ChatClient接口抽象。你今天用deepseek-v4-pro,明天切到qwen2-7b,代码里只改一行配置,连@Bean定义都不用动。第二,Spring Boot Starter即开即用spring-ai-openai-spring-boot-starter这类模块,把密钥注入、客户端初始化、重试策略、超时设置、日志埋点全打包进autoconfigure,你只要在application.yml里填上spring.ai.openai.api-keyChatClient就自动出现在你的@Autowired列表里。第三,也是最关键的——它彻底放弃了“模型即服务”的旧范式,转向“模型即Bean”的Spring原生思维。在传统方案里,你得手动new OpenAiClient(),再传一堆Builder参数;而在Spring AI里,ChatClient就是一个普普通通的Spring Bean,可以被AOP拦截、被@Retryable修饰、被@Cacheable缓存响应,甚至能用@EventListener监听流式输出的每个token事件。这不是语法糖,这是把AI能力真正织进了Spring的毛细血管。

我上周带一个刚转Java三个月的前端同学实操,他连Maven都没配熟,但当他把spring-ai-deepseek-spring-boot-starter加进pom.xml,在application.yml里贴上从DeepSeek控制台复制的API Key,然后写完这三行代码:

@RestController public class AiController { private final ChatClient chatClient; public AiController(ChatClient chatClient) { this.chatClient = chatClient; } @GetMapping("/chat") public String ask(@RequestParam String q) { return chatClient.call(new UserMessage(q)).getResult().getOutput().getContent(); } }

启动应用,浏览器访问/chat?q=Java+中+ArrayList+和+LinkedList+的区别,返回结果秒出。他盯着控制台里打印的[INFO] o.s.a.o.OpenAiChatClient - Calling OpenAI API...那行日志,愣了三秒,说:“这玩意儿……真没偷偷连外网?”——其实他猜对了一半:Spring AI默认启用了spring.ai.client.default-model配置,而DeepSeek的starter内部已预置了https://api.deepseek.com/v1/chat/completions这个Endpoint,连Content-TypeAuthorization头都帮你设好了。所谓“5分钟”,本质是Spring生态二十年沉淀下来的约定优于配置(Convention over Configuration)在AI时代的终极兑现。你不需要理解Transformer的注意力机制,但你必须理解@ConfigurationProperties怎么绑定YAML字段——而后者,正是每个Java开发者刻在DNA里的本能。

提示:很多初学者卡在第一步不是因为代码写错,而是因为没意识到Spring AI的Starter会主动扫描classpath下的spring.factories,并触发AutoConfiguration。如果你用的是Spring Boot 3.2+,请确认你的Starter版本是否匹配——比如spring-ai-deepseek-spring-boot-starter的2.0.0-rc2版本,要求Spring Boot最低为3.2.0,否则ChatClient根本不会被创建,@Autowired会直接报NoSuchBeanDefinitionException。这不是Bug,是Spring Boot版本契约的刚性约束。

2. DeepSeek接入实战:从控制台申请Key到Spring Boot自动装配的完整链路

现在我们把“5分钟”拆解成可验证的步骤。重点不是教你怎么点鼠标,而是讲清楚每一步背后的技术决策依据常见断点位置。以DeepSeek为例,它的API设计非常符合Java工程师的直觉:没有复杂的OAuth流程,没有动态Token刷新,就是一个静态API Key + 标准RESTful Endpoint。这种极简主义,恰恰是Spring AI能快速集成的根本前提。

2.1 获取DeepSeek API Key的三个关键动作

去DeepSeek官网控制台申请Key,表面看只是复制粘贴,但实际有三个极易被忽略的细节:

  1. Key的作用域(Scope)必须选对:控制台里有chatembeddingsaudio三个权限开关。如果你只勾选了embeddings,那么后续调用ChatClient时会收到403 Forbidden,错误信息里却只显示Invalid API key。这是因为DeepSeek的网关层在鉴权时,会先检查Key是否有对应Endpoint的权限,再校验Key本身有效性。很多开发者反复换Key重试,最后才发现是权限没开全。

  2. Key的命名要有业务标识:不要起名my-first-keytest-key。建议按env-service-purpose格式命名,例如prod-java-app-chat。原因很简单:当你的应用部署到K8s集群后,所有Pod共享同一个Secret,运维同事排查问题时,看到prod-java-app-chat就能立刻定位到是哪个服务在调用DeepSeek,而不是在几十个test-key里大海捞针。

  3. Key的生命周期管理要前置:DeepSeek控制台提供Key的禁用/启用开关,但不提供自动轮换。这意味着你在application.yml里硬编码Key的行为,本质上是把密钥当成了代码的一部分。生产环境必须用Spring Cloud Config或Vault来管理,但开发阶段,你可以利用Spring Profile的特性,在application-dev.yml里放测试Key,在application-prod.yml里留空,靠CI/CD流水线注入。这样既保证本地调试流畅,又杜绝密钥泄露风险。

我实测过,从打开DeepSeek控制台到复制Key,平均耗时47秒。超过1分钟的,基本都是卡在权限勾选或命名纠结上。

2.2 Spring Boot项目初始化:两个必须规避的“新手坑”

新建Spring Boot项目,看似简单,但两个经典陷阱会让后续所有步骤失效:

  • Maven依赖的scope陷阱:很多教程让你直接加spring-ai-deepseek-spring-boot-starter,却没说明这个Starter内部依赖了spring-ai-corespring-webflux。如果你的项目里已经显式引入了低版本的spring-webflux(比如2.7.x),Maven的依赖调解机制会保留旧版本,导致Spring AI的WebClientChatClient无法初始化——因为它需要WebClientmutate()方法,该方法在2.7.x中尚未存在。解决方案只有一条:在pom.xml里用<exclusions>排除掉所有旧版spring-webflux,让Starter自带的3.2.x版本成为唯一来源。

  • JDK版本的隐性门槛:Spring AI 2.0要求JDK 17+,但很多Java开发者本地装着JDK 8和JDK 17双版本,IDEA里项目SDK设成了17,却忘了检查Maven Runner的JDK配置。结果就是mvn clean package时编译失败,报错Unsupported class file major version 61(JDK 17的class文件版本号)。这个错误信息极其误导人,因为它指向的是编译器版本,而非运行时版本。正确做法是:在IDEA的Settings > Build > Build Tools > Maven > Importing里,把JDK for importer也设成17;同时在Runner里确认JRE选项是17。

这两步做完,你的pom.xml应该长这样(精简版):

<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- DeepSeek Starter,它会拉取spring-webflux 3.2.x --> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-deepseek-spring-boot-starter</artifactId> <version>2.0.0-rc2</version> </dependency> <!-- 如果你用Lombok,确保版本>=1.18.30,否则与Spring AI的record类冲突 --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> </dependencies>

2.3 application.yml配置:为什么必须显式指定model-name

很多开发者照着文档填完spring.ai.deepseek.api-key就以为万事大吉,结果启动时报No qualifying bean of type 'org.springframework.ai.chat.client.ChatClient'。根源在于:Spring AI的自动配置类DeepSeekChatAutoConfiguration有一个硬性条件——它只在spring.ai.deepseek.base-urlspring.ai.deepseek.model-name至少一个存在时才生效。而DeepSeek的Starter并没有预设model-name,因为DeepSeek官方支持多个模型(deepseek-v4-prodeepseek-v4deepseek-coder),你必须明确告诉它用哪个。

所以,最简可用的application.yml必须包含:

spring: ai: deepseek: api-key: sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx model-name: deepseek-v4-pro # 这行不能少! base-url: https://api.deepseek.com/v1 # 可选,Starter已内置,但显式写出更清晰

这里有个深度经验:model-name的值必须和DeepSeek官方文档里列出的完全一致,包括大小写和连字符。我曾把deepseek-v4-pro写成DeepSeek-V4-Pro,结果客户端发出去的请求头里model字段变成了DeepSeek-V4-Pro,DeepSeek网关直接返回400 Bad Request,错误信息是The supported api model names are deepseek-v4-pro or deepseek-v4。注意,它只告诉你合法值,却不告诉你你传了什么非法值——这种“静默失败”是API集成中最折磨人的调试场景。

当你完成这三步(获取Key、修正依赖、配置YAML),执行mvn spring-boot:run,控制台会出现两行关键日志:

[INFO] o.s.b.a.c.ConditionEvaluationReportLoggingListener : ============================ CONDITIONS EVALUATION REPORT ============================ DeepSeekChatAutoConfiguration matched: - @ConditionalOnClass found required class 'org.springframework.ai.deepseek.DeepSeekChatClient' - @ConditionalOnProperty (spring.ai.deepseek.api-key) matched [INFO] o.s.c.s.PostProcessorRegistrationDelegate$BeanPostProcessorChecker : Bean 'deepSeekChatClient' of type [org.springframework.ai.deepseek.DeepSeekChatClient] is not eligible for getting processed by all BeanPostProcessors

看到DeepSeekChatAutoConfiguration matched,你就成功了。整个过程,严格计时,从创建项目到看到这条日志,我的最快记录是4分38秒。

3. ChatClient核心API详解:不只是call(),还有你不知道的五种调用姿势

ChatClient看起来只有一个call()方法,但它的设计远比表面复杂。Spring AI把它拆成了五个语义明确的调用入口,每一种都对应不同的业务场景。很多开发者只用call(String),结果在做流式响应、多轮对话、结构化输出时踩得满地找牙。下面我把这五种姿势拆开揉碎,讲清楚每种的适用边界和底层原理。

3.1 最简模式:call(String) —— 适合POC和单次问答

String response = chatClient.call("Java中HashMap的扩容机制是怎样的?");

这行代码背后发生了什么?Spring AI会自动构建一个UserMessage对象,内容就是传入的字符串,并调用ChatClient的默认实现DefaultChatClient。它会:

  1. UserMessage包装成ChatRequest,设置model为配置的deepseek-v4-pro
  2. 调用WebClient发起POST请求,Body是标准的OpenAI兼容JSON:
    { "model": "deepseek-v4-pro", "messages": [{"role": "user", "content": "Java中HashMap的扩容机制是怎样的?"}], "stream": false }
  3. 解析响应,提取choices[0].message.content作为返回值。

优点是极致简单,缺点是完全丧失控制权:你无法设置temperature、maxTokens、stopSequences,也无法获取原始响应里的usage统计(token消耗量)。这就像开车只用D档,能走,但上坡无力,下坡刹不住。

3.2 精确控制模式:call(ChatRequest) —— 掌握所有调优参数

当你需要精细调控模型行为时,必须用ChatRequest

ChatRequest request = ChatRequest.builder() .model("deepseek-v4-pro") .messages(List.of(new UserMessage("用Java实现一个线程安全的单例模式"))) .temperature(0.3) // 降低随机性,答案更确定 .maxTokens(512) // 防止无限生成 .topP(0.9) // 核采样,平衡多样性与质量 .stopSequences(List.of("```")) // 遇到代码块标记就停止 .build(); ChatResponse response = chatClient.call(request); String content = response.getResult().getOutput().getContent(); int inputTokens = response.getMetadata().getUsage().getInputTokens(); int outputTokens = response.getMetadata().getUsage().getOutputTokens();

这里的关键洞察是:ChatRequestmodel字段,优先级高于application.yml里的spring.ai.deepseek.model-name。也就是说,你可以在全局配置一个默认模型,但在特定业务逻辑里临时切换到deepseek-coder来处理代码生成任务,且不影响其他地方。这种“全局默认+局部覆盖”的设计,正是Spring生态的精髓。

3.3 流式响应模式:stream(String) —— 实现打字机效果和实时进度

前端要实现“AI正在思考…”的打字机效果?后端必须用流式API:

@GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE) public Flux<ChatResponse> stream(@RequestParam String q) { return chatClient.stream(new UserMessage(q)) .doOnNext(resp -> { // 每个token到达时触发,可用于记录token级延迟 log.info("Received token: {}", resp.getResult().getOutput().getContent()); }); }

stream()方法返回Flux<ChatResponse>,每个ChatResponse只包含一个token(或一小段文本),Content-Type被设为text/event-stream,完美对接SSE(Server-Sent Events)。但要注意:DeepSeek的流式响应格式和OpenAI略有不同,它的delta.content字段可能为空字符串,真正的文本在delta.roledelta.function_call里。Spring AI的DeepSeekStreamingChatClient已经做了适配,你拿到的ChatResponse永远是结构化的,无需手动解析JSON chunk。

3.4 多轮对话模式:withHistory(List ) —— 构建有记忆的AI

ChatClient本身无状态,但withHistory()方法能给它注入上下文:

List<Message> history = new ArrayList<>(); history.add(new UserMessage("你好")); history.add(new AiMessage("你好!我是DeepSeek,有什么可以帮您?")); history.add(new UserMessage("Java中ArrayList和LinkedList的区别是什么?")); ChatResponse response = chatClient.withHistory(history) .call(new UserMessage("它们的迭代器实现有什么不同?"));

withHistory()返回一个新的ChatClient实例,它内部持有一个Conversation对象,将历史消息和当前消息合并成ChatRequest.messages。这解决了状态管理的难题:你不需要自己维护ConcurrentHashMap<String, List<Message>>来存用户会话,Spring AI帮你把“对话”这个概念封装成了不可变对象。而且,Conversation实现了Serializable,你可以把它存进Redis,实现跨服务的会话恢复。

3.5 结构化输出模式:structured(String, Class ) —— 让AI输出JSON Schema

这是最被低估的能力。当你需要AI返回严格格式的数据(比如订单信息、用户画像、API响应体),用structured()

record OrderInfo(String orderId, String productName, BigDecimal amount, String status) {} OrderInfo order = chatClient.structured( "根据以下描述生成订单信息:用户张三购买了iPhone 15 Pro,价格8999元,状态为已支付", OrderInfo.class ); // 返回 OrderInfo[orderId=null, productName="iPhone 15 Pro", amount=8999, status="已支付"]

原理是Spring AI在ChatRequest里注入了一个response_format字段,值为{"type": "json_object"},并提示模型:“请严格按照以下JSON Schema输出,不要有任何额外文字”。DeepSeek-v4-pro对JSON Schema的支持度极高,实测准确率92%以上。这比你自己用正则从文本里提取字段可靠得多,也比调用专门的JSON解析模型成本低。

注意:structured()方法要求目标Class必须是record或有无参构造器+getter的POJO,且字段名要和提示词里的关键词强匹配。比如提示词里写“订单ID”,Class里字段就得叫orderId,不能叫id,否则模型无法建立映射。

4. 生产级避坑指南:从内存溢出到API限流的七类高频故障排查

跑通Demo只是开始,上线后你会遇到一系列只有在真实流量下才会暴露的问题。我整理了过去半年在三个Java AI项目中踩过的坑,按发生频率排序,给出可落地的解决方案。

4.1OutOfMemoryError: insufficient memory—— 不是堆内存不够,是Direct Memory泄漏

现象:应用运行几小时后,jstat -gc显示老年代占用率持续上升,最终OOM。但-Xmx设了4G,jmap -histo里对象数量正常。很多人第一反应是加大堆内存,这是错的。

根因:Spring AI底层用WebClient,而WebClient基于Netty,Netty大量使用DirectByteBuffer(堆外内存)。DeepSeek的流式响应会持续分配Direct Buffer,如果响应体很大(比如生成一篇长文),而GC来不及回收,就会耗尽-XX:MaxDirectMemorySize(默认等于-Xmx)。解决方案有两个:

  • 短期急救:启动时加JVM参数-XX:MaxDirectMemorySize=2g,把堆外内存上限设为堆内存的一半;
  • 长期根治:在application.yml里配置spring.ai.client.streaming.buffer-size=8192,把流式响应的缓冲区从默认的64KB降到8KB,减少单次分配量。

我在线上环境实测,加了这个配置后,Direct Memory峰值下降67%,连续运行7天无OOM。

4.2429 Too Many Requests—— DeepSeek的限流策略与Spring Retry的协同

DeepSeek免费版QPS限制是3,超出就返回429。很多开发者直接加@Retryable,结果发现重试了三次还是429。问题在于:DeepSeek的限流是滑动窗口,不是固定时间窗。比如你在第1秒发了3个请求,第1.1秒再发1个,依然会被限流,因为窗口内最近1秒的请求数是4。

Spring AI的RetryTemplate默认用FixedBackOffPolicy,每次重试间隔固定1秒,这正好撞在DeepSeek的滑动窗口枪口上。正确做法是用ExponentialBackOffPolicy,并开启random

@Bean public RetryTemplate retryTemplate() { RetryTemplate template = new RetryTemplate(); ExponentialBackOffPolicy backOff = new ExponentialBackOffPolicy(); backOff.setInitialInterval(1000); // 初始1秒 backOff.setMultiplier(2.0); // 每次翻倍 backOff.setMaxInterval(10000); // 最大10秒 backOff.setRandom(true); // 加入随机抖动,避免雪崩 template.setBackOffPolicy(backOff); return template; }

更重要的是,要在ChatClient调用前,用RateLimiter做前置拦截:

private final RateLimiter rateLimiter = RateLimiter.create(3.0); // 3 QPS public ChatResponse safeCall(ChatRequest request) { rateLimiter.acquire(); // 阻塞直到获得许可 return chatClient.call(request); }

RateLimiter的令牌桶算法,能平滑请求速率,比纯重试更治本。

4.3Connection refused—— 本地开发时的代理与DNS陷阱

在公司内网开发时,经常遇到Connection refused: api.deepseek.com/123.56.78.90:443。你以为是网络问题,其实是DNS污染或代理劫持。

DeepSeek的域名api.deepseek.com在国内解析可能指向错误IP。解决方案不是换DNS,而是强制指定IP

spring: ai: deepseek: base-url: https://123.56.78.90/v1 # 用dig api.deepseek.com查到的真实IP

或者,如果你必须走公司代理,Spring Boot提供了标准配置:

spring: http: proxy: host: your-proxy.company.com port: 8080 username: user password: pass

Spring AI的WebClient会自动读取这个配置,无需额外代码。

4.4400 Bad Request—— 模型名称拼写与请求体格式的双重校验

前面提过model-name拼写错误,但还有一个更隐蔽的坑:DeepSeek要求messages数组里必须有且仅有一个user角色消息,且不能有system消息(除非你用的是deepseek-v4-pro且显式开启)。很多开发者从OpenAI迁移过来,习惯性加SystemMessage,结果400。

解决方案是写一个ChatClient装饰器,在发送前校验:

public class DeepSeekChatClientDecorator implements ChatClient { private final ChatClient delegate; public DeepSeekChatClientDecorator(ChatClient delegate) { this.delegate = delegate; } @Override public ChatResponse call(ChatRequest request) { // 移除所有SystemMessage,DeepSeek不支持 List<Message> filtered = request.getMessages().stream() .filter(msg -> !"system".equals(msg.getRole())) .collect(Collectors.toList()); ChatRequest fixed = ChatRequest.from(request).messages(filtered).build(); return delegate.call(fixed); } }

4.5 日志爆炸 —— 如何只记录关键字段而不泄露密钥

Spring AI默认开启DEBUG日志,会把完整的HTTP请求头(含Authorization: Bearer sk-xxx)和响应体全打出来。这在生产环境是严重安全风险。

正确做法是配置Logback,用MaskingPatternLayout过滤敏感字段:

<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> <encoder class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder"> <providers> <timestamp/> <pattern> <pattern>{"level":"%level","msg":"%msg","req":"%replace(%mdc{request}){'sk-[a-zA-Z0-9]{32}','sk-***'}"}</pattern> </pattern> </providers> </encoder> </appender>

或者更简单:在application.yml里关闭HTTP日志:

logging: level: org.springframework.ai: WARN org.springframework.web.reactive.function.client.ExchangeFunctions: OFF

4.6 模型切换失败 ——spring.ai.client.default-model的覆盖规则

你想在不同Profile下用不同模型,比如dev用deepseek-v4,prod用deepseek-v4-pro。但发现application-prod.yml里的配置没生效。原因是:spring.ai.client.default-model的优先级低于具体Provider的配置(如spring.ai.deepseek.model-name)。所以,你必须在application-prod.yml里写:

spring: ai: deepseek: model-name: deepseek-v4-pro

而不是只写spring.ai.client.default-model

4.7 单元测试失真 —— 如何Mock ChatClient而不连真实API

写JUnit测试时,别用@MockBean ChatClient,因为ChatClient是接口,Mock后你得手写所有call()stream()的返回逻辑,极其繁琐。

Spring AI提供了ChatClientTestUtils,一行代码搞定:

@SpringBootTest class AiServiceTest { @Autowired private ChatClient chatClient; @Test void shouldReturnExpectedResponse() { // 给ChatClient注入一个模拟响应 ChatClientTestUtils.mock(chatClient, "这是一个模拟的AI回答"); String result = aiService.ask("测试问题"); assertThat(result).isEqualTo("这是一个模拟的AI回答"); } }

ChatClientTestUtils会替换ChatClient的底层实现,所有调用都走内存,100%隔离外部依赖。

5. 从Hello World到企业级架构:Spring AI在真实项目中的演进路径

一个Java团队不可能永远停留在chatClient.call("hello")阶段。随着业务深入,你会面临模型治理、多模态、Agent编排等新挑战。Spring AI的设计早已预留了升级路径,关键是要理解每一步的演进动因。

5.1 第一阶段:单模型单场景(0-3个月)

典型场景:客服机器人、内部知识库问答。技术栈就是spring-ai-deepseek-spring-boot-starter+ChatClient。此时核心矛盾是快速验证价值,所以一切以最小可行产品(MVP)为目标。我建议在这个阶段就做两件事:

  • 建立Token消耗监控:用Micrometer收集spring.ai.client.usage.*指标,接入Prometheus。哪怕只是看个趋势图,也能让你在业务方问“为什么这个月账单涨了3倍”时,拿出数据说话。

  • 固化Prompt模板:不要把提示词硬编码在Java字符串里。用src/main/resources/prompts/faq.ftl放FreeMarker模板,ChatClient支持PromptTemplate

    PromptTemplate template = new PromptTemplate("请用中文回答:${question}"); ChatRequest request = template.execute(Map.of("question", "Java内存模型"));

    这样,运营同事改话术不用发版,改个配置文件重启即可。

5.2 第二阶段:多模型路由(3-6个月)

业务扩展后,你会发现:简单问答用deepseek-v4足够,但代码生成必须用deepseek-coder,而摘要任务qwen2-7b性价比更高。这时需要模型路由层。

Spring AI的RouterChatClient就是为此而生:

@Bean public ChatClient routerChatClient() { Map<String, ChatClient> clients = Map.of( "coder", coderChatClient(), // deepseek-coder专用Client "qa", qaChatClient(), // deepseek-v4专用Client "summary", summaryChatClient() // qwen2-7b专用Client ); return new RouterChatClient(clients, new ModelNameRouter()); } // 路由策略:根据问题关键词选择模型 public class ModelNameRouter implements RouterStrategy { @Override public String route(ChatRequest request) { String content = request.getMessages().get(0).getContent().toLowerCase(); if (content.contains("代码") || content.contains("实现")) return "coder"; if (content.contains("总结") || content.contains("概括")) return "summary"; return "qa"; // 默认 } }

RouterChatClient对外仍是ChatClient接口,业务代码零修改,只在配置层增加路由逻辑。这就是抽象的价值。

5.3 第三阶段:Agent工作流(6-12个月)

当需求变成“帮我分析这份PDF合同,提取甲方乙方信息,再对比我们的标准条款,给出风险评分”,单次调用就不够了。你需要Agent:能规划、能调用工具、能反思。

Spring AI 2.0引入了ChatClientwithTools()方法,支持函数调用(Function Calling):

ChatClient agentClient = chatClient .withTools(List.of( new Tool("extract_pdf_text", "从PDF中提取纯文本", extractPdfTextSchema), new Tool("compare_clauses", "对比两条合同条款", compareClausesSchema) )); ChatResponse response = agentClient.call( new UserMessage("分析附件合同,提取双方信息并评分") ); // response.getResults()里会包含tool_calls,你需要解析并执行对应工具

这里extractPdfTextSchema是一个JSON Schema,描述工具的输入参数。DeepSeek-v4-pro对Function Calling的支持非常成熟,实测工具调用准确率89%。你不需要自己写LLM Orchestrator,Spring AI帮你把Agent的“思考-行动-观察”循环封装成了声明式API。

5.4 第四阶段:私有化与合规(12个月+)

金融、政务类客户要求模型完全私有化。这时你要把DeepSeek模型部署到本地GPU服务器,用Ollama或vLLM托管,Endpoint变成http://ollama.internal:11434/api/chat

Spring AI的解耦设计再次显现威力:你只需要把spring-ai-deepseek-spring-boot-starter换成spring-ai-ollama-spring-boot-starter,改两行配置:

spring: ai: ollama: base-url: http://ollama.internal:11434 model-name: deepseek-v4-pro:latest # Ollama里的模型名

所有业务代码,包括RouterChatClientwithTools(),全部无缝迁移。因为它们操作的,始终是ChatClient这个抽象,而不是某个具体的HTTP Client。

我在某银行项目里实测,从公有云DeepSeek切换到本地Ollama部署,只花了2小时改配置、1小时压测,零代码修改。这才是框架该有的样子:它不绑架你,而是在你需要转身时,默默铺好下一段路。

我个人在实际操作中的体会是:Spring AI的价值,不在于它让你“更快地调用AI”,而在于它让你“更少地思考AI”。当你不再为密钥管理、重试逻辑、流式解析、模型切换这些基建问题分心,你才能真正聚焦在业务价值上——比如设计一个让销售顾问10秒内生成个性化提案的Prompt,比如构建一个能自动归档会议纪要的Agent工作流。技术终将隐形,而业务价值,永远闪耀。

http://www.gsyq.cn/news/1584495.html

相关文章:

  • AI编程工具真实效能评测:上下文理解与工程适配才是关键
  • VS Code状态栏实时会话感知系统设计与实现
  • 汽车智能客服RAG实战:Spring AI 2.0 + Chroma落地指南
  • imToken企业级安全入口标准化实践:域名验证与可信请求构造
  • 永不停止的学习:大型语言模型的持续进化与自我迭代传奇
  • 【2027最新】基于SpringBoot+Vue的靓车汽车销售网站管理系统源码+MyBatis+MySQL
  • Claude Opus 4.7:面向工程师的AI编码、看图与长任务三合一生产力引擎
  • VS Code终端Python环境智能仲裁系统
  • Claude Code上下文优化:Agent分工与长会话的Token工程实践
  • 大语言模型不是自动驾驶:厘清AI智能体的技术边界与落地现实
  • superpowers协议:开发者工具间互通的智能协作标准
  • Java Web 校园社团信息管理pf系统源码-SpringBoot2+Vue3+MyBatis-Plus+MySQL8.0【含文档】
  • Claude Code接入MySQL的MCP服务器搭建与避坑指南
  • Python自动化测试实战:从环境搭建到CI/CD集成
  • 单目3D检测工程落地:SMOKE与MonoFlex的车规级改造实战
  • OpenClaw龙虾AI部署实战:飞书工作流编排与JSON配置深度解析
  • 基于pytest的接口自动化测试框架搭建实战指南
  • K2.6代码智能体:无工具调用下的端到端自主编程实测
  • TRAE与MCP协议:重构开发者工作流的VibeCoding实践
  • CoPaw:轻量级多平台AI助理框架实战指南
  • Java实现ReAct智能体:从LangChain到生产级AI服务
  • OpenClaw300:面向中文场景的龙虾智能体工作流平台
  • Gemini 3.1 Flash-Lite:面向API低延迟场景的大模型优化实践
  • 自动驾驶多模态感知:VLM与BEV融合的工业落地实践
  • UI自动化测试PO模式封装:从原理到工程实践
  • Alpamayo-R1:面向实车部署的VLA+RLVR端到端具身智能工程实践
  • BEV感知演进:从2D图像到多模态融合的工程实践
  • 【2027最新】基于SpringBoot+Vue的学生宿舍信息系统管理系统源码+MyBatis+MySQL
  • 企业级Agent落地四阶段:POC到规模化实战指南
  • Python自动化测试实战:pytest核心机制与工程化配置详解