建議 model:opus/effort:medium — 改的是容器對 AI 的呼叫方式,且要動套件+容器+宿主注入邏輯,牽涉 D12 決策細節。
本卡屬 FR-107(母卡 CM-1843)第 .5 棒(子需求卡 CM-1848),子任務 T-5.3b:從 T-5.3 拆出來的「多供應商」半部——原 T-5.3(CM-1866)只留 prompt 三段組裝,改名 T-5.3a;本卡負責讓分類容器能用 Anthropic 或 OpenAI,由系統設定裡填了哪家的金鑰決定可選哪家、哪個模型(決策者 2026-09-16 D12)。
現在分類容器寫死呼叫 Anthropic(Claude)。這張卡讓它能改叫 OpenAI,而且不是寫死二選一——系統設定裡填了哪幾家的 API 金鑰,畫面上跳出來的模型選單就有哪幾家。沒填金鑰的供應商,選單裡不會出現。
具體做法:金鑰存進系統設定(system_configs 新一組 AI_PROVIDER_CONFIG,租戶各自可填,沒填就退回讀環境變數);新增一支「型號碼表」列出各家有哪些模型;容器裡抽一個共同介面,Anthropic 和 OpenAI 各自實作一份,容器不再認定「一定是打 Claude」。
套件(走 path dependency 開發)
~/Projects/Jedicogy/module/jedi-python-package/jedi-evidence-classification/
.../app/service/evidence_classification_service.py
:81 ALLOWED_MODELS 白名單 ← 退場,改讀 llm_models.py 碼表 × 可用供應商聯集
.../app/service/llm_models.py ← 新檔,型號碼表
LlmModelSpec(provider, model_id, display_name, supports_vision, default)
.../domain/ports.py(或就近模組)
LlmClient(Protocol): classify(system_block, user_parts) -> dict
AnthropicClient ← 搬 container_entrypoint.py:635 起 classify_with_claude() 邏輯進來
OpenAIClient ← chat.completions 風格,endpoint 可配(相容 Azure/Ollama 走它)
.../docker/container_entrypoint.py
:635 classify_with_claude() 邏輯搬進 AnthropicClient,容器改用 LlmClient 介面呼叫
圖片證據遇 supports_vision=False 的型號,明確標「此模型不支援圖片,未分類」,不靜默跳過
主專案(宿主)
system_configs 新 group AI_PROVIDER_CONFIG(CRUD/租戶級,比照 STORAGE_CONFIG:
ROOT 給預設、新租戶從 ROOT 複製、值加密不回顯明文)
core/plugins/evidence_classification.py(或對應容器啟動邏輯)
← 起容器時只塞「這批選定供應商」的金鑰環境變數(ANTHROPIC_API_KEY 或 OPENAI_API_KEY 擇一注入,不兩者都塞)
新端點 GET /classification/available-models
← 回傳「該租戶目前可用」的模型:已配置金鑰(或 env 有值)的供應商 × 碼表
class LlmClient(Protocol):
def classify(self, system_block: str, user_parts: list[dict]) -> dict: ...
# 回傳格式與現有 classify_with_claude() 對齊:分類結果 + usage/成本估算
class AnthropicClient:
def __init__(self, api_key: str, model_id: str): ...
class OpenAIClient:
def __init__(self, api_key: str, model_id: str, base_url: str | None = None): ...
# base_url 供 Azure OpenAI/Ollama 等相容 API 覆寫端點