webagent — LLM Providers
設計原則
- BYOK(Bring Your Own Key) — 套件不代管 API key
- Provider 抽象 — host 換 provider 不用改 agent 邏輯
- v1 支援 OpenAI + Google AI Studio 兩家(Google 同一個 key 可同時呼叫 Gemini + Gemma),其他先不上
- Function calling 走 OpenAI 格式為內部標準(Google 用 adapter 轉)
- Per-role 模型分流 — 透過
LLMRouter讓不同 role 用不同 provider/model,省成本
LLMProvider 介面
interface LLMProvider {
readonly name: string;
complete(opts: CompleteOptions): Promise<CompleteResult>;
}
interface CompleteOptions {
messages: LLMMessage[];
tools?: ToolDefinition[];
temperature?: number;
maxTokens?: number;
model?: string;
signal?: AbortSignal;
}
interface CompleteResult {
content: string;
toolCalls?: ToolCall[];
usage?: { promptTokens: number; completionTokens: number };
finishReason: 'stop' | 'tool_calls' | 'length' | 'content_filter';
}
interface LLMMessage {
role: 'system' | 'user' | 'assistant' | 'tool';
content: string | ContentPart[];
toolCallId?: string; // role=tool 時
toolCalls?: ToolCall[]; // role=assistant 時
}
interface ContentPart {
type: 'text' | 'image';
text?: string;
image?: string; // URL 或 base64
}
interface ToolDefinition {
name: string;
description: string;
parameters: JSONSchema; // 標準 JSON Schema
}
interface ToolCall {
id: string;
name: string;
arguments: Record<string, any>;
}
OpenAI Provider
import { OpenAIProvider } from '@perhapxin/webagent';
const llm = new OpenAIProvider({
apiKey: 'sk-...',
model: 'gpt-5.5', // 預設 'gpt-5.4-mini'
baseURL?: 'https://api.openai.com/v1', // 可改自架 / 反代理
organization?: string,
});
支援所有 OpenAI 相容 endpoint(Azure OpenAI、自架 OpenRouter、Cloudflare AI Gateway)。
gpt-5.x / o-series reasoning models 的特殊處理
新一代 reasoning models(gpt-5.x、o1、o3、o4)不接受:
max_tokens— 改用max_completion_tokens- 自訂
temperature— 必須是 default1
Provider 內部 isReasoningModel 會偵測 model 前綴自動切:
| Model 前綴 | token field | temperature |
|---|---|---|
gpt-5.x, o[1-9], gpt-1[0-9].x |
max_completion_tokens |
省略(用 default) |
| 其他 | max_tokens |
套用 opts.temperature ?? 0.7 |
Host 不需要關心 — 換 model 名稱字串就好。
建議 model(2026 年中)
| 用途 | model |
|---|---|
| flagship | gpt-5.5 |
| 中等便宜 | gpt-5.4-mini |
| 最便宜最快 | gpt-5.4-nano |
Google Provider(Gemini + Gemma 共用)
import { GoogleProvider } from '@perhapxin/webagent';
const llm = new GoogleProvider({
apiKey: '...', // Google AI Studio key
model: 'gemini-3.1-pro-preview', // 預設
});
同一個 Google AI Studio key 可以打 Gemini 跟 Gemma 兩個 model family(只差 model id)。內部把 OpenAI 格式的 tool definitions 轉成 Google function declarations;把 Google 回應的 function call 轉回 OpenAI ToolCall。
建議 model(2026 年中)
| 用途 | model |
|---|---|
| flagship | gemini-3.1-pro-preview |
| 中等 | gemini-2.5-pro |
| 快 / 便宜 | gemini-3.1-flash-lite-preview |
| Open-weight (Gemma) | gemma-4-31b-it 或 gemma-4-26b-a4b-it |
Per-role 模型分流(LLMRouter)
不一定要全部一個 model,可以每個 role 配不同 provider/model:
import { OpenAIProvider, GoogleProvider, type LLMRouter } from '@perhapxin/webagent';
const router: LLMRouter = {
webagent: new OpenAIProvider({ apiKey: openai, model: 'gpt-5.5' }),
webagentWithSelection: new OpenAIProvider({ apiKey: openai, model: 'gpt-5.4-mini' }),
select: new GoogleProvider({ apiKey: google, model: 'gemini-3.1-flash-lite-preview' }),
voiceCleanup: new GoogleProvider({ apiKey: google, model: 'gemini-3.1-flash-lite-preview' }),
};
new WebAgent({ llm: router }); // accepts LLMProvider | LLMRouter
未設的 role 自動 fallback 到 webagent(必填)。
Roles:
| Role | 何時用 | 推薦特性 |
|---|---|---|
webagent |
webagent 主迴圈,無 selection | smart, 大 model |
webagentWithSelection |
開 palette 時有選取 → webagent | 中等(task 範圍受限) |
select |
inline 工具列、translate/summarize 等短任務 | 快、便宜 |
voiceCleanup |
語音 STT 結果 post-processing | 最便宜 |
多 provider 切換策略
Host 可以根據用戶設定 / 環境變數 / cost 動態挑:
const provider = userPlan === 'pro'
? new OpenAIProvider({ apiKey: openaiKey, model: 'gpt-5.5' })
: new GoogleProvider({ apiKey: googleKey, model: 'gemini-3.1-flash-lite-preview' });
const agent = new WebAgent({ llm: provider });
API key 哪裡放
不要 hardcode 在 client bundle。三個建議:
- 用戶自己填(推薦) — 提供 settings UI 讓用戶輸入,存 localStorage / IndexedDB
- 反代理 — 自己架一個 worker / edge function 當代理,把 key 放 server env(見
ProxyProvider) - OAuth — 用 Google OAuth + AI Studio 的 user-scoped key(適合企業場景)
預設假設 1。
Image 支援
兩家都支援 vision,介面統一:
const messages: LLMMessage[] = [{
role: 'user',
content: [
{ type: 'text', text: '這張圖是什麼?' },
{ type: 'image', image: 'data:image/png;base64,...' },
],
}];
await llm.complete({ messages });
Provider 內部處理格式差異。
Streaming
v1 不做 streaming(agent 是 turn-based,每一步要完整 tool call 才能執行)。
v2 考慮 streaming 用在 show_subtitle 場景(讓 subtitle 邊打邊出)。
自己寫 provider
class MyProvider implements LLMProvider {
readonly name = 'my-provider';
async complete(opts: CompleteOptions): Promise<CompleteResult> {
// 你的實作
}
}
只要實作 complete() 就行,agent 不在乎你怎麼接。
v1 不支援的(明確列出來,避免被問)
- ❌ Anthropic Claude(v2 加)
- ❌ Ollama / 本機模型(v2 加)
- ❌ Embedding(不在 webagent 範圍)
- ❌ Fine-tune API(不在範圍)
- ❌ Streaming(v2 看情況)