LLM 代理服务实现原理文档

我们 (蔓藤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 代理服务:

  1. 多提供者支持:通过配置即可添加新的上游服务
  2. 统一接口:对外提供 OpenAI 兼容的 API,降低客户端接入成本
  3. 流式响应:支持实时流式输出,提升用户体验
  4. 知识库增强:自动注入相关知识,提升回答质量
  5. 生命周期管理:完善的请求追踪和状态管理
  6. 错误处理:优雅处理客户端断开、超时等异常情况
相关推荐
fliter1 小时前
Rust 的承诺:不是没有复杂性,而是把复杂性放到你能看见的地方
后端
fliter2 小时前
Rust 模块和文件不是一回事:一次讲清 `mod`、`use`、`pub use`
后端
绯雾sama2 小时前
易扣AI (Go + CloudWeGo) 企业级AI智能体项目教程 第2章:后端项目用户模块搭建
后端
fliter2 小时前
半小时读懂 Rust:从语法符号到所有权思维
后端
fliter2 小时前
深入 Rust enum 的内存世界
后端
_Evan_Yao2 小时前
从“全量发布”到“小步快跑”:灰度发布的简单实践与学习路径
java·后端·学习
石小石Orz2 小时前
给Claude增加状态栏显示:claude-hud保姆级教程
前端·人工智能·后端
折哥的程序人生 · 物流技术专研2 小时前
《Java 100 天进阶之路》第21篇:Java Object类
java·开发语言·后端·面试·哈希算法