Skip to content

学习目标:理解 Provider 如何抽象多种 AI 模型,提供统一的调用接口 前置知识:第1-9章(项目概览到扩展开发) 源码路径src/providers/阅读时间:55分钟

Source Snapshot

源码快照

分支main
Commitlatest
验证时间2024-03
src/providers/
src/providers/interface.ts

10.1 概念引入

10.1.1 为什么需要 Provider 抽象?

OpenClaw 需要支持 多种 AI 模型

  • OpenAI (GPT-4, GPT-3.5)
  • Anthropic (Claude 3)
  • xAI (Grok)
  • Mistral
  • 本地模型 (Ollama, LM Studio)

每个模型都有不同的:

  • API 格式:请求/响应结构
  • 认证方式:API Key、OAuth
  • 功能差异:流式响应、函数调用、视觉能力

Provider 的职责:统一这些差异,让 Agent 可以无缝切换模型。

10.1.2 Provider 在架构中的位置

10.1.3 Provider 的核心职责

职责说明
模型选择根据任务选择合适的模型
认证管理处理 API Key、OAuth 等认证
请求构建构建符合模型 API 的请求
响应解析解析模型响应为统一格式
流式处理支持 SSE 流式输出
错误处理处理限流、超时等错误

10.2 核心接口设计

10.2.1 Provider 基础接口

typescript
// src/providers/interface.ts (概念示意)

interface Provider {
  // 基础信息
  readonly id: string;              // 提供者 ID
  readonly name: string;            // 显示名称
  readonly models: string[];        // 支持的模型列表
  
  // 认证
  readonly authMethod: AuthMethod;
  authenticate(credentials: Credentials): Promise<void>;
  
  // 聊天完成
  chat(request: ChatRequest): Promise<ChatResponse>;
  chatStream(request: ChatRequest): AsyncIterable<ChatChunk>;
  
  // 嵌入(可选)
  embed?(request: EmbedRequest): Promise<EmbedResponse>;
  
  // 模型信息
  getModelInfo(model: string): ModelInfo;
}

10.2.2 认证方法

typescript
interface AuthMethod {
  type: 'api_key' | 'oauth' | 'custom';
  fields: AuthField[];
}

interface AuthField {
  name: string;
  label: string;
  type: 'text' | 'password' | 'url';
  required: boolean;
  placeholder?: string;
}

// 示例:OpenAI 的认证方法
const openaiAuth: AuthMethod = {
  type: 'api_key',
  fields: [
    { name: 'apiKey', label: 'API Key', type: 'password', required: true }
  ]
};

// 示例:自定义端点的认证方法
const ollamaAuth: AuthMethod = {
  type: 'custom',
  fields: [
    { name: 'baseUrl', label: 'Ollama URL', type: 'url', required: true, placeholder: 'http://localhost:11434' }
  ]
};

10.2.3 统一请求/响应格式

typescript
// 统一的聊天请求
interface ChatRequest {
  model: string;                    // 模型 ID
  messages: Message[];              // 消息历史
  temperature?: number;             // 温度参数
  maxTokens?: number;               // 最大 Token 数
  topP?: number;                    // Top P 采样
  stop?: string[];                  // 停止序列
  tools?: Tool[];                   // 可用工具
  toolChoice?: 'auto' | 'none' | { type: 'function'; name: string };
}

// 统一的聊天响应
interface ChatResponse {
  id: string;                       // 响应 ID
  model: string;                    // 使用的模型
  choices: Choice[];                // 响应选项
  usage: Usage;                     // Token 使用统计
}

interface Choice {
  index: number;
  message: Message;
  finishReason: 'stop' | 'length' | 'tool_calls' | 'content_filter';
}

interface Message {
  role: 'system' | 'user' | 'assistant' | 'tool';
  content: string;
  name?: string;
  toolCalls?: ToolCall[];
  toolCallId?: string;
}

interface Usage {
  promptTokens: number;
  completionTokens: number;
  totalTokens: number;
}

10.3 内置 Provider 实现

10.3.1 OpenAI Provider

typescript
// src/providers/openai.ts

class OpenAIProvider implements Provider {
  readonly id = 'openai';
  readonly name = 'OpenAI';
  readonly models = ['gpt-4', 'gpt-4-turbo', 'gpt-3.5-turbo'];
  
  private client: OpenAI;
  
