Skip to content

feat(core): normalize inference provider-data key by provider id - #6468

Merged
mattf merged 2 commits into
ogx-ai:mainfrom
mattf:feat/provider-id-provider-data-key
Sep 12, 2026
Merged

mattf merged 2 commits into
ogx-ai:mainfrom
mattf:feat/provider-id-provider-data-key

Conversation

@mattf

@mattf mattf commented Sep 1, 2026

Copy link
Copy Markdown
Collaborator

Per-request credentials for inference providers are passed via the X-OGX-Provider-Data header, keyed by each provider's internal provider_data_api_key_field. That field is shaped by the backend type (e.g. vllm_api_token for a remote::vllm provider), so the header leaked the provider type to the client. A user had to know that a provider was backed by vLLM in order to send the correct per-request credential key, which defeats the point of addressing a provider purely by its name. It also made it impossible to run two providers of the same type and give each a distinct credential, since both shared the same type-shaped key.

Resolve the leak by normalizing the key in the inference router. The router maps the client-facing {provider_id}_api_key onto the provider's internal provider_data_api_key_field on the request provider-data context before dispatching. Clients authenticate a named provider without knowing its backend type, and same-type providers can each be keyed by their own provider id (e.g. two remote::vllm providers using providerA_api_key and providerB_api_key).

The mapping lives in the router rather than in the provider implementations so each provider's per-type provider-data validator is left untouched: it still observes the internal key it already knows. The legacy type-shaped key keeps working unchanged for backward compatibility, and when both are present the provider-id key takes precedence. The missing-key error in the OpenAI mixin now references {provider_id}_api_key instead of the backend type, closing the remaining leak.

Scope: the inference router, covering the OpenAI-mixin inference providers. Non-OpenAI remote providers and other router surfaces are follow-up work.

Adds unit tests for the normalization helper, the router call-site, and the error-message branches, plus integration tests exercising the provider-id key against a vllm provider, and documents the convention in the "Using OGX as a Library" guide.

Per-request credentials for inference providers are passed via the
X-OGX-Provider-Data header, keyed by each provider's internal
provider_data_api_key_field. That field is shaped by the backend type
(e.g. vllm_api_token for a remote::vllm provider), so the header leaked
the provider type to the client. A user had to know that a provider was
backed by vLLM in order to send the correct per-request credential key,
which defeats the point of addressing a provider purely by its name. It
also made it impossible to run two providers of the same type and give
each a distinct credential, since both shared the same type-shaped key.

Resolve the leak by normalizing the key in the inference router. The
router maps the client-facing {provider_id}_api_key onto the provider's
internal provider_data_api_key_field on the request provider-data
context before dispatching. Clients authenticate a named provider
without knowing its backend type, and same-type providers can each be
keyed by their own provider id (e.g. two remote::vllm providers using
providerA_api_key and providerB_api_key).

The mapping lives in the router rather than in the provider
implementations so each provider's per-type provider-data validator is
left untouched: it still observes the internal key it already knows.
The legacy type-shaped key keeps working unchanged for backward
compatibility, and when both are present the provider-id key takes
precedence. The missing-key error in the OpenAI mixin now references
{provider_id}_api_key instead of the backend type, closing the
remaining leak.

Scope: the inference router, covering the OpenAI-mixin inference
providers. Non-OpenAI remote providers and other router surfaces are
follow-up work.

Adds unit tests for the normalization helper, the router call-site, and
the error-message branches, plus integration tests exercising the
provider-id key against a vllm provider, and documents the convention
in the "Using OGX as a Library" guide.

Signed-off-by: Matthew Farrellee <matt@cs.wisc.edu>
@mattf

mattf commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator Author

@franciscojavierarceo fyi, this should address a core Chat Playground UI use case

@VANDRANKI VANDRANKI left a comment

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.

Community review, not a merge gate.

Traced _normalize_provider_data_key in src/ogx/core/routers/inference.py and its call site in _get_model_provider, plus the companion error-message change in openai_mixin.py. The core idea is sound: let a client authenticate a specific named provider instance via {provider_id}_api_key without knowing the backend type's internal key name (e.g. vllm_api_token), matched case-insensitively so the client doesn't need to reproduce the provider id's exact casing, while leaving the legacy type-keyed field untouched for existing callers. The refactor of _get_model_provider to a single return path (calling normalize before returning in both the model-lookup and fallback branches) is a clean way to guarantee the normalization always runs regardless of which branch resolved the provider.

The case-insensitive match is careful about one thing I'd have expected to be a bug and isn't: test_normalize_provider_data_key_ignores_legacy_key_casing confirms a differently-cased legacy key (VLLM_API_TOKEN) is not mistaken for the client key, since the match target is built from provider_id, not from impl_key. Good, that's the kind of case that's easy to get wrong.

The tests are solid: they cover no-provider-data, absent client key, absent impl key, client-key-overrides-legacy-value precedence, case variations, and an end-to-end check that the normalized key actually reaches the constructed AsyncOpenAI client's api_key. _get_model_provider_normalizes_provider_data_key and its "without provider data" counterpart also verify the router-level integration, not just the helper in isolation.

One thing I didn't verify: PROVIDER_DATA_VAR is a contextvar re-set mid-request with {**provider_data, impl_key: ...}. That's safe as long as each request runs in its own context (standard for ASGI frameworks), but I didn't trace how this specific framework propagates context across the background-task path mentioned earlier in the same file (_log_background_task_error), so I can't personally rule out a cross-request leak in that specific path — it's probably fine, just outside what I traced.

@mattf

mattf commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator Author

@VANDRANKI thanks. which model and what was your prompt?

@VANDRANKI

Copy link
Copy Markdown
Contributor

To be clear: my review was a code-level trace of _normalize_provider_data_key, _get_model_provider, and the OpenAI mixin error message change, not a live run through the Chat Playground. I did not call a model or send a prompt through this PR, so I do not have one to report. If it helps, I can walk through the specific code path I traced instead, but the live-playground check against a real model would need to come from someone who actually ran it.

@mattf

mattf commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator Author

@VANDRANKI ok, any changes you'd suggest?

@VANDRANKI

Copy link
Copy Markdown
Contributor

No changes from what I traced. My review covered _normalize_provider_data_key, _get_model_provider, and the OpenAI mixin error message, and the logic held up: case-insensitive matching against provider_id (not impl_key) so a differently-cased legacy key isn't mistaken for the client key, the single-return-path refactor guaranteeing normalization runs on both branches, and test coverage for the precedence and end-to-end cases. I didn't find anything worth changing there.

The one thing I flagged as unverified, not as a problem: I didn't trace how PROVIDER_DATA_VAR (a contextvar re-set per request) propagates across the background-task path in _log_background_task_error. That's a "didn't check" note, not a suspected bug, so I wouldn't block on it without a concrete reason to think context leaks across requests there.

If you still want the live Chat Playground check, that needs someone who can actually run a model through it. I only traced code, I didn't execute anything.

@mattf
mattf merged commit 0a4921a into ogx-ai:main Sep 12, 2026
58 checks passed
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.

2 participants