使用 Spring Boot WebClient 调用大模型 API(OpenAI、文心一言、通义千问)

使用 Spring Boot WebClient 调用大模型 API(OpenAI、文心一言、通义千问)

在当今 AI 时代,大模型 API(如 OpenAI 的 GPT、百度的文心一言、阿里云的通义千问)已成为开发者集成智能功能的核心工具。Spring Boot 作为现代 Java 开发的首选框架,其内置的 WebClient(基于 Reactor 的非阻塞 HTTP 客户端)是调用 REST API 的高效、灵活的方式。本文将详细介绍如何使用 Spring Boot 的 WebClient 来调用主流大模型 API,帮助你快速上手。


一、为什么选择 WebClient?

特性

RestTemplate

WebClient

同步/异步

同步(阻塞)

异步(非阻塞,响应式)

线程模型

每个请求占用一个线程

事件驱动,高并发

流式处理

不支持

支持响应流(Streaming)

错误处理

复杂

链式调用,简洁

Spring 生态

旧版(Spring 5 开始弃用)

Spring 5+ 推荐

推荐使用 WebClient:它更符合现代微服务架构的需求,能显著提升系统吞吐量。


二、项目准备

1. 创建 Spring Boot 项目

使用 Spring Initializr 创建项目,选择以下依赖:

  • Spring Webflux(自动包含 WebClient
  • Lombok(可选,简化 DTO 代码)
2. pom.xml 依赖
<dependencies> <!-- Spring WebFlux (包含 WebClient) --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency> <!-- JSON 处理(Jackson 自动包含) --> <!-- 可选:Lombok 简化 DTO --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <scope>provided</scope> </dependency> </dependencies> 
3. 读取 API 密钥(推荐使用环境变量)

application.yml 中配置密钥(切勿硬编码):

api: openai: key: ${OPENAI_API_KEY} # 从环境变量读取 wenxin: api-key: ${WENXIN_API_KEY} secret-key: ${WENXIN_SECRET_KEY} tongyi: key: ${TONGYI_API_KEY}

在服务类中注入:

@Service public class ApiConfig { @Value("${api.openai.key}") private String openaiApiKey; @Value("${api.wenxin.api-key}") private String wenxinApiKey; @Value("${api.wenxin.secret-key}") private String wenxinSecretKey; @Value("${api.tongyi.key}") private String tongyiApiKey; }

三、WebClient 基础配置

创建一个 WebClientConfig 类,封装通用配置:

import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.client.reactive.ReactorClientHttpConnector; import org.springframework.web.reactive.function.client.WebClient; import reactor.netty.http.client.HttpClient; @Configuration public class WebClientConfig { // 可选:配置超时、重试、连接池 @Bean public WebClient webClient() { HttpClient httpClient = HttpClient.create() .responseTimeout(java.time.Duration.ofSeconds(30)); // 请求超时 30 秒 return WebClient.builder() .clientConnector(new ReactorClientHttpConnector(httpClient)) .build(); } }

💡 提示:生产环境中应配置重试策略(如 Retry)、日志跟踪等。


四、调用 OpenAI API

1. OpenAI API 简介
  • 端点https://api.openai.com/v1/chat/completions
  • 必需头Authorization: Bearer <API_KEY>
  • 请求体:JSON 格式,包含 modelmessages 等字段。
  • 官方文档OpenAI API Reference
2. DTO 定义
import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.util.List; // 请求体 @Data @NoArgsConstructor @AllArgsConstructor class OpenAiRequest { private String model; // 模型名,如 "gpt-3.5-turbo" private List<Message> messages; } @Data @NoArgsConstructor @AllArgsConstructor class Message { private String role; // "user", "assistant", "system" private String content; } // 响应体(简化版,仅展示关键字段) @Data class OpenAiResponse { private List<Choice> choices; @Data static class Choice { private Message message; } }
3. 服务实现
import org.springframework.beans.factory.annotation.Value; import org.springframework.http.MediaType; import org.springframework.stereotype.Service; import org.springframework.web.reactive.function.client.WebClient; import reactor.core.publisher.Mono; @Service public class OpenAiService { private final WebClient webClient; private final String apiKey; public OpenAiService(WebClient.Builder webClientBuilder, @Value("${api.openai.key}") String apiKey) { this.webClient = webClientBuilder .baseUrl("https://api.openai.com/v1") .defaultHeader("Authorization", "Bearer " + apiKey) .build(); this.apiKey = apiKey; } public Mono<String> chat(String prompt) { // 构造请求体 OpenAiRequest request = new OpenAiRequest( "gpt-3.5-turbo", List.of(new Message("user", prompt)) ); // 发送请求 return webClient.post() .uri("/chat/completions") .contentType(MediaType.APPLICATION_JSON) .bodyValue(request) .retrieve() .bodyToMono(OpenAiResponse.class) .map(response -> response.getChoices().get(0).getMessage().getContent()); } }
4. 使用示例(Controller)
import org.springframework.web.bind.annotation.*; import reactor.core.publisher.Mono; @RestController @RequestMapping("/ai") public class AiController { private final OpenAiService openAiService; public AiController(OpenAiService openAiService) { this.openAiService = openAiService; } @GetMapping("/ask") public Mono<String> askOpenAi(@RequestParam String question) { return openAiService.chat(question); } }

🌟 测试
发送 GET 请求 http://localhost:8080/ai/ask?question=你好,将返回 GPT 的回答。


五、调用文心一言 API

1. 文心一言 API 简介
  • 获取访问令牌(Access Token)
    • 首先需调用 OAuth 2.0 获取 access_token
    • 端点:https://aip.baidubce.com/oauth/2.0/token
    • 参数:grant_type=client_credentials&client_id=<API_KEY>&client_secret=<SECRET_KEY>
  • 聊天接口
    • 端点:https://aip.baidubce.com/rpc/2.0/ai/custom/v1/wenxinworkshop/chat/completions?access_token=<TOKEN>
    • 请求体格式类似 OpenAI,但字段名不同。

📌 注意:文心一言需在百度智能云控制台创建应用并获取密钥。

2. DTO 定义
// 访问令牌响应 @Data class WenxinTokenResponse { private String access_token; private String expires_in; } // 文心一言请求体 @Data @NoArgsConstructor @AllArgsConstructor class WenxinRequest { private String model = "eb-instant"; // 模型名(如 eb-instant、ernie-3.5-4k-0205) private List<Message> messages; } // 文心一言响应体 @Data class WenxinResponse { private List<Choice> result; @Data static class Choice { private String content; } }
3. 服务实现
import org.springframework.beans.factory.annotation.Value; import org.springframework.http.MediaType; import org.springframework.stereotype.Service; import org.springframework.web.reactive.function.client.WebClient; import reactor.core.publisher.Mono; @Service public class WenxinService { private final WebClient webClient; private final String apiKey; private final String secretKey; public WenxinService(WebClient.Builder webClientBuilder, @Value("${api.wenxin.api-key}") String apiKey, @Value("${api.wenxin.secret-key}") String secretKey) { this.webClient = webClientBuilder.build(); this.apiKey = apiKey; this.secretKey = secretKey; } // Step 1: 获取 Access Token private Mono<String> getAccessToken() { return webClient.post() .uri("https://aip.baidubce.com/oauth/2.0/token") .queryParam("grant_type", "client_credentials") .queryParam("client_id", apiKey) .queryParam("client_secret", secretKey) .retrieve() .bodyToMono(WenxinTokenResponse.class) .map(WenxinTokenResponse::getAccess_token); } // Step 2: 调用聊天接口 public Mono<String> chat(String prompt) { return getAccessToken().flatMap(accessToken -> { WenxinRequest request = new WenxinRequest( List.of(new Message("user", prompt)) ); return webClient.post() .uri("https://aip.baidubce.com/rpc/2.0/ai/custom/v1/wenxinworkshop/chat/completions") .queryParam("access_token", accessToken) .contentType(MediaType.APPLICATION_JSON) .bodyValue(request) .retrieve() .bodyToMono(WenxinResponse.class) .map(resp -> resp.getResult().get(0).getContent()); }); } }
4. Controller 使用
@RestController @RequestMapping("/ai") public class AiController { private final WenxinService wenxinService; public AiController(WenxinService wenxinService) { this.wenxinService = wenxinService; } @GetMapping("/wenxin") public Mono<String> askWenxin(@RequestParam String question) { return wenxinService.chat(question); } }

六、调用通义千问 API

1. 通义千问 API 简介
  • 端点https://dashscope.aliyuncs.com/api/v1/services/aigc/text-generation/generation
  • 必需头Authorization: Bearer <API_KEY>
  • 模型dall-e-xl(图像)或 qwen-plus(文本)
  • 官方文档通义千问 API 文档

📌 注意:通义千问由阿里云提供,需在阿里云控制台获取 API 密钥。

2. DTO 定义
// 请求体 @Data @NoArgsConstructor @AllArgsConstructor class TongyiRequest { private String model = "qwen-long"; // 模型名 private List<Message> input = new ArrayList<>(); private Parameters parameters = new Parameters(); @Data @NoArgsConstructor @AllArgsConstructor static class Parameters { private float temperature = 0.7f; // 随机性 private int max_tokens = 800; // 最多生成 token 数 } } // 响应体 @Data class TongyiResponse { private Output output; @Data static class Output { private List<Text> text; @Data static class Text { private String content; } } }
3. 服务实现
import org.springframework.beans.factory.annotation.Value; import org.springframework.http.MediaType; import org.springframework.stereotype.Service; import org.springframework.web.reactive.function.client.WebClient; import reactor.core.publisher.Mono; @Service public class TongyiService { private final WebClient webClient; private final String apiKey; public TongyiService(WebClient.Builder webClientBuilder, @Value("${api.tongyi.key}") String apiKey) { this.webClient = webClientBuilder .baseUrl("https://dashscope.aliyuncs.com") .defaultHeader("Authorization", "Bearer " + apiKey) .defaultHeader("Content-Type", MediaType.APPLICATION_JSON) .build(); this.apiKey = apiKey; } public Mono<String> chat(String prompt) { TongyiRequest request = new TongyiRequest(); request.getInput().add(new Message("user", prompt)); return webClient.post() .uri("/api/v1/services/aigc/text-generation/generation") .bodyValue(request) .retrieve() .bodyToMono(TongyiResponse.class) .map(resp -> resp.getOutput().getText().get(0).getContent()); } }
4. Controller 使用
@RestController @RequestMapping("/ai") public class AiController { private final TongyiService tongyiService; public AiController(TongyiService tongyiService) { this.tongyiService = tongyiService; } @GetMapping("/tongyi") public Mono<String> askTongyi(@RequestParam String question) { return tongyiService.chat(question); } }

七、错误处理与最佳实践

1. 优雅处理 HTTP 错误

使用 onStatus() 捕获 4xx/5xx 错误:

public Mono<String> chat(String prompt) { return webClient.post() .uri("/chat/completions") .bodyValue(request) .retrieve() .onStatus(httpStatus -> httpStatus.value() >= 400, clientResponse -> { // 读取错误信息 return clientResponse.bodyToMono(String.class) .flatMap(errorBody -> Mono.error(new RuntimeException( "API 调用失败: " + httpStatus.getReasonPhrase() + ", " + errorBody ))); }) .bodyToMono(OpenAiResponse.class) .map(...); }
2. 重要最佳实践

实践

原因

密钥隔离

使用环境变量、Vault 或 KMS,绝不硬编码在代码中

超时设置

避免请求长时间挂起(如 .responseTimeout(Duration.ofSeconds(30))

重试机制

对 5xx 错误使用 Retry(如 Retry.backoff(3, Duration.ofSeconds(1))

流式处理

大模型响应可能很长,支持流式输出(如 OpenAI 的 stream=true

日志与监控

记录请求参数、响应码、耗时,方便排查问题

令牌刷新

文心一言的 access_token 有有效期(24 小时),需定期刷新

响应式编程

在 Reactive 项目中,避免使用 .block(),直接返回 Mono/Flux

3. 流式输出示例(OpenAI)
public Flux<String> streamChat(String prompt) { OpenAiRequest request = new OpenAiRequest("gpt-3.5-turbo", List.of(new Message("user", prompt))); request.setStream(true); // 开启流式 return webClient.post() .uri("/chat/completions") .bodyValue(request) .accept(MediaType.TEXT_EVENT_STREAM) // SSE 格式 .exchangeToFlux(clientResponse -> clientResponse.bodyToFlux(String.class)) .filter(chunk -> !chunk.trim().isEmpty()) .map(chunk -> chunk.split("data: ")[1]) .filter(data -> !data.equals("[\u200b]")) // 过滤心跳 .map(data -> { /* 解析 JSON 得到内容 */ }); }

八、完整项目结构

src ├── main │ ├── java │ │ └── com.example.demo │ │ ├── DemoApplication.java │ │ ├── config │ │ │ └── WebClientConfig.java │ │ ├── controller │ │ │ └── AiController.java │ │ ├── dto │ │ │ ├── OpenAiRequest.java │ │ │ ├── WenxinRequest.java │ │ │ └── TongyiRequest.java │ │ ├── service │ │ │ ├── OpenAiService.java │ │ │ ├── WenxinService.java │ │ │ └── TongyiService.java │ │ └── ApiConfig.java │ └── resources │ └── application.yml

九、总结

通过本文,你掌握了:

  1. 如何配置 Spring Boot 的 WebClient 作为高性能 HTTP 客户端。
  2. 三大主流大模型 API 的调用流程
    • OpenAI:直接传递 Bearer Token
    • 文心一言:先获取 access_token,再带参调用。
    • 通义千问:使用 Bearer 方式传递 API 密钥。
  1. 响应式编程实践:使用 Mono/Flux 处理异步调用,避免线程阻塞。
  2. 生产级考量:错误处理、密钥管理、超时重试、流式输出等。

💡 下一步建议

  • 集成 Spring AI(官方支持的 AI 框架),简化大模型调用。
  • 尝试 流式输出,提升用户体验(如实时显示模型思考过程)。
  • 使用 缓存 存储重复请求的结果,降低 API 调用成本。

现在,你已经可以轻松将大模型能力集成到自己的 Spring Boot 应用中!🚀


重要提醒
大模型 API 通常需要付费(按 token 计费),请理性使用,并遵守各平台的使用政策。本文示例仅用于学习,实际项目中请仔细阅读官方文档获取最新接口规范。

Read more

OpenClaw WebUI 中 Chat 的工作流程及主要程序名称

## 整体架构 OpenClaw WebUI 是一个基于 Web Components 的现代前端应用,提供了直观的聊天界面来与 OpenClaw Agent 进行交互。 ## 主要程序名称 ### 前端程序 1. control-ui/index.html - WebUI 主页面 2. control-ui/assets/index-BeKTXH1m.js - 打包后的前端核心代码 3. control-ui/assets/index-DWhx-9JL.css - 前端样式文件 ### 后端服务 1. Gateway 服务 - 运行在端口 18789,提供 API 端点 2. Agent 服务 - 处理代理逻辑 3.

尤雨溪官宣:前端新工具来了,比Prettier快45倍!

尤雨溪官宣:前端新工具来了,比Prettier快45倍!

🚀 尤雨溪的"神秘包裹" 10月19日,Vue之父尤雨溪在Twitter上晒了一张截图,前端圈瞬间炸锅: 格式化太慢?Lint卡到爆?不,我们全都要! 这就好比你一直骑共享单车上班,突然有人送你一辆特斯拉——还是带自动驾驶的! 📊 性能对比:这是开了外挂吧? oxfmt:格式化界的"闪电侠" 🐢 Prettier:格式化1000个文件 = 45秒(够泡杯咖啡) 🚀 oxfmt:格式化1000个文件 = 1秒(咖啡还没反应过来) 速度提升:45倍! 对比Biome:快2-3倍 对比Prettier:快45倍 你的感受:从"等等等"到"好了?!" oxlint:代码检查界的"透视眼" 🐌 ESLint:

前端多语言别再硬编码!3步搞定i18n让老外也夸你代码香

前端多语言别再硬编码!3步搞定i18n让老外也夸你代码香

前端多语言别再硬编码!3步搞定i18n让老外也夸你代码香 * 前端多语言别再硬编码!3步搞定i18n让老外也夸你代码香 * 开篇先吐槽:还在用if-else判断语言?Out啦 * 到底啥是i18n,别被缩写吓住 * 选对工具真的能少加班,主流方案大乱斗 * i18next:老牌劲旅,生态丰富到怀疑人生 * vue-i18n:Vue亲儿子,丝滑得像德芙 * react-i18next:React界的扛把子 * formatjs:Google出品,必属精品? * 到底怎么选? * 撸起袖子干:从零搭建多语言架构的骚操作 * 文件目录怎么摆?强迫症患者的终极抉择 * JSON资源文件编写规范:key的命名艺术 * 动态加载语言包:别让首屏慢得像蜗牛 * 检测用户语言的几种姿势 * 切换语言时的"瞬移"问题 * 那些让人头秃的深水区:复数、性别和日期格式化 * 复数地狱:俄语和阿拉伯语教你做人 * 性别相关的文案处理 * 时间和数字

Android WebView 版本升级方案详解

Android WebView 版本升级方案详解 目录 1. 问题背景 2. WebViewUpgrade 项目介绍 3. 升级方法详解 4. 替代方案对比 5. 接入与使用步骤 6. 注意事项与限制 7. 总结与建议 问题背景 WebView 版本差异带来的问题 Android 5.0 以后,WebView 升级需要去 Google Play 安装 APK,但即使安装了也不一定能正常工作。像华为、Amazon 等特殊机型的 WebView 的 Chromium 版本一般比较低,只能使用它自己的 WebView,无法使用 Google 的 WebView。 典型问题场景 H.265 视频播放问题: