MINARA

LLM 集成

Provider 抽象、提示詞緩存、模型路由

Minara Agent 通過統一的內部接口對接多個 LLM 服務商。 在 Anthropic、OpenAI、OpenRouter 或本地模型之間切換, 無需改動 Agent 循環。 本頁介紹 provider 層的結構、提示詞緩存的組織方式,以及模型路由的工作原理。

為什麼用自定義路由器,而不直接調用各廠商 SDK? Agent 循環、回測、反思、子 Agent 等功能對成本和延遲的要求各不相同。 Minara 的路由器可以在同一代碼庫中,將需要穩定緩存的高成本任務路由到 Sonnet, 將低成本批量評估路由到 Haiku;當某個 provider 觸發限流時也能優雅降級。 直接綁定單一廠商 SDK,會將你鎖定在其定價體系中。

實際使用:通過環境變量ANTHROPIC_API_KEYOPENAI_API_KEYOPENROUTER_API_KEY)配置 provider。

Provider 接口

所有 provider 均實現 apps/agent/src/core/agent-loop.ts 中的 LLMClient

interface LLMClient {
  createMessage(params: {
    model: string;
    system: SystemPrompt;           // string 或可缓存块
    messages: MessageParam[];
    tools?: ToolDefinition[];
    maxTokens?: number;
    stream?: boolean;
  }): Promise<LLMResponse>;

  streamMessage?(params: ...): AsyncIterable<LLMStreamEvent>;
  vision?(call: LLMVisionCall): Promise<LLMVisionResult>;
}

具體實現位於 apps/agent/src/llm/

文件Provider鑑權方式
anthropic-api-key.tsAnthropicANTHROPIC_API_KEY
anthropic-oauth.tsAnthropic(Claude.ai)OAuth refresh token
anthropic-wire.ts共享 wire 協議
openai-wire.tsOpenAI / OpenRouterOPENAI_API_KEY / OPENROUTER_API_KEY
openrouter.tsOpenRouter 路由器OPENROUTER_API_KEY

select-provider.ts 在啟動時按優先級順序,根據環境變量選擇具體客戶端:

  1. Anthropic OAuth(CLAUDE_CODE_OAUTH_TOKEN 已設置時)
  2. Anthropic API key(ANTHROPIC_API_KEY 已設置時)
  3. OpenRouter(OPENROUTER_API_KEY 已設置時)
  4. OpenAI(OPENAI_API_KEY 已設置時)

四項均未配置時,進程將拒絕啟動並輸出明確報錯信息。 選擇邏輯在 app.ts 中只有一行;下游全部通過 LLMClient 通信。

提示詞緩存:不可省略

Anthropic 提示詞緩存的 TTL 為 5 分鐘,命中緩存時費用僅為正常輸入 token 價格的 10%。 對於每輪都會發起大量相似調用的 Agent(每次工具調用往返都需要 catalog 加 identity), 緩存命中率決定了成本是"可接受"還是"過於昂貴"。

因此,系統提示詞被拆分為若干顯式塊:

system: [
  { type: "text", text: identityPrompt,      cache: true  },
  { type: "text", text: skillCatalog,        cache: true  },
  { type: "text", text: activeSkillPrompts,  cache: false },
  { type: "text", text: signalContextBlock,  cache: false },
  { type: "text", text: pendingConfirmation, cache: false },
]

提示詞構建器強制執行以下不變式:

  1. 可緩存塊靠前排列。 非可緩存塊之後的內容無法緩存,因為緩存鍵採用嚴格的前綴匹配。
  2. 可緩存塊保持穩定。 identity 和 catalog 僅在技能註冊表或基礎提示詞變更時才會更新,不會按輪次變化。
  3. 動態內容置於末尾。 活躍技能片段、信號上下文、待確認項以及對話歷史,均放在緩存邊界之後。

anthropic-wire.tsSystemPromptBlock[] 映射為 Anthropic 的 cache_control: {type: "ephemeral"} 標記, 並在響應元數據中追蹤緩存命中率; 結構化日誌中對應字段為 llm.cache_read_input_tokensllm.cache_creation_input_tokens

對於不支持提示詞緩存的 provider(如撰寫本文時的 OpenAI), wire 層會靜默地將所有塊拼接為單一系統字符串。 調用方無感知,行為也不會產生差異。

模型選擇

每輪使用的模型由 apps/agent/src/learning/llm-router.ts 決定(注意:此路由器與技能路由器無關,專門負責將 LLM 調用路由到具體模型)。 默認策略如下:

  • 主 Agent 輪次:Claude Sonnet 4.6,上下文窗口 1M。
  • 深度研究子 Agent:Claude Opus 4.6(高推理能力)。
  • 視覺調用:選用支持視覺能力的 provider。
  • 向量嵌入:通過專用環境變量 EMBEDDING_PROVIDER 指定。

每次調用都會將 {provider, model, reason} 寫入審計日誌, 可直接用 SQL 查詢回答"為什麼這輪花費了 X",無需逐層排查代碼。

覆蓋方式:

  • AGENT_MODEL 固定主 Agent 循環使用的模型。 也可通過 minara config set model.defaultModel <id>/model 斜槓命令交互式設置, 兩者均會持久化到 $MINARA_DATA_DIR/env

工具調用格式

Anthropic 和 OpenAI 均支持工具調用,但 wire 格式不同。 Agent 循環內部採用 Anthropic 的格式, openai-wire.ts 負責雙向轉換。 具體影響如下:

  • 工具 schema 只定義一次,以 JSON Schema 形式存於 ToolEntry.schema.parameters,兩個 wire 層共用同一份 schema。
  • 停止原因已歸一化。 Anthropic 的 "end_turn" / "tool_use" / "max_tokens" 與 OpenAI 的 "stop" / "tool_calls" / "length" 均映射為循環消費的統一枚舉值。
  • 流式事件已歸一化為統一的 LLMStreamEvent 聯合類型, /chat/stream 無需感知上游 provider。

視覺能力

視覺調用通過獨立的 LLMClient.vision() 方法處理, 因為並非所有 provider 都支持在工具調用中內聯視覺能力。 vision_analyze 工具會顯式調用該方法; 需要讀取截圖的工具(browser.* 工具集)會先將圖片編碼為 base64 再發起調用。

Anthropic OAuth 流程

Anthropic OAuth 允許用戶使用 Claude.ai 賬號登錄,無需提供 API key。 相關流程位於 apps/agent/src/llm/oauth/

  1. /auth/anthropic/init 啟動 PKCE 流程,返回授權 URL。
  2. 用戶訪問該 URL,授權後被重定向回應用。
  3. /auth/anthropic/exchange 用授權碼換取 refresh token。
  4. Refresh token 使用本地密鑰加密後存入 SQLite。
  5. 每次 LLM 調用在需要時會自動透明地刷新 access token。

OpenAI 和 OpenRouter 採用相同模式。 具體路由請參閱鑑權接口參考文檔。

新增 Provider

接入新的 LLM provider:

  1. apps/agent/src/llm/<name>-wire.ts 中實現 LLMClient。 提示詞緩存支持是可選項,但強烈建議實現。
  2. select-provider.ts 中添加對應的選擇分支。
  3. 將環境變量添加到 .env.example環境變量清單
  4. tests/integration/llm/ 下編寫集成測試, 使用 tests/fakes/llm/ 中的 wire 層 fake。

不要修改 Agent 循環來添加 provider 專屬代碼路徑。 若某個 provider 有特殊行為,應將其封裝在 wire 層內部處理。

本頁目錄