feat: add AI APM module for AI/LLM call telemetry

- internal/aiapm/types.go: AICallRecord, filter, summary, and stats types
- internal/aiapm/pricing.go: vendor pricing tables (Anthropic, OpenAI, Google, Mistral, DeepSeek, Groq)
- internal/aiapm/store.go: PostgreSQL storage with batch insert, filtered queries, aggregations, timeseries
- internal/aiapm/collector.go: async collector with buffered channel and background batch writer
- internal/api/aiapm_handlers.go: Fiber route handlers for ingest, summary, models, vendors, costs, calls, pricing
- cmd/server/main.go: register AI APM routes and create ai_calls table at startup
This commit is contained in:
2026-02-08 05:13:38 -03:00
parent f150ef6ac8
commit c9e68c5048
6 changed files with 781 additions and 0 deletions

102
internal/aiapm/types.go Normal file
View File

@@ -0,0 +1,102 @@
package aiapm
import "time"
// AICallRecord represents a single AI/LLM API call
type AICallRecord struct {
ID string `json:"id"`
Timestamp time.Time `json:"timestamp"`
ServiceName string `json:"service_name"`
ProjectID string `json:"project_id"`
Vendor string `json:"vendor"`
Model string `json:"model"`
TokensIn int `json:"tokens_in"`
TokensOut int `json:"tokens_out"`
TokensCacheRead int `json:"tokens_cache_read"`
TokensCacheWrite int `json:"tokens_cache_write"`
EstimatedCost float64 `json:"estimated_cost"`
LatencyMs int `json:"latency_ms"`
TTFBMs int `json:"ttfb_ms"`
Status string `json:"status"`
ErrorMessage string `json:"error_message,omitempty"`
Stream bool `json:"stream"`
Cached bool `json:"cached"`
Tags map[string]string `json:"tags,omitempty"`
}
// AICallFilter defines query filters for AI call records
type AICallFilter struct {
ServiceName string `json:"service_name"`
ProjectID string `json:"project_id"`
Vendor string `json:"vendor"`
Model string `json:"model"`
Status string `json:"status"`
From time.Time `json:"from"`
To time.Time `json:"to"`
Limit int `json:"limit"`
Offset int `json:"offset"`
}
// AIUsageSummary aggregated usage statistics
type AIUsageSummary struct {
TotalCalls int `json:"total_calls"`
TotalTokensIn int64 `json:"total_tokens_in"`
TotalTokensOut int64 `json:"total_tokens_out"`
TotalCacheRead int64 `json:"total_cache_read"`
TotalCacheWrite int64 `json:"total_cache_write"`
TotalCost float64 `json:"total_cost"`
AvgLatencyMs float64 `json:"avg_latency_ms"`
AvgTTFBMs float64 `json:"avg_ttfb_ms"`
ErrorCount int `json:"error_count"`
ErrorRate float64 `json:"error_rate"`
CacheHitRate float64 `json:"cache_hit_rate"`
UniqueModels int `json:"unique_models"`
UniqueVendors int `json:"unique_vendors"`
UniqueServices int `json:"unique_services"`
}
// AIModelStats per-model breakdown
type AIModelStats struct {
Vendor string `json:"vendor"`
Model string `json:"model"`
TotalCalls int `json:"total_calls"`
TotalTokens int64 `json:"total_tokens"`
TotalCost float64 `json:"total_cost"`
AvgLatencyMs float64 `json:"avg_latency_ms"`
ErrorCount int `json:"error_count"`
ErrorRate float64 `json:"error_rate"`
}
// AIVendorStats per-vendor breakdown
type AIVendorStats struct {
Vendor string `json:"vendor"`
TotalCalls int `json:"total_calls"`
TotalTokens int64 `json:"total_tokens"`
TotalCost float64 `json:"total_cost"`
AvgLatencyMs float64 `json:"avg_latency_ms"`
ModelCount int `json:"model_count"`
ErrorCount int `json:"error_count"`
ErrorRate float64 `json:"error_rate"`
}
// AICostBreakdown cost breakdown by dimension
type AICostBreakdown struct {
Dimension string `json:"dimension"` // vendor, model, service, project
Key string `json:"key"`
Cost float64 `json:"cost"`
Calls int `json:"calls"`
Tokens int64 `json:"tokens"`
}
// TimeseriesPoint a single point in a time series
type TimeseriesPoint struct {
Timestamp time.Time `json:"timestamp"`
Value float64 `json:"value"`
Count int `json:"count"`
}
// IngestRequest payload for the ingest endpoint
type IngestRequest struct {
Call *AICallRecord `json:"call,omitempty"`
Calls []AICallRecord `json:"calls,omitempty"`
}