""" CrewAI Client for Router Handles decision: direct LLM vs CrewAI orchestration """ import os import json import logging import httpx from typing import Dict, Any, Optional, Tuple, List from pathlib import Path logger = logging.getLogger(__name__) CREWAI_URL = os.getenv("CREWAI_URL", "http://dagi-staging-crewai-service:9010") CREWAI_ENABLED = os.getenv("CREWAI_ENABLED", "true").lower() == "true" CREWAI_AGENTS_PATH = os.getenv("CREWAI_AGENTS_PATH", "/config/crewai_agents.json") FALLBACK_CREWAI_PATH = "/app/config/crewai_agents.json" MIN_PROMPT_LENGTH_FOR_CREW = 100 COMPLEXITY_KEYWORDS = [ "план", "plan", "аналіз", "analysis", "дослідження", "research", "порівняй", "compare", "розроби", "develop", "створи стратегію", "декомпозиція", "decompose", "крок за кроком", "step by step", "детально", "in detail", "комплексний", "comprehensive" ] _crewai_cache = None def load_crewai_config(): global _crewai_cache if _crewai_cache is not None: return _crewai_cache for path in [CREWAI_AGENTS_PATH, FALLBACK_CREWAI_PATH]: try: p = Path(path) if p.exists(): with open(p, "r", encoding="utf-8") as f: data = json.load(f) logger.info(f"Loaded CrewAI config from {path}") _crewai_cache = data return data except Exception as e: logger.warning(f"Could not load from {path}: {e}") return None def get_agent_crewai_info(agent_id): config = load_crewai_config() if not config: return {"enabled": False, "orchestrator": False, "team": []} orchestrators = config.get("orchestrators", []) teams = config.get("teams", {}) is_orchestrator = any(o.get("id") == agent_id for o in orchestrators) team_info = teams.get(agent_id, {}) team_members = team_info.get("members", []) return { "enabled": is_orchestrator, "orchestrator": is_orchestrator, "team": team_members } def should_use_crewai(agent_id, prompt, agent_config, force_crewai=False): """ Decide whether to use CrewAI orchestration or direct LLM. Returns: (use_crewai: bool, reason: str) """ if not CREWAI_ENABLED: return False, "crewai_disabled_globally" if force_crewai: return True, "force_crewai_requested" crewai_info = get_agent_crewai_info(agent_id) if not crewai_info.get("enabled", False): return False, "agent_crewai_disabled" if not crewai_info.get("orchestrator", False): return False, "agent_not_orchestrator" team = crewai_info.get("team", []) if not team: return False, "agent_has_no_team" if len(prompt) < MIN_PROMPT_LENGTH_FOR_CREW: return False, "prompt_too_short" prompt_lower = prompt.lower() has_complexity = any(kw in prompt_lower for kw in COMPLEXITY_KEYWORDS) if has_complexity: return True, "complexity_keywords_detected" return False, "default_direct_llm" async def call_crewai(agent_id, task, context=None, team=None): try: if not team: crewai_info = get_agent_crewai_info(agent_id) team = crewai_info.get("team", []) async with httpx.AsyncClient(timeout=180.0) as client: payload = { "task": task, "orchestrator": agent_id, "context": context or {}, } if team: payload["team"] = [ m.get("role", str(m)) if isinstance(m, dict) else m for m in team ] logger.info(f"CrewAI call: agent={agent_id}, team={len(team)} members") response = await client.post(f"{CREWAI_URL}/crew/run", json=payload) if response.status_code == 200: data = response.json() success = data.get("success") logger.info(f"CrewAI response: success={success}") return data else: logger.error(f"CrewAI error: {response.status_code}") return {"success": False, "result": None, "agents_used": [], "error": f"HTTP {response.status_code}"} except Exception as e: logger.error(f"CrewAI exception: {e}") return {"success": False, "result": None, "agents_used": [], "error": str(e)} async def get_crewai_health(): try: async with httpx.AsyncClient(timeout=5.0) as client: response = await client.get(f"{CREWAI_URL}/health") return response.json() except Exception as e: return {"status": "error", "error": str(e)}