Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions packages/uipath_langchain_client/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

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

## [1.9.3] - 2026-04-20

### Changed
- `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.

## [1.9.2] - 2026-04-17

### Changed
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
__title__ = "UiPath LangChain Client"
__description__ = "A Python client for interacting with UiPath's LLM services via LangChain."
__version__ = "1.9.2"
__version__ = "1.9.3"
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,10 @@ def get_chat_model(

match discovered_vendor_type:
case VendorType.OPENAI:
# OpenAI chat defaults to the Responses API when no flavor is specified.
if api_flavor is None:
api_flavor = ApiFlavor.RESPONSES

if model_family == ModelFamily.OPENAI:
from uipath_langchain_client.clients.openai.chat_models import (
UiPathAzureChatOpenAI,
Expand Down
Binary file modified tests/cassettes.db
Binary file not shown.
86 changes: 85 additions & 1 deletion tests/langchain/features/test_factory_function.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
from unittest.mock import MagicMock

import pytest
from uipath_langchain_client.clients.normalized.chat_models import UiPathChat
from uipath_langchain_client.clients.normalized.embeddings import UiPathEmbeddings
from uipath_langchain_client.factory import get_chat_model, get_embedding_model

from tests.langchain.conftest import COMPLETION_MODEL_NAMES, EMBEDDING_MODEL_NAMES
from uipath.llm_client.settings import UiPathBaseSettings
from uipath.llm_client.settings import ApiFlavor, UiPathBaseSettings


@pytest.mark.vcr
Expand Down Expand Up @@ -44,3 +46,85 @@ def test_get_embedding_model_custom_class(
)
assert embedding_model is not None
assert isinstance(embedding_model, UiPathEmbeddings)


class TestFactoryDefaultApiFlavor:
"""Unit tests for the default api_flavor picked by the chat factory.

The factory returns concrete LangChain model classes whose construction is
non-trivial. Instead of fully instantiating them, we patch the concrete
classes with a sentinel that captures the kwargs the factory passes.
"""

def _captured_kwargs(
self,
monkeypatch: pytest.MonkeyPatch,
model_info: dict,
**factory_kwargs,
) -> dict:
settings = MagicMock()
settings.get_model_info.return_value = model_info
captured: dict = {}

class _StubModel:
def __init__(self, **kwargs):
captured.update(kwargs)

monkeypatch.setattr(
"uipath_langchain_client.clients.openai.chat_models.UiPathChatOpenAI",
_StubModel,
)
monkeypatch.setattr(
"uipath_langchain_client.clients.openai.chat_models.UiPathAzureChatOpenAI",
_StubModel,
)
get_chat_model(
model_name=model_info["modelName"],
client_settings=settings,
**factory_kwargs,
)
return captured

def test_openai_chat_defaults_to_responses_when_no_flavor_discovered(
self, monkeypatch: pytest.MonkeyPatch
):
"""UiPath-owned OpenAI (apiFlavor=null) should default to the Responses API."""
captured = self._captured_kwargs(
monkeypatch,
{
"modelName": "gpt-4o",
"vendor": "OpenAi",
"apiFlavor": None,
"modelFamily": "OpenAi",
},
)
assert captured["api_flavor"] == ApiFlavor.RESPONSES

def test_openai_chat_respects_user_api_flavor_override(self, monkeypatch: pytest.MonkeyPatch):
"""Explicit api_flavor from the caller still wins over the default."""
captured = self._captured_kwargs(
monkeypatch,
{
"modelName": "gpt-4o",
"vendor": "OpenAi",
"apiFlavor": None,
"modelFamily": "OpenAi",
},
api_flavor=ApiFlavor.CHAT_COMPLETIONS,
)
assert captured["api_flavor"] == ApiFlavor.CHAT_COMPLETIONS

def test_openai_chat_respects_discovered_byom_chat_completions(
self, monkeypatch: pytest.MonkeyPatch
):
"""BYOM-discovered chat-completions still maps to chat-completions."""
captured = self._captured_kwargs(
monkeypatch,
{
"modelName": "custom-gpt",
"vendor": "OpenAi",
"apiFlavor": "OpenAiChatCompletions",
"modelFamily": None,
},
)
assert captured["api_flavor"] == ApiFlavor.CHAT_COMPLETIONS