diff --git a/proxy/internal/handler/handlers.go b/proxy/internal/handler/handlers.go index 4cc3223..4c51379 100644 --- a/proxy/internal/handler/handlers.go +++ b/proxy/internal/handler/handlers.go @@ -76,6 +76,13 @@ func (h *Handler) Messages(w http.ResponseWriter, r *http.Request) { } // Create request log with routing information + var apiURL string + var provider string + if decision.Provider != nil { + apiURL = decision.Provider.GetBaseURL() + provider = decision.Provider.Name() + } + requestLog := &model.RequestLog{ RequestID: requestID, Timestamp: time.Now().Format(time.RFC3339), @@ -88,6 +95,8 @@ func (h *Handler) Messages(w http.ResponseWriter, r *http.Request) { RoutedModel: decision.TargetModel, UserAgent: r.Header.Get("User-Agent"), ContentType: r.Header.Get("Content-Type"), + APIURL: apiURL, + Provider: provider, } if _, err := h.storageService.SaveRequest(requestLog); err != nil { diff --git a/proxy/internal/model/models.go b/proxy/internal/model/models.go index a5d03c0..f58f1c1 100644 --- a/proxy/internal/model/models.go +++ b/proxy/internal/model/models.go @@ -36,6 +36,8 @@ type RequestLog struct { RoutedModel string `json:"routedModel,omitempty"` UserAgent string `json:"userAgent"` ContentType string `json:"contentType"` + APIURL string `json:"apiUrl,omitempty"` + Provider string `json:"provider,omitempty"` PromptGrade *PromptGrade `json:"promptGrade,omitempty"` Response *ResponseLog `json:"response,omitempty"` } diff --git a/proxy/internal/provider/anthropic.go b/proxy/internal/provider/anthropic.go index 64b3039..42ad542 100644 --- a/proxy/internal/provider/anthropic.go +++ b/proxy/internal/provider/anthropic.go @@ -32,6 +32,10 @@ func (p *AnthropicProvider) Name() string { return "anthropic" } +func (p *AnthropicProvider) GetBaseURL() string { + return p.config.BaseURL +} + func (p *AnthropicProvider) ForwardRequest(ctx context.Context, originalReq *http.Request) (*http.Response, error) { // Clone the request to avoid modifying the original proxyReq := originalReq.Clone(ctx) diff --git a/proxy/internal/provider/openai.go b/proxy/internal/provider/openai.go index 5973026..f7a596f 100644 --- a/proxy/internal/provider/openai.go +++ b/proxy/internal/provider/openai.go @@ -35,6 +35,10 @@ func (p *OpenAIProvider) Name() string { return "openai" } +func (p *OpenAIProvider) GetBaseURL() string { + return p.config.BaseURL +} + func (p *OpenAIProvider) ForwardRequest(ctx context.Context, originalReq *http.Request) (*http.Response, error) { // First, we need to convert the Anthropic request to OpenAI format bodyBytes, err := io.ReadAll(originalReq.Body) diff --git a/proxy/internal/provider/provider.go b/proxy/internal/provider/provider.go index d76757c..c93ffac 100644 --- a/proxy/internal/provider/provider.go +++ b/proxy/internal/provider/provider.go @@ -12,4 +12,7 @@ type Provider interface { // ForwardRequest forwards a request to the provider's API ForwardRequest(ctx context.Context, req *http.Request) (*http.Response, error) + + // GetBaseURL returns the provider's base API URL + GetBaseURL() string } diff --git a/proxy/internal/service/storage_sqlite.go b/proxy/internal/service/storage_sqlite.go index 77a52b4..ec0d210 100644 --- a/proxy/internal/service/storage_sqlite.go +++ b/proxy/internal/service/storage_sqlite.go @@ -51,6 +51,8 @@ func (s *sqliteStorageService) createTables() error { model TEXT, original_model TEXT, routed_model TEXT, + api_url TEXT, + provider TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); @@ -60,7 +62,24 @@ func (s *sqliteStorageService) createTables() error { ` _, err := s.db.Exec(schema) - return err + if err != nil { + return err + } + + // Add new columns if they don't exist (for backward compatibility) + migrations := []string{ + "ALTER TABLE requests ADD COLUMN api_url TEXT", + "ALTER TABLE requests ADD COLUMN provider TEXT", + } + + for _, migration := range migrations { + _, err := s.db.Exec(migration) + if err != nil && !strings.Contains(err.Error(), "duplicate column name") { + return err + } + } + + return nil } func (s *sqliteStorageService) SaveRequest(request *model.RequestLog) (string, error) { @@ -74,9 +93,10 @@ func (s *sqliteStorageService) SaveRequest(request *model.RequestLog) (string, e return "", fmt.Errorf("failed to marshal body: %w", err) } + query := ` - INSERT INTO requests (id, timestamp, method, endpoint, headers, body, user_agent, content_type, model, original_model, routed_model) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + INSERT INTO requests (id, timestamp, method, endpoint, headers, body, user_agent, content_type, model, original_model, routed_model, api_url, provider) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ` _, err = s.db.Exec(query, @@ -91,6 +111,8 @@ func (s *sqliteStorageService) SaveRequest(request *model.RequestLog) (string, e request.Model, request.OriginalModel, request.RoutedModel, + request.APIURL, + request.Provider, ) if err != nil { @@ -111,7 +133,7 @@ func (s *sqliteStorageService) GetRequests(page, limit int) ([]model.RequestLog, // Get paginated results offset := (page - 1) * limit query := ` - SELECT id, timestamp, method, endpoint, headers, body, model, user_agent, content_type, prompt_grade, response, original_model, routed_model + SELECT id, timestamp, method, endpoint, headers, body, model, user_agent, content_type, prompt_grade, response, original_model, routed_model, api_url, provider FROM requests ORDER BY timestamp DESC LIMIT ? OFFSET ? @@ -128,6 +150,7 @@ func (s *sqliteStorageService) GetRequests(page, limit int) ([]model.RequestLog, var req model.RequestLog var headersJSON, bodyJSON string var promptGradeJSON, responseJSON sql.NullString + var apiURL, provider sql.NullString err := rows.Scan( &req.RequestID, @@ -143,6 +166,8 @@ func (s *sqliteStorageService) GetRequests(page, limit int) ([]model.RequestLog, &responseJSON, &req.OriginalModel, &req.RoutedModel, + &apiURL, + &provider, ) if err != nil { // Error scanning row - skip @@ -176,6 +201,15 @@ func (s *sqliteStorageService) GetRequests(page, limit int) ([]model.RequestLog, } } + // Set new fields + if apiURL.Valid { + req.APIURL = apiURL.String + } + if provider.Valid { + req.Provider = provider.String + } + + requests = append(requests, req) } @@ -301,7 +335,7 @@ func (s *sqliteStorageService) GetConfig() *config.StorageConfig { func (s *sqliteStorageService) GetAllRequests(modelFilter string) ([]*model.RequestLog, error) { query := ` - SELECT id, timestamp, method, endpoint, headers, body, model, user_agent, content_type, prompt_grade, response, original_model, routed_model + SELECT id, timestamp, method, endpoint, headers, body, model, user_agent, content_type, prompt_grade, response, original_model, routed_model, api_url, provider FROM requests ` args := []interface{}{} @@ -325,6 +359,7 @@ func (s *sqliteStorageService) GetAllRequests(modelFilter string) ([]*model.Requ var req model.RequestLog var headersJSON, bodyJSON string var promptGradeJSON, responseJSON sql.NullString + var apiURL, provider sql.NullString err := rows.Scan( &req.RequestID, @@ -340,6 +375,8 @@ func (s *sqliteStorageService) GetAllRequests(modelFilter string) ([]*model.Requ &responseJSON, &req.OriginalModel, &req.RoutedModel, + &apiURL, + &provider, ) if err != nil { // Error scanning row - skip @@ -373,6 +410,14 @@ func (s *sqliteStorageService) GetAllRequests(modelFilter string) ([]*model.Requ } } + // Set new fields + if apiURL.Valid { + req.APIURL = apiURL.String + } + if provider.Valid { + req.Provider = provider.String + } + requests = append(requests, &req) } diff --git a/web/app/components/RequestDetailContent.tsx b/web/app/components/RequestDetailContent.tsx index 6b291c1..7955bf6 100644 --- a/web/app/components/RequestDetailContent.tsx +++ b/web/app/components/RequestDetailContent.tsx @@ -32,6 +32,8 @@ interface Request { headers: Record; originalModel?: string; routedModel?: string; + apiUrl?: string; + provider?: string; body?: { model?: string; messages?: Array<{ @@ -156,6 +158,19 @@ export default function RequestDetailContent({ request, onGrade }: RequestDetail {getChatCompletionsEndpoint(request.routedModel, request.endpoint)} + {request.apiUrl && ( +
+ API URL: + + {request.apiUrl} + + {request.provider && ( + + {request.provider} + + )} +
+ )}