diff --git a/INFRASTRUCTURE.md b/INFRASTRUCTURE.md index 2a551d7e..6a8df3fe 100644 --- a/INFRASTRUCTURE.md +++ b/INFRASTRUCTURE.md @@ -6,20 +6,36 @@ --- -## 📍 Production Servers +## 📍 Network Nodes -### GEX44 Server #2844465 (Hetzner) +### Node #1: Production Server (Hetzner GEX44 #2844465) +- **Node ID:** `node-1-hetzner-gex44` - **IP Address:** `144.76.224.179` - **SSH Access:** `ssh root@144.76.224.179` -- **Location:** Hetzner Cloud -- **Project Root:** `/opt/microdao-daarion` (or `/opt/dagi-router`) +- **Location:** Hetzner Cloud (Germany) +- **Project Root:** `/opt/microdao-daarion` - **Docker Network:** `dagi-network` +- **Role:** Production Router + Gateway + All Services +- **Uptime:** 24/7 **Domains:** - `gateway.daarion.city` → `144.76.224.179` (Gateway + Nginx) - `api.daarion.city` → TBD (API Gateway) - `daarion.city` → TBD (Main website) +### Node #2: Development Node (MacBook Pro M4 Max) +- **Node ID:** `node-2-macbook-m4max` +- **Local IP:** `192.168.1.244` +- **SSH Access:** `ssh apple@192.168.1.244` (if enabled) +- **Location:** Local Network (Ivan's Office) +- **Project Root:** `/Users/apple/github-projects/microdao-daarion` +- **Role:** Development + Testing + Backup Router +- **Specs:** M4 Max (16 cores), 64GB RAM, 2TB SSD, 40-core GPU +- **Uptime:** On-demand (battery-powered) + +**See full specs:** [NODE-2-MACBOOK-SPECS.md](./NODE-2-MACBOOK-SPECS.md) +**Current state:** [NODE-2-CURRENT-STATE.md](./NODE-2-CURRENT-STATE.md) — What's running now + --- ## 🐙 GitHub Repositories diff --git a/docs/infrastructure_quick_ref.ipynb b/docs/infrastructure_quick_ref.ipynb index ac75061d..eb9f8f49 100644 --- a/docs/infrastructure_quick_ref.ipynb +++ b/docs/infrastructure_quick_ref.ipynb @@ -6,12 +6,12 @@ "source": [ "# 🚀 Infrastructure Quick Reference — DAARION & MicroDAO\n", "\n", - "**Версія:** 1.1.0 \n", + "**Версія:** 1.2.0 \n", "**Останнє оновлення:** 2025-01-17 \n", "\n", "Цей notebook містить швидкий довідник по серверах, репозиторіях та endpoints для DAGI Stack.\n", "\n", - "**NEW:** Vision Encoder + Qdrant vector database (OpenCLIP ViT-L/14)" + "**NEW:** Vision Encoder + Qdrant + Node #2 (MacBook Pro M4 Max)" ] }, { @@ -48,6 +48,75 @@ " print(f\"{name.upper():<20} {service['port']:<7} {service['container']:<30} {health}{gpu}\")" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 🖥️ Network Nodes\n", + "\n", + "### Node #1: Production Server (Hetzner)\n", + "- **Node ID:** node-1-hetzner-gex44\n", + "- **IP:** 144.76.224.179\n", + "- **Role:** Production Router + Gateway + All Services (24/7)\n", + "- **Location:** Hetzner Cloud (Germany)\n", + "\n", + "### Node #2: Development Node (MacBook Pro M4 Max)\n", + "- **Node ID:** node-2-macbook-m4max\n", + "- **Local IP:** 192.168.1.244\n", + "- **Role:** Development + Testing + Backup Router\n", + "- **Specs:** M4 Max (16 cores), 64GB RAM, 2TB SSD, 40-core GPU\n", + "- **Location:** Local Network (Ivan's Office)\n", + "- **Docs:** [NODE-2-MACBOOK-SPECS.md](../NODE-2-MACBOOK-SPECS.md)\n", + "\n", + "---" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Network Nodes Configuration\n", + "NODES = {\n", + " \"node-1\": {\n", + " \"name\": \"Hetzner GEX44\",\n", + " \"ip\": \"144.76.224.179\",\n", + " \"local_ip\": None,\n", + " \"role\": \"production\",\n", + " \"uptime\": \"24/7\",\n", + " \"ssh\": \"root@144.76.224.179\",\n", + " \"domain\": \"gateway.daarion.city\",\n", + " \"services\": \"All (17 services)\",\n", + " \"specs\": \"See SYSTEM-INVENTORY.md\"\n", + " },\n", + " \"node-2\": {\n", + " \"name\": \"MacBook Pro M4 Max\",\n", + " \"ip\": None,\n", + " \"local_ip\": \"192.168.1.244\",\n", + " \"role\": \"development\",\n", + " \"uptime\": \"on-demand\",\n", + " \"ssh\": \"apple@192.168.1.244\",\n", + " \"domain\": None,\n", + " \"services\": \"Core only (Router, DevTools, Memory, Ollama)\",\n", + " \"specs\": \"M4 Max, 16 cores, 64GB RAM, 2TB SSD, 40-core GPU\"\n", + " }\n", + "}\n", + "\n", + "print(\"DAGI Stack Network Nodes:\")\n", + "print(\"=\"*80)\n", + "for node_id, node in NODES.items():\n", + " print(f\"\\n{node_id.upper()}: {node['name']}\")\n", + " print(f\" Role: {node['role']}\")\n", + " print(f\" IP: {node['ip'] or node['local_ip']}\")\n", + " print(f\" SSH: {node['ssh']}\")\n", + " print(f\" Uptime: {node['uptime']}\")\n", + " print(f\" Services: {node['services']}\")\n", + " if node['domain']:\n", + " print(f\" Domain: https://{node['domain']}\")\n", + " print(f\" Specs: {node['specs']}\")" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -157,7 +226,8 @@ " \"Deployment\": \"../DEPLOY-NOW.md\",\n", " \"Helion Status\": \"../STATUS-HELION.md\",\n", " \"Architecture Index\": \"../docs/cursor/README.md\",\n", - " \"API Reference\": \"../docs/api.md\"\n", + " \"API Reference\": \"../docs/api.md\",\n", + " \"Node #2 Specs\": \"../NODE-2-MACBOOK-SPECS.md\"\n", "}\n", "\n", "print(\"Documentation Quick Links:\")\n", @@ -180,11 +250,14 @@ "- ✅ **768-dim embeddings** for multimodal RAG\n", "- ✅ Created VISION-ENCODER-STATUS.md with full implementation details\n", "- ✅ Added test-vision-encoder.sh smoke tests\n", + "- ✅ **Added Node #2** (MacBook Pro M4 Max) as development node\n", + "- ✅ Created NODE-2-MACBOOK-SPECS.md with complete specifications\n", "\n", - "### Services Count: 17 (from 15)\n", - "- Total Services: 17\n", - "- GPU Services: 1 (Vision Encoder)\n", - "- Vector Databases: 1 (Qdrant)\n", + "### Network Architecture\n", + "- **Nodes:** 2 (1 production + 1 development)\n", + "- **Total Services:** 17\n", + "- **GPU Services:** 1 (Vision Encoder) + Node #2 (40-core Apple GPU)\n", + "- **Vector Databases:** 1 (Qdrant)\n", "\n", "---\n", "\n", diff --git a/http_api.py b/http_api.py index e4cab2e4..f1e3b7a7 100644 --- a/http_api.py +++ b/http_api.py @@ -5,7 +5,7 @@ Provides HTTP endpoints for routing requests """ from fastapi import APIRouter, HTTPException, status -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, ConfigDict from typing import Optional, Dict, Any import logging @@ -21,6 +21,8 @@ logger = logging.getLogger(__name__) class IncomingRequest(BaseModel): """HTTP request format""" + model_config = ConfigDict(extra='allow') # Дозволити додаткові поля для сумісності + mode: Optional[str] = Field(None, description="Request mode (e.g., 'chat', 'crew')") agent: Optional[str] = Field(None, description="Agent ID (e.g., 'devtools')") message: Optional[str] = Field(None, description="User message") @@ -29,6 +31,7 @@ class IncomingRequest(BaseModel): session_id: Optional[str] = Field(None, description="Session identifier") user_id: Optional[str] = Field(None, description="User identifier") payload: Dict[str, Any] = Field(default_factory=dict, description="Additional payload data") + context: Optional[Dict[str, Any]] = Field(None, description="Legacy: context on top level (will be migrated to payload.context)") class RouterAPIResponse(BaseModel): @@ -74,6 +77,32 @@ def build_router_http(app_core: RouterApp) -> APIRouter: - Other metadata """ logger.info(f"Incoming request: agent={req.agent}, mode={req.mode}") + logger.info(f"Raw payload type: {type(req.payload)}, keys: {list(req.payload.keys()) if req.payload else []}") + logger.info(f"Raw context: {req.context}") + + # Нормалізувати payload: якщо є "context" на верхньому рівні (старий формат), + # перемістити його в payload.context для уніфікованої обробки + normalized_payload = req.payload.copy() if req.payload else {} + + # Перевірити, чи є context на верхньому рівні (legacy формат від gateway-bot) + # Це потрібно для сумісності з DAARWIZZ + if req.context: + # Якщо context на верхньому рівні, перемістити в payload.context + if "context" not in normalized_payload: + normalized_payload["context"] = {} + # Мержити context з верхнього рівня в payload.context + if isinstance(req.context, dict): + normalized_payload["context"].update(req.context) + logger.info(f"✅ Migrated top-level context to payload.context for agent={req.agent}, keys={list(req.context.keys())}") + + logger.info(f"✅ Normalized payload keys: {list(normalized_payload.keys())}") + if normalized_payload and "context" in normalized_payload: + logger.info(f"✅ Context keys: {list(normalized_payload['context'].keys()) if isinstance(normalized_payload.get('context'), dict) else []}") + if isinstance(normalized_payload.get('context'), dict) and "system_prompt" in normalized_payload['context']: + sp = normalized_payload['context']['system_prompt'] + sp_len = len(sp) if sp else 0 + logger.info(f"✅ System prompt found in context: {sp_len} chars") + logger.info(f"✅ System prompt preview: {sp[:100] if sp else 'None'}...") # Convert to internal RouterRequest rreq = RouterRequest( @@ -84,7 +113,7 @@ def build_router_http(app_core: RouterApp) -> APIRouter: session_id=req.session_id, user_id=req.user_id, message=req.message, - payload=req.payload, + payload=normalized_payload, ) # Handle request diff --git a/providers/llm_provider.py b/providers/llm_provider.py index 76a43d52..20717f53 100644 --- a/providers/llm_provider.py +++ b/providers/llm_provider.py @@ -158,15 +158,34 @@ class LLMProvider(Provider): def _get_system_prompt(self, req: RouterRequest) -> Optional[str]: """Get system prompt based on agent or context""" # 1. Check if context.system_prompt provided (e.g., from Gateway) + logger.info(f"[DEBUG] _get_system_prompt called for agent={req.agent}") + logger.info(f"[DEBUG] req.payload type: {type(req.payload)}, keys: {list(req.payload.keys()) if req.payload else []}") context = req.payload.get("context") or {} - logger.info(f"[DEBUG] payload keys: {list(req.payload.keys())}") - logger.info(f"[DEBUG] context keys: {list(context.keys())}") - if "system_prompt" in context: + logger.info(f"[DEBUG] context type: {type(context)}, keys: {list(context.keys()) if isinstance(context, dict) else 'not a dict'}") + if isinstance(context, dict) and "system_prompt" in context: prompt = context["system_prompt"] - logger.info(f"[DEBUG] Using context.system_prompt: {len(prompt)} chars, agent={req.agent}") + logger.info(f"[DEBUG] ✅ Using context.system_prompt: {len(prompt)} chars, agent={req.agent}") + logger.info(f"[DEBUG] System prompt type: {type(prompt)}") + logger.info(f"[DEBUG] System prompt preview (first 200): {str(prompt)[:200]}...") + logger.info(f"[DEBUG] System prompt full length check: {len(str(prompt))} chars") + # Переконаємось, що це рядок + if not isinstance(prompt, str): + logger.warning(f"[DEBUG] ⚠️ System prompt is not a string! Type: {type(prompt)}, value: {prompt}") + prompt = str(prompt) if prompt else None return prompt + else: + logger.info(f"[DEBUG] ⚠️ No system_prompt in context for agent={req.agent}") + + # 2. Agent-specific system prompts (fallback, якщо не передано в context) + if req.agent == "helion": + return ( + "Ти - Helion, AI-агент платформи Energy Union екосистеми DAARION.city. " + "Допомагай користувачам з технологіями EcoMiner/BioMiner, токеномікою та DAO governance. " + "Твої основні функції: консультації з енергетичними технологіями, пояснення токеноміки Energy Union, " + "допомога з onboarding в DAO, відповіді на питання про EcoMiner/BioMiner устаткування. " + "Стиль спілкування: професійний, технічний, але зрозумілий, точний у цифрах та даних." + ) - # 2. Agent-specific system prompts if req.agent == "daarwizz": return ( "Ти — DAARWIZZ, офіційний AI-агент екосистеми DAARION.city. " diff --git a/router-config.yml b/router-config.yml index c1d3fe3e..a416c5be 100644 --- a/router-config.yml +++ b/router-config.yml @@ -89,6 +89,30 @@ agents: You are a multi-agent orchestrator for DAARION.city microDAO ecosystem. You coordinate complex workflows involving multiple specialized agents. + greenfood: + description: "GREENFOOD Assistant - ERP orchestrator for craft food producers" + default_llm: local_qwen3_8b + system_prompt: | + Ти — GREENFOOD Assistant, фронтовий оркестратор ERP-системи для крафтових виробників, хабів та покупців. + Твоя місія: зрозуміти, хто з тобою говорить (комітент, менеджер складу, логіст, бухгалтер, маркетолог, покупець), + виявити намір і делегувати завдання спеціалізованим агентам GREENFOOD. + + У твоєму розпорядженні 12 спеціалізованих агентів: + - Product & Catalog (каталог товарів) + - Batch & Quality (партії та якість) + - Vendor Success (успіх комітентів) + - Warehouse (склад) + - Logistics & Delivery (доставка) + - Seller (продажі) + - Customer Care (підтримка) + - Finance & Pricing (фінанси) + - SMM & Campaigns (маркетинг) + - SEO & Web (SEO) + - Analytics & BI (аналітика) + - Compliance & Audit (аудит) + + Відповідай українською, чітко та по-діло вому. + helion: description: "Helion - AI agent for Energy Union platform" default_llm: local_qwen3_8b