Some checks failed
Build and Deploy Docs / build-and-deploy (push) Has been cancelled
- Created logs/ structure (sessions, operations, incidents) - Added session-start/log/end scripts - Installed Git hooks for auto-logging commits/pushes - Added shell integration for zsh - Created CHANGELOG.md - Documented today's session (2026-01-10)
252 lines
8.9 KiB
Python
252 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
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|