Files
microdao-daarion/services/router/crewai_client.py

158 lines
5.4 KiB
Python

"""
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_ORCHESTRATORS_ALWAYS = os.getenv("CREWAI_ORCHESTRATORS_ALWAYS", "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, metadata=None, 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"
# Architecture mode: top-level orchestrators go through CrewAI API by default.
if CREWAI_ORCHESTRATORS_ALWAYS:
return True, "orchestrator_default_crewai"
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, profile=None):
try:
if not team:
crewai_info = get_agent_crewai_info(agent_id)
team = crewai_info.get("team", [])
async with httpx.AsyncClient(timeout=600.0) as client:
effective_context = context or {}
effective_profile = profile or (effective_context.get("metadata", {}) or {}).get("crewai_profile")
if not effective_profile and agent_id == "clan":
effective_profile = "zhos_mvp"
payload = {
"task": task,
"orchestrator_id": agent_id,
"context": effective_context,
}
if effective_profile:
payload["profile"] = effective_profile
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)}