  readonly authMethod = {
    type: 'api_key' as const,
    fields: [
      { name: 'apiKey', label: 'API Key', type: 'password', required: true }
    ]
  };
  
  async authenticate(credentials: { apiKey: string }): Promise<void> {
    this.client = new OpenAI({ apiKey: credentials.apiKey });
  }
  
  async chat(request: ChatRequest): Promise<ChatResponse> {
    const response = await this.client.chat.completions.create({
      model: request.model,
      messages: this.convertMessages(request.messages),
      temperature: request.temperature,
      max_tokens: request.maxTokens,
      tools: this.convertTools(request.tools)
    });
    
    return this.convertResponse(response);
  }
  
  async *chatStream(request: ChatRequest): AsyncIterable<ChatChunk> {
    const stream = await this.client.chat.completions.create({
      model: request.model,
      messages: this.convertMessages(request.messages),
      stream: true
    });
    
    for await (const chunk of stream) {
      yield this.convertChunk(chunk);
    }
  }
  
  // 格式转换方法
  private convertMessages(messages: Message[]): OpenAIMessage[] {
    return messages.map(msg => ({
      role: msg.role,
      content: msg.content,
      name: msg.name,
      tool_calls: msg.toolCalls
    }));
  }
  
  private convertResponse(response: OpenAIResponse): ChatResponse {
    return {
      id: response.id,
      model: response.model,
      choices: response.choices.map(choice => ({
        index: choice.index,
        message: {
          role: choice.message.role,
          content: choice.message.content,
          toolCalls: choice.message.tool_calls
        },
        finishReason: choice.finish_reason
      })),
      usage: {
        promptTokens: response.usage.prompt_tokens,
        completionTokens: response.usage.completion_tokens,
        totalTokens: response.usage.total_tokens
      }
    };
  }
}

10.3.2 Anthropic Provider

typescript
// src/providers/anthropic.ts

class AnthropicProvider implements Provider {
  readonly id = 'anthropic';
  readonly name = 'Anthropic';
  readonly models = ['claude-3-opus', 'claude-3-sonnet', 'claude-3-haiku'];
  
  private client: Anthropic;
  
  async chat(request: ChatRequest): Promise<ChatResponse> {
    const response = await this.client.messages.create({
      model: request.model,
      messages: this.convertMessages(request.messages),
      system: this.extractSystemPrompt(request.messages),
      max_tokens: request.maxTokens || 4096
    });
    
    return this.convertResponse(response);
  }
  
  // Claude 特有:System Prompt 单独传递
  private extractSystemPrompt(messages: Message[]): string | undefined {
    const systemMsg = messages.find(m => m.role === 'system');
    return systemMsg?.content;
  }
  
  private convertMessages(messages: Message[]): AnthropicMessage[] {
    return messages
      .filter(m => m.role !== 'system')
      .map(msg => ({
        role: msg.role === 'assistant' ? 'assistant' : 'user',
        content: msg.content
      }));
  }
}

10.3.3 Ollama Provider(本地模型)

typescript
// src/providers/ollama.ts

class OllamaProvider implements Provider {
  readonly id = 'ollama';
  readonly name = 'Ollama';
  readonly models: string[] = [];  // 动态获取
  
  private baseUrl: string;
  
  readonly authMethod = {
    type: 'custom' as const,
    fields: [
      { name: 'baseUrl', label: 'Ollama URL', type: 'url', required: true, placeholder: 'http://localhost:11434' }
    ]
  };
  
  async authenticate(credentials: { baseUrl: string }): Promise<void> {
    this.baseUrl = credentials.baseUrl;
    await this.fetchModels();
  }
  
  private async fetchModels(): Promise<void> {
    const response = await fetch(`${this.baseUrl}/api/tags`);
    const data = await response.json();
    this.models = data.models.map((m: any) => m.name);
  }
  
  async chat(request: ChatRequest): Promise<ChatResponse> {
    const response = await fetch(`${this.baseUrl}/api/chat`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        model: request.model,
        messages: request.messages,
        stream: false
      })
    });
    
    const data = await response.json();
    return this.convertResponse(data);
  }
  
  async *chatStream(request: ChatRequest): AsyncIterable<ChatChunk> {
    const response = await fetch(`${this.baseUrl}/api/chat`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        model: request.model,
        messages: request.messages,
        stream: true
      })
    });
    
    const reader = response.body!.getReader();
    const decoder = new TextDecoder();
    
    while (true) {
      const { done, value } = await reader.read();
      if (done) break;
      
      const line = decoder.decode(value);
      const chunk = JSON.parse(line);
      yield this.convertChunk(chunk);
    }
  }
}

10.4 Provider 注册与管理

10.4.1 Provider Registry

typescript
// src/providers/registry.ts

class ProviderRegistry {
  private providers = new Map<string, Provider>();
  private factories = new Map<string, ProviderFactory>();
  
  // 注册 Provider 工厂
  registerFactory(id: string, factory: ProviderFactory): void {
    this.factories.set(id, factory);
  }
  
  // 创建 Provider 实例
  async createProvider(id: string, config: ProviderConfig): Promise<Provider> {
    const factory = this.factories.get(id);
    if (!factory) {
      throw new Error(`Unknown provider: ${id}`);
    }
    
    const provider = await factory.create(config);
    await provider.authenticate(config.credentials);
    this.providers.set(provider.id, provider);
    return provider;
  }
  
  // 获取 Provider
  getProvider(id: string): Provider | undefined {
    return this.providers.get(id);
  }
  
  // 获取所有可用 Provider
  getAvailableProviders(): ProviderInfo[] {
    return Array.from(this.factories.entries()).map(([id, factory]) => ({
      id,
      name: factory.name,
      models: factory.models,
      authMethod: factory.authMethod
    }));
  }
}

10.4.2 模型选择策略

typescript
// src/providers/selector.ts

interface ModelSelector {
  selectModel(request: ChatRequest, providers: Provider[]): ModelSelection;
}

class DefaultModelSelector implements ModelSelector {
  selectModel(request: ChatRequest, providers: Provider[]): ModelSelection {
    // 1. 如果指定了模型,直接使用
    if (request.model) {
      const provider = this.findProviderForModel(request.model, providers);
      if (provider) {
        return { provider, model: request.model };
      }
    }
    
    // 2. 根据任务类型选择最佳模型
    const taskType = this.detectTaskType(request.messages);
    const bestModel = this.getBestModelForTask(taskType, providers);
    
    return bestModel;
  }
  
  private detectTaskType(messages: Message[]): TaskType {
    // 检测任务类型:代码、推理、创意等
    const lastMessage = messages[messages.length - 1];
    const content = lastMessage.content.toLowerCase();
    
    if (content.includes('代码') || content.includes('code')) {
      return 'code';
    }
    if (content.includes('分析') || content.includes('analyze')) {
      return 'reasoning';
    }
    return 'general';
  }
}

10.5 与 Agent 的集成

10.5.1 Agent 调用 Provider

typescript
// Agent 中的 Provider 调用
class Agent {
  private provider: Provider;
  
  async process(message: Message): Promise<Message> {
    // 1. 构建请求
    const request: ChatRequest = {
      model: this.config.model,
      messages: this.session.messages,
      temperature: this.config.temperature,
      maxTokens: this.config.maxTokens,
      tools: this.getAvailableTools()
    };
    
    // 2. 调用 Provider
    if (this.config.stream) {
      return await this.processStream(request);
    } else {
      const response = await this.provider.chat(request);
      return response.choices[0].message;
    }
  }
  
  private async processStream(request: ChatRequest): Promise<Message> {
    let fullContent = '';
    
    for await (const chunk of this.provider.chatStream(request)) {
      const content = chunk.choices[0]?.delta?.content || '';
      fullContent += content;
      
      // 回调流式输出
      this.onStreamChunk?.(content);
    }
    
    return { role: 'assistant', content: fullContent };
  }
}

10.6 概念→代码映射表

概念组件对应目录/文件核心作用
Provider 接口src/providers/interface.ts统一的模型调用接口
OpenAI 实现src/providers/openai.tsOpenAI GPT 系列模型
Anthropic 实现src/providers/anthropic.tsClaude 系列模型
Ollama 实现src/providers/ollama.ts本地模型支持
Provider 注册src/providers/registry.ts管理提供者实例
模型选择器src/providers/selector.ts自动选择最佳模型

10.7 小结

Provider 是 OpenClaw 的AI 模型抽象层,负责:

  • 统一接口:屏蔽不同模型的 API 差异
  • 模型管理:注册、选择、切换模型
  • 认证管理:处理各种认证方式

理解 Provider 后,你可以轻松添加新的 AI 模型支持。


下一章第11章:Plugin SDK - 深入了解插件开发 SDK

基于 OpenClaw 开源项目学习整理