Files
microdao-daarion/services/usage-engine/aggregators.py
Apple 6bd769ef40 feat(city-map): Add 2D City Map with coordinates and agent presence
- Add migration 013_city_map_coordinates.sql with map coordinates, zones, and agents table
- Add /city/map API endpoint in city-service
- Add /city/agents and /city/agents/online endpoints
- Extend presence aggregator to include agents[] in snapshot
- Add AgentsSource for fetching agent data from DB
- Create CityMap component with interactive room tiles
- Add useCityMap hook for fetching map data
- Update useGlobalPresence to include agents
- Add map/list view toggle on /city page
- Add agent badges to room cards and map tiles
2025-11-27 07:00:47 -08:00

241 lines
8.9 KiB
Python

"""
Usage Data Aggregators
Queries and aggregates usage data from database
"""
import asyncpg
from datetime import datetime, timedelta
from typing import Optional, List
from models import (
UsageSummary,
ModelUsage,
AgentUsage,
ToolUsage,
UsageQueryRequest
)
class UsageAggregator:
"""Aggregates usage data for reporting"""
def __init__(self, db_pool: asyncpg.Pool):
self.db_pool = db_pool
async def get_summary(
self,
microdao_id: Optional[str] = None,
agent_id: Optional[str] = None,
period_hours: int = 24
) -> UsageSummary:
"""Get aggregated usage summary"""
period_start = datetime.utcnow() - timedelta(hours=period_hours)
period_end = datetime.utcnow()
async with self.db_pool.acquire() as conn:
# LLM stats
llm_stats = await conn.fetchrow("""
SELECT
COUNT(*) as calls,
SUM(total_tokens) as tokens_total,
SUM(prompt_tokens) as tokens_prompt,
SUM(completion_tokens) as tokens_completion,
AVG(latency_ms) as latency_avg
FROM usage_llm
WHERE timestamp >= $1 AND timestamp <= $2
AND ($3::text IS NULL OR microdao_id = $3)
AND ($4::text IS NULL OR agent_id = $4)
""", period_start, period_end, microdao_id, agent_id)
# Tool stats
tool_stats = await conn.fetchrow("""
SELECT
COUNT(*) as calls,
SUM(CASE WHEN success THEN 1 ELSE 0 END) as success,
SUM(CASE WHEN NOT success THEN 1 ELSE 0 END) as failed,
AVG(latency_ms) as latency_avg
FROM usage_tool
WHERE timestamp >= $1 AND timestamp <= $2
AND ($3::text IS NULL OR microdao_id = $3)
AND ($4::text IS NULL OR agent_id = $4)
""", period_start, period_end, microdao_id, agent_id)
# Agent stats
agent_stats = await conn.fetchrow("""
SELECT
COUNT(*) as invocations,
SUM(CASE WHEN success THEN 1 ELSE 0 END) as success,
SUM(CASE WHEN NOT success THEN 1 ELSE 0 END) as failed
FROM usage_agent
WHERE timestamp >= $1 AND timestamp <= $2
AND ($3::text IS NULL OR microdao_id = $3)
AND ($4::text IS NULL OR agent_id = $4)
""", period_start, period_end, microdao_id, agent_id)
# Message stats
message_stats = await conn.fetchrow("""
SELECT
COUNT(*) as sent,
SUM(message_length) as total_length
FROM usage_message
WHERE timestamp >= $1 AND timestamp <= $2
AND ($3::text IS NULL OR microdao_id = $3)
""", period_start, period_end, microdao_id)
return UsageSummary(
period_start=period_start,
period_end=period_end,
microdao_id=microdao_id,
agent_id=agent_id,
llm_calls_total=llm_stats['calls'] or 0,
llm_tokens_total=llm_stats['tokens_total'] or 0,
llm_tokens_prompt=llm_stats['tokens_prompt'] or 0,
llm_tokens_completion=llm_stats['tokens_completion'] or 0,
llm_latency_avg_ms=float(llm_stats['latency_avg'] or 0),
tool_calls_total=tool_stats['calls'] or 0,
tool_calls_success=tool_stats['success'] or 0,
tool_calls_failed=tool_stats['failed'] or 0,
tool_latency_avg_ms=float(tool_stats['latency_avg'] or 0),
agent_invocations_total=agent_stats['invocations'] or 0,
agent_invocations_success=agent_stats['success'] or 0,
agent_invocations_failed=agent_stats['failed'] or 0,
messages_sent=message_stats['sent'] or 0,
messages_total_length=message_stats['total_length'] or 0
)
async def get_model_breakdown(
self,
microdao_id: Optional[str] = None,
period_hours: int = 24
) -> List[ModelUsage]:
"""Get usage breakdown by model"""
period_start = datetime.utcnow() - timedelta(hours=period_hours)
period_end = datetime.utcnow()
async with self.db_pool.acquire() as conn:
rows = await conn.fetch("""
SELECT
model,
provider,
COUNT(*) as calls,
SUM(total_tokens) as tokens,
AVG(latency_ms) as latency_avg
FROM usage_llm
WHERE timestamp >= $1 AND timestamp <= $2
AND ($3::text IS NULL OR microdao_id = $3)
GROUP BY model, provider
ORDER BY tokens DESC
LIMIT 20
""", period_start, period_end, microdao_id)
return [
ModelUsage(
model=row['model'],
provider=row['provider'],
calls=row['calls'],
tokens=row['tokens'] or 0,
avg_latency_ms=float(row['latency_avg'] or 0)
)
for row in rows
]
async def get_agent_breakdown(
self,
microdao_id: Optional[str] = None,
period_hours: int = 24
) -> List[AgentUsage]:
"""Get usage breakdown by agent"""
period_start = datetime.utcnow() - timedelta(hours=period_hours)
period_end = datetime.utcnow()
async with self.db_pool.acquire() as conn:
rows = await conn.fetch("""
SELECT
a.agent_id,
COUNT(DISTINCT a.event_id) as invocations,
COALESCE(SUM(a.llm_calls), 0) as llm_calls,
COALESCE(SUM(a.tool_calls), 0) as tool_calls,
COALESCE(llm.tokens, 0) as total_tokens,
COALESCE(msg.messages, 0) as messages_sent
FROM usage_agent a
LEFT JOIN (
SELECT agent_id, SUM(total_tokens) as tokens
FROM usage_llm
WHERE timestamp >= $1 AND timestamp <= $2
AND ($3::text IS NULL OR microdao_id = $3)
GROUP BY agent_id
) llm ON llm.agent_id = a.agent_id
LEFT JOIN (
SELECT actor_id, COUNT(*) as messages
FROM usage_message
WHERE timestamp >= $1 AND timestamp <= $2
AND actor_type = 'agent'
AND ($3::text IS NULL OR microdao_id = $3)
GROUP BY actor_id
) msg ON msg.actor_id = a.agent_id
WHERE a.timestamp >= $1 AND a.timestamp <= $2
AND ($3::text IS NULL OR a.microdao_id = $3)
GROUP BY a.agent_id, llm.tokens, msg.messages
ORDER BY invocations DESC
LIMIT 20
""", period_start, period_end, microdao_id)
return [
AgentUsage(
agent_id=row['agent_id'],
invocations=row['invocations'],
llm_calls=row['llm_calls'],
tool_calls=row['tool_calls'],
messages_sent=row['messages_sent'],
total_tokens=row['total_tokens']
)
for row in rows
]
async def get_tool_breakdown(
self,
microdao_id: Optional[str] = None,
period_hours: int = 24
) -> List[ToolUsage]:
"""Get usage breakdown by tool"""
period_start = datetime.utcnow() - timedelta(hours=period_hours)
period_end = datetime.utcnow()
async with self.db_pool.acquire() as conn:
rows = await conn.fetch("""
SELECT
tool_id,
tool_name,
COUNT(*) as calls,
AVG(CASE WHEN success THEN 1.0 ELSE 0.0 END) as success_rate,
AVG(latency_ms) as latency_avg
FROM usage_tool
WHERE timestamp >= $1 AND timestamp <= $2
AND ($3::text IS NULL OR microdao_id = $3)
GROUP BY tool_id, tool_name
ORDER BY calls DESC
LIMIT 20
""", period_start, period_end, microdao_id)
return [
ToolUsage(
tool_id=row['tool_id'],
tool_name=row['tool_name'],
calls=row['calls'],
success_rate=float(row['success_rate'] or 0),
avg_latency_ms=float(row['latency_avg'] or 0)
)
for row in rows
]