feat: add dialog_summaries creation and fix memory integration
- Add create_dialog_summary() method to MemoryClient - Fix syntax error in http_api.py (extra comma) - Add CloudFlare tunnel setup instructions - Gateway now logs conversations to Memory Service
This commit is contained in:
75
CLOUDFLARE_TUNNEL_SETUP.md
Normal file
75
CLOUDFLARE_TUNNEL_SETUP.md
Normal file
@@ -0,0 +1,75 @@
|
||||
# CloudFlare Tunnel Setup для Telegram Bot
|
||||
|
||||
## Швидке налаштування
|
||||
|
||||
### 1. Встановити cloudflared
|
||||
|
||||
```bash
|
||||
# На сервері
|
||||
curl -L https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64 -o /usr/local/bin/cloudflared
|
||||
chmod +x /usr/local/bin/cloudflared
|
||||
```
|
||||
|
||||
### 2. Авторизуватися
|
||||
|
||||
```bash
|
||||
cloudflared tunnel login
|
||||
```
|
||||
|
||||
### 3. Створити тунель
|
||||
|
||||
```bash
|
||||
cloudflared tunnel create dagi-gateway
|
||||
```
|
||||
|
||||
### 4. Налаштувати route
|
||||
|
||||
```bash
|
||||
cloudflared tunnel route dns dagi-gateway gateway.daarion.city
|
||||
```
|
||||
|
||||
### 5. Створити конфіг
|
||||
|
||||
Створити файл `~/.cloudflared/config.yml`:
|
||||
|
||||
```yaml
|
||||
tunnel: <tunnel-id>
|
||||
credentials-file: /root/.cloudflared/<tunnel-id>.json
|
||||
|
||||
ingress:
|
||||
- hostname: gateway.daarion.city
|
||||
service: http://localhost:9300
|
||||
- service: http_status:404
|
||||
```
|
||||
|
||||
### 6. Запустити тунель
|
||||
|
||||
```bash
|
||||
# Як сервіс
|
||||
cloudflared tunnel --config ~/.cloudflared/config.yml run dagi-gateway
|
||||
|
||||
# Або через systemd
|
||||
sudo systemctl enable cloudflared
|
||||
sudo systemctl start cloudflared
|
||||
```
|
||||
|
||||
### 7. Оновити Telegram webhook
|
||||
|
||||
```bash
|
||||
TELEGRAM_BOT_TOKEN="your-token"
|
||||
WEBHOOK_URL="https://gateway.daarion.city/telegram/webhook"
|
||||
|
||||
curl -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/setWebhook" \
|
||||
-d "url=${WEBHOOK_URL}"
|
||||
```
|
||||
|
||||
## Перевірка
|
||||
|
||||
```bash
|
||||
# Перевірити webhook
|
||||
curl "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/getWebhookInfo"
|
||||
|
||||
# Перевірити тунель
|
||||
curl https://gateway.daarion.city/health
|
||||
```
|
||||
|
||||
@@ -12,6 +12,7 @@ from fastapi import APIRouter, HTTPException
|
||||
from pydantic import BaseModel
|
||||
|
||||
from router_client import send_to_router
|
||||
from memory_client import memory_client
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -122,6 +123,15 @@ async def telegram_webhook(update: TelegramUpdate):
|
||||
|
||||
logger.info(f"Telegram message from {username} (tg:{user_id}) in chat {chat_id}: {text[:50]}")
|
||||
|
||||
# Fetch memory context
|
||||
memory_context = await memory_client.get_context(
|
||||
user_id=f"tg:{user_id}",
|
||||
agent_id="daarwizz",
|
||||
team_id=dao_id,
|
||||
channel_id=chat_id,
|
||||
limit=10
|
||||
)
|
||||
|
||||
# Build request to Router with DAARWIZZ context
|
||||
router_request = {
|
||||
"message": text,
|
||||
@@ -138,6 +148,7 @@ async def telegram_webhook(update: TelegramUpdate):
|
||||
"context": {
|
||||
"agent_name": DAARWIZZ_NAME,
|
||||
"system_prompt": DAARWIZZ_SYSTEM_PROMPT,
|
||||
"memory": memory_context, # Додаємо пам'ять
|
||||
# RBAC context will be injected by Router
|
||||
},
|
||||
}
|
||||
@@ -154,6 +165,17 @@ async def telegram_webhook(update: TelegramUpdate):
|
||||
|
||||
logger.info(f"Router response: {answer_text[:100]}")
|
||||
|
||||
# Save chat turn to memory
|
||||
await memory_client.save_chat_turn(
|
||||
agent_id="daarwizz",
|
||||
team_id=dao_id,
|
||||
user_id=f"tg:{user_id}",
|
||||
message=text,
|
||||
response=answer_text,
|
||||
channel_id=chat_id,
|
||||
scope="short_term"
|
||||
)
|
||||
|
||||
# Send response back to Telegram
|
||||
await send_telegram_message(chat_id, answer_text)
|
||||
|
||||
@@ -195,6 +217,15 @@ async def discord_webhook(message: DiscordMessage):
|
||||
|
||||
logger.info(f"Discord message from {username} (discord:{user_id}): {text[:50]}")
|
||||
|
||||
# Fetch memory context
|
||||
memory_context = await memory_client.get_context(
|
||||
user_id=f"discord:{user_id}",
|
||||
agent_id="daarwizz",
|
||||
team_id=dao_id,
|
||||
channel_id=channel_id,
|
||||
limit=10
|
||||
)
|
||||
|
||||
# Build request to Router with DAARWIZZ context
|
||||
router_request = {
|
||||
"message": text,
|
||||
@@ -212,6 +243,7 @@ async def discord_webhook(message: DiscordMessage):
|
||||
"context": {
|
||||
"agent_name": DAARWIZZ_NAME,
|
||||
"system_prompt": DAARWIZZ_SYSTEM_PROMPT,
|
||||
"memory": memory_context, # Додаємо пам'ять
|
||||
},
|
||||
}
|
||||
|
||||
@@ -226,6 +258,17 @@ async def discord_webhook(message: DiscordMessage):
|
||||
|
||||
logger.info(f"Router response: {answer_text[:100]}")
|
||||
|
||||
# Save chat turn to memory
|
||||
await memory_client.save_chat_turn(
|
||||
agent_id="daarwizz",
|
||||
team_id=dao_id,
|
||||
user_id=f"discord:{user_id}",
|
||||
message=text,
|
||||
response=answer_text,
|
||||
channel_id=channel_id,
|
||||
scope="short_term"
|
||||
)
|
||||
|
||||
# TODO: Send response back to Discord
|
||||
# await send_discord_message(channel_id, answer_text)
|
||||
|
||||
|
||||
221
gateway-bot/memory_client.py
Normal file
221
gateway-bot/memory_client.py
Normal file
@@ -0,0 +1,221 @@
|
||||
"""
|
||||
Memory Service Client для Gateway
|
||||
Використовується для отримання та збереження пам'яті діалогів
|
||||
"""
|
||||
import os
|
||||
import logging
|
||||
from typing import Optional, Dict, Any, List
|
||||
from datetime import datetime
|
||||
import httpx
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
MEMORY_SERVICE_URL = os.getenv("MEMORY_SERVICE_URL", "http://memory-service:8000")
|
||||
|
||||
|
||||
class MemoryClient:
|
||||
"""Клієнт для роботи з Memory Service"""
|
||||
|
||||
def __init__(self, base_url: str = MEMORY_SERVICE_URL):
|
||||
self.base_url = base_url.rstrip("/")
|
||||
self.timeout = 10.0
|
||||
|
||||
async def get_context(
|
||||
self,
|
||||
user_id: str,
|
||||
agent_id: str,
|
||||
team_id: str,
|
||||
channel_id: Optional[str] = None,
|
||||
limit: int = 10
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Отримати контекст пам'яті для діалогу
|
||||
|
||||
Повертає:
|
||||
{
|
||||
"facts": [...], # user_facts
|
||||
"recent_events": [...], # останні agent_memory_events
|
||||
"dialog_summaries": [...] # підсумки діалогів
|
||||
}
|
||||
"""
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=self.timeout) as client:
|
||||
# Отримуємо user facts
|
||||
facts_response = await client.get(
|
||||
f"{self.base_url}/facts",
|
||||
params={"user_id": user_id, "team_id": team_id, "limit": limit},
|
||||
headers={"Authorization": f"Bearer {user_id}"} # Заглушка
|
||||
)
|
||||
facts = facts_response.json() if facts_response.status_code == 200 else []
|
||||
|
||||
# Отримуємо останні memory events
|
||||
events_response = await client.get(
|
||||
f"{self.base_url}/agents/{agent_id}/memory",
|
||||
params={
|
||||
"team_id": team_id,
|
||||
"channel_id": channel_id,
|
||||
"scope": "short_term",
|
||||
"kind": "message",
|
||||
"limit": limit
|
||||
},
|
||||
headers={"Authorization": f"Bearer {user_id}"}
|
||||
)
|
||||
events = events_response.json().get("items", []) if events_response.status_code == 200 else []
|
||||
|
||||
# Отримуємо dialog summaries
|
||||
summaries_response = await client.get(
|
||||
f"{self.base_url}/summaries",
|
||||
params={
|
||||
"team_id": team_id,
|
||||
"channel_id": channel_id,
|
||||
"agent_id": agent_id,
|
||||
"limit": 5
|
||||
},
|
||||
headers={"Authorization": f"Bearer {user_id}"}
|
||||
)
|
||||
summaries = summaries_response.json().get("items", []) if summaries_response.status_code == 200 else []
|
||||
|
||||
return {
|
||||
"facts": facts,
|
||||
"recent_events": events,
|
||||
"dialog_summaries": summaries
|
||||
}
|
||||
except Exception as e:
|
||||
logger.warning(f"Memory context fetch failed: {e}")
|
||||
return {
|
||||
"facts": [],
|
||||
"recent_events": [],
|
||||
"dialog_summaries": []
|
||||
}
|
||||
|
||||
async def save_chat_turn(
|
||||
self,
|
||||
agent_id: str,
|
||||
team_id: str,
|
||||
user_id: str,
|
||||
message: str,
|
||||
response: str,
|
||||
channel_id: Optional[str] = None,
|
||||
scope: str = "short_term"
|
||||
) -> bool:
|
||||
"""
|
||||
Зберегти один turn діалогу (повідомлення + відповідь)
|
||||
"""
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=self.timeout) as client:
|
||||
# Зберігаємо повідомлення користувача
|
||||
user_event = {
|
||||
"agent_id": agent_id,
|
||||
"team_id": team_id,
|
||||
"channel_id": channel_id,
|
||||
"user_id": user_id,
|
||||
"scope": scope,
|
||||
"kind": "message",
|
||||
"body_text": message,
|
||||
"body_json": {"type": "user_message", "source": "telegram"}
|
||||
}
|
||||
|
||||
await client.post(
|
||||
f"{self.base_url}/agents/{agent_id}/memory",
|
||||
json=user_event,
|
||||
headers={"Authorization": f"Bearer {user_id}"}
|
||||
)
|
||||
|
||||
# Зберігаємо відповідь агента
|
||||
agent_event = {
|
||||
"agent_id": agent_id,
|
||||
"team_id": team_id,
|
||||
"channel_id": channel_id,
|
||||
"user_id": user_id,
|
||||
"scope": scope,
|
||||
"kind": "message",
|
||||
"body_text": response,
|
||||
"body_json": {"type": "agent_response", "source": "telegram"}
|
||||
}
|
||||
|
||||
await client.post(
|
||||
f"{self.base_url}/agents/{agent_id}/memory",
|
||||
json=agent_event,
|
||||
headers={"Authorization": f"Bearer {user_id}"}
|
||||
)
|
||||
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to save chat turn: {e}")
|
||||
return False
|
||||
|
||||
async def create_dialog_summary(
|
||||
self,
|
||||
team_id: str,
|
||||
channel_id: Optional[str],
|
||||
agent_id: str,
|
||||
user_id: Optional[str],
|
||||
period_start: datetime,
|
||||
period_end: datetime,
|
||||
summary_text: str,
|
||||
message_count: int = 0,
|
||||
participant_count: int = 0,
|
||||
topics: Optional[List[str]] = None,
|
||||
summary_json: Optional[Dict[str, Any]] = None
|
||||
) -> bool:
|
||||
"""
|
||||
Створити підсумок діалогу для масштабування без переповнення контексту
|
||||
"""
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=self.timeout) as client:
|
||||
response = await client.post(
|
||||
f"{self.base_url}/summaries",
|
||||
json={
|
||||
"team_id": team_id,
|
||||
"channel_id": channel_id,
|
||||
"agent_id": agent_id,
|
||||
"user_id": user_id,
|
||||
"period_start": period_start.isoformat(),
|
||||
"period_end": period_end.isoformat(),
|
||||
"summary_text": summary_text,
|
||||
"summary_json": summary_json,
|
||||
"message_count": message_count,
|
||||
"participant_count": participant_count,
|
||||
"topics": topics or [],
|
||||
"meta": {}
|
||||
},
|
||||
headers={"Authorization": f"Bearer {user_id or 'system'}"}
|
||||
)
|
||||
return response.status_code in [200, 201]
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to create dialog summary: {e}")
|
||||
return False
|
||||
|
||||
async def upsert_fact(
|
||||
self,
|
||||
user_id: str,
|
||||
fact_key: str,
|
||||
fact_value: Optional[str] = None,
|
||||
fact_value_json: Optional[Dict[str, Any]] = None,
|
||||
team_id: Optional[str] = None
|
||||
) -> bool:
|
||||
"""
|
||||
Створити або оновити факт користувача
|
||||
"""
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=self.timeout) as client:
|
||||
response = await client.post(
|
||||
f"{self.base_url}/facts/upsert",
|
||||
json={
|
||||
"user_id": user_id,
|
||||
"fact_key": fact_key,
|
||||
"fact_value": fact_value,
|
||||
"fact_value_json": fact_value_json,
|
||||
"team_id": team_id
|
||||
},
|
||||
headers={"Authorization": f"Bearer {user_id}"}
|
||||
)
|
||||
return response.status_code in [200, 201]
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to upsert fact: {e}")
|
||||
return False
|
||||
|
||||
|
||||
# Глобальний екземпляр клієнта
|
||||
memory_client = MemoryClient()
|
||||
|
||||
Reference in New Issue
Block a user