Files
microdao-daarion/services/usage-engine/aggregators.py
Apple 3de3c8cb36 feat: Add presence heartbeat for Matrix online status
- matrix-gateway: POST /internal/matrix/presence/online endpoint
- usePresenceHeartbeat hook with activity tracking
- Auto away after 5 min inactivity
- Offline on page close/visibility change
- Integrated in MatrixChatRoom component
2025-11-27 00:19:40 -08:00

240 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
]