AI 多供应商适配层设计:OpenAI/Claude/DeepSeek/Ollama 统一接口与动态路由

作者:忆笙智云官方 | 发布时间:2026-05-27 16:45 | 更新时间:2026-06-27 16:45

AI 多供应商适配层设计思路

引言

随着 AI 大模型生态的蓬勃发展,企业级应用往往需要对接多家 AI 供应商:OpenAI、Claude、智谱 GLM、DeepSeek、MiniMax、豆包、Moonshot、Ollama 等。不同供应商的 API 接口、请求格式、响应结构各不相同,如果业务代码直接调用各供应商 SDK,将面临以下问题:

本文将分享 AI 多供应商适配层的设计思路,通过统一接口抽象 + 工厂模式 + 策略模式,实现供应商切换无需改代码。

一、供应商差异分析

1.1 接口差异对比

供应商 Chat 接口 流式响应 Embedding 模型列表
OpenAI /v1/chat/completions SSE /v1/embeddings /v1/models
Claude /v1/messages SSE 不支持 不支持
智谱 GLM /api/paas/v4/chat/completions SSE /api/paas/v4/embeddings /api/paas/v4/models
DeepSeek /v1/chat/completions SSE /v1/embeddings /v1/models
Ollama /api/chat NDJSON /api/embeddings /api/tags
豆包 /api/v3/chat/completions SSE /api/v3/embeddings /api/v3/models

1.2 核心差异点

graph LR
    A[供应商差异] --> B[API地址不同]
    A --> C[请求格式不同]
    A --> D[响应格式不同]
    A --> E[认证方式不同]
    A --> F[流式协议不同]
    A --> G[模型命名不同]

    style A fill:#ffcdd2,stroke:#c62828

二、适配层架构设计

2.1 分层架构

graph TB
    subgraph 业务层
        A[ChatService<br/>对话服务]
        B[EmbeddingService<br/>向量服务]
        C[ImageService<br/>图像服务]
    end

    subgraph 适配层
        D[AIAdapter<br/>统一接口]
        E[AdapterFactory<br/>供应商工厂]
        F[RequestConverter<br/>请求转换器]
        G[ResponseConverter<br/>响应转换器]
    end

    subgraph 供应商实现
        H[OpenAIAdapter]
        I[ClaudeAdapter]
        J[DeepSeekAdapter]
        K[OllamaAdapter]
        L[ZhipuAdapter]
        M[MiniMaxAdapter]
    end

    A --> D
    B --> D
    C --> D
    D --> E
    E --> H
    E --> I
    E --> J
    E --> K
    E --> L
    E --> M

    style D fill:#e1f5fe,stroke:#0288d1
    style E fill:#f3e5f5,stroke:#7b1fa2

2.2 核心接口定义

/**
 * AI供应商适配器统一接口
 * 所有供应商实现此接口
 */
public interface AIAdapter {

    /**
     * 获取供应商类型标识
     */
    String getProvider();

    /**
     * 同步对话
     * @param request 统一请求
     * @return 统一响应
     */
    ChatResponse chat(ChatRequest request);

    /**
     * 流式对话
     * @param request 统一请求
     * @return 流式响应
     */
    Flux<ChatResponse> chatStream(ChatRequest request);

    /**
     * 获取Embedding向量
     * @param request 统一请求
     * @return 向量响应
     */
    EmbeddingResponse embedding(EmbeddingRequest request);

    /**
     * 获取可用模型列表
     * @return 模型列表
     */
    List<ModelInfo> listModels();
}

2.3 统一请求/响应对象

/**
 * 统一对话请求
 */
public record ChatRequest(
    String model,               // 模型名称
    List<ChatMessage> messages, // 消息列表
    Double temperature,         // 温度参数
    Double topP,                // Top-P参数
    Integer maxTokens,          // 最大Token数
    Boolean stream              // 是否流式
) {}

/**
 * 统一消息
 */
public record ChatMessage(
    String role,    // system/user/assistant
    String content  // 消息内容
) {}

/**
 * 统一对话响应
 */
public record ChatResponse(
    String id,                     // 响应ID
    String model,                  // 使用的模型
    List<ChatChoice> choices,      // 回复选项
    UsageInfo usage                // Token用量
) {}

/**
 * 回复选项
 */
public record ChatChoice(
    Integer index,
    ChatMessage message,
    String finishReason
) {}

/**
 * Token用量信息
 */
public record UsageInfo(
    Integer promptTokens,
    Integer completionTokens,
    Integer totalTokens
) {}

三、供应商适配器实现

