From 635f2d7e37ec177da5a7adaba5692eba5f7e86a6 Mon Sep 17 00:00:00 2001 From: Apple Date: Wed, 18 Feb 2026 09:21:47 -0800 Subject: [PATCH] helion: deepseek-first, on-demand CrewAI, local subagent profiles, concise post-synthesis --- config/crewai_teams.yml | 20 +-- gateway-bot/http_api.py | 117 +++++++++--------- gateway-bot/router_client.py | 3 + .../app/config/crewai_teams.yml | 20 +-- services/router/crewai_client.py | 22 +++- services/router/main.py | 15 ++- 6 files changed, 117 insertions(+), 80 deletions(-) diff --git a/config/crewai_teams.yml b/config/crewai_teams.yml index caf9fee5..789e1d81 100644 --- a/config/crewai_teams.yml +++ b/config/crewai_teams.yml @@ -64,7 +64,7 @@ helion: synthesis: role_context: HELION Orchestrator system_prompt_ref: roles/helion/orchestrator_synthesis.md - llm_profile: reasoning + llm_profile: science team: - id: energy_researcher role_context: Energy Researcher @@ -73,7 +73,7 @@ helion: - id: systems_modeler role_context: Systems Modeler system_prompt_ref: roles/helion/systems_modeler.md - llm_profile: reasoning + llm_profile: science - id: policy_analyst role_context: Policy Analyst system_prompt_ref: roles/helion/policy_analyst.md @@ -81,7 +81,7 @@ helion: - id: risk_assessor role_context: Risk Assessor system_prompt_ref: roles/helion/risk_assessor.md - llm_profile: reasoning + llm_profile: science - id: communicator role_context: Communicator system_prompt_ref: roles/helion/communicator.md @@ -95,12 +95,12 @@ helion: synthesis: role_context: Executive Synthesis (CEO-mode) system_prompt_ref: roles/helion/HELION_CORE/orchestrator_synthesis.md - llm_profile: reasoning + llm_profile: science team: - id: orchestrator_front_desk_router role_context: Orchestrator (Front Desk / Router) system_prompt_ref: roles/helion/HELION_CORE/orchestrator_front_desk_router.md - llm_profile: reasoning + llm_profile: science - id: knowledge_curator_rag_librarian role_context: Knowledge Curator (L1–L3 RAG Librarian) system_prompt_ref: roles/helion/HELION_CORE/knowledge_curator_rag_librarian.md @@ -108,15 +108,15 @@ helion: - id: safety_anti_hallucination_gate role_context: Safety & Anti-Hallucination Gate system_prompt_ref: roles/helion/HELION_CORE/safety_anti_hallucination_gate.md - llm_profile: reasoning + llm_profile: science - id: legal_compliance_gdpr_mica_aml_kyc role_context: Legal & Compliance (GDPR/MiCA/AML/KYC) system_prompt_ref: roles/helion/HELION_CORE/legal_compliance_gdpr_mica_aml_kyc.md - llm_profile: reasoning + llm_profile: science - id: security_anti_fraud_anti_fake role_context: Security & Anti-Fraud / Anti-Fake system_prompt_ref: roles/helion/HELION_CORE/security_anti_fraud_anti_fake.md - llm_profile: reasoning + llm_profile: science - id: energy_systems_engineer role_context: Energy Systems Engineer (GGU/BioMiner/SES) system_prompt_ref: roles/helion/HELION_CORE/energy_systems_engineer.md @@ -124,7 +124,7 @@ helion: - id: finance_roi_modeler role_context: Finance & ROI Modeler system_prompt_ref: roles/helion/HELION_CORE/finance_roi_modeler.md - llm_profile: reasoning + llm_profile: science - id: dao_guide_governance_onboarding role_context: DAO Guide (Governance & Onboarding) system_prompt_ref: roles/helion/HELION_CORE/dao_guide_governance_onboarding.md @@ -132,7 +132,7 @@ helion: - id: tokenization_rwa_nft_architect role_context: Tokenization & RWA/NFT Architect system_prompt_ref: roles/helion/HELION_CORE/tokenization_rwa_nft_architect.md - llm_profile: reasoning + llm_profile: science - id: growth_soft_selling_cx role_context: Growth & Soft-Selling CX system_prompt_ref: roles/helion/HELION_CORE/growth_soft_selling_cx.md diff --git a/gateway-bot/http_api.py b/gateway-bot/http_api.py index 579218e9..e7f0b4e2 100644 --- a/gateway-bot/http_api.py +++ b/gateway-bot/http_api.py @@ -469,6 +469,18 @@ SENPAI_CONFIG = load_agent_config( default_prompt="Ти — Гордон Сенпай (Gordon Senpai), радник з ринків капіталу та цифрових активів. Допомагаєш з трейдингом, ризик-менеджментом, аналізом ринків.", ) +# 1OK Configuration +ONEOK_CONFIG = load_agent_config( + agent_id="oneok", + name=os.getenv("ONEOK_NAME", "1OK"), + prompt_path=os.getenv( + "ONEOK_PROMPT_PATH", + str(Path(__file__).parent / "oneok_prompt.txt"), + ), + telegram_token_env="ONEOK_TELEGRAM_BOT_TOKEN", + default_prompt="Ти — 1OK, асистент віконного майстра. Допомагаєш з кваліфікацією ліда, підготовкою заміру та формуванням комерційної пропозиції.", +) + # SOUL / Athena Configuration SOUL_CONFIG = load_agent_config( agent_id="soul", @@ -517,6 +529,7 @@ AGENT_REGISTRY: Dict[str, AgentConfig] = { "clan": CLAN_CONFIG, "eonarch": EONARCH_CONFIG, "senpai": SENPAI_CONFIG, + "oneok": ONEOK_CONFIG, "soul": SOUL_CONFIG, "yaromir": YAROMIR_CONFIG, "sofiia": SOFIIA_CONFIG, @@ -707,6 +720,11 @@ async def eonarch_telegram_webhook(update: TelegramUpdate): async def senpai_telegram_webhook(update: TelegramUpdate): return await handle_telegram_webhook(SENPAI_CONFIG, update) +# 1OK webhook endpoint +@router.post("/oneok/telegram/webhook") +async def oneok_telegram_webhook(update: TelegramUpdate): + return await handle_telegram_webhook(ONEOK_CONFIG, update) + # SOUL / Athena webhook endpoint @router.post("/soul/telegram/webhook") @@ -897,50 +915,6 @@ def _resolve_stt_upload_url() -> str: # Helper Functions # ======================================== -async def send_telegram_message(chat_id: str, text: str, bot_token: Optional[str] = None) -> bool: - """ - Відправити повідомлення в Telegram. - - Args: - chat_id: ID чату - text: Текст повідомлення - bot_token: Telegram bot token (якщо None, використовується TELEGRAM_BOT_TOKEN) - - Returns: - True якщо успішно, False інакше - """ - try: - token = bot_token or os.getenv("TELEGRAM_BOT_TOKEN") - if not token: - logger.error("TELEGRAM_BOT_TOKEN not set") - return False - - # Strip ... tags (DeepSeek reasoning leak) - import re - text = re.sub(r'.*?', '', text, flags=re.DOTALL) - text = re.sub(r'.*$', '', text, flags=re.DOTALL) # unclosed tag - # Strip any DSML/XML-like markup - text = re.sub(r']*>', '', text) - text = text.strip() - if not text: - text = "..." - - url = f"https://api.telegram.org/bot{token}/sendMessage" - payload = { - "chat_id": chat_id, - "text": text, - "parse_mode": "Markdown" - } - - async with httpx.AsyncClient() as client: - response = await client.post(url, json=payload, timeout=10.0) - response.raise_for_status() - return True - except Exception as e: - logger.error(f"Failed to send Telegram message: {e}") - return False - - async def get_telegram_file_path(file_id: str, bot_token: Optional[str] = None) -> Optional[str]: """ Отримати шлях до файлу з Telegram API. @@ -2491,7 +2465,11 @@ async def handle_telegram_webhook( + "\n(Не потрібно щоразу представлятися по імені або писати шаблонне: 'чим можу допомогти'.)" ) - if needs_complex_reasoning: + # Helion policy: DeepSeek-first primary response path. + if agent_config.agent_id == "helion": + router_request["metadata"]["provider"] = "cloud_deepseek" + router_request["metadata"]["reason"] = "helion_primary_deepseek" + elif needs_complex_reasoning: router_request["metadata"]["provider"] = "cloud_deepseek" router_request["metadata"]["reason"] = "auto_complex" @@ -3546,27 +3524,52 @@ async def _artifact_job_done(job_id: str, note: str) -> None: raise HTTPException(status_code=502, detail=f"Job done error: {resp.text[:200]}") -async def send_telegram_message(chat_id: str, text: str, bot_token: str = None): - """Send message to Telegram chat""" +async def send_telegram_message(chat_id: str, text: str, bot_token: Optional[str] = None) -> bool: + """Send message to Telegram chat with explicit error diagnostics.""" telegram_token = bot_token or os.getenv("TELEGRAM_BOT_TOKEN") if not telegram_token: logger.error("TELEGRAM_BOT_TOKEN not set") - return - + return False + + # Defensive cleanup for occasional reasoning/markup leaks. + import re + safe_text = re.sub(r'.*?', '', text or "", flags=re.DOTALL) + safe_text = re.sub(r'.*$', '', safe_text, flags=re.DOTALL) + safe_text = safe_text.strip() or "..." + + token_id = telegram_token.split(":", 1)[0] if ":" in telegram_token else "unknown" url = f"https://api.telegram.org/bot{telegram_token}/sendMessage" payload = { - "chat_id": chat_id, - "text": text, - # "parse_mode": "Markdown", # Removed to prevent 400 errors + "chat_id": str(chat_id), + "text": safe_text, + "disable_web_page_preview": True, } - + try: async with httpx.AsyncClient() as client: - response = await client.post(url, json=payload, timeout=10.0) - response.raise_for_status() - logger.info(f"Telegram message sent to chat {chat_id}") + response = await client.post(url, json=payload, timeout=15.0) + + if response.status_code >= 400: + err_desc = response.text[:300] + try: + body = response.json() + err_desc = body.get("description") or err_desc + except Exception: + pass + logger.error( + "Telegram sendMessage failed: bot_id=%s chat_id=%s status=%s desc=%s", + token_id, + chat_id, + response.status_code, + err_desc, + ) + return False + + logger.info("Telegram message sent: bot_id=%s chat_id=%s", token_id, chat_id) + return True except Exception as e: - logger.error(f"Error sending Telegram message: {e}") + logger.error("Telegram sendMessage exception: bot_id=%s chat_id=%s error=%s", token_id, chat_id, e) + return False # ======================================== diff --git a/gateway-bot/router_client.py b/gateway-bot/router_client.py index 1e01aad4..b0a289ca 100644 --- a/gateway-bot/router_client.py +++ b/gateway-bot/router_client.py @@ -25,6 +25,7 @@ GATEWAY_MAX_TOKENS_CONCISE = int(os.getenv("GATEWAY_MAX_TOKENS_CONCISE", "220")) GATEWAY_MAX_TOKENS_TRAINING = int(os.getenv("GATEWAY_MAX_TOKENS_TRAINING", "900")) GATEWAY_TEMPERATURE_DEFAULT = float(os.getenv("GATEWAY_TEMPERATURE_DEFAULT", "0.4")) GATEWAY_MAX_TOKENS_SENPAI_DEFAULT = int(os.getenv("GATEWAY_MAX_TOKENS_SENPAI_DEFAULT", "320")) +GATEWAY_MAX_TOKENS_HELION_DEFAULT = int(os.getenv("GATEWAY_MAX_TOKENS_HELION_DEFAULT", "240")) GATEWAY_MAX_TOKENS_DETAILED = int(os.getenv("GATEWAY_MAX_TOKENS_DETAILED", "900")) @@ -87,6 +88,8 @@ async def send_to_router(body: Dict[str, Any]) -> Dict[str, Any]: # Senpai tends to over-verbose responses in Telegram; use lower default unless user asked details. if agent_id == "senpai": max_tokens = GATEWAY_MAX_TOKENS_SENPAI_DEFAULT + elif agent_id == "helion": + max_tokens = min(max_tokens, GATEWAY_MAX_TOKENS_HELION_DEFAULT) if metadata.get("is_training_group"): max_tokens = GATEWAY_MAX_TOKENS_TRAINING diff --git a/services/crewai-service/app/config/crewai_teams.yml b/services/crewai-service/app/config/crewai_teams.yml index 0970f4f0..9d897790 100644 --- a/services/crewai-service/app/config/crewai_teams.yml +++ b/services/crewai-service/app/config/crewai_teams.yml @@ -61,7 +61,7 @@ helion: synthesis: role_context: HELION Orchestrator system_prompt_ref: roles/helion/orchestrator_synthesis.md - llm_profile: reasoning + llm_profile: science team: - id: energy_researcher role_context: Energy Researcher @@ -70,7 +70,7 @@ helion: - id: systems_modeler role_context: Systems Modeler system_prompt_ref: roles/helion/systems_modeler.md - llm_profile: reasoning + llm_profile: science - id: policy_analyst role_context: Policy Analyst system_prompt_ref: roles/helion/policy_analyst.md @@ -78,7 +78,7 @@ helion: - id: risk_assessor role_context: Risk Assessor system_prompt_ref: roles/helion/risk_assessor.md - llm_profile: reasoning + llm_profile: science - id: communicator role_context: Communicator system_prompt_ref: roles/helion/communicator.md @@ -92,12 +92,12 @@ helion: synthesis: role_context: Executive Synthesis (CEO-mode) system_prompt_ref: roles/helion/HELION_CORE/orchestrator_synthesis.md - llm_profile: reasoning + llm_profile: science team: - id: orchestrator_front_desk_router role_context: Orchestrator (Front Desk / Router) system_prompt_ref: roles/helion/HELION_CORE/orchestrator_front_desk_router.md - llm_profile: reasoning + llm_profile: science - id: knowledge_curator_rag_librarian role_context: Knowledge Curator (L1–L3 RAG Librarian) system_prompt_ref: roles/helion/HELION_CORE/knowledge_curator_rag_librarian.md @@ -105,15 +105,15 @@ helion: - id: safety_anti_hallucination_gate role_context: Safety & Anti-Hallucination Gate system_prompt_ref: roles/helion/HELION_CORE/safety_anti_hallucination_gate.md - llm_profile: reasoning + llm_profile: science - id: legal_compliance_gdpr_mica_aml_kyc role_context: Legal & Compliance (GDPR/MiCA/AML/KYC) system_prompt_ref: roles/helion/HELION_CORE/legal_compliance_gdpr_mica_aml_kyc.md - llm_profile: reasoning + llm_profile: science - id: security_anti_fraud_anti_fake role_context: Security & Anti-Fraud / Anti-Fake system_prompt_ref: roles/helion/HELION_CORE/security_anti_fraud_anti_fake.md - llm_profile: reasoning + llm_profile: science - id: energy_systems_engineer role_context: Energy Systems Engineer (GGU/BioMiner/SES) system_prompt_ref: roles/helion/HELION_CORE/energy_systems_engineer.md @@ -121,7 +121,7 @@ helion: - id: finance_roi_modeler role_context: Finance & ROI Modeler system_prompt_ref: roles/helion/HELION_CORE/finance_roi_modeler.md - llm_profile: reasoning + llm_profile: science - id: dao_guide_governance_onboarding role_context: DAO Guide (Governance & Onboarding) system_prompt_ref: roles/helion/HELION_CORE/dao_guide_governance_onboarding.md @@ -129,7 +129,7 @@ helion: - id: tokenization_rwa_nft_architect role_context: Tokenization & RWA/NFT Architect system_prompt_ref: roles/helion/HELION_CORE/tokenization_rwa_nft_architect.md - llm_profile: reasoning + llm_profile: science - id: growth_soft_selling_cx role_context: Growth & Soft-Selling CX system_prompt_ref: roles/helion/HELION_CORE/growth_soft_selling_cx.md diff --git a/services/router/crewai_client.py b/services/router/crewai_client.py index 1ff4d6ba..52f4b190 100644 --- a/services/router/crewai_client.py +++ b/services/router/crewai_client.py @@ -14,6 +14,7 @@ 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" +HELION_CREWAI_TEAM_LIMIT = int(os.getenv("HELION_CREWAI_TEAM_LIMIT", "3")) CREWAI_AGENTS_PATH = os.getenv("CREWAI_AGENTS_PATH", "/config/crewai_agents.json") FALLBACK_CREWAI_PATH = "/app/config/crewai_agents.json" @@ -90,6 +91,19 @@ def should_use_crewai(agent_id, prompt, agent_config, metadata=None, force_crewa if not team: return False, "agent_has_no_team" + metadata = metadata or {} + force_detailed = bool(metadata.get("force_detailed")) + requires_complex = bool(metadata.get("requires_complex_reasoning")) + + # Helion policy: DeepSeek direct path by default; CrewAI only on-demand. + # This keeps first-touch replies fast and concise. + if agent_id == "helion": + prompt_lower = prompt.lower() + has_complexity = any(kw in prompt_lower for kw in COMPLEXITY_KEYWORDS) + if force_detailed or requires_complex or has_complexity: + return True, "helion_complex_or_detailed" + return False, "helion_direct_deepseek_first" + # Architecture mode: top-level orchestrators go through CrewAI API by default. if CREWAI_ORCHESTRATORS_ALWAYS: return True, "orchestrator_default_crewai" @@ -111,9 +125,15 @@ async def call_crewai(agent_id, task, context=None, team=None, profile=None): if not team: crewai_info = get_agent_crewai_info(agent_id) team = crewai_info.get("team", []) + + effective_context = context or {} + metadata = (effective_context.get("metadata", {}) or {}) + force_detailed = bool(metadata.get("force_detailed")) + # Helion policy: limit CrewAI participants unless user requested detailed mode. + if agent_id == "helion" and not force_detailed and HELION_CREWAI_TEAM_LIMIT > 0 and len(team) > HELION_CREWAI_TEAM_LIMIT: + team = team[:HELION_CREWAI_TEAM_LIMIT] 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" diff --git a/services/router/main.py b/services/router/main.py index 85aa19da..212d5a34 100644 --- a/services/router/main.py +++ b/services/router/main.py @@ -1412,6 +1412,17 @@ async def agent_infer(agent_id: str, request: InferRequest): if isinstance(row, dict): logger.info(json.dumps(row, ensure_ascii=False)) logger.info(f"✅ CrewAI success for {agent_id}: {latency:.2f}s") + final_response_text = crew_result["result"] + # Helion: keep first-touch answers short by default, even after CrewAI. + if ( + agent_id == "helion" + and isinstance(final_response_text, str) + and effective_metadata.get("force_concise") + and not effective_metadata.get("force_detailed") + ): + parts = re.split(r"(?<=[.!?])\s+", final_response_text.strip()) + if len(parts) > 3: + final_response_text = " ".join(parts[:3]).strip() # Store interaction in memory if MEMORY_RETRIEVAL_AVAILABLE and memory_retrieval and chat_id and user_id: @@ -1423,13 +1434,13 @@ async def agent_infer(agent_id: str, request: InferRequest): agent_id=request_agent_id, username=username, user_message=request.prompt, - assistant_response=crew_result["result"] + assistant_response=final_response_text ) except Exception as e: logger.warning(f"⚠️ Memory storage failed: {e}") return InferResponse( - response=crew_result["result"], + response=final_response_text, model="crewai-" + agent_id, backend="crewai", tokens_used=0