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:
@@ -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"]
|
||||||
|
|||||||
37
gateway-bot/daarwizz_prompt.txt
Normal file
37
gateway-bot/daarwizz_prompt.txt
Normal 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 контексту.
|
||||||
@@ -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(),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user