3.1 OpenAI 适配器

/**
 * OpenAI供应商适配器
 */
@Component
public class OpenAIAdapter implements AIAdapter {

    private final RestTemplate restTemplate;

    @Value("${ai.openai.api-key}")
    private String apiKey;

    @Value("${ai.openai.base-url:https://api.openai.com}")
    private String baseUrl;

    @Override
    public String getProvider() {
        return "openai";
    }

    @Override
    public ChatResponse chat(ChatRequest request) {
        // 转换为OpenAI请求格式
        Map<String, Object> openaiRequest = convertRequest(request);
        openaiRequest.put("stream", false);

        // 发送请求
        HttpHeaders headers = new HttpHeaders();
        headers.setBearerAuth(apiKey);
        headers.setContentType(MediaType.APPLICATION_JSON);

        HttpEntity<Map<String, Object>> entity =
            new HttpEntity<>(openaiRequest, headers);

        ResponseEntity<Map> response = restTemplate.exchange(
            baseUrl + "/v1/chat/completions",
            HttpMethod.POST, entity, Map.class);

        // 转换为统一响应格式
        return convertResponse(response.getBody());
    }

    @Override
    public Flux<ChatResponse> chatStream(ChatRequest request) {
        Map<String, Object> openaiRequest = convertRequest(request);
        openaiRequest.put("stream", true);

        // 使用WebClient处理SSE流
        return webClient.post()
            .uri(baseUrl + "/v1/chat/completions")
            .header("Authorization", "Bearer " + apiKey)
            .bodyValue(openaiRequest)
            .retrieve()
            .bodyToFlux(String.class)
            .filter(line -> !line.equals("[DONE]"))
            .map(this::parseStreamChunk);
    }

    /**
     * 将统一请求转换为OpenAI格式
     */
    private Map<String, Object> convertRequest(ChatRequest request) {
        Map<String, Object> map = new HashMap<>();
        map.put("model", request.model());
        map.put("messages", request.messages().stream()
            .map(m -> Map.of("role", m.role(), "content", m.content()))
            .collect(Collectors.toList()));
        if (request.temperature() != null) {
            map.put("temperature", request.temperature());
        }
        if (request.maxTokens() != null) {
            map.put("max_tokens", request.maxTokens());
        }
        return map;
    }
}

3.2 Ollama 适配器

/**
 * Ollama本地模型适配器
 * Ollama的API格式与OpenAI不同,需要特殊处理
 */
@Component
public class OllamaAdapter implements AIAdapter {

    @Value("${ai.ollama.base-url:http://localhost:11434}")
    private String baseUrl;

    @Override
    public String getProvider() {
        return "ollama";
    }

    @Override
    public ChatResponse chat(ChatRequest request) {
        // Ollama使用不同的请求格式
        Map<String, Object> ollamaRequest = Map.of(
            "model", request.model(),
            "messages", request.messages().stream()
                .map(m -> Map.of("role", m.role(), "content", m.content()))
                .collect(Collectors.toList()),
            "stream", false
        );

        ResponseEntity<Map> response = restTemplate.exchange(
            baseUrl + "/api/chat",
            HttpMethod.POST,
            new HttpEntity<>(ollamaRequest),
            Map.class
        );

        return convertOllamaResponse(response.getBody());
    }

    /**
     * Ollama响应格式转换
     * Ollama返回的格式与OpenAI不同,需要映射
     */
    private ChatResponse convertOllamaResponse(Map body) {
        Map message = (Map) body.get("message");
        ChatMessage chatMessage = new ChatMessage(
            (String) message.get("role"),
            (String) message.get("content")
        );

        return new ChatResponse(
            UUID.randomUUID().toString(),
            (String) body.get("model"),
            List.of(new ChatChoice(0, chatMessage, "stop")),
            new UsageInfo(null, null, null) // Ollama不返回token用量
        );
    }
}

四、供应商工厂与动态路由

4.1 供应商工厂

/**
 * AI供应商工厂
 * 管理所有供应商适配器,支持动态路由
 */
@Component
public class AIAdapterFactory {

    private final Map<String, AIAdapter> adapterMap = new ConcurrentHashMap<>();

    /** 默认供应商 */
    @Value("${ai.default-provider:openai}")
    private String defaultProvider;

    /**
     * 自动注入所有AIAdapter实现
     */
    public AIAdapterFactory(List<AIAdapter> adapters) {
        adapters.forEach(a -> adapterMap.put(a.getProvider(), a));
    }

