209 lines
5.9 KiB
Go
209 lines
5.9 KiB
Go
package sqlite
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
type RouteDecisionLog struct {
|
|
ID int64
|
|
RequestID string
|
|
LogicalGroupID string
|
|
PublicModel string
|
|
UserKey string
|
|
ConversationKey string
|
|
StickyKey string
|
|
StickyKeyType string
|
|
StickyHit bool
|
|
SelectedRouteID string
|
|
SelectedShadowGroupID string
|
|
FallbackUsed bool
|
|
ErrorClass string
|
|
UpstreamStatus int
|
|
LatencyMS int
|
|
CreatedAt string
|
|
}
|
|
|
|
type RouteDecisionLogFilter struct {
|
|
RequestID string
|
|
LogicalGroupID string
|
|
PublicModel string
|
|
SelectedRouteID string
|
|
StickyKey string
|
|
Limit int
|
|
}
|
|
|
|
type RouteDecisionLogsRepo struct {
|
|
db execQuerier
|
|
}
|
|
|
|
func newRouteDecisionLogsRepo(db execQuerier) *RouteDecisionLogsRepo {
|
|
return &RouteDecisionLogsRepo{db: db}
|
|
}
|
|
|
|
func (r *RouteDecisionLogsRepo) Create(ctx context.Context, row RouteDecisionLog) (int64, error) {
|
|
row, err := normalizeRouteDecisionLog(row)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
result, err := r.db.ExecContext(
|
|
ctx,
|
|
`INSERT INTO route_decision_logs (
|
|
request_id,
|
|
logical_group_id,
|
|
public_model,
|
|
user_key,
|
|
conversation_key,
|
|
sticky_key,
|
|
sticky_key_type,
|
|
sticky_hit,
|
|
selected_route_id,
|
|
selected_shadow_group_id,
|
|
fallback_used,
|
|
error_class,
|
|
upstream_status,
|
|
latency_ms
|
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
row.RequestID,
|
|
row.LogicalGroupID,
|
|
row.PublicModel,
|
|
row.UserKey,
|
|
row.ConversationKey,
|
|
row.StickyKey,
|
|
row.StickyKeyType,
|
|
boolToSQLiteInt(row.StickyHit),
|
|
row.SelectedRouteID,
|
|
row.SelectedShadowGroupID,
|
|
boolToSQLiteInt(row.FallbackUsed),
|
|
row.ErrorClass,
|
|
row.UpstreamStatus,
|
|
row.LatencyMS,
|
|
)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("insert route decision log %q: %w", row.RequestID, err)
|
|
}
|
|
|
|
id, err := result.LastInsertId()
|
|
if err != nil {
|
|
return 0, fmt.Errorf("read inserted route decision log id for %q: %w", row.RequestID, err)
|
|
}
|
|
return id, nil
|
|
}
|
|
|
|
func (r *RouteDecisionLogsRepo) ListRecent(ctx context.Context, filter RouteDecisionLogFilter) ([]RouteDecisionLog, error) {
|
|
clauses := make([]string, 0, 5)
|
|
args := make([]any, 0, 6)
|
|
|
|
if requestID := strings.TrimSpace(filter.RequestID); requestID != "" {
|
|
clauses = append(clauses, "request_id = ?")
|
|
args = append(args, requestID)
|
|
}
|
|
if logicalGroupID := strings.TrimSpace(filter.LogicalGroupID); logicalGroupID != "" {
|
|
clauses = append(clauses, "logical_group_id = ?")
|
|
args = append(args, logicalGroupID)
|
|
}
|
|
if publicModel := strings.TrimSpace(filter.PublicModel); publicModel != "" {
|
|
clauses = append(clauses, "public_model = ?")
|
|
args = append(args, publicModel)
|
|
}
|
|
if selectedRouteID := strings.TrimSpace(filter.SelectedRouteID); selectedRouteID != "" {
|
|
clauses = append(clauses, "selected_route_id = ?")
|
|
args = append(args, selectedRouteID)
|
|
}
|
|
if stickyKey := strings.TrimSpace(filter.StickyKey); stickyKey != "" {
|
|
clauses = append(clauses, "sticky_key = ?")
|
|
args = append(args, stickyKey)
|
|
}
|
|
|
|
query := `SELECT id, request_id, logical_group_id, public_model, user_key, conversation_key, sticky_key, sticky_key_type, sticky_hit, selected_route_id, selected_shadow_group_id, fallback_used, error_class, upstream_status, latency_ms, created_at
|
|
FROM route_decision_logs`
|
|
if len(clauses) > 0 {
|
|
query += " WHERE " + strings.Join(clauses, " AND ")
|
|
}
|
|
query += " ORDER BY id DESC LIMIT ?"
|
|
args = append(args, normalizeRouteLogListLimit(filter.Limit))
|
|
|
|
rows, err := r.db.QueryContext(ctx, query, args...)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("list route decision logs: %w", err)
|
|
}
|
|
defer rows.Close()
|
|
|
|
items := make([]RouteDecisionLog, 0)
|
|
for rows.Next() {
|
|
var (
|
|
item RouteDecisionLog
|
|
stickyHit int
|
|
fallbackUsed int
|
|
)
|
|
if err := rows.Scan(
|
|
&item.ID,
|
|
&item.RequestID,
|
|
&item.LogicalGroupID,
|
|
&item.PublicModel,
|
|
&item.UserKey,
|
|
&item.ConversationKey,
|
|
&item.StickyKey,
|
|
&item.StickyKeyType,
|
|
&stickyHit,
|
|
&item.SelectedRouteID,
|
|
&item.SelectedShadowGroupID,
|
|
&fallbackUsed,
|
|
&item.ErrorClass,
|
|
&item.UpstreamStatus,
|
|
&item.LatencyMS,
|
|
&item.CreatedAt,
|
|
); err != nil {
|
|
return nil, fmt.Errorf("scan route decision log: %w", err)
|
|
}
|
|
item.StickyHit = stickyHit != 0
|
|
item.FallbackUsed = fallbackUsed != 0
|
|
items = append(items, item)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, fmt.Errorf("iterate route decision logs: %w", err)
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
func normalizeRouteDecisionLog(row RouteDecisionLog) (RouteDecisionLog, error) {
|
|
row.RequestID = strings.TrimSpace(row.RequestID)
|
|
row.LogicalGroupID = strings.TrimSpace(row.LogicalGroupID)
|
|
row.PublicModel = strings.TrimSpace(row.PublicModel)
|
|
row.UserKey = strings.TrimSpace(row.UserKey)
|
|
row.ConversationKey = strings.TrimSpace(row.ConversationKey)
|
|
row.StickyKey = strings.TrimSpace(row.StickyKey)
|
|
row.StickyKeyType = strings.TrimSpace(row.StickyKeyType)
|
|
row.SelectedRouteID = strings.TrimSpace(row.SelectedRouteID)
|
|
row.SelectedShadowGroupID = strings.TrimSpace(row.SelectedShadowGroupID)
|
|
row.ErrorClass = strings.TrimSpace(row.ErrorClass)
|
|
|
|
switch {
|
|
case row.RequestID == "":
|
|
return RouteDecisionLog{}, fmt.Errorf("request_id is required")
|
|
case row.LogicalGroupID == "":
|
|
return RouteDecisionLog{}, fmt.Errorf("logical_group_id is required")
|
|
case row.PublicModel == "":
|
|
return RouteDecisionLog{}, fmt.Errorf("public_model is required")
|
|
case row.SelectedRouteID == "":
|
|
return RouteDecisionLog{}, fmt.Errorf("selected_route_id is required")
|
|
case row.SelectedShadowGroupID == "":
|
|
return RouteDecisionLog{}, fmt.Errorf("selected_shadow_group_id is required")
|
|
case row.UpstreamStatus < 0:
|
|
return RouteDecisionLog{}, fmt.Errorf("upstream_status must be >= 0")
|
|
case row.LatencyMS < 0:
|
|
return RouteDecisionLog{}, fmt.Errorf("latency_ms must be >= 0")
|
|
}
|
|
|
|
return row, nil
|
|
}
|
|
|
|
func boolToSQLiteInt(value bool) int {
|
|
if value {
|
|
return 1
|
|
}
|
|
return 0
|
|
}
|