Skip to content

Commit 6c98bb5

Browse files
cosminachoclaude
andcommitted
Force LiteLLM OpenAI default to Responses; drop discovery tie-break
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>
1 parent abaabc2 commit 6c98bb5

8 files changed

Lines changed: 35 additions & 109 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ All notable changes to `uipath_llm_client` (core package) will be documented in
55
## [1.9.3] - 2026-04-20
66

77
### Changed
8-
- `UiPathBaseSettings.get_model_info()` now prefers the Responses API when discovery returns multiple OpenAI entries for the same model (both `chat-completions` and `responses` flavors present). The LiteLLM client keeps its `chat-completions` fallback for the single-entry / `apiFlavor=null` case because the same client serves embedding requests.
8+
- `UiPathLiteLLM` now defaults to the OpenAI Responses API (`ApiFlavor.RESPONSES`) when discovery does not specify a flavor. Explicit `api_flavor=` and BYOM-discovered flavors still take precedence.
9+
- `UiPathLiteLLM.embedding()` / `aembedding()` use the raw model name instead of `_litellm_model`. The `responses/` / `invoke/` / `converse/` route prefixes are completion-only, so this keeps OpenAI embeddings working when the client defaults to Responses on the completions side.
910

1011
## [1.9.2] - 2026-04-17
1112

packages/uipath_langchain_client/CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ All notable changes to `uipath_langchain_client` will be documented in this file
66

77
### Changed
88
- `get_chat_model()` now defaults to the OpenAI Responses API (`ApiFlavor.RESPONSES`) when discovery does not specify a flavor for an OpenAI chat model. Explicit `api_flavor=` on the call and BYOM-discovered flavors still take precedence.
9-
- Minimum `uipath-llm-client` bumped to 1.9.3 for the `get_model_info()` Responses-preference tie-break.
9+
- Minimum `uipath-llm-client` bumped to 1.9.3 to align with the matching LiteLLM default.
1010

1111
## [1.9.2] - 2026-04-17
1212

src/uipath/llm_client/clients/litellm/client.py

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -213,13 +213,9 @@ def _discover_and_build_api_config(
213213
else:
214214
resolved_flavor = discovered_flavor
215215

216-
# OpenAI defaults to chat-completions when no flavor is discovered.
217-
# RESPONSES is not a safe default here: this client serves both
218-
# completions and embeddings, and the ``responses/`` model prefix in
219-
# ``_resolve_litellm_model`` would break embedding calls on OpenAI
220-
# embedding models that discover with ``apiFlavor=null``.
216+
# OpenAI defaults to responses when no flavor is discovered
221217
if resolved_flavor is None and resolved_vendor in ("openai", "azure"):
222-
resolved_flavor = ApiFlavor.CHAT_COMPLETIONS
218+
resolved_flavor = ApiFlavor.RESPONSES
223219

224220
# Claude detection: modelFamily from discovery, or name heuristic for BYOM
225221
# (BYOM discovery does not expose modelFamily).
@@ -270,7 +266,12 @@ def _resolve_llm_provider(self) -> str:
270266
return _VENDOR_TO_LITELLM.get(vendor, vendor)
271267

272268
def _resolve_litellm_model(self) -> str:
273-
"""Build the model name litellm expects, with route prefixes where needed."""
269+
"""Build the completions model name litellm expects, with route prefixes where needed.
270+
271+
Only applied on the completions path — ``embedding()`` uses the raw model
272+
name because the ``responses/`` / ``invoke/`` / ``converse/`` prefixes are
273+
completion-only route hints.
274+
"""
274275
model = self._model_name
275276
flavor = str(self._api_config.api_flavor) if self._api_config.api_flavor else None
276277

@@ -534,7 +535,7 @@ def embedding(
534535
**kwargs: Any,
535536
) -> EmbeddingResponse:
536537
return litellm.embedding( # type: ignore[return-value]
537-
model=self._litellm_model,
538+
model=self._model_name,
538539
input=input,
539540
custom_llm_provider=self._embedding_llm_provider,
540541
api_key="PLACEHOLDER",
@@ -560,7 +561,7 @@ async def aembedding(
560561
**kwargs: Any,
561562
) -> EmbeddingResponse:
562563
return await litellm.aembedding(
563-
model=self._litellm_model,
564+
model=self._model_name,
564565
input=input,
565566
custom_llm_provider=self._embedding_llm_provider,
566567
api_key="PLACEHOLDER",

src/uipath/llm_client/settings/base.py

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,7 @@
1313
from pydantic import BaseModel, model_validator
1414
from pydantic_settings import BaseSettings, SettingsConfigDict
1515

16-
from uipath.llm_client.settings.constants import (
17-
ApiFlavor,
18-
ApiType,
19-
ByomApiFlavor,
20-
RoutingMode,
21-
VendorType,
22-
)
16+
from uipath.llm_client.settings.constants import ApiFlavor, ApiType, RoutingMode, VendorType
2317

2418

2519
class UiPathAPIConfig(BaseModel):
@@ -244,19 +238,6 @@ def get_model_info(
244238
)
245239
]
246240

247-
# When multiple OpenAI entries remain (both chat-completions and responses
248-
# flavors discovered), prefer the Responses API.
249-
if len(matching_models) > 1:
250-
vendor = str(matching_models[0].get("vendor", "")).lower()
251-
if vendor == VendorType.OPENAI:
252-
responses_matches = [
253-
m
254-
for m in matching_models
255-
if m.get("apiFlavor") in (ApiFlavor.RESPONSES, ByomApiFlavor.OPENAI_RESPONSES)
256-
]
257-
if responses_matches:
258-
matching_models = responses_matches
259-
260241
if not matching_models:
261242
raise ValueError(
262243
f"Model {model_name} not found. "

tests/core/clients/litellm/test_integration.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,17 +21,29 @@
2121

2222
@pytest.fixture
2323
def openai_gpt4o_client(client_settings: UiPathBaseSettings) -> UiPathLiteLLM:
24-
return UiPathLiteLLM(model_name="gpt-4o-2024-11-20", client_settings=client_settings)
24+
return UiPathLiteLLM(
25+
model_name="gpt-4o-2024-11-20",
26+
client_settings=client_settings,
27+
api_flavor=ApiFlavor.CHAT_COMPLETIONS,
28+
)
2529

2630

2731
@pytest.fixture
2832
def openai_client(client_settings: UiPathBaseSettings) -> UiPathLiteLLM:
29-
return UiPathLiteLLM(model_name="gpt-5.2-2025-12-11", client_settings=client_settings)
33+
return UiPathLiteLLM(
34+
model_name="gpt-5.2-2025-12-11",
35+
client_settings=client_settings,
36+
api_flavor=ApiFlavor.CHAT_COMPLETIONS,
37+
)
3038

3139

3240
@pytest.fixture
3341
def openai_gpt54_client(client_settings: UiPathBaseSettings) -> UiPathLiteLLM:
34-
return UiPathLiteLLM(model_name="gpt-5.4-2026-03-05", client_settings=client_settings)
42+
return UiPathLiteLLM(
43+
model_name="gpt-5.4-2026-03-05",
44+
client_settings=client_settings,
45+
api_flavor=ApiFlavor.CHAT_COMPLETIONS,
46+
)
3547

3648

3749
@pytest.fixture

tests/core/clients/litellm/test_unit.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -148,9 +148,10 @@ def _make_client(self, model_data: dict, **kwargs) -> UiPathLiteLLM:
148148
**kwargs,
149149
)
150150

151-
def test_openai_model_name_unchanged(self):
151+
def test_openai_model_name_has_responses_prefix(self):
152+
"""OpenAI defaults to responses, so the litellm model carries the responses/ prefix."""
152153
client = self._make_client(_OPENAI_MODEL)
153-
assert client._litellm_model == "gpt-5.2-2025-12-11"
154+
assert client._litellm_model == "responses/gpt-5.2-2025-12-11"
154155

155156
def test_gemini_model_name_unchanged(self):
156157
client = self._make_client(_GEMINI_MODEL)
@@ -187,9 +188,9 @@ def _make_client(self, model_data: dict, **kwargs) -> UiPathLiteLLM:
187188
**kwargs,
188189
)
189190

190-
def test_openai_defaults_to_chat_completions(self):
191+
def test_openai_defaults_to_responses(self):
191192
client = self._make_client(_OPENAI_MODEL)
192-
assert client._api_config.api_flavor == ApiFlavor.CHAT_COMPLETIONS
193+
assert client._api_config.api_flavor == ApiFlavor.RESPONSES
193194
assert client._api_config.vendor_type == "openai"
194195
assert client._api_config.routing_mode == RoutingMode.PASSTHROUGH
195196

tests/core/features/settings/test_llmgateway.py

Lines changed: 0 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -619,73 +619,3 @@ def test_skips_validate_byo_model_for_uipath_owned(self, llmgw_env_vars):
619619
with patch.object(settings, "validate_byo_model") as mock_validate:
620620
settings.get_model_info("claude-3-opus")
621621
mock_validate.assert_not_called()
622-
623-
def test_prefers_responses_when_both_openai_flavors_available(self, llmgw_env_vars):
624-
"""When OpenAI discovery returns both chat-completions and responses entries,
625-
get_model_info returns the responses one."""
626-
models = [
627-
{
628-
"modelName": "custom-gpt",
629-
"vendor": "OpenAi",
630-
"apiFlavor": "OpenAiChatCompletions",
631-
"modelSubscriptionType": "BYO",
632-
"byomDetails": {
633-
"integrationServiceConnectionId": "conn-1",
634-
"availableOperationCodes": ["op1"],
635-
},
636-
},
637-
{
638-
"modelName": "custom-gpt",
639-
"vendor": "OpenAi",
640-
"apiFlavor": "OpenAiResponses",
641-
"modelSubscriptionType": "BYO",
642-
"byomDetails": {
643-
"integrationServiceConnectionId": "conn-1",
644-
"availableOperationCodes": ["op1"],
645-
},
646-
},
647-
]
648-
settings = self._make_settings(llmgw_env_vars, models=models)
649-
info = settings.get_model_info("custom-gpt", byo_connection_id="conn-1")
650-
assert info["apiFlavor"] == "OpenAiResponses"
651-
652-
def test_prefers_responses_with_plain_apiflavor_strings(self, llmgw_env_vars):
653-
"""Tie-break also recognises the routing-form apiFlavor values."""
654-
models = [
655-
{
656-
"modelName": "gpt-x",
657-
"vendor": "OpenAi",
658-
"apiFlavor": "chat-completions",
659-
"modelSubscriptionType": "UiPathOwned",
660-
},
661-
{
662-
"modelName": "gpt-x",
663-
"vendor": "OpenAi",
664-
"apiFlavor": "responses",
665-
"modelSubscriptionType": "UiPathOwned",
666-
},
667-
]
668-
settings = self._make_settings(llmgw_env_vars, models=models)
669-
info = settings.get_model_info("gpt-x")
670-
assert info["apiFlavor"] == "responses"
671-
672-
def test_no_responses_preference_for_non_openai(self, llmgw_env_vars):
673-
"""The responses preference should not fire for non-OpenAI vendors."""
674-
models = [
675-
{
676-
"modelName": "claude-x",
677-
"vendor": "Anthropic",
678-
"apiFlavor": "anthropic-claude",
679-
"modelSubscriptionType": "UiPathOwned",
680-
},
681-
{
682-
"modelName": "claude-x",
683-
"vendor": "Anthropic",
684-
"apiFlavor": "converse",
685-
"modelSubscriptionType": "UiPathOwned",
686-
},
687-
]
688-
settings = self._make_settings(llmgw_env_vars, models=models)
689-
info = settings.get_model_info("claude-x")
690-
# First entry wins (no preference logic for Anthropic)
691-
assert info["apiFlavor"] == "anthropic-claude"

tests/langchain/clients/litellm/conftest.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
from uipath.llm_client.settings.constants import ApiFlavor
1818

1919
OPENAI_CONFIGS = [
20-
{"model_class": UiPathChatLiteLLM},
20+
{"model_class": UiPathChatLiteLLM, "model_kwargs": {"api_flavor": ApiFlavor.CHAT_COMPLETIONS}},
2121
]
2222

2323
OPENAI_RESPONSES_CONFIGS = [

0 commit comments

Comments
 (0)