Route requests only with parameters ZDR endpoints declare, and list them

The gpt-5.6 family's zero-data-retention endpoints declare
max_completion_tokens, so sending max_tokens under require_parameters
excluded every ZDR route and returned HTTP 404 for the whole family.
The cap is retired: the strict schema, the finish_reason check and the
64 KiB read cap already bound the response.

The model fields now offer the provider's public ZDR catalog filtered
by the exact conditions completions are routed under (live endpoint,
strict structured outputs), fetched server-side, cached for an hour,
and served at GET /api/models; the inputs stay free text so an unlisted
model remains usable when the catalog is unreachable.
This commit is contained in:
Lars Nolden
2026-09-12 22:33:34 +02:00
parent 588c16ad19
commit ec99434002
13 changed files with 284 additions and 54 deletions
+55
View File
@@ -0,0 +1,55 @@
package classification
import (
"context"
"net/http"
"net/http/httptest"
"testing"
)
// The dropdown must offer only models a fail-closed request can actually
// route to: live ZDR endpoints with strict structured outputs, deduplicated
// across providers, in stable order.
func TestVerifiedModelsFilterDedupeAndOrder(t *testing.T) {
catalog := `{"data":[
{"model_id":"openai/gpt-5.6-luna","model_name":"GPT-5.6 Luna","status":-2,"supported_parameters":["response_format","structured_outputs"]},
{"model_id":"z-ai/glm-5.3","model_name":"GLM 5.3","status":0,"supported_parameters":["response_format","structured_outputs"]},
{"model_id":"anthropic/claude-sonnet-5","model_name":"Claude Sonnet 5","status":0,"supported_parameters":["response_format","structured_outputs"]},
{"model_id":"anthropic/claude-sonnet-5","model_name":"Claude Sonnet 5 (dup)","status":0,"supported_parameters":["response_format","structured_outputs"]},
{"model_id":"amazon/titan","model_name":"Titan","status":0,"supported_parameters":["response_format"]},
{"model_id":"","model_name":"nameless","status":0,"supported_parameters":["response_format","structured_outputs"]}
]}`
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/endpoints/zdr" {
t.Errorf("unexpected path %s", r.URL.Path)
w.WriteHeader(404)
return
}
if r.Header.Get("Authorization") != "" {
t.Error("credential attached to a public catalog request")
}
w.Write([]byte(catalog))
}))
defer server.Close()
c := &Client{BaseURL: server.URL, HTTPClient: server.Client()}
models, err := c.VerifiedModels(context.Background())
if err != nil {
t.Fatal(err)
}
if len(models) != 2 ||
models[0] != (VerifiedModel{ID: "anthropic/claude-sonnet-5", Name: "Claude Sonnet 5"}) ||
models[1] != (VerifiedModel{ID: "z-ai/glm-5.3", Name: "GLM 5.3"}) {
t.Fatalf("wrong verified list: %+v", models)
}
}
func TestVerifiedModelsUnavailableCatalogFailsClosed(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusBadGateway)
}))
defer server.Close()
c := &Client{BaseURL: server.URL, HTTPClient: server.Client()}
if _, err := c.VerifiedModels(context.Background()); err == nil {
t.Fatal("unavailable catalog must not produce an empty verified list")
}
}