簡介
一個實用的人工智慧平台需要支援許多LLM提供者。您無法對 OpenAI 進行硬編碼 - 客戶可能希望在本地使用 Claude、Gemini 或 Ollama。 LLM Router 使用適配器模式解決了這個問題。
1.LLMAdapter接口
// packages/core/src/llm/types.ts
export interface LLMAdapter {
readonly provider: string;
chat(
messages: LLMMessage[],
tools?: ToolDefinition[],
options?: LLMOptions,
): Promise<LLMResponse>;
chatStream(
messages: LLMMessage[],
tools?: ToolDefinition[],
options?: LLMOptions,
): AsyncGenerator<StreamEvent>;
listModels(): Promise<ModelInfo[]>;
}
export interface LLMOptions {
temperature?: number;
maxTokens?: number;
topP?: number;
stop?: string[];
jsonMode?: boolean;
}
export interface ModelInfo {
id: string;
name: string;
provider: string;
contextWindow: number;
pricing?: { inputPer1k: number; outputPer1k: number };
}
每個 LLM 提供者都必須實作此介面 - 這就是 適配器模式。
2.OpenAI 適配器
// packages/core/src/llm/adapters/openai-adapter.ts
import OpenAI from 'openai';
export class OpenAIAdapter implements LLMAdapter {
readonly provider = 'openai';
private client: OpenAI;
constructor(config: { apiKey: string; baseUrl?: string }) {
this.client = new OpenAI({
apiKey: config.apiKey,
baseURL: config.baseUrl,
});
}
async chat(messages: LLMMessage[], tools?: ToolDefinition[]): Promise<LLMResponse> {
const response = await this.client.chat.completions.create({
model: 'gpt-4o',
messages: this.convertMessages(messages),
tools: tools ? this.convertTools(tools) : undefined,
});
const choice = response.choices[0];
return {
content: choice.message.content ?? '',
toolCalls: choice.message.tool_calls?.map(tc => ({
id: tc.id,
name: tc.function.name,
arguments: JSON.parse(tc.function.arguments),
})),
usage: response.usage ? {
promptTokens: response.usage.prompt_tokens,
completionTokens: response.usage.completion_tokens,
totalTokens: response.usage.total_tokens,
} : undefined,
};
}
async *chatStream(messages: LLMMessage[], tools?: ToolDefinition[]): AsyncGenerator<StreamEvent> {
const stream = await this.client.chat.completions.create({
model: 'gpt-4o',
messages: this.convertMessages(messages),
tools: tools ? this.convertTools(tools) : undefined,
stream: true,
});
for await (const chunk of stream) {
const delta = chunk.choices[0]?.delta;
if (delta?.content) {
yield { type: 'text-delta', delta: delta.content };
}
if (delta?.tool_calls) {
for (const tc of delta.tool_calls) {
if (tc.function?.name) {
yield { type: 'tool-call-start', toolCallId: tc.id!, toolName: tc.function.name };
}
if (tc.function?.arguments) {
yield { type: 'tool-call-args', toolCallId: tc.id!, args: tc.function.arguments };
}
}
}
}
yield { type: 'finish' };
}
private convertMessages(messages: LLMMessage[]) {
// Convert platform-agnostic format → OpenAI format
return messages.map(m => ({
role: m.role as 'system' | 'user' | 'assistant' | 'tool',
content: m.content,
tool_calls: m.toolCalls?.map(tc => ({
id: tc.id,
type: 'function' as const,
function: { name: tc.name, arguments: JSON.stringify(tc.arguments) },
})),
tool_call_id: m.toolCallId,
}));
}
private convertTools(tools: ToolDefinition[]) {
return tools.map(t => ({
type: 'function' as const,
function: {
name: t.name,
description: t.description,
parameters: t.parameters,
},
}));
}
}
3.LLM 路由器 — 路由邏輯
// packages/core/src/llm/llm-router.ts
export type TaskComplexity = 'fast' | 'smart' | 'cheap';
export class LLMRouter {
private adapters = new Map<string, LLMAdapter>();
private fallbackChains: Record<TaskComplexity, string[]> = {
fast: ['groq', 'openai', 'anthropic'],
smart: ['anthropic', 'openai', 'google'],
cheap: ['ollama', 'groq', 'openai'],
};
register(adapter: LLMAdapter) {
this.adapters.set(adapter.provider, adapter);
}
// Resolve chain — tìm provider available theo priority
resolveChain(complexity: TaskComplexity): LLMAdapter {
const chain = this.fallbackChains[complexity];
for (const provider of chain) {
const adapter = this.adapters.get(provider);
if (adapter) return adapter;
}
// Fallback: lấy bất kỳ adapter nào available
const first = this.adapters.values().next().value;
if (!first) throw new Error('No LLM providers configured');
return first;
}
async chat(
messages: LLMMessage[],
tools?: ToolDefinition[],
complexity: TaskComplexity = 'smart',
): Promise<LLMResponse> {
const adapter = this.resolveChain(complexity);
try {
return await adapter.chat(messages, tools);
} catch (error) {
// Auto-fallback to next provider
const chain = this.fallbackChains[complexity];
const currentIndex = chain.indexOf(adapter.provider);
for (let i = currentIndex + 1; i < chain.length; i++) {
const fallback = this.adapters.get(chain[i]);
if (fallback) {
console.warn(`Falling back from ${adapter.provider} to ${chain[i]}`);
return await fallback.chat(messages, tools);
}
}
throw error;
}
}
}
4. Anthropic 與 Google 轉接器
// packages/core/src/llm/adapters/anthropic-adapter.ts
export class AnthropicAdapter implements LLMAdapter {
readonly provider = 'anthropic';
async chat(messages: LLMMessage[], tools?: ToolDefinition[]): Promise<LLMResponse> {
// Anthropic API has a different format:
// - system prompt is separate from messages
// - tool_use blocks instead of tool_calls
const systemMsg = messages.find(m => m.role === 'system');
const otherMsgs = messages.filter(m => m.role !== 'system');
const response = await this.client.messages.create({
model: 'claude-sonnet-4-20250514',
system: systemMsg?.content,
messages: this.convertMessages(otherMsgs),
tools: tools?.map(t => ({
name: t.name,
description: t.description,
input_schema: t.parameters,
})),
max_tokens: 4096,
});
return this.parseResponse(response);
}
}
每個適配器都有一個獨特的任務:在平台無關的介面和提供者的特定 API 之間轉換格式。
5. 總結
| 圖案 | 角色 |
|---|---|
| 適配器 | 規格 LLM 提供者之間的 API 差異 |
| 策略 | 根據任務複雜度選擇提供者 |
| 責任鏈 | 提供者失敗時的後備鏈 |
下一篇文章: 工具註冊表 — 為 AI Agent 註冊並執行工具。