Skip to content

server: support provider/model selectors and optional provider field#114

Merged
SantiagoDePolonia merged 8 commits intomainfrom
feature/provider-selector-requests
Mar 4, 2026
Merged

server: support provider/model selectors and optional provider field#114
SantiagoDePolonia merged 8 commits intomainfrom
feature/provider-selector-requests

Conversation

@SantiagoDePolonia
Copy link
Copy Markdown
Contributor

@SantiagoDePolonia SantiagoDePolonia commented Mar 1, 2026

Summary

  • add optional request provider field for chat/responses/embeddings
  • support model: "{provider}/{model}" plus conflict validation against provider field
  • normalize selectors in middleware/handlers/router and strip provider before upstream calls
  • update batch item model extraction and Swagger docs

Testing

  • go test ./internal/core ./internal/providers ./internal/server -count=1
  • go test ./... -count=1
  • make swagger

Summary by CodeRabbit

  • New Features

    • API requests for chat, embeddings, and responses now accept a provider field and support provider/model inline (provider/model).
    • End-to-end routing now respects provider selection and normalizes model identifiers across endpoints.
    • Validation returns errors for conflicts between model prefixes and explicit provider fields.
    • Usage logging and model/provider resolution are now consistent across file, batch, and request endpoints.
  • Tests

    • Added tests covering provider selection, model parsing, routing, and validation.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 1, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: c52a2c6f-c679-42c2-8ff4-cee83d93d723

📥 Commits

Reviewing files that changed from the base of the PR and between d6bec63 and 2a7edfc.

📒 Files selected for processing (3)
  • tests/contract/golden_helpers_test.go
  • tests/contract/main_test.go
  • tests/contract/replay_harness_test.go
💤 Files with no reviewable changes (1)
  • tests/contract/main_test.go

📝 Walkthrough

Walkthrough

Added a Provider field to chat, embedding, and responses request types; implemented ModelSelector parsing and qualification; normalized model+provider resolution across validation, handlers, router, and provider forwarding; updated tests and OpenAPI docs to include the provider field.

Changes

Cohort / File(s) Summary
Request Type Definitions
internal/core/types.go, internal/core/responses.go
Added Provider string json:"provider,omitempty"toChatRequest, EmbeddingRequest, and ResponsesRequest; ensured copies (e.g., WithStreaming) preserve Provider`.
Model Selector Implementation
internal/core/model_selector.go, internal/core/model_selector_test.go
New ModelSelector type with ParseModelSelector(model, provider) and QualifiedModel(); unit tests for parsing, conflicts, and errors.
Router and Provider Forwarding
internal/providers/router.go, internal/providers/router_test.go
Added resolveProvider (uses ParseModelSelector); endpoints strip provider before forwarding, set response provider type; tests record upstream requests and assert provider/model stripping and conflict handling.
Server Handlers & Validation
internal/server/handlers.go, internal/server/handlers_test.go, internal/server/model_validation.go, internal/server/model_validation_test.go
Centralized resolveModelSelector and logUsage; handlers normalize model/provider early; validation uses selector-based checks; tests for prefixed models, provider field, and conflicts.
Provider Adapter
internal/providers/responses_adapter.go
Propagates Provider from ResponsesRequest into converted ChatRequest.
Docs / Swagger
cmd/gomodel/docs/docs.go, cmd/gomodel/docs/swagger.json
Added provider string field to generated OpenAPI docs/schemas for ChatRequest, EmbeddingRequest, and ResponsesRequest.
Misc Tests & Helpers
tests/contract/*, tests/contract/main_test.go
Minor test refactors: golden file loader removal, small parsing simplifications; no behavioral changes.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client
    participant Server as Server
    participant Router as Router
    participant Provider as Provider
    Client->>Server: POST /chat { model, provider? }
    Server->>Server: ParseModelSelector(model, provider)
    Server->>Router: resolveProvider(selector)
    Router->>Provider: Forward request (Model unqualified, Provider cleared)
    Provider-->>Router: Response (providerType)
    Router-->>Server: Response (attach providerType)
    Server-->>Client: 200 { provider: providerType, ... }
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

enhancement

Poem

🐇 I hop through models, tidy and spry,
I nibble "openai/gpt" and tuck providers by,
I parse and route, each selector a track,
Forward neat models, strip the extra pack,
Hooray — a rabbit's routing snack!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 15.79% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main changes: adding support for provider/model selectors and an optional provider field across multiple API endpoints.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/provider-selector-requests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@cmd/gomodel/docs/swagger.json`:
- Around line 1778-1780: The swagger.json change added the provider field but
docs.go was not regenerated/committed; run the docs generator used in the
project to regenerate cmd/gomodel/docs/docs.go so it reflects the updated
swagger.json (ensuring the new "provider" schema is included), then add and
commit the regenerated docs.go alongside cmd/gomodel/docs/swagger.json in this
PR so both generated artifacts stay in sync.

In `@internal/providers/router.go`:
- Around line 64-66: Replace the plain fmt.Errorf("no provider found for model:
%s", lookupModel) with a typed invalid-request error so callers get a consistent
HTTP 400; specifically, update the error returned in the provider lookup
branches (the checks using the provider variable and lookupModel) to use
core.NewInvalidRequestError with the same descriptive message, and apply the
same change to the other occurrences in this file (the similar nil-provider
checks around the logic referenced at lines 90, 124, 149, and 169) so all "no
provider found" cases return a core.NewInvalidRequestError instead of
fmt.Errorf.

ℹ️ Review info

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c15d59f and da62fe4.

📒 Files selected for processing (13)
  • cmd/gomodel/docs/docs.go
  • cmd/gomodel/docs/swagger.json
  • internal/core/model_selector.go
  • internal/core/model_selector_test.go
  • internal/core/responses.go
  • internal/core/types.go
  • internal/providers/responses_adapter.go
  • internal/providers/router.go
  • internal/providers/router_test.go
  • internal/server/handlers.go
  • internal/server/handlers_test.go
  • internal/server/model_validation.go
  • internal/server/model_validation_test.go

Comment on lines +1778 to +1780
"provider": {
"type": "string"
},
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# Check if docs.go is in the changed files for this PR
git diff --name-only | grep -E "cmd/gomodel/docs/docs.go" && echo "docs.go found in changes" || echo "docs.go NOT found in changes"

# If docs.go exists and is changed, check for provider field
if [ -f cmd/gomodel/docs/docs.go ]; then
  echo "=== Checking provider field in docs.go ==="
  grep -n '"provider"' cmd/gomodel/docs/docs.go | head -20
fi

# Also verify the swagger.json has the provider fields mentioned in the review
echo "=== Verifying swagger.json provider fields ==="
grep -n '"provider"' cmd/gomodel/docs/swagger.json | head -10

Repository: ENTERPILOT/GOModel

Length of output: 1420


Commit cmd/gomodel/docs/docs.go alongside swagger.json per coding guidelines.

The coding guidelines explicitly require committing both cmd/gomodel/docs/swagger.json and cmd/gomodel/docs/docs.go together when making API changes. While swagger.json is included in this PR with the provider field additions, docs.go is not in the changed files. Regenerate and commit docs.go with this PR to maintain consistency between the generated Swagger files.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/gomodel/docs/swagger.json` around lines 1778 - 1780, The swagger.json
change added the provider field but docs.go was not regenerated/committed; run
the docs generator used in the project to regenerate cmd/gomodel/docs/docs.go so
it reflects the updated swagger.json (ensuring the new "provider" schema is
included), then add and commit the regenerated docs.go alongside
cmd/gomodel/docs/swagger.json in this PR so both generated artifacts stay in
sync.

Comment on lines 64 to 66
if provider == nil {
return nil, fmt.Errorf("no provider found for model: %s", req.Model)
return nil, fmt.Errorf("no provider found for model: %s", lookupModel)
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Consider using typed error for "no provider found".

The "no provider found" error is returned as a plain fmt.Errorf, while the batch operations (e.g., line 211) use core.NewInvalidRequestError for the same scenario. Using a typed error ensures consistent HTTP 400 responses.

♻️ Proposed fix for consistent error typing
 	provider := r.lookup.GetProvider(lookupModel)
 	if provider == nil {
-		return nil, fmt.Errorf("no provider found for model: %s", lookupModel)
+		return nil, core.NewInvalidRequestError(fmt.Sprintf("no provider found for model: %s", lookupModel), nil)
 	}

Apply similar changes to lines 90, 124, 149, and 169.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if provider == nil {
return nil, fmt.Errorf("no provider found for model: %s", req.Model)
return nil, fmt.Errorf("no provider found for model: %s", lookupModel)
}
provider := r.lookup.GetProvider(lookupModel)
if provider == nil {
return nil, core.NewInvalidRequestError(fmt.Sprintf("no provider found for model: %s", lookupModel), nil)
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/providers/router.go` around lines 64 - 66, Replace the plain
fmt.Errorf("no provider found for model: %s", lookupModel) with a typed
invalid-request error so callers get a consistent HTTP 400; specifically, update
the error returned in the provider lookup branches (the checks using the
provider variable and lookupModel) to use core.NewInvalidRequestError with the
same descriptive message, and apply the same change to the other occurrences in
this file (the similar nil-provider checks around the logic referenced at lines
90, 124, 149, and 169) so all "no provider found" cases return a
core.NewInvalidRequestError instead of fmt.Errorf.

SantiagoDePolonia and others added 3 commits March 4, 2026 22:27
Backward compatibility is not a concern, so the version field and
mismatch guard in the model cache are unnecessary complexity.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Restructure ModelCache to group models by provider, eliminating
per-model repetition of provider_type, owned_by, and object fields.
Drop the always-"model" Object field entirely.

Fix splitModelSelector fallthrough: when a qualified lookup fails
(e.g. "meta-llama/Meta-Llama-3-70B" where "meta-llama" is not a
provider name), fall through to unqualified lookup using the original
model string instead of returning nil. Applied to GetProvider,
GetModel, Supports, and GetProviderType.

Harden cache tests with fatal guards before slice indexing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@SantiagoDePolonia SantiagoDePolonia self-assigned this Mar 4, 2026
@SantiagoDePolonia SantiagoDePolonia changed the base branch from feature/provider-instance-cache to main March 4, 2026 22:09
SantiagoDePolonia and others added 3 commits March 4, 2026 23:10
Remove unused loadGoldenFile generic function, apply De Morgan's law
(QF1001), and use strings.TrimPrefix instead of HasPrefix+slice (S1017).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/server/handlers.go`:
- Around line 593-607: GetFileContent's response handler assumes result is
non-nil and does a direct type assertion to *core.FileContentResponse, which
will panic if r.GetFileContent returns (nil, nil); update the anonymous result
handler passed to fileByID to first check if result == nil and return a suitable
HTTP error (e.g., 404 Not Found or an explicit error) before performing the type
assertion, then proceed to cast to *core.FileContentResponse and use
resp.ContentType/resp.Data; reference the GetFileContent function, the fileByID
call, the anonymous result handler, and core.FileContentResponse when making the
change.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: c723c19a-39d0-47b8-8745-6adbc10d52d4

📥 Commits

Reviewing files that changed from the base of the PR and between da62fe4 and d6bec63.

📒 Files selected for processing (2)
  • internal/providers/router.go
  • internal/server/handlers.go

Comment on lines 593 to 607
func (h *Handler) GetFileContent(c echo.Context) error {
nativeRouter, err := h.nativeFileRouter()
if err != nil {
return handleError(c, err)
}

id := strings.TrimSpace(c.Param("id"))
if id == "" {
return handleError(c, core.NewInvalidRequestError("file id is required", nil))
}
if providerType := strings.TrimSpace(c.QueryParam("provider")); providerType != "" {
auditlog.EnrichEntry(c, "file", providerType)
resp, err := nativeRouter.GetFileContent(c.Request().Context(), providerType, id)
if err != nil {
return handleError(c, err)
}
contentType := strings.TrimSpace(resp.ContentType)
if contentType == "" {
contentType = "application/octet-stream"
}
return c.Blob(http.StatusOK, contentType, resp.Data)
}

providers, err := h.fileProviderTypes(c)
if err != nil {
return handleError(c, err)
}
auditlog.EnrichEntry(c, "file", "")

var firstErr error
for _, candidate := range providers {
resp, err := nativeRouter.GetFileContent(c.Request().Context(), candidate, id)
if err == nil {
return h.fileByID(c,
func(r core.NativeFileRoutableProvider, provider, id string) (any, error) {
return r.GetFileContent(c.Request().Context(), provider, id)
},
func(c echo.Context, result any) error {
resp := result.(*core.FileContentResponse)
contentType := strings.TrimSpace(resp.ContentType)
if contentType == "" {
contentType = "application/octet-stream"
}
return c.Blob(http.StatusOK, contentType, resp.Data)
}
if isNotFoundGatewayError(err) || isUnsupportedNativeFilesError(err) {
continue
}
if firstErr == nil {
firstErr = err
}
continue
}
if firstErr != nil {
return handleError(c, firstErr)
}
return handleError(c, core.NewNotFoundError("file not found: "+id))
},
)
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Potential nil pointer dereference if provider returns (nil, nil).

At line 599, the type assertion result.(*core.FileContentResponse) assumes result is non-nil. If r.GetFileContent returns (nil, nil), this will panic.

🛡️ Proposed fix
 		func(c echo.Context, result any) error {
+			if result == nil {
+				return handleError(c, core.NewNotFoundError("file content not available"))
+			}
 			resp := result.(*core.FileContentResponse)
 			contentType := strings.TrimSpace(resp.ContentType)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/server/handlers.go` around lines 593 - 607, GetFileContent's
response handler assumes result is non-nil and does a direct type assertion to
*core.FileContentResponse, which will panic if r.GetFileContent returns (nil,
nil); update the anonymous result handler passed to fileByID to first check if
result == nil and return a suitable HTTP error (e.g., 404 Not Found or an
explicit error) before performing the type assertion, then proceed to cast to
*core.FileContentResponse and use resp.ContentType/resp.Data; reference the
GetFileContent function, the fileByID call, the anonymous result handler, and
core.FileContentResponse when making the change.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant