我们 (蔓藤AI) 基于java封装了一个调用 OpenAI的服务,能够在国内访问其大模型,下面我从技术实现角度来介绍实现原理。
1. 项目概述
本项目是一个基于 Spring Boot 的 AI API 网关服务,专门用于代理和管理多个上游 LLM(大语言模型)服务。通过统一的 OpenAI 兼容接口,为客户端提供访问多种 AI 模型的能力,同时实现了计费管理、知识库增强、流式响应等核心功能。
核心特性:
- 多上游服务聚合
- OpenAI 兼容 API
- 流式/非流式响应支持
- 知识库自动增强
- 请求生命周期管理
- 完善的错误处理机制
2. 整体架构设计
2.1 架构层次
scss
┌─────────────────────────────────────────────────────────────────┐
│ Controller 层 │
│ OpenAiController (REST API入口) │
└────────────────────────────┬────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ Service 层 │
│ LLMServiceImpl ── LlmChatService ── LlmExecutionOrchestrator │
└────────────────────────────┬────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ Core 层 │
│ LlmCoreSupport ── LlmProviderRegistry │
└────────────────────────────┬────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ Provider 层 │
│ OpenAiCompatibleProvider (支持多个上游服务) │
└────────────────────────────┬────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 上游 LLM 服务 │
│ OpenAI API / Claude API / 其他 OpenAI-Compatible API │
└─────────────────────────────────────────────────────────────────┘
2.2 核心组件职责
| 组件 | 职责 | 关键功能 |
|---|---|---|
| OpenAiController | REST API入口 | 接收请求、参数校验、错误处理 |
| LLMServiceImpl | 业务逻辑聚合 | 模型管理、会话管理、响应组装 |
| LlmChatService | 聊天服务 | 流式/非流式请求处理、连接管理 |
| LlmExecutionOrchestrator | 执行编排 | 请求生命周期管理、记录状态 |
| LlmCoreSupport | 核心支持 | 模型路由、请求转发、知识库增强 |
| LlmProviderRegistry | 提供者注册 | 多提供者管理、模型匹配 |
| OpenAiCompatibleProvider | 上游代理 | HTTP请求封装、响应解析 |
3. 请求处理流程
3.1 非流式请求流程
scss
客户端请求 → OpenAiController → LLMServiceImpl → LlmChatService
↓
LlmExecutionOrchestrator.startExecution()
├─ enrichKnowledge() // 知识库增强
├─ enrichStreamOptions() // 计费选项
├─ resolveChatServiceType() // 服务类型解析
├─ estimatePreDeductUnits() // 预扣配额
└─ addInteractiveRecord() // 创建交互记录
↓
LlmCoreSupport.completeChatCompletion()
├─ normalizeRequestedModel() // 模型名标准化
├─ applyModelSpecificOptions()// 模型特定配置
├─ getProviderForModel() // 获取提供者
└─ provider.complete() // 同步调用上游
↓
LlmExecutionOrchestrator.completeExecution()
├─ 记录完成状态
├─ 计算实际消耗
└─ 更新交互记录
↓
返回响应给客户端
3.2 流式请求流程
流式请求采用异步响应模式 ,使用 ResponseBodyEmitter 实现 Server-Sent Events (SSE):
php
客户端请求 → OpenAiController → LLMServiceImpl → LlmChatService
↓
LlmExecutionOrchestrator.startExecution()
↓
创建 ResponseBodyEmitter (超时300秒)
↓
Flux.from(publisher).subscribe(
onNext: 收集chunk → 发送给客户端
onError: 记录失败 → 关闭连接
onComplete: 计算消耗 → 完成记录 → 关闭连接
)
↓
返回 emitter 给客户端
4. 核心实现原理
4.1 多提供者架构
项目采用策略模式 + 注册表模式实现多上游服务支持:
java
// LlmProviderRegistry - 提供者注册中心
public LlmProvider getProviderForModel(String modelId) {
if (modelId == null || modelId.isBlank()) {
return getDefaultProvider();
}
// 遍历所有提供者,找到支持该模型的提供者
for (LlmProvider provider : providers.values()) {
if (provider.supports(modelId)) {
return provider;
}
}
throw new BusinessException("The model '" + modelId + "' does not exist");
}
配置示例(application.yml):
yaml
llm:
default-provider: openai
providers:
openai:
type: openai
base-url: https://api.openai.com/v1
api-key: sk-xxx
owned-by: openai
models:
- gpt-4o
- gpt-4-turbo
- gpt-3.5-turbo
claude:
type: openai
base-url: https://api.anthropic.com/v1
api-key: sk-ant-xxx
owned-by: anthropic
models:
- claude-3-opus
- claude-3-sonnet
4.2 模型匹配机制
OpenAiCompatibleProvider 使用通配符匹配支持灵活的模型路由:
java
public boolean supports(String modelId) {
if (modelId == null || config.getModels() == null) {
return false;
}
for (String pattern : config.getModels()) {
if (matchesModelPattern(modelId, pattern.trim())) {
return true;
}
}
return false;
}
private boolean matchesModelPattern(String modelId, String pattern) {
if (pattern.equals(modelId)) {
return true; // 精确匹配
}
if (!pattern.contains("*")) {
return false;
}
// 通配符转换为正则表达式
String regex = pattern.replace(".", "\\.").replace("*", ".*");
return modelId.matches(regex);
}
匹配规则:
gpt-4o→ 精确匹配gpt-*→ 匹配所有以gpt-开头的模型*-turbo→ 匹配所有以-turbo结尾的模型
4.3 HTTP 请求封装
使用 Spring WebClient 构建高性能异步HTTP客户端:
java
private WebClient buildWebClient(LlmProviderProperties.Provider config) {
WebClient.Builder builder = WebClient.builder()
.baseUrl(config.getBaseUrl())
.defaultHeader(CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
// 添加API Key认证
if (config.getApiKey() != null && !config.getApiKey().isBlank()) {
builder.defaultHeader(AUTHORIZATION, "Bearer " + config.getApiKey());
}
// 添加自定义Header
if (!CollectionUtils.isEmpty(config.getHeaders())) {
config.getHeaders().forEach(builder::defaultHeader);
}
return builder.build();
}
同步调用:
java
public Mono<ChatCompletionResponse> complete(ChatCompletionRequest request) {
return webClient.post()
.uri(normalizePath(config.getChatCompletionsPath(), "/chat/completions"))
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(request)
.retrieve()
.bodyToMono(String.class)
.map(this::parseCompletionResponsePayload);
}
流式调用:
java
public Flux<String> stream(ChatCompletionRequest request) {
return webClient.post()
.uri(normalizePath(config.getChatCompletionsPath(), "/chat/completions"))
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(request)
.retrieve()
.bodyToFlux(ServerSentEvent.class)
.map(event -> formatSseEvent((ServerSentEvent<?>) event, doneSent));
}
4.4 响应规范化
统一处理不同上游服务的响应格式:
java
public void normalizeCompletionResponse(ChatCompletionResponse response, String modelId) {
if (response == null) return;
// 统一object字段
if (response.getObject() == null || response.getObject().isBlank()) {
response.setObject("chat.completion");
}
// 统一created时间戳
if (response.getCreated() == null) {
response.setCreated(System.currentTimeMillis() / 1000);
}
// 统一model字段
if (response.getModel() == null || response.getModel().isBlank()) {
response.setModel(modelId);
}
}
4.5 知识库增强
自动从数据库检索相关知识并注入到请求中:
java
public void enrichKnowledge(String md5, ChatCompletionRequest request) {
if (md5 == null || request == null) return;
try {
// 查询最近5条相关知识
String sql = "select content from knowledge where md5=? and type='text' order by id desc limit 5";
List<String> contents = jdbcTemplate.queryForList(sql, String.class, md5);
if (contents != null && !contents.isEmpty()) {
List<ChatMessage> newMessages = new ArrayList<>();
for (String content : contents) {
if (content != null && !content.isEmpty()) {
// 作为system消息注入
newMessages.add(ChatMessage.systemMessage("知识库:" + content));
}
}
// 将原消息追加到知识库之后
if (request.getMessages() != null && !request.getMessages().isEmpty()) {
newMessages.addAll(request.getMessages());
}
request.setMessages(newMessages);
}
} catch (Exception e) {
log.error("enrichKnowledge error", e);
}
}
5. 执行生命周期管理
5.1 状态流转
scss
┌──────────────────┐
│ startExecution│
└────────┬─────────┘
▼
┌──────────────────┐ 成功
│ 执行中(PENDING)│ ────────────┐
└────────┬─────────┘ │
│ 失败 ▼
▼ ┌──────────────────┐
┌──────────────────┐ │ COMPLETED │
│ FAILED │ │ (计算实际消耗) │
└──────────────────┘ └──────────────────┘
5.2 关键方法
java
// LlmExecutionOrchestrator
public ExecutionContext startExecution(String md5, ChatCompletionRequest request) {
// 1. 增强知识库
llmCoreSupport.enrichKnowledge(md5, request);
// 2. 增强计费选项
llmBillingService.enrichStreamOptions(request);
// 3. 解析服务类型
ServiceTypeEnum serviceType = llmCoreSupport.resolveChatServiceType(request.getModel());
// 4. 预估预扣配额
int preDeductUnits = llmBillingService.estimatePreDeductUnits(request);
// 5. 创建交互记录
String prompt = interactiveExecutionService.serializeToJson(request);
InteractiveRecordDO record = interactiveExecutionService.addInteractiveRecord(
md5, prompt, preDeductUnits, serviceType, 0
);
return new ExecutionContext(record, preDeductUnits);
}
public void completeExecution(ExecutionContext context, ChatCompletionRequest request, ChatCompletionResponse response) {
if (context == null || context.record == null) return;
InteractiveRecordDO record = context.record;
record.setStatus(ProcessStatus.COMPLETED.name());
// 提取回复内容
String replyText = response.getReplyText();
if (replyText != null && !replyText.isBlank()) {
record.setResultUrl(replyText);
}
// 计算实际消耗
int actualUnits = llmBillingService.resolveActualUnits(request, response);
record.setQuota(actualUnits > 0 ? actualUnits : context.preDeductUnits);
// 更新记录
interactiveRecordService.updateInteractiveRecord(record);
}
6. 错误处理与稳定性保障
6.1 客户端断开检测
java
private boolean isClientAbort(Throwable throwable) {
Throwable current = throwable;
while (current != null) {
if (current instanceof ClientAbortException) {
return true;
}
String message = current.getMessage();
if (message != null) {
String lowerCaseMessage = message.toLowerCase();
if (lowerCaseMessage.contains("broken pipe") ||
lowerCaseMessage.contains("connection reset by peer")) {
return true;
}
}
current = current.getCause();
}
return false;
}
6.2 超时处理
java
emitter.onTimeout(() -> {
llmExecutionOrchestrator.failExecution(executionContext, "stream timeout");
if (completed.compareAndSet(false, true)) {
emitter.complete();
}
});
7. API 接口
7.1 接口列表
| 接口 | 方法 | 描述 |
|---|---|---|
/v1/models |
GET | 获取模型列表 |
/v1/models/{modelId} |
GET | 获取单个模型信息 |
/v1/balance |
GET | 获取用户余额 |
/v1/chat/completions |
POST | 聊天补全(支持流式) |
/v1/responses |
POST | Responses API |
/v1/get_chat_quota |
GET | 获取聊天配额 |
7.2 请求示例
非流式请求:
bash
curl -X POST http://localhost:8080/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{
"model": "gpt-4o",
"messages": [{"role": "user", "content": "Hello"}]
}'
流式请求:
bash
curl -X POST http://localhost:8080/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{
"model": "gpt-4o",
"messages": [{"role": "user", "content": "Write a poem"}],
"stream": true
}'
8. 设计亮点
| 设计点 | 实现方式 | 优势 |
|---|---|---|
| 多提供者支持 | 策略模式 + 注册表 | 灵活扩展,支持任意OpenAI兼容API |
| 模型路由 | 通配符匹配 | 支持模糊匹配,配置简洁 |
| 流式响应 | Reactor + ResponseBodyEmitter | 低延迟,高并发 |
| 生命周期管理 | ExecutionOrchestrator | 统一的请求追踪和状态管理 |
| 知识库增强 | SQL查询 + 消息注入 | 自动增强上下文,提升回答质量 |
| 响应规范化 | normalizeCompletionResponse | 屏蔽上游差异,统一对外接口 |
| 异步非阻塞 | WebClient + Flux | 高性能,资源占用低 |
9. 配置说明
9.1 主要配置项
yaml
llm:
default-provider: openai # 默认提供者
default-model: gpt-4o # 默认模型
providers:
openai:
type: openai # 类型(目前只支持openai兼容)
base-url: https://api.openai.com/v1
api-key: sk-xxx
owned-by: openai
chat-completions-path: /chat/completions
responses-path: /responses
responses-api-supported: false
remote-mcp-supported: false
models:
- gpt-4o
- gpt-4-turbo
- gpt-3.5-turbo
headers: # 自定义请求头
X-Custom-Header: value
9.2 配置说明
| 配置项 | 类型 | 必填 | 说明 |
|---|---|---|---|
llm.default-provider |
String | 是 | 默认使用的提供者名称 |
llm.default-model |
String | 是 | 默认模型名称 |
llm.providers.[name].type |
String | 是 | 提供者类型,目前只支持 openai |
llm.providers.[name].base-url |
String | 是 | 上游服务基础URL |
llm.providers.[name].api-key |
String | 否 | API Key(部分服务可能不需要) |
llm.providers.[name].models |
List | 是 | 该提供者支持的模型列表 |
llm.providers.[name].headers |
Map | 否 | 自定义请求头 |
10. 代码结构
bash
src/main/java/org/aigc/llm/
├── controller/
│ └── OpenAiController.java # REST API控制器
├── service/
│ ├── LLMService.java # 服务接口
│ └── impl/
│ ├── LLMServiceImpl.java # 服务实现
│ ├── LlmChatService.java # 聊天服务
│ ├── LlmCoreSupport.java # 核心支持服务
│ ├── LlmExecutionOrchestrator.java # 执行编排器
│ ├── LlmBalanceService.java # 余额服务
│ ├── LlmBillingServiceImpl.java # 计费服务
│ └── LlmResponsesService.java # Responses API服务
├── provider/
│ ├── LlmProvider.java # 提供者接口
│ ├── LlmProviderRegistry.java # 提供者注册中心
│ └── OpenAiCompatibleProvider.java # OpenAI兼容提供者
├── config/
│ ├── LlmBillingProperties.java # 计费配置
│ └── LlmProviderProperties.java # 提供者配置
└── completion/model/
├── ChatCompletionRequest.java # 请求模型
├── ChatCompletionResponse.java# 响应模型
├── ChatCompletionChoice.java # 选择项模型
├── ChatMessage.java # 消息模型
└── ChatMessageRole.java # 消息角色枚举
11. 总结
本项目通过分层架构和策略模式,实现了一个灵活、高效、稳定的 LLM 代理服务:
- 多提供者支持:通过配置即可添加新的上游服务
- 统一接口:对外提供 OpenAI 兼容的 API,降低客户端接入成本
- 流式响应:支持实时流式输出,提升用户体验
- 知识库增强:自动注入相关知识,提升回答质量
- 生命周期管理:完善的请求追踪和状态管理
- 错误处理:优雅处理客户端断开、超时等异常情况