feat: add AI Bridge request logs model filter (#22230)

This pull-request implements a simple filtering logic so that we're able
to pick which model the user actually used when logs were sent to AI
Bridge.

- Add `GET /aibridge/models` API endpoint that returns distinct model
names from AI Bridge interceptions, with pagination and search support
- New `ListAIBridgeModels` SQL query using case-sensitive prefix
matching (`LIKE model || '%'`) to allow B-tree index usage
- Hand-written `ListAuthorizedAIBridgeModels` in `modelqueries.go` for
RBAC authorization filter injection
- `AIBridgeModels` search query parser in searchquery/search.go
(defaults bare terms to the `model` field)
- dbauthz wrappers, dbmetrics, and dbmock implementations for the new
query

<img width="292" height="185" alt="image"
src="https://github.com/user-attachments/assets/134771df-2d26-4c54-acc4-27f58128b351"
/>
This commit is contained in:
Jake Howell
2026-02-26 02:40:45 +11:00
committed by GitHub
parent 1dec1ec4ad
commit d2787df442
20 changed files with 457 additions and 3 deletions
+54
View File
@@ -928,6 +928,60 @@ func (q *sqlQuerier) ListAIBridgeInterceptionsTelemetrySummaries(ctx context.Con
return items, nil
}
const listAIBridgeModels = `-- name: ListAIBridgeModels :many
SELECT
model
FROM
aibridge_interceptions
WHERE
-- Remove inflight interceptions (ones which lack an ended_at value).
aibridge_interceptions.ended_at IS NOT NULL
-- Filter model
AND CASE
WHEN $1::text != '' THEN aibridge_interceptions.model LIKE $1::text || '%'
ELSE true
END
-- We use an ` + "`" + `@authorize_filter` + "`" + ` as we are attempting to list models that are relevant
-- to the user and what they are allowed to see.
-- Authorize Filter clause will be injected below in ListAIBridgeModelsAuthorized
-- @authorize_filter
GROUP BY
model
ORDER BY
model ASC
LIMIT COALESCE(NULLIF($3::integer, 0), 100)
OFFSET $2
`
type ListAIBridgeModelsParams struct {
Model string `db:"model" json:"model"`
Offset int32 `db:"offset_" json:"offset_"`
Limit int32 `db:"limit_" json:"limit_"`
}
func (q *sqlQuerier) ListAIBridgeModels(ctx context.Context, arg ListAIBridgeModelsParams) ([]string, error) {
rows, err := q.db.QueryContext(ctx, listAIBridgeModels, arg.Model, arg.Offset, arg.Limit)
if err != nil {
return nil, err
}
defer rows.Close()
var items []string
for rows.Next() {
var model string
if err := rows.Scan(&model); err != nil {
return nil, err
}
items = append(items, model)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listAIBridgeTokenUsagesByInterceptionIDs = `-- name: ListAIBridgeTokenUsagesByInterceptionIDs :many
SELECT
id, interception_id, provider_response_id, input_tokens, output_tokens, metadata, created_at