Skip to content

Commit b735b61

Browse files
cosminachoclaude
andauthored
Feat: default OpenAI chat to Responses API where possible (#66)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent f5a09a9 commit b735b61

5 files changed

Lines changed: 95 additions & 2 deletions

File tree

packages/uipath_langchain_client/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22

33
All notable changes to `uipath_langchain_client` will be documented in this file.
44

5+
## [1.9.3] - 2026-04-20
6+
7+
### Changed
8+
- `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. The LiteLLM client still defaults to chat-completions for OpenAI because LiteLLM 1.83.x drops the injected httpx `client` when its acompletion→aresponses bridge fires, which breaks async auth against the UiPath gateway.
9+
510
## [1.9.2] - 2026-04-17
611

712
### Changed
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
__title__ = "UiPath LangChain Client"
22
__description__ = "A Python client for interacting with UiPath's LLM services via LangChain."
3-
__version__ = "1.9.2"
3+
__version__ = "1.9.3"

packages/uipath_langchain_client/src/uipath_langchain_client/factory.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,10 @@ def get_chat_model(
124124

125125
match discovered_vendor_type:
126126
case VendorType.OPENAI:
127+
# OpenAI chat defaults to the Responses API when no flavor is specified.
128+
if api_flavor is None:
129+
api_flavor = ApiFlavor.RESPONSES
130+
127131
if model_family == ModelFamily.OPENAI:
128132
from uipath_langchain_client.clients.openai.chat_models import (
129133
UiPathAzureChatOpenAI,

tests/cassettes.db

816 KB
Binary file not shown.

tests/langchain/features/test_factory_function.py

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1+
from unittest.mock import MagicMock
2+
13
import pytest
24
from uipath_langchain_client.clients.normalized.chat_models import UiPathChat
35
from uipath_langchain_client.clients.normalized.embeddings import UiPathEmbeddings
46
from uipath_langchain_client.factory import get_chat_model, get_embedding_model
57

68
from tests.langchain.conftest import COMPLETION_MODEL_NAMES, EMBEDDING_MODEL_NAMES
7-
from uipath.llm_client.settings import UiPathBaseSettings
9+
from uipath.llm_client.settings import ApiFlavor, UiPathBaseSettings
810

911

1012
@pytest.mark.vcr
@@ -44,3 +46,85 @@ def test_get_embedding_model_custom_class(
4446
)
4547
assert embedding_model is not None
4648
assert isinstance(embedding_model, UiPathEmbeddings)
49+
50+
51+
class TestFactoryDefaultApiFlavor:
52+
"""Unit tests for the default api_flavor picked by the chat factory.
53+
54+
The factory returns concrete LangChain model classes whose construction is
55+
non-trivial. Instead of fully instantiating them, we patch the concrete
56+
classes with a sentinel that captures the kwargs the factory passes.
57+
"""
58+
59+
def _captured_kwargs(
60+
self,
61+
monkeypatch: pytest.MonkeyPatch,
62+
model_info: dict,
63+
**factory_kwargs,
64+
) -> dict:
65+
settings = MagicMock()
66+
settings.get_model_info.return_value = model_info
67+
captured: dict = {}
68+
69+
class _StubModel:
70+
def __init__(self, **kwargs):
71+
captured.update(kwargs)
72+
73+
monkeypatch.setattr(
74+
"uipath_langchain_client.clients.openai.chat_models.UiPathChatOpenAI",
75+
_StubModel,
76+
)
77+
monkeypatch.setattr(
78+
"uipath_langchain_client.clients.openai.chat_models.UiPathAzureChatOpenAI",
79+
_StubModel,
80+
)
81+
get_chat_model(
82+
model_name=model_info["modelName"],
83+
client_settings=settings,
84+
**factory_kwargs,
85+
)
86+
return captured
87+
88+
def test_openai_chat_defaults_to_responses_when_no_flavor_discovered(
89+
self, monkeypatch: pytest.MonkeyPatch
90+
):
91+
"""UiPath-owned OpenAI (apiFlavor=null) should default to the Responses API."""
92+
captured = self._captured_kwargs(
93+
monkeypatch,
94+
{
95+
"modelName": "gpt-4o",
96+
"vendor": "OpenAi",
97+
"apiFlavor": None,
98+
"modelFamily": "OpenAi",
99+
},
100+
)
101+
assert captured["api_flavor"] == ApiFlavor.RESPONSES
102+
103+
def test_openai_chat_respects_user_api_flavor_override(self, monkeypatch: pytest.MonkeyPatch):
104+
"""Explicit api_flavor from the caller still wins over the default."""
105+
captured = self._captured_kwargs(
106+
monkeypatch,
107+
{
108+
"modelName": "gpt-4o",
109+
"vendor": "OpenAi",
110+
"apiFlavor": None,
111+
"modelFamily": "OpenAi",
112+
},
113+
api_flavor=ApiFlavor.CHAT_COMPLETIONS,
114+
)
115+
assert captured["api_flavor"] == ApiFlavor.CHAT_COMPLETIONS
116+
117+
def test_openai_chat_respects_discovered_byom_chat_completions(
118+
self, monkeypatch: pytest.MonkeyPatch
119+
):
120+
"""BYOM-discovered chat-completions still maps to chat-completions."""
121+
captured = self._captured_kwargs(
122+
monkeypatch,
123+
{
124+
"modelName": "custom-gpt",
125+
"vendor": "OpenAi",
126+
"apiFlavor": "OpenAiChatCompletions",
127+
"modelFamily": None,
128+
},
129+
)
130+
assert captured["api_flavor"] == ApiFlavor.CHAT_COMPLETIONS

0 commit comments

Comments
 (0)