    /**
     * 获取默认适配器
     */
    public AIAdapter getDefault() {
        return adapterMap.get(defaultProvider);
    }

    /**
     * 根据供应商类型获取适配器
     */
    public AIAdapter getAdapter(String provider) {
        AIAdapter adapter = adapterMap.get(provider);
        if (adapter == null) {
            throw new BusinessException("不支持的AI供应商: " + provider);
        }
        return adapter;
    }

    /**
     * 获取所有支持的供应商
     */
    public List<String> listProviders() {
        return new ArrayList<>(adapterMap.keySet());
    }
}

4.2 模型路由策略

某些场景需要根据模型名称自动路由到对应供应商:

/**
 * 模型路由器
 * 根据模型名称自动选择供应商
 */
@Component
public class ModelRouter {

    /** 模型 -> 供应商映射 */
    private final Map<String, String> modelProviderMap = new ConcurrentHashMap<>();

    @PostConstruct
    public void init() {
        // OpenAI模型
        modelProviderMap.put("gpt-4o", "openai");
        modelProviderMap.put("gpt-4o-mini", "openai");
        // DeepSeek模型
        modelProviderMap.put("deepseek-chat", "deepseek");
        modelProviderMap.put("deepseek-coder", "deepseek");
        // 智谱模型
        modelProviderMap.put("glm-4", "zhipu");
        // Claude模型
        modelProviderMap.put("claude-3-5-sonnet", "claude");
        // Ollama模型
        modelProviderMap.put("qwen2.5:7b", "ollama");
        modelProviderMap.put("llama3:8b", "ollama");
    }

    /**
     * 根据模型名称获取供应商
     */
    public String route(String model) {
        String provider = modelProviderMap.get(model);
        if (provider == null) {
            throw new BusinessException("未知的模型: " + model);
        }
        return provider;
    }
}

五、业务调用示例

5.1 对话服务

/**
 * AI对话服务
 */
@Service
public class AIChatService {

    private final AIAdapterFactory adapterFactory;
    private final ModelRouter modelRouter;

    /**
     * 对话(指定供应商)
     */
    public ChatResponse chat(String provider, ChatRequest request) {
        AIAdapter adapter = adapterFactory.getAdapter(provider);
        return adapter.chat(request);
    }

    /**
     * 对话(根据模型自动路由)
     */
    public ChatResponse chat(ChatRequest request) {
        String provider = modelRouter.route(request.model());
        AIAdapter adapter = adapterFactory.getAdapter(provider);
        return adapter.chat(request);
    }

    /**
     * 流式对话
     */
    public Flux<ChatResponse> chatStream(String provider, ChatRequest request) {
        AIAdapter adapter = adapterFactory.getAdapter(provider);
        return adapter.chatStream(request);
    }
}

5.2 Controller 层

@RestController
@RequestMapping("/api/ai")
public class AIController {

    private final AIChatService chatService;

    /**
     * 同步对话
     */
    @PostMapping("/chat")
    public R<ChatResponse> chat(@RequestBody ChatRequest request,
                                 @RequestParam(required = false) String provider) {
        ChatResponse response;
        if (provider != null) {
            response = chatService.chat(provider, request);
        } else {
            response = chatService.chat(request);
        }
        return R.ok(response);
    }

    /**
     * 流式对话(SSE)
     */
    @PostMapping(value = "/chat/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<ChatResponse> chatStream(@RequestBody ChatRequest request,
                                          @RequestParam String provider) {
        return chatService.chatStream(provider, request);
    }
}

六、供应商切换流程

flowchart TD
    A[业务发起AI请求] --> B{指定供应商?}
    B -->|是| C[从工厂获取指定适配器]
    B -->|否| D{指定模型?}
    D -->|是| E[模型路由器自动匹配供应商]
    D -->|否| F[使用默认供应商]
    E --> C
    F --> C
    C --> G[适配器转换请求格式]
    G --> H[调用供应商API]
    H --> I[适配器转换响应格式]
    I --> J[返回统一ChatResponse]

    style C fill:#e1f5fe,stroke:#0288d1
    style G fill:#fff9c4,stroke:#f9a825
    style I fill:#fff9c4,stroke:#f9a825

结论与建议

核心设计原则

  1. 统一接口抽象:定义与供应商无关的请求/响应对象,业务层只依赖抽象
  2. 适配器模式:每个供应商一个适配器,负责请求转换和响应转换
  3. 工厂 + 路由:工厂管理适配器实例,路由器根据模型自动选择供应商
  4. 配置驱动:供应商配置外部化,切换只需改配置

扩展建议

相关资源