Skip to content

Feat: default OpenAI chat to Responses API where possible#66

Merged
cosminacho merged 4 commits into
mainfrom
feat/default-openai-responses
Apr 20, 2026
Merged

Feat: default OpenAI chat to Responses API where possible#66
cosminacho merged 4 commits into
mainfrom
feat/default-openai-responses

Conversation

@cosminacho

Copy link
Copy Markdown
Collaborator

Summary

  • Prefer the OpenAI Responses API (ApiFlavor.RESPONSES) for OpenAI chat whenever discovery lets us. Two touch-points:
    • Core: UiPathBaseSettings.get_model_info() tie-breaks multiple OpenAI matches toward the responses / OpenAiResponses entry when discovery returns both flavors.
    • LangChain: get_chat_model() defaults api_flavor=RESPONSES for OpenAI when neither discovery nor the caller specified one (handled inside the OpenAI match arm).
  • LiteLLM client's single-entry OpenAI fallback stays on chat-completions on purpose — the same client serves both completions and embeddings, and the responses/ model prefix in _resolve_litellm_model would break embedding calls on OpenAI embedding models that discover with apiFlavor=null. The new get_model_info tie-break still benefits this client when the backend advertises both flavors explicitly.
  • Both packages bumped to 1.9.3; langchain dep pinned to uipath-llm-client>=1.9.3.

Packages affected

  • uipath-llm-client (core) — get_model_info tie-break, base.py imports.
  • uipath-langchain-client — factory default for OpenAI chat.

Test plan

  • ruff check, ruff format --check, pyright pass
  • pytest tests — 1521 passed, 736 skipped, 9 xpassed
  • New tests cover:
    • get_model_info prefers Responses over Chat Completions when discovery returns both (BYOM and routing-form strings)
    • get_model_info tie-break does not fire for non-OpenAI vendors
    • get_chat_model defaults api_flavor=RESPONSES for OpenAI UiPath-owned (apiFlavor=null)
    • Caller's explicit api_flavor still wins over the default
    • BYOM-discovered OpenAiChatCompletions still routes as chat-completions

🤖 Generated with Claude Code

Prefer the OpenAI Responses API (`ApiFlavor.RESPONSES`) when discovery
signals both flavors are available, and when the langchain factory has
no other flavor to route with:

- `UiPathBaseSettings.get_model_info()`: when multiple OpenAI entries
  remain after existing filtering, prefer the `responses` /
  `OpenAiResponses` flavor over `chat-completions`.
- `get_chat_model()` (langchain): inside the OpenAI match arm, default
  `api_flavor=RESPONSES` when discovery doesn't specify one and the
  caller didn't either.

The LiteLLM client keeps `chat-completions` as its fallback for the
single-entry / `apiFlavor=null` case. It serves both completion and
embedding paths from the same instance, and the `responses/` model
prefix in `_resolve_litellm_model` would break embedding calls if
Responses were the default on a UiPath-owned OpenAI embedding model
that reports `apiFlavor=null` at discovery. The discovery-level
tie-break in `get_model_info` still benefits that client whenever
the backend advertises both flavors explicitly.

Bumps both packages to 1.9.3.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Revises the PR in two directions:

- `UiPathLiteLLM` now matches the langchain factory: when no `api_flavor`
  is discovered or supplied for an OpenAI model, the client defaults to
  `ApiFlavor.RESPONSES`. Existing `openai_*_client` test fixtures
  that rely on previously-recorded chat-completions cassettes now pin
  `api_flavor=CHAT_COMPLETIONS` explicitly; the
  `openai_responses_client` fixture and `OPENAI_RESPONSES_CONFIGS`
  continue to exercise Responses against their own cassettes.
- To keep OpenAI embeddings working under the new default, the LiteLLM
  client's `embedding()` / `aembedding()` now pass the raw
  `self._model_name` (no `responses/` / `invoke/` / `converse/` route
  prefix) — those prefixes are completion-only.
- Drops the `get_model_info()` responses tie-break that was part of
  the first commit. The LiteLLM default + langchain factory default
  together cover the user-visible behavior; the tie-break was
  redundant and narrowed the data model for a case already handled
  by the defaults.

Related test updates:
- `test_openai_defaults_to_responses` replaces
  `test_openai_defaults_to_chat_completions`; the litellm-model-name
  assertion now expects the `responses/` prefix on the default path.
- Removes the three `get_model_info` tie-break tests added earlier.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Regenerated by the local test run after switching `UiPathLiteLLM`'s
OpenAI default to the Responses API.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
LiteLLM 1.83.x drops the injected httpx client when its
acompletion→aresponses bridge fires: in
``completion_extras/litellm_responses_transformation/handler.py`` the
async ``acompletion`` path calls ``transform_request`` without
``client=kwargs.get("client")`` and then ``aresponses(**request_data)``
without re-threading it, so by the time ``response_api_handler`` runs
``kwargs.get("client")`` is ``None``. LiteLLM then builds a fresh
``httpx.AsyncClient`` with no base URL, no auth, and sends
``Authorization: Bearer PLACEHOLDER`` — which the UiPath gateway
rejects with ``Unable to extract claim sub_type from token``.

The sync LiteLLM Responses path doesn't hit this bug (the sync bridge
does forward ``client``), but routing async OpenAI callers through
Responses unconditionally regresses every async user until the
upstream fix lands.

Keeping the Responses default only in the langchain factory, which
goes through ``UiPathChatOpenAI`` / ``UiPathAzureChatOpenAI`` (the
OpenAI SDK, not LiteLLM), where sync and async both work. The
LiteLLM client keeps its existing chat-completions fallback and
its existing integration cassettes.

Versioning: langchain-only change, so only
``uipath_langchain_client`` bumps to 1.9.3. Core stays at 1.9.2.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@cosminacho cosminacho merged commit b735b61 into main Apr 20, 2026
8 checks passed
@cosminacho cosminacho deleted the feat/default-openai-responses branch April 20, 2026 11:30
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