feat: Add DAARWIZZ agent with personality

DAARWIZZ - Official AI agent for DAARION.city ecosystem

Changes:
- gateway-bot/daarwizz_prompt.txt: System prompt defining DAARWIZZ personality
- gateway-bot/http_api.py: Load and inject DAARWIZZ context into Router requests
- gateway-bot/Dockerfile: Copy DAARWIZZ prompt file to container
- providers/llm_provider.py: Support context.system_prompt from Gateway

Features:
- Telegram webhook sends agent='daarwizz' to Router
- System prompt loaded from file (customizable)
- LLM receives full DAARWIZZ context + RBAC
- Discord support included

Usage:
- User messages DAARWIZZ in Telegram
- Gateway enriches with system prompt + RBAC
- Router routes to LLM with full context
- DAARWIZZ responds with DAO-aware answers

Next: Set TELEGRAM_BOT_TOKEN and test first dialog
This commit is contained in:
Ivan Tytar
2025-11-15 15:31:58 +01:00
parent 244c6171a8
commit be95bbad9c
4 changed files with 187 additions and 53 deletions

View File

@@ -2,21 +2,29 @@
FROM python:3.11-slim FROM python:3.11-slim
LABEL maintainer="DAARION.city Team" LABEL maintainer="DAARION.city Team"
LABEL description="Bot Gateway - Telegram/Discord webhook handler" LABEL description="Bot Gateway - Telegram/Discord webhook handler with DAARWIZZ"
LABEL version="0.2.0" LABEL version="0.2.0"
WORKDIR /app WORKDIR /app
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/* RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
COPY ../requirements.txt . # Copy requirements and install
RUN pip install --no-cache-dir -r requirements.txt COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt httpx
COPY . . # Copy gateway code
COPY gateway-bot/ /app/gateway-bot/
# Copy DAARWIZZ system prompt
COPY gateway-bot/daarwizz_prompt.txt /app/gateway-bot/daarwizz_prompt.txt
EXPOSE 9300 EXPOSE 9300
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD curl -f http://localhost:9300/health || exit 1 CMD curl -f http://localhost:9300/health || exit 1
ENV DAARWIZZ_NAME=DAARWIZZ
ENV DAARWIZZ_PROMPT_PATH=/app/gateway-bot/daarwizz_prompt.txt
CMD ["python", "-m", "gateway_bot.main", "--host", "0.0.0.0", "--port", "9300"] CMD ["python", "-m", "gateway_bot.main", "--host", "0.0.0.0", "--port", "9300"]

View File

@@ -0,0 +1,37 @@
Ти — DAARWIZZ, офіційний AI-агент екосистеми DAARION.city.
Твоя роль:
1. Допомагати учасникам спільноти розібратися з microDAO, ролями, правами доступу і DAO-процесами.
2. Пояснювати просто, конкретно і практично. Уникай довгої теорії, одразу давай корисні кроки.
3. Орієнтуватися на безпеку: не розкривати інформацію або дії, якщо у користувача немає відповідних прав (entitlements).
4. Якщо не знаєш точної відповіді — чесно це скажи і запропонуй, що можна зробити/кого запитати.
Контекст:
- Ти працюєш всередині системи DAGI Stack (Router + DevTools + CrewAI + microDAO RBAC).
- Ти отримуєш від Gateway інформацію про: dao_id, user_id, roles[], entitlements[].
- Ти відповідаєш від імені системи, але не прикидаєшся людиною.
Обмеження:
- Не давай юридичних, фінансових або медичних порад.
- Не вигадуй факти про DAO або токеноміку, яких немає в офіційних документах DAARION/microDAO.
- Якщо питання виходить за межі DAARION/microDAO, дай коротку відповідь і поверни розмову в контекст спільноти.
Стиль:
- Дружній, але професійний.
- Короткі абзаци, без води.
- Якщо користувач просить інструкцію — давай її покроково, нумерованим списком.
Про DAARION:
- DAARION.city — це екосистема децентралізованих автономних організацій (microDAO).
- Кожен microDAO має свою структуру ролей: admin, member, contributor, guest.
- Система RBAC (Role-Based Access Control) контролює доступ до функцій.
- Учасники можуть голосувати за пропозиції, виконувати завдання, отримувати винагороди.
Твої можливості:
- Відповідати на запитання про DAO, ролі, процеси
- Пояснювати, як використовувати систему
- Допомагати новим учасникам (onboarding)
- Перевіряти права доступу перед відповіддю
- Запускати workflows (через mode=crew) для складних завдань
Якщо користувач запитує про конкретний microDAO, використовуй інформацію з dao_id та rbac контексту.

View File

@@ -3,6 +3,8 @@ Bot Gateway HTTP API
Handles incoming webhooks from Telegram, Discord, etc. Handles incoming webhooks from Telegram, Discord, etc.
""" """
import logging import logging
import os
from pathlib import Path
from typing import Dict, Any, Optional from typing import Dict, Any, Optional
from datetime import datetime from datetime import datetime
@@ -16,6 +18,36 @@ logger = logging.getLogger(__name__)
router = APIRouter() router = APIRouter()
# ========================================
# DAARWIZZ Configuration
# ========================================
DAARWIZZ_NAME = os.getenv("DAARWIZZ_NAME", "DAARWIZZ")
DAARWIZZ_PROMPT_PATH = os.getenv(
"DAARWIZZ_PROMPT_PATH",
str(Path(__file__).parent / "daarwizz_prompt.txt"),
)
def load_daarwizz_prompt() -> str:
"""Load DAARWIZZ system prompt from file"""
try:
p = Path(DAARWIZZ_PROMPT_PATH)
if not p.exists():
logger.warning(f"DAARWIZZ prompt file not found: {DAARWIZZ_PROMPT_PATH}")
return f"Ти — {DAARWIZZ_NAME}, AI-агент екосистеми DAARION.city. Допомагай учасникам з DAO-процесами."
prompt = p.read_text(encoding="utf-8")
logger.info(f"DAARWIZZ system prompt loaded ({len(prompt)} chars)")
return prompt
except Exception as e:
logger.error(f"Error loading DAARWIZZ prompt: {e}")
return f"Ти — {DAARWIZZ_NAME}, AI-агент екосистеми DAARION.city."
DAARWIZZ_SYSTEM_PROMPT = load_daarwizz_prompt()
# ======================================== # ========================================
# Request Models # Request Models
# ======================================== # ========================================
@@ -90,38 +122,45 @@ async def telegram_webhook(update: TelegramUpdate):
logger.info(f"Telegram message from {username} (tg:{user_id}) in chat {chat_id}: {text[:50]}") logger.info(f"Telegram message from {username} (tg:{user_id}) in chat {chat_id}: {text[:50]}")
# Build request to Router # Build request to Router with DAARWIZZ context
router_request = { router_request = {
"prompt": text,
"mode": "chat", "mode": "chat",
"source": "telegram", "agent": "daarwizz", # DAARWIZZ agent identifier
"dao_id": dao_id, "metadata": {
"user_id": f"tg:{user_id}", "source": "telegram",
"session_id": f"tg:{chat_id}:{dao_id}", "dao_id": dao_id,
"message": text, "user_id": f"tg:{user_id}",
"payload": { "session_id": f"tg:{chat_id}:{dao_id}",
"message": text,
"username": username, "username": username,
"chat_id": chat_id, "chat_id": chat_id,
"timestamp": datetime.now().isoformat() },
} "context": {
"agent_name": DAARWIZZ_NAME,
"system_prompt": DAARWIZZ_SYSTEM_PROMPT,
# RBAC context will be injected by Router
},
} }
# Send to Router # Send to Router
router_response = await send_to_router(router_request) logger.info(f"Sending to Router: agent=daarwizz, dao={dao_id}, user=tg:{user_id}")
response = await send_to_router(router_request)
# TODO: Send response back to Telegram via Bot API # Extract response text
# For now, just return the router response if isinstance(response, dict):
answer_text = response.get("response", "Вибач, я зараз не можу відповісти.")
else:
answer_text = "Вибач, сталася помилка."
logger.info(f"Router response: {answer_text[:100]}")
# Send response back to Telegram
await send_telegram_message(chat_id, answer_text)
return {"ok": True, "agent": "daarwizz"}
return {
"status": "ok",
"processed": True,
"router_response": router_response
}
except HTTPException:
raise
except Exception as e: except Exception as e:
logger.error(f"Telegram webhook error: {e}") logger.error(f"Error handling Telegram webhook: {e}", exc_info=True)
raise HTTPException(status_code=500, detail=str(e)) raise HTTPException(status_code=500, detail=str(e))
@@ -151,50 +190,87 @@ async def discord_webhook(message: DiscordMessage):
user_id = author.get("id", "unknown") user_id = author.get("id", "unknown")
username = author.get("username", "") username = author.get("username", "")
# Get DAO ID for this guild/channel # Get DAO ID for this channel
dao_id = get_dao_id(guild_id, "discord") dao_id = get_dao_id(channel_id, "discord")
logger.info(f"Discord message from {username} (dc:{user_id}) in guild {guild_id}: {text[:50]}") logger.info(f"Discord message from {username} (discord:{user_id}): {text[:50]}")
# Build request to Router # Build request to Router with DAARWIZZ context
router_request = { router_request = {
"prompt": text,
"mode": "chat", "mode": "chat",
"source": "discord", "agent": "daarwizz",
"dao_id": dao_id, "metadata": {
"user_id": f"dc:{user_id}", "source": "discord",
"session_id": f"dc:{channel_id}:{dao_id}", "dao_id": dao_id,
"message": text, "user_id": f"discord:{user_id}",
"payload": { "session_id": f"discord:{channel_id}:{dao_id}",
"message": text,
"username": username, "username": username,
"channel_id": channel_id, "channel_id": channel_id,
"guild_id": guild_id, "guild_id": guild_id,
"timestamp": datetime.now().isoformat() },
} "context": {
"agent_name": DAARWIZZ_NAME,
"system_prompt": DAARWIZZ_SYSTEM_PROMPT,
},
} }
# Send to Router # Send to Router
router_response = await send_to_router(router_request) response = await send_to_router(router_request)
# TODO: Send response back to Discord via Bot API # Extract response text
if isinstance(response, dict):
answer_text = response.get("response", "Sorry, I can't respond right now.")
else:
answer_text = "Sorry, an error occurred."
logger.info(f"Router response: {answer_text[:100]}")
# TODO: Send response back to Discord
# await send_discord_message(channel_id, answer_text)
return {"ok": True, "agent": "daarwizz", "response": answer_text}
return {
"status": "ok",
"processed": True,
"router_response": router_response
}
except HTTPException:
raise
except Exception as e: except Exception as e:
logger.error(f"Discord webhook error: {e}") logger.error(f"Error handling Discord webhook: {e}", exc_info=True)
raise HTTPException(status_code=500, detail=str(e)) raise HTTPException(status_code=500, detail=str(e))
# ========================================
# Helper Functions
# ========================================
async def send_telegram_message(chat_id: str, text: str):
"""Send message to Telegram chat"""
import httpx
telegram_token = os.getenv("TELEGRAM_BOT_TOKEN")
if not telegram_token:
logger.error("TELEGRAM_BOT_TOKEN not set")
return
url = f"https://api.telegram.org/bot{telegram_token}/sendMessage"
payload = {
"chat_id": chat_id,
"text": text,
"parse_mode": "Markdown",
}
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}")
except Exception as e:
logger.error(f"Error sending Telegram message: {e}")
@router.get("/health") @router.get("/health")
async def health(): async def health():
"""Health check endpoint""" """Health check endpoint"""
return { return {
"status": "healthy", "status": "healthy",
"service": "bot-gateway" "agent": DAARWIZZ_NAME,
"system_prompt_loaded": len(DAARWIZZ_SYSTEM_PROMPT) > 0,
"timestamp": datetime.utcnow().isoformat(),
} }

View File

@@ -156,8 +156,20 @@ class LLMProvider(Provider):
) )
def _get_system_prompt(self, req: RouterRequest) -> Optional[str]: def _get_system_prompt(self, req: RouterRequest) -> Optional[str]:
"""Get system prompt based on agent""" """Get system prompt based on agent or context"""
# This can be enhanced to load from config # 1. Check if context.system_prompt provided (e.g., from Gateway)
context = req.payload.get("context") or {}
if "system_prompt" in context:
return context["system_prompt"]
# 2. Agent-specific system prompts
if req.agent == "daarwizz":
return (
"Ти — DAARWIZZ, офіційний AI-агент екосистеми DAARION.city. "
"Допомагай учасникам з microDAO, ролями та процесами. "
"Відповідай коротко, практично, враховуй RBAC контекст користувача."
)
if req.agent == "devtools": if req.agent == "devtools":
return ( return (
"Ти - DevTools Agent в екосистемі DAARION.city. " "Ти - DevTools Agent в екосистемі DAARION.city. "
@@ -165,4 +177,5 @@ class LLMProvider(Provider):
"рефакторингом та написанням тестів. " "рефакторингом та написанням тестів. "
"Відповідай коротко, конкретно, з прикладами коду коли потрібно." "Відповідай коротко, конкретно, з прикладами коду коли потрібно."
) )
return None return None