-
Notifications
You must be signed in to change notification settings - Fork 1
Add bidirectional Chat Completions <-> Responses shape translation #1012
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
seonghobae
wants to merge
12
commits into
main
Choose a base branch
from
feat/chat-responses-shape-translation
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
e93dfc7
feat(orchestrator): translate between Chat Completions and Responses β¦
seonghobae df18139
fix(chat-responses-shape): close the chat()/stream_chat() translationβ¦
seonghobae 478f8ac
Merge remote-tracking branch 'origin/main' into feat/chat-responses-sβ¦
claude 1296043
fix(planning): renumber ADR 0126 to 0127 to resolve merge collision
claude d179966
fix(planning): renumber ADR 0127 to 0128 (second collision with PR #1β¦
claude 0c0c9d9
fix(chat-responses-shape): close probe()/batch_chat() responses_only gap
claude 2882451
Merge branch 'main' into feat/chat-responses-shape-translation
seonghobae cd57b0b
Merge remote-tracking branch 'origin/main' into feat/chat-responses-sβ¦
claude 456f0d5
fix: apply provider version header to readiness probe
seonghobae 1a95f24
fix(api): preserve chat responses translation fidelity
seonghobae 5f00cb9
test(api): cover translated endpoint boundaries
seonghobae 0a3f7dd
feat(api): stream responses-only providers
seonghobae File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,85 @@ | ||
| """Per-provider required API-version metadata, applied to every outgoing request. | ||
|
|
||
| Some providers require every request to carry an explicit API version -- | ||
| either as a request header (Anthropic's ``anthropic-version``) or as a URL | ||
| query parameter (Azure OpenAI's ``api-version``). This module is the single | ||
| data-driven registry for that requirement: adding a new versioned provider | ||
| means adding one entry to :data:`PROVIDER_API_VERSIONS`, never a new branch | ||
| in the request-dispatch code that actually sends requests | ||
| (``ModelClient._provider_url``/``_send_raw`` and its sibling transport | ||
| methods in ``orchestrator.py``, all of which key their lookup on | ||
| ``ModelAgent.provider_name``). This mirrors the org's existing "provider | ||
| group names are not hardcoded into routing logic" convention -- the same | ||
| shape ``ModelAgent.auth_scheme`` already uses to vary the Authorization | ||
| header's value per provider without a per-provider branch in | ||
| ``format_authorization_header``. | ||
|
|
||
| The registry ships empty: no provider configured in this repo today | ||
| requires a declared version, so an unregistered ``provider_name`` is a | ||
| silent, correct no-op (the omitted argument's own default). See | ||
| ``docs/planning/adrs/0128-openai-chat-responses-shape-translation.md`` for | ||
| why Azure OpenAI and native Anthropic are not populated here yet even | ||
| though they motivated this mechanism. | ||
| """ | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| from dataclasses import dataclass | ||
| from urllib.parse import parse_qsl, urlencode, urlsplit, urlunsplit | ||
|
|
||
|
|
||
| @dataclass(frozen=True) | ||
| class ProviderApiVersion: | ||
| """One provider's required API-version declaration. | ||
|
|
||
| Exactly one of ``header_name``/``query_param_name`` is the expected | ||
| shape for a real provider; nothing here forbids declaring both. ``value`` | ||
| is the exact version string sent verbatim, unmodified by this module. | ||
| """ | ||
|
|
||
| header_name: str = "" | ||
| query_param_name: str = "" | ||
| value: str = "" | ||
|
|
||
| def __post_init__(self) -> None: | ||
| if not self.header_name and not self.query_param_name: | ||
| raise ValueError( | ||
| "ProviderApiVersion needs a header_name or query_param_name" | ||
| ) | ||
| if not self.value.strip(): | ||
| raise ValueError("ProviderApiVersion.value must be non-empty") | ||
|
|
||
|
|
||
| # provider_name -> its required API version declaration. Empty by default; | ||
| # see the module docstring for why. A provider not present here is | ||
| # unaffected -- never a hard error, just no header/query injected. | ||
| PROVIDER_API_VERSIONS: dict[str, ProviderApiVersion] = {} | ||
|
|
||
|
|
||
| def api_version_for(provider_name: str) -> ProviderApiVersion | None: | ||
| """Return the declared API version for a provider name, or ``None``.""" | ||
| return PROVIDER_API_VERSIONS.get(provider_name) if provider_name else None | ||
|
|
||
|
|
||
| def apply_query_param(url: str, version: ProviderApiVersion | None) -> str: | ||
| """Append a provider's declared version query parameter to a request URL. | ||
|
|
||
| Returns ``url`` unchanged when ``version`` is ``None`` or declares no | ||
| query parameter. An existing query parameter of the same name on | ||
| ``url`` is replaced, never duplicated. | ||
| """ | ||
| if version is None or not version.query_param_name: | ||
| return url | ||
| parts = urlsplit(url) | ||
| query = dict(parse_qsl(parts.query, keep_blank_values=True)) | ||
| query[version.query_param_name] = version.value | ||
| return urlunsplit((parts.scheme, parts.netloc, parts.path, urlencode(query), parts.fragment)) | ||
|
|
||
|
|
||
| def apply_header(headers: dict[str, str], version: ProviderApiVersion | None) -> None: | ||
| """Inject a provider's declared version header into an outgoing headers dict. | ||
|
|
||
| A no-op when ``version`` is ``None`` or declares no header name. | ||
| """ | ||
| if version is not None and version.header_name: | ||
| headers[version.header_name] = version.value | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.