feat(#309): Add LLM usage tracking and analytics

Implements comprehensive LLM usage tracking with analytics endpoints.

Implementation:
- Added LlmUsageLog model to Prisma schema
- Created llm-usage module with service, controller, and DTOs
- Added tracking for token usage, costs, and durations
- Implemented analytics aggregation by provider, model, and task type
- Added filtering by workspace, provider, model, user, and date range

Testing:
- 20 unit tests with 90.8% coverage (exceeds 85% requirement)
- Tests for service and controller with full error handling
- Tests use Vitest following project conventions

API Endpoints:
- GET /api/llm-usage/analytics - Aggregated usage analytics
- GET /api/llm-usage/by-workspace/:workspaceId - Workspace usage logs
- GET /api/llm-usage/by-workspace/:workspaceId/provider/:provider - Provider logs
- GET /api/llm-usage/by-workspace/:workspaceId/model/:model - Model logs

Database:
- LlmUsageLog table with indexes for efficient queries
- Relations to User, Workspace, and LlmProviderInstance
- Ready for migration with: pnpm prisma migrate dev

Refs #309

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Jason Woltje
2026-02-04 13:41:45 -06:00
parent 6516843612
commit b836940b89
12 changed files with 2187 additions and 248 deletions

View File

@@ -221,6 +221,7 @@ model User {
knowledgeEntryVersions KnowledgeEntryVersion[] @relation("EntryVersionAuthor")
llmProviders LlmProviderInstance[] @relation("UserLlmProviders")
federatedIdentities FederatedIdentity[]
llmUsageLogs LlmUsageLog[] @relation("UserLlmUsageLogs")
@@map("users")
}
@@ -272,6 +273,7 @@ model Workspace {
federationConnections FederationConnection[]
federationMessages FederationMessage[]
federationEventSubscriptions FederationEventSubscription[]
llmUsageLogs LlmUsageLog[]
@@index([ownerId])
@@map("workspaces")
@@ -1036,6 +1038,7 @@ model LlmProviderInstance {
user User? @relation("UserLlmProviders", fields: [userId], references: [id], onDelete: Cascade)
personalities Personality[] @relation("PersonalityLlmProvider")
workspaceLlmSettings WorkspaceLlmSettings[] @relation("WorkspaceLlmProvider")
llmUsageLogs LlmUsageLog[] @relation("LlmUsageLogs")
@@index([userId])
@@index([providerType])
@@ -1383,3 +1386,53 @@ model FederationEventSubscription {
@@index([workspaceId, isActive])
@@map("federation_event_subscriptions")
}
// ============================================
// LLM USAGE TRACKING MODULE
// ============================================
model LlmUsageLog {
id String @id @default(uuid()) @db.Uuid
workspaceId String @map("workspace_id") @db.Uuid
userId String @map("user_id") @db.Uuid
// LLM provider and model info
provider String @db.VarChar(50)
model String @db.VarChar(100)
providerInstanceId String? @map("provider_instance_id") @db.Uuid
// Token usage
promptTokens Int @default(0) @map("prompt_tokens")
completionTokens Int @default(0) @map("completion_tokens")
totalTokens Int @default(0) @map("total_tokens")
// Optional cost (in cents for precision)
costCents Float? @map("cost_cents")
// Task type for routing analytics
taskType String? @map("task_type") @db.VarChar(50)
// Optional reference to conversation/session
conversationId String? @map("conversation_id") @db.Uuid
// Duration in milliseconds
durationMs Int? @map("duration_ms")
// Timestamp
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
// Relations
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
user User @relation("UserLlmUsageLogs", fields: [userId], references: [id], onDelete: Cascade)
llmProviderInstance LlmProviderInstance? @relation("LlmUsageLogs", fields: [providerInstanceId], references: [id], onDelete: SetNull)
@@index([workspaceId])
@@index([workspaceId, createdAt])
@@index([userId])
@@index([provider])
@@index([model])
@@index([providerInstanceId])
@@index([taskType])
@@index([conversationId])
@@map("llm_usage_logs")
}