Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions proxy/internal/handler/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand All @@ -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 {
Expand Down
2 changes: 2 additions & 0 deletions proxy/internal/model/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
}
Expand Down
4 changes: 4 additions & 0 deletions proxy/internal/provider/anthropic.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
4 changes: 4 additions & 0 deletions proxy/internal/provider/openai.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
3 changes: 3 additions & 0 deletions proxy/internal/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
55 changes: 50 additions & 5 deletions proxy/internal/service/storage_sqlite.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
);

Expand All @@ -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) {
Expand All @@ -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,
Expand All @@ -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 {
Expand All @@ -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 ?
Expand All @@ -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,
Expand All @@ -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
Expand Down Expand Up @@ -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)
}

Expand Down Expand Up @@ -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{}{}
Expand All @@ -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,
Expand All @@ -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
Expand Down Expand Up @@ -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)
}

Expand Down
15 changes: 15 additions & 0 deletions web/app/components/RequestDetailContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ interface Request {
headers: Record<string, string[]>;
originalModel?: string;
routedModel?: string;
apiUrl?: string;
provider?: string;
body?: {
model?: string;
messages?: Array<{
Expand Down Expand Up @@ -156,6 +158,19 @@ export default function RequestDetailContent({ request, onGrade }: RequestDetail
{getChatCompletionsEndpoint(request.routedModel, request.endpoint)}
</code>
</div>
{request.apiUrl && (
<div className="flex items-center space-x-3">
<span className="text-gray-500 font-medium min-w-[80px]">API URL:</span>
<code className="text-green-600 bg-green-50 px-2 py-1 rounded font-mono text-xs border border-green-200 break-all max-w-[300px]">
{request.apiUrl}
</code>
{request.provider && (
<span className="text-xs bg-gray-100 text-gray-700 px-2 py-1 rounded-full border border-gray-300">
{request.provider}
</span>
)}
</div>
)}
</div>
<div className="space-y-3">
<div className="flex items-center space-x-3">
Expand Down