""" LLM Factory — підтримка Anthropic Claude / DeepSeek / OpenAI / fallback. Пріоритет: 1. ANTHROPIC_API_KEY → claude-sonnet-4-5 (через langchain-anthropic / crewai) 2. DEEPSEEK_API_KEY → deepseek-chat (через langchain-openai compatible) 3. OPENAI_API_KEY → gpt-4o-mini (через langchain-openai) 4. None → повертає None Змінні середовища: ANTHROPIC_API_KEY — ключ Anthropic Claude (найвищий пріоритет для Sofiia) ANTHROPIC_MODEL — модель (default: claude-sonnet-4-5) DEEPSEEK_API_KEY — ключ DeepSeek DEEPSEEK_MODEL — модель (default: deepseek-chat) OPENAI_API_KEY — ключ OpenAI (fallback) OPENAI_MODEL — модель (default: gpt-4o-mini) Використання: from crews.agromatrix_crew.llm_factory import make_llm agent = Agent(..., llm=make_llm()) """ from __future__ import annotations import logging import os logger = logging.getLogger(__name__) def make_llm(force_provider: str | None = None): """ Повертає LLM-інстанс для crewAI агентів. Fail-safe: якщо жоден ключ не знайдений — повертає None і логує warning. Args: force_provider: 'anthropic', 'deepseek', 'openai' — примусово обрати провайдера. """ anthropic_key = os.getenv("ANTHROPIC_API_KEY", "").strip() deepseek_key = os.getenv("DEEPSEEK_API_KEY", "").strip() openai_key = os.getenv("OPENAI_API_KEY", "").strip() # ── Варіант 0: Anthropic Claude ────────────────────────────────────────── if anthropic_key and force_provider in (None, "anthropic"): # Try langchain-anthropic first try: from langchain_anthropic import ChatAnthropic # type: ignore[import-untyped] model = os.getenv("ANTHROPIC_MODEL", "claude-sonnet-4-5") llm = ChatAnthropic( model=model, api_key=anthropic_key, temperature=0.2, max_tokens=8192, ) logger.info("LLM: Anthropic Claude via langchain-anthropic (model=%s)", model) return llm except ImportError: pass except Exception as exc: logger.warning("langchain-anthropic init failed (%s), trying crewai.LLM", exc) # Try crewai.LLM with anthropic provider try: from crewai import LLM # type: ignore[import-untyped] model = os.getenv("ANTHROPIC_MODEL", "claude-sonnet-4-5") llm = LLM( model=f"anthropic/{model}", api_key=anthropic_key, temperature=0.2, max_tokens=8192, ) logger.info("LLM: Anthropic Claude via crewai.LLM (model=%s)", model) return llm except (ImportError, Exception) as exc: logger.warning("crewai.LLM Anthropic init failed (%s), trying DeepSeek fallback", exc) # ── Варіант 1: DeepSeek через OpenAI-compatible API ────────────────────── if deepseek_key and force_provider in (None, "deepseek"): try: from langchain_openai import ChatOpenAI model = os.getenv("DEEPSEEK_MODEL", "deepseek-chat") base_url = os.getenv("DEEPSEEK_BASE_URL", "https://api.deepseek.com") llm = ChatOpenAI( model=model, api_key=deepseek_key, base_url=base_url, temperature=0.3, ) logger.info("LLM: DeepSeek via ChatOpenAI (model=%s, base_url=%s)", model, base_url) return llm except (ImportError, Exception) as exc: logger.warning("DeepSeek LLM init failed (%s), trying OpenAI fallback", exc) # ── Варіант 2: OpenAI ──────────────────────────────────────────────────── if openai_key and force_provider in (None, "openai"): try: from langchain_openai import ChatOpenAI model = os.getenv("OPENAI_MODEL", "gpt-4o-mini") llm = ChatOpenAI( model=model, api_key=openai_key, temperature=0.3, ) logger.info("LLM: OpenAI ChatOpenAI (model=%s)", model) return llm except ImportError: pass try: from crewai import LLM model = os.getenv("OPENAI_MODEL", "gpt-4o-mini") llm = LLM( model=f"openai/{model}", api_key=openai_key, temperature=0.3, ) logger.info("LLM: OpenAI via crewai.LLM (model=%s)", model) return llm except (ImportError, Exception) as exc: logger.warning("OpenAI LLM init failed: %s", exc) # ── Нічого немає ──────────────────────────────────────────────────────── logger.error( "LLM: no API key configured! " "Set ANTHROPIC_API_KEY (preferred for Sofiia), DEEPSEEK_API_KEY, or OPENAI_API_KEY." ) return None def make_sofiia_llm(): """Спеціалізований LLM для Sofiia — Claude Sonnet з розширеним контекстом.""" return make_llm(force_provider="anthropic")