2025-01-04 18:38:41 +00:00

272 lines
8.4 KiB
Python

import datetime
from collections import defaultdict
from typing import List
from fastapi import APIRouter
from fastapi import Depends
from fastapi import HTTPException
from pydantic import BaseModel
from sqlalchemy.orm import Session
from ee.onyx.db.analytics import fetch_assistant_message_analytics
from ee.onyx.db.analytics import fetch_assistant_unique_users
from ee.onyx.db.analytics import fetch_assistant_unique_users_total
from ee.onyx.db.analytics import fetch_onyxbot_analytics
from ee.onyx.db.analytics import fetch_per_user_query_analytics
from ee.onyx.db.analytics import fetch_persona_message_analytics
from ee.onyx.db.analytics import fetch_persona_unique_users
from ee.onyx.db.analytics import fetch_query_analytics
from ee.onyx.db.analytics import user_can_view_assistant_stats
from onyx.auth.users import current_admin_user
from onyx.auth.users import current_user
from onyx.db.engine import get_session
from onyx.db.models import User
router = APIRouter(prefix="/analytics")
_DEFAULT_LOOKBACK_DAYS = 30
class QueryAnalyticsResponse(BaseModel):
total_queries: int
total_likes: int
total_dislikes: int
date: datetime.date
@router.get("/admin/query")
def get_query_analytics(
start: datetime.datetime | None = None,
end: datetime.datetime | None = None,
_: User | None = Depends(current_admin_user),
db_session: Session = Depends(get_session),
) -> list[QueryAnalyticsResponse]:
daily_query_usage_info = fetch_query_analytics(
start=start
or (
datetime.datetime.utcnow() - datetime.timedelta(days=_DEFAULT_LOOKBACK_DAYS)
), # default is 30d lookback
end=end or datetime.datetime.utcnow(),
db_session=db_session,
)
return [
QueryAnalyticsResponse(
total_queries=total_queries,
total_likes=total_likes,
total_dislikes=total_dislikes,
date=date,
)
for total_queries, total_likes, total_dislikes, date in daily_query_usage_info
]
class UserAnalyticsResponse(BaseModel):
total_active_users: int
date: datetime.date
@router.get("/admin/user")
def get_user_analytics(
start: datetime.datetime | None = None,
end: datetime.datetime | None = None,
_: User | None = Depends(current_admin_user),
db_session: Session = Depends(get_session),
) -> list[UserAnalyticsResponse]:
daily_query_usage_info_per_user = fetch_per_user_query_analytics(
start=start
or (
datetime.datetime.utcnow() - datetime.timedelta(days=_DEFAULT_LOOKBACK_DAYS)
), # default is 30d lookback
end=end or datetime.datetime.utcnow(),
db_session=db_session,
)
user_analytics: dict[datetime.date, int] = defaultdict(int)
for __, ___, ____, date, _____ in daily_query_usage_info_per_user:
user_analytics[date] += 1
return [
UserAnalyticsResponse(
total_active_users=cnt,
date=date,
)
for date, cnt in user_analytics.items()
]
class OnyxbotAnalyticsResponse(BaseModel):
total_queries: int
auto_resolved: int
date: datetime.date
@router.get("/admin/onyxbot")
def get_onyxbot_analytics(
start: datetime.datetime | None = None,
end: datetime.datetime | None = None,
_: User | None = Depends(current_admin_user),
db_session: Session = Depends(get_session),
) -> list[OnyxbotAnalyticsResponse]:
daily_onyxbot_info = fetch_onyxbot_analytics(
start=start
or (
datetime.datetime.utcnow() - datetime.timedelta(days=_DEFAULT_LOOKBACK_DAYS)
), # default is 30d lookback
end=end or datetime.datetime.utcnow(),
db_session=db_session,
)
resolution_results = [
OnyxbotAnalyticsResponse(
total_queries=total_queries,
# If it hits negatives, something has gone wrong...
auto_resolved=max(0, total_queries - total_negatives),
date=date,
)
for total_queries, total_negatives, date in daily_onyxbot_info
]
return resolution_results
class PersonaMessageAnalyticsResponse(BaseModel):
total_messages: int
date: datetime.date
persona_id: int
@router.get("/admin/persona/messages")
def get_persona_messages(
persona_id: int,
start: datetime.datetime | None = None,
end: datetime.datetime | None = None,
_: User | None = Depends(current_admin_user),
db_session: Session = Depends(get_session),
) -> list[PersonaMessageAnalyticsResponse]:
"""Fetch daily message counts for a single persona within the given time range."""
start = start or (
datetime.datetime.utcnow() - datetime.timedelta(days=_DEFAULT_LOOKBACK_DAYS)
)
end = end or datetime.datetime.utcnow()
persona_message_counts = []
for count, date in fetch_persona_message_analytics(
db_session=db_session,
persona_id=persona_id,
start=start,
end=end,
):
persona_message_counts.append(
PersonaMessageAnalyticsResponse(
total_messages=count,
date=date,
persona_id=persona_id,
)
)
return persona_message_counts
class PersonaUniqueUsersResponse(BaseModel):
unique_users: int
date: datetime.date
persona_id: int
@router.get("/admin/persona/unique-users")
def get_persona_unique_users(
persona_id: int,
start: datetime.datetime,
end: datetime.datetime,
_: User | None = Depends(current_admin_user),
db_session: Session = Depends(get_session),
) -> list[PersonaUniqueUsersResponse]:
"""Get unique users per day for a single persona."""
unique_user_counts = []
daily_counts = fetch_persona_unique_users(
db_session=db_session,
persona_id=persona_id,
start=start,
end=end,
)
for count, date in daily_counts:
unique_user_counts.append(
PersonaUniqueUsersResponse(
unique_users=count,
date=date,
persona_id=persona_id,
)
)
return unique_user_counts
class AssistantDailyUsageResponse(BaseModel):
date: datetime.date
total_messages: int
total_unique_users: int
class AssistantStatsResponse(BaseModel):
daily_stats: List[AssistantDailyUsageResponse]
total_messages: int
total_unique_users: int
@router.get("/assistant/{assistant_id}/stats")
def get_assistant_stats(
assistant_id: int,
start: datetime.datetime | None = None,
end: datetime.datetime | None = None,
user: User | None = Depends(current_user),
db_session: Session = Depends(get_session),
) -> AssistantStatsResponse:
"""
Returns daily message and unique user counts for a user's assistant,
along with the overall total messages and total distinct users.
"""
start = start or (
datetime.datetime.utcnow() - datetime.timedelta(days=_DEFAULT_LOOKBACK_DAYS)
)
end = end or datetime.datetime.utcnow()
if not user_can_view_assistant_stats(db_session, user, assistant_id):
raise HTTPException(
status_code=403, detail="Not allowed to access this assistant's stats."
)
# Pull daily usage from the DB calls
messages_data = fetch_assistant_message_analytics(
db_session, assistant_id, start, end
)
unique_users_data = fetch_assistant_unique_users(
db_session, assistant_id, start, end
)
# Map each day => (messages, unique_users).
daily_messages_map = {date: count for count, date in messages_data}
daily_unique_users_map = {date: count for count, date in unique_users_data}
all_dates = set(daily_messages_map.keys()) | set(daily_unique_users_map.keys())
# Merge both sets of metrics by date
daily_results: list[AssistantDailyUsageResponse] = []
for date in sorted(all_dates):
daily_results.append(
AssistantDailyUsageResponse(
date=date,
total_messages=daily_messages_map.get(date, 0),
total_unique_users=daily_unique_users_map.get(date, 0),
)
)
# Now pull a single total distinct user count across the entire time range
total_msgs = sum(d.total_messages for d in daily_results)
total_users = fetch_assistant_unique_users_total(
db_session, assistant_id, start, end
)
return AssistantStatsResponse(
daily_stats=daily_results,
total_messages=total_msgs,
total_unique_users=total_users,
)