mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
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:
@@ -22,7 +22,9 @@ import (
|
||||
|
||||
const (
|
||||
maxListInterceptionsLimit = 1000
|
||||
maxListModelsLimit = 1000
|
||||
defaultListInterceptionsLimit = 100
|
||||
defaultListModelsLimit = 100
|
||||
// aiBridgeRateLimitWindow is the fixed duration for rate limiting AI Bridge
|
||||
// requests. This is hardcoded to keep configuration simple.
|
||||
aiBridgeRateLimitWindow = time.Second
|
||||
@@ -41,6 +43,7 @@ func aibridgeHandler(api *API, middlewares ...func(http.Handler) http.Handler) f
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Use(middlewares...)
|
||||
r.Get("/interceptions", api.aiBridgeListInterceptions)
|
||||
r.Get("/models", api.aiBridgeListModels)
|
||||
})
|
||||
|
||||
// Apply overload protection middleware to the aibridged handler.
|
||||
@@ -173,6 +176,58 @@ func (api *API) aiBridgeListInterceptions(rw http.ResponseWriter, r *http.Reques
|
||||
})
|
||||
}
|
||||
|
||||
// aiBridgeListModels returns all AI Bridge models a user can see.
|
||||
//
|
||||
// @Summary List AI Bridge models
|
||||
// @ID list-ai-bridge-models
|
||||
// @Security CoderSessionToken
|
||||
// @Produce json
|
||||
// @Tags AI Bridge
|
||||
// @Success 200 {array} string
|
||||
// @Router /aibridge/models [get]
|
||||
func (api *API) aiBridgeListModels(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
page, ok := coderd.ParsePagination(rw, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
if page.Limit == 0 {
|
||||
page.Limit = defaultListModelsLimit
|
||||
}
|
||||
|
||||
if page.Limit > maxListModelsLimit || page.Limit < 1 {
|
||||
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
|
||||
Message: "Invalid pagination limit value.",
|
||||
Detail: fmt.Sprintf("Pagination limit must be in range (0, %d]", maxListModelsLimit),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
queryStr := r.URL.Query().Get("q")
|
||||
filter, errs := searchquery.AIBridgeModels(queryStr, page)
|
||||
|
||||
if len(errs) > 0 {
|
||||
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
|
||||
Message: "Invalid AI Bridge models search query.",
|
||||
Validations: errs,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
models, err := api.Database.ListAIBridgeModels(ctx, filter)
|
||||
if err != nil {
|
||||
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Internal error getting AI Bridge models.",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
httpapi.Write(ctx, rw, http.StatusOK, models)
|
||||
}
|
||||
|
||||
func populatedAndConvertAIBridgeInterceptions(ctx context.Context, db database.Store, dbInterceptions []database.ListAIBridgeInterceptionsRow) ([]codersdk.AIBridgeInterception, error) {
|
||||
ids := make([]uuid.UUID, len(dbInterceptions))
|
||||
for i, row := range dbInterceptions {
|
||||
|
||||
Reference in New Issue
Block a user