Skip to content

LLM 适配器

LLM 适配器采用适配器模式统一接入多家 AI 模型供应商(OpenAI、Claude、Gemini),通过抽象层屏蔽不同供应商的 API 差异。

适配器架构

核心特性

  • 统一接口 - 所有适配器提供一致的请求/响应接口,内部使用统一的 Chaite 消息格式
  • 工具调用解析 - 原生工具调用 + parseXmlToolCalls 文本回退解析(支持 <tools>/<tool_call>/<function_call>/<invoke>/```json 代码块/裸 JSON 等多种格式,自动修复错误 JSON)
  • 流式响应 - 三家适配器均实现 streamMessage
  • 工具调用循环 - sendMessage 内置工具调用递归执行、去重(deduplicateToolCalls)、并行/串行分流与审批预检
  • thinking/reasoning - 三家均支持推理模式(机制不同,见下表)
  • 图片预处理 - preprocessImageUrls 将媒体 URL 转 base64(Gemini 模型强制预处理)
  • 多渠道管理 - 多 API Key 轮询、故障转移、健康检查(由 ChannelManager 负责)

AbstractClient

所有适配器的抽象基类,位于 src/core/adapters/AbstractClient.js

构造函数关键 options

constructor(options, context) 先经 BaseClientOptions.create(options) 规范化,读取字段包括:

  • baseUrl / chatPath / modelsPath / responsePath(Responses API 路径)/ endpoints{ chat, models, embeddings, images }
  • apiKeymultipleKeyStrategy(默认 MultipleKeyStrategyChoice.RANDOM
  • featurestoolshistoryManagerlogger
  • toolCallLimitConfig(默认 DEFAULT_TOOL_CALL_LIMIT)、onMessageWithToolCall(工具调用中间消息回调)

主要方法

方法说明
sendMessage(message, options)主入口,含工具调用递归循环
_sendMessage(histories, apiKey, options)抽象方法,由子类实现具体请求
sendMessageWithHistory(history, options)带历史的发送
streamMessage(history, options)流式消息(基类抛未实现,子类覆盖)
getEmbedding(text, options)文本嵌入(基类抛异常)
listModels() / getModelInfo(modelId)模型列表与信息
supportsFeature(feature)基于 features 判断能力
executeToolCalls(toolCalls, options)工具调用执行(并行/串行分流、审批预检)
deduplicateToolCalls(toolCalls)工具调用去重

模块级导出函数

  • parseXmlToolCalls(text) - 多格式工具调用文本解析器(含去重与 MAX_TOOL_CALLS = 15 限制)
  • preprocessMediaToBase64(histories, options) / preprocessImageUrls(histories) - 媒体转 base64
  • needsBase64Preprocess(model) / needsImageBase64Preprocess(model) - 模型名含 gemini 时返回 true

OpenAIClient

class OpenAIClient extends AbstractClientthis.name = 'openai'),基于 openai SDK,支持 OpenAI API 及所有兼容接口。

Chat Completions 与 Responses 双模式

接口模式由 getOpenAIInterfaceMode 读取 apiInterface(默认 'chat')决定:

  • Chat Completions(默认):client.chat.completions.create,支持流式增量聚合(content / reasoning_content / tool_calls,含 <think> 标签剥离)
  • Responses APIshouldUseOpenAIResponses 判定模式为 'responses'/'response' 时启用,完整管线含 buildResponsesPayloadresponsesOutputToChatCompletion、流式事件状态机 applyResponsesStreamEvent(覆盖 text/reasoning/function_call/mcp/code_interpreter/web_search/image_generation 等事件)
  • 实验性 WebSocket ResponsescreateResponsesViaSdkWebSocket 通过动态加载 ResponsesWS 实现,由 shouldUseExperimentalOpenAIWs 判定,失败自动回退 HTTP

reasoning/thinking

mergeOpenAIReasoningOptions 读取 enableReasoningreasoningEffort(默认 'low')、thinkingVendorControl(默认 'auto')。合法 effort 集合:none/minimal/low/medium/high/xhigh

  • Chat 模式写入 reasoning_effortmax_completion_tokens
  • Responses 模式写入 reasoning.effort
  • 厂商 thinking(智谱/GLM/BigModel):applyVendorThinkingPayload 在 baseUrl 命中 bigmodel/zhipu/glm/maasthinkingVendorControl==='glm' 时写入 thinking = { type: 'enabled'|'disabled' }

ClaudeClient

class ClaudeClient extends AbstractClientthis.name = 'claude'),基于 @anthropic-ai/sdk

  • 端点buildClaudeClientOptions + normalizeClaudeEndpointPath(自动补 /v1 前缀)
  • thinkinggetClaudeThinkingConfigenableReasoningmaxTokens > 1024 时返回 { type: 'enabled', budget_tokens }budget_tokensreasoningBudgetTokens,默认为 maxTokens/2,并夹在 [1024, maxTokens-1]
  • streamingstreamMessagestream = true,处理 content_block_start(tool_use)、content_block_delta(text_delta/input_json_delta)
  • tool callinggetFromChaiteToolConverter('claude') + resolveToolChoice(..., 'claude'),文本回退 parseXmlToolCalls
  • 嵌入getEmbedding 明确不支持(抛异常)
  • 模型列表listModels/getModelInfo API 失败时回退内置列表

GeminiClient

class GeminiClient extends AbstractClientthis.name = 'gemini'),基于 @google/generative-ai

  • 安全设置:四类 HarmCategory 全部 BLOCK_NONE
  • thinkinggetGeminiThinkingConfigenableReasoning 时按 effort 映射预算(minimal:256 / low:1024 / medium:4096 / high:8192 / xhigh:16384),写入 generationConfig.thinkingConfig.thinkingBudget;用量统计读取 thoughtsTokenCount
  • streamingstreamMessagegenerateContentStream,逐 chunk chunk.text(),聚合 chunk.functionCalls()
  • tool calling:以 [{ functionDeclarations: tools }] 形式传入,normalizeGeminiToolConfig 映射 functionCallingConfig.mode(AUTO/ANY/NONE);文本回退 parseXmlToolCalls
  • 嵌入getEmbedding 支持(默认模型 text-embedding-004

thinking / reasoning 机制对比

适配器开关字段预算/强度参数底层字段
OpenAIenableReasoningreasoningEffort(none~xhigh)reasoning_effort / reasoning.effort;厂商 thinking.type
ClaudeenableReasoningreasoningBudgetTokens(默认 maxTokens/2)thinking.budget_tokens
GeminienableReasoningeffort→预算映射 或 reasoningBudgetTokensthinkingConfig.thinkingBudget

消息格式转换

插件使用统一的内部格式(Chaite 格式),通过 Converter 系统在不同 API 格式间转换:

javascript
// src/core/utils/converter.js

// 注册转换器
registerFromChaiteConverter('openai', chaiteToOpenAI)
registerIntoChaiteConverter('openai', openAIToChaite)

// 使用转换器
const openaiMessages = getFromChaiteConverter('openai')(chaiteMessages)
const chaiteMessages = getIntoChaiteConverter('openai')(openaiMessages)

导出接口

javascript
// src/core/adapters/index.js
// 从 AbstractClient.js 重导出
export {
  AbstractClient,
  parseXmlToolCalls,
  preprocessMediaToBase64,
  preprocessImageUrls,
  needsBase64Preprocess,
  needsImageBase64Preprocess
} from './AbstractClient.js'

// 三个适配器类
export { OpenAIClient } from './openai/OpenAIClient.js'
export { GeminiClient } from './gemini/GeminiClient.js'
export { ClaudeClient } from './claude/ClaudeClient.js'

// 导入 converter.js 触发转换器注册(副作用)
import './openai/converter.js'
import './gemini/converter.js'
import './claude/converter.js'

// 从 utils/converter.js 重导出转换器注册/获取函数
export {
  registerFromChaiteConverter,
  registerFromChaiteToolConverter,
  registerIntoChaiteConverter,
  getFromChaiteConverter,
  getFromChaiteToolConverter,
  getIntoChaiteConverter
} from '../utils/converter.js'

tooling.js 提供跨供应商的工具定义与 tool_choice 归一化(resolveToolChoicetoOpenAIChatTool/toClaudeTool/toGeminiToolattachToolMetadata 等),由各适配器直接 import,不经 index.js 导出。

调用流程

消息处理流程

类继承关系

工具调用解析

parseXmlToolCalls 函数支持多种工具调用格式:

  • <tools>JSON</tools> - XML 包裹的 JSON
  • <tool_call>...</tool_call> - XML 格式
  • {"tool_calls": [...]} - 裸 JSON
  • 函数调用风格 funcName({...})
  • 自动修复格式错误的 JSON

扩展适配器

添加新的 LLM 适配器:

javascript
// 1. 继承 AbstractClient
import { AbstractClient } from './AbstractClient.js'

export class MyClient extends AbstractClient {
  async sendMessage(context, option) {
    // 实现聊天逻辑
  }
}

// 2. 注册消息转换器
import './my-converter.js'  // 注册 Chaite <-> MyAPI 转换

// 3. 在 index.js 中导出
export { MyClient } from './my/MyClient.js'

下一步

基于 MIT 许可发布