diff --git a/services/anthropic_service.py b/services/anthropic_service.py index 94efafa..a3f13cf 100644 --- a/services/anthropic_service.py +++ b/services/anthropic_service.py @@ -85,8 +85,23 @@ class AnthropicService: logger.error("[Anthropic] SDK 初始化失敗: %s", e) def is_available(self) -> bool: - """SDK 是否就緒可呼叫(API key 有設且 client 初始化成功)""" - return self._client is not None + """SDK 是否就緒可呼叫(API key 有設且 client 初始化成功) + + Phase 23(2026-05-04)整合 cost_throttle: + 若 'claude' provider 被 throttle(月底推估 > 110%),is_available 回 False + 讓 caller 自動走 Gemini fallback,不送 Claude 請求。 + COST_THROTTLE_ENABLED=false 時不影響行為(戰役預設)。 + """ + if self._client is None: + return False + try: + from services.cost_throttle_service import is_provider_throttled + if is_provider_throttled('claude'): + logger.info("[Anthropic] is_available()=False — cost throttled, caller 應 fallback Gemini") + return False + except Exception: + pass # cost_throttle 不可用不阻擋 + return True def generate( self, diff --git a/services/ollama_service.py b/services/ollama_service.py index 03ad371..c3f02c8 100644 --- a/services/ollama_service.py +++ b/services/ollama_service.py @@ -387,7 +387,21 @@ class OllamaService: 請確保所有內容使用繁體中文,風格一致,並突出商品價值:""" # 文案生成使用更長的超時時間 - return self.generate(prompt, system_prompt=system_prompt, temperature=0.8, timeout=COPY_TIMEOUT) + # Phase 22.1(2026-05-04):caller × context 動態 model 路由 + # 短文 < 100 字 → gemma3:4b(輕量快),長文 → llama3.1:8b(既有預設) + # MODEL_ROUTER_ENABLED=false 時直接回 default(向下相容) + try: + from services.llm_model_router import select_model + expected_length = len(product_name) * 5 # 商品名長 × 5 推估文案輸出長度 + chosen_model = select_model( + caller='sales_copy', + context={'expected_length': expected_length}, + default=self.model, # llama3.1:8b 預設 + ) + except Exception: + chosen_model = self.model # router 失敗不影響主流程 + return self.generate(prompt, model=chosen_model, system_prompt=system_prompt, + temperature=0.8, timeout=COPY_TIMEOUT) def extract_keywords(self, text: str, max_keywords: int = 10) -> OllamaResponse: """