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

@@ -0,0 +1,68 @@
import { Controller, Get, Param, Query } from "@nestjs/common";
import { LlmUsageService } from "./llm-usage.service";
import type { UsageAnalyticsQueryDto } from "./dto";
/**
* LLM Usage Controller
*
* Provides endpoints for querying LLM usage analytics and logs.
* All endpoints return data in the standard API response format.
*/
@Controller("llm-usage")
export class LlmUsageController {
constructor(private readonly llmUsageService: LlmUsageService) {}
/**
* Get aggregated usage analytics.
* Supports filtering by workspace, provider, model, user, and date range.
*
* @param query - Analytics query parameters
* @returns Aggregated usage analytics
*/
@Get("analytics")
async getAnalytics(@Query() query: UsageAnalyticsQueryDto) {
const data = await this.llmUsageService.getUsageAnalytics(query);
return { data };
}
/**
* Get all usage logs for a specific workspace.
*
* @param workspaceId - Workspace UUID
* @returns Array of usage logs
*/
@Get("by-workspace/:workspaceId")
async getUsageByWorkspace(@Param("workspaceId") workspaceId: string) {
const data = await this.llmUsageService.getUsageByWorkspace(workspaceId);
return { data };
}
/**
* Get usage logs for a specific provider within a workspace.
*
* @param workspaceId - Workspace UUID
* @param provider - Provider name
* @returns Array of usage logs
*/
@Get("by-workspace/:workspaceId/provider/:provider")
async getUsageByProvider(
@Param("workspaceId") workspaceId: string,
@Param("provider") provider: string
) {
const data = await this.llmUsageService.getUsageByProvider(workspaceId, provider);
return { data };
}
/**
* Get usage logs for a specific model within a workspace.
*
* @param workspaceId - Workspace UUID
* @param model - Model name
* @returns Array of usage logs
*/
@Get("by-workspace/:workspaceId/model/:model")
async getUsageByModel(@Param("workspaceId") workspaceId: string, @Param("model") model: string) {
const data = await this.llmUsageService.getUsageByModel(workspaceId, model);
return { data };
}
}