New router intelligence modules (26 files): alert_ingest/store, audit_store, architecture_pressure, backlog_generator/store, cost_analyzer, data_governance, dependency_scanner, drift_analyzer, incident_* (5 files), llm_enrichment, platform_priority_digest, provider_budget, release_check_runner, risk_* (6 files), signature_state_store, sofiia_auto_router, tool_governance New services: - sofiia-console: Dockerfile, adapters/, monitor/nodes/ops/voice modules, launchd, react static - memory-service: integration_endpoints, integrations, voice_endpoints, static UI - aurora-service: full app suite (analysis, job_store, orchestrator, reporting, schemas, subagents) - sofiia-supervisor: new supervisor service - aistalk-bridge-lite: Telegram bridge lite - calendar-service: CalDAV calendar service with reminders - mlx-stt-service / mlx-tts-service: Apple Silicon speech services - binance-bot-monitor: market monitor service - node-worker: STT/TTS memory providers New tools (9): agent_email, browser_tool, contract_tool, observability_tool, oncall_tool, pr_reviewer_tool, repo_tool, safe_code_executor, secure_vault New crews: agromatrix_crew (10 modules: depth_classifier, doc_facts, doc_focus, farm_state, light_reply, llm_factory, memory_manager, proactivity, reflection_engine, session_context, style_adapter, telemetry) Tests: 85+ test files for all new modules Made-with: Cursor
133 lines
5.6 KiB
Python
133 lines
5.6 KiB
Python
"""
|
||
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")
|