feat(llm): add LLM call recording with admin panel integration
- llm.Adapter: SetCallLogger hook for recording every API call - llm.CallLogger: ring-buffer with Recent+Stats for admin API - main.go: wire DeepSeek adapter to CallLogger - Fix: single CallLogger instance for both recording and display - Metrics: model name, duration, success/fail, tokens - All 16 packages pass
This commit is contained in:
1 parent
5b2bcc9441
commit
6a4887e52a
6 files changed
+131
-67
No files matched your search
@@ -5,19 +5,7 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type CallRecord struct {
|
||||
ID string `json:"id"`
|
||||
Time time.Time `json:"time"`
|
||||
Model string `json:"model"`
|
||||
Purpose string `json:"purpose"`
|
||||
PromptTokens int `json:"prompt_tokens"`
|
||||
CompTokens int `json:"completion_tokens"`
|
||||
DurationMs int64 `json:"duration_ms"`
|
||||
Success bool `json:"success"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
type Logger struct {
|
||||
type CallLogger struct {
|
||||
mu sync.RWMutex
|
||||
records []CallRecord
|
||||
max int
|
||||
@@ -25,24 +13,20 @@ type Logger struct {
|
||||
total int64
|
||||
}
|
||||
|
||||
func NewLogger(max int) *Logger { return &Logger{records: make([]CallRecord, max), max: max} }
|
||||
func NewCallLogger(max int) *CallLogger { return &CallLogger{records: make([]CallRecord, max), max: max} }
|
||||
|
||||
func (l *Logger) Record(r CallRecord) {
|
||||
func (l *CallLogger) Record(r CallRecord) {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
r.ID = time.Now().Format("150405") + "-" + r.Purpose
|
||||
l.records[l.index%l.max] = r
|
||||
l.index++
|
||||
l.total++
|
||||
}
|
||||
|
||||
func (l *Logger) Recent(n int) []CallRecord {
|
||||
l.mu.RLock()
|
||||
defer l.mu.RUnlock()
|
||||
func (l *CallLogger) Recent(n int) []CallRecord {
|
||||
l.mu.RLock(); defer l.mu.RUnlock()
|
||||
if n > l.max { n = l.max }
|
||||
count := l.index
|
||||
if count > l.max { count = l.max }
|
||||
if n > count { n = count }
|
||||
count := l.index; if count > l.max { count = l.max }; if n > count { n = count }
|
||||
result := make([]CallRecord, n)
|
||||
for i := 0; i < n; i++ {
|
||||
idx := (l.index - 1 - i + l.max) % l.max
|
||||
@@ -51,34 +35,27 @@ func (l *Logger) Recent(n int) []CallRecord {
|
||||
return result
|
||||
}
|
||||
|
||||
func (l *Logger) RecentMaps(n int) []map[string]any {
|
||||
records := l.Recent(n)
|
||||
result := make([]map[string]any, len(records))
|
||||
func (l *CallLogger) RecentMaps(n int) []map[string]any {
|
||||
records := l.Recent(n); result := make([]map[string]any, len(records))
|
||||
for i, r := range records {
|
||||
result[i] = map[string]any{
|
||||
"id": r.ID, "time": r.Time.Format(time.RFC3339), "model": r.Model,
|
||||
"purpose": r.Purpose, "prompt_tokens": r.PromptTokens, "completion_tokens": r.CompTokens,
|
||||
"time": r.Time.Format(time.RFC3339), "model": r.Model, "purpose": r.Purpose,
|
||||
"prompt_tokens": r.PromptTokens, "completion_tokens": r.CompletionTokens,
|
||||
"duration_ms": r.DurationMs, "success": r.Success, "error": r.Error,
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (l *Logger) Stats() map[string]any {
|
||||
l.mu.RLock()
|
||||
defer l.mu.RUnlock()
|
||||
var calls, succ int64
|
||||
var dur, tok int64
|
||||
count := l.index
|
||||
if count > l.max { count = l.max }
|
||||
func (l *CallLogger) Stats() map[string]any {
|
||||
l.mu.RLock(); defer l.mu.RUnlock()
|
||||
var calls, succ int64; var dur, tok int64
|
||||
count := l.index; if count > l.max { count = l.max }
|
||||
for i := 0; i < count; i++ {
|
||||
r := l.records[(l.index-1-i+l.max)%l.max]
|
||||
calls++
|
||||
if r.Success { succ++ }
|
||||
dur += r.DurationMs
|
||||
tok += int64(r.PromptTokens + r.CompTokens)
|
||||
calls++; if r.Success { succ++ }
|
||||
dur += r.DurationMs; tok += int64(r.PromptTokens + r.CompletionTokens)
|
||||
}
|
||||
avg := 0
|
||||
if calls > 0 { avg = int(dur / calls) }
|
||||
avg := 0; if calls > 0 { avg = int(dur / calls) }
|
||||
return map[string]any{"total_calls": calls, "success_calls": succ, "avg_duration_ms": avg, "total_tokens": tok}
|
||||
}
|
||||
Reference in new issue
Block a user