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
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "uipath-langchain"
version = "0.10.15"
version = "0.10.16"
description = "Python SDK that enables developers to build and deploy LangGraph agents to the UiPath Cloud Platform"
readme = { file = "README.md", content-type = "text/markdown" }
requires-python = ">=3.11"
Expand Down
15 changes: 15 additions & 0 deletions src/uipath_langchain/chat/_settings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import os

from pydantic import model_validator


class _AgentHubConfigDefaultMixin:
@model_validator(mode="after")
def _clear_agenthub_config_default(self):
if os.getenv("UIPATH_AGENTHUB_CONFIG") is None:
client_settings = getattr(self, "client_settings", None)
if client_settings is not None and hasattr(
client_settings, "agenthub_config"
):
client_settings.agenthub_config = None
return self
22 changes: 19 additions & 3 deletions src/uipath_langchain/chat/bedrock.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,42 @@

from pydantic import model_validator
from uipath_langchain_client.clients.bedrock.chat_models import (
UiPathChatAnthropicBedrock,
UiPathChatBedrock,
UiPathChatAnthropicBedrock as _UpstreamUiPathChatAnthropicBedrock,
)
from uipath_langchain_client.clients.bedrock.chat_models import (
UiPathChatBedrock as _UpstreamUiPathChatBedrock,
)
from uipath_langchain_client.clients.bedrock.chat_models import (
UiPathChatBedrockConverse as _UpstreamUiPathChatBedrockConverse,
)

from ._settings import _AgentHubConfigDefaultMixin

DEFAULT_MODEL_NAME = "anthropic.claude-haiku-4-5-20251001-v1:0"


def _default_factory() -> str:
return os.getenv("UIPATH_MODEL_NAME", DEFAULT_MODEL_NAME)


class UiPathChatBedrock(_AgentHubConfigDefaultMixin, _UpstreamUiPathChatBedrock):
pass


class UiPathChatAnthropicBedrock(
_AgentHubConfigDefaultMixin, _UpstreamUiPathChatAnthropicBedrock
):
pass


for _cls in (UiPathChatBedrock, UiPathChatAnthropicBedrock):
_cls.model_fields["model_name"].default_factory = _default_factory
_cls.model_rebuild(force=True)


class UiPathChatBedrockConverse(_UpstreamUiPathChatBedrockConverse):
class UiPathChatBedrockConverse(
_AgentHubConfigDefaultMixin, _UpstreamUiPathChatBedrockConverse
):
@model_validator(mode="before")
@classmethod
def _inject_default_model(cls, values: Any) -> Any:
Expand Down
9 changes: 8 additions & 1 deletion src/uipath_langchain/chat/models.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import os

from uipath_langchain_client.clients.normalized.chat_models import UiPathChat
from uipath_langchain_client.clients.normalized.chat_models import (
UiPathChat as _UpstreamUiPathChat,
)

from ._settings import _AgentHubConfigDefaultMixin
from .openai import UiPathAzureChatOpenAI, UiPathChatOpenAI

DEFAULT_MODEL_NAME = "gpt-4.1-mini-2025-04-14"
Expand All @@ -11,6 +14,10 @@ def _default_factory() -> str:
return os.getenv("UIPATH_MODEL_NAME", DEFAULT_MODEL_NAME)


class UiPathChat(_AgentHubConfigDefaultMixin, _UpstreamUiPathChat):
pass


UiPathChat.model_fields["model_name"].default_factory = _default_factory
UiPathChat.model_rebuild(force=True)

Expand Down
18 changes: 16 additions & 2 deletions src/uipath_langchain/chat/openai.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,31 @@
import os

from uipath_langchain_client.clients.openai.chat_models import (
UiPathAzureChatOpenAI,
UiPathChatOpenAI,
UiPathAzureChatOpenAI as _UpstreamUiPathAzureChatOpenAI,
)
from uipath_langchain_client.clients.openai.chat_models import (
UiPathChatOpenAI as _UpstreamUiPathChatOpenAI,
)

from ._settings import _AgentHubConfigDefaultMixin

DEFAULT_MODEL_NAME = "gpt-4.1-mini-2025-04-14"


def _default_factory() -> str:
return os.getenv("UIPATH_MODEL_NAME", DEFAULT_MODEL_NAME)


class UiPathChatOpenAI(_AgentHubConfigDefaultMixin, _UpstreamUiPathChatOpenAI):
pass


class UiPathAzureChatOpenAI(
_AgentHubConfigDefaultMixin, _UpstreamUiPathAzureChatOpenAI
):
pass


for _cls in (UiPathAzureChatOpenAI, UiPathChatOpenAI):
_cls.model_fields["model_name"].default_factory = _default_factory
_cls.model_rebuild(force=True)
Expand Down
10 changes: 9 additions & 1 deletion src/uipath_langchain/chat/vertex.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,24 @@
import os

from uipath_langchain_client.clients.google.chat_models import (
UiPathChatGoogleGenerativeAI,
UiPathChatGoogleGenerativeAI as _UpstreamUiPathChatGoogleGenerativeAI,
)

from ._settings import _AgentHubConfigDefaultMixin

DEFAULT_MODEL_NAME = "gemini-2.5-flash"


def _default_factory() -> str:
return os.getenv("UIPATH_MODEL_NAME", DEFAULT_MODEL_NAME)


class UiPathChatGoogleGenerativeAI(
_AgentHubConfigDefaultMixin, _UpstreamUiPathChatGoogleGenerativeAI
):
pass


UiPathChatGoogleGenerativeAI.model_fields[
"model_name"
].default_factory = _default_factory
Expand Down
96 changes: 96 additions & 0 deletions tests/chat/test_agenthub_config_default.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
"""Tests for the chat-client agenthub_config default behavior.

Direct construction of UiPathChat / UiPathChatOpenAI / UiPathAzureChatOpenAI /
UiPathChatBedrock / UiPathChatBedrockConverse / UiPathChatAnthropicBedrock /
UiPathChatGoogleGenerativeAI / UiPathChatVertex must default
client_settings.agenthub_config to None.

The upstream classes (used by chat_model_factory for low-code runtime)
must keep the upstream library default of "agentsruntime", proving the
local override does not leak globally onto the upstream class.
"""

import pytest

from uipath_langchain.chat import (
UiPathAzureChatOpenAI,
UiPathChat,
UiPathChatAnthropicBedrock,
UiPathChatBedrock,
UiPathChatBedrockConverse,
UiPathChatGoogleGenerativeAI,
UiPathChatOpenAI,
UiPathChatVertex,
)

_FAKE_JWT = (
"eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9."
"eyJzdWIiOiAidGVzdCIsICJpc3MiOiAidGVzdCJ9."
"signature"
)


@pytest.fixture(autouse=True)
def _platform_env(monkeypatch):
monkeypatch.setenv("UIPATH_ACCESS_TOKEN", _FAKE_JWT)
monkeypatch.setenv("UIPATH_URL", "https://example.com/org/tenant/orchestrator_/")
monkeypatch.setenv("UIPATH_TENANT_ID", "tenant")
monkeypatch.setenv("UIPATH_ORGANIZATION_ID", "org")
monkeypatch.delenv("UIPATH_AGENTHUB_CONFIG", raising=False)
monkeypatch.delenv("UIPATH_MODEL_NAME", raising=False)


_DIRECT_CTOR_CASES = [
UiPathChat,
UiPathAzureChatOpenAI,
UiPathChatOpenAI,
UiPathChatBedrock,
UiPathChatBedrockConverse,
UiPathChatAnthropicBedrock,
UiPathChatGoogleGenerativeAI,
UiPathChatVertex,
]


@pytest.mark.parametrize("cls", _DIRECT_CTOR_CASES)
class TestDirectConstructorAgentHubConfig:
def test_default_is_none(self, cls):
llm = cls()
assert llm.client_settings.agenthub_config is None

def test_env_var_is_honored(self, cls, monkeypatch):
monkeypatch.setenv("UIPATH_AGENTHUB_CONFIG", "agentsplayground")
llm = cls()
assert llm.client_settings.agenthub_config == "agentsplayground"


_UPSTREAM_CASES = [
"uipath_langchain_client.clients.normalized.chat_models:UiPathChat",
"uipath_langchain_client.clients.openai.chat_models:UiPathChatOpenAI",
"uipath_langchain_client.clients.openai.chat_models:UiPathAzureChatOpenAI",
"uipath_langchain_client.clients.bedrock.chat_models:UiPathChatBedrock",
"uipath_langchain_client.clients.bedrock.chat_models:UiPathChatBedrockConverse",
"uipath_langchain_client.clients.bedrock.chat_models:UiPathChatAnthropicBedrock",
"uipath_langchain_client.clients.google.chat_models:UiPathChatGoogleGenerativeAI",
]


@pytest.mark.parametrize("upstream_path", _UPSTREAM_CASES)
class TestUpstreamAgentHubConfigUntouched:
"""Deployed runtimes go through chat_model_factory, which instantiates the
upstream classes directly. Those must keep the upstream library default of
'agentsruntime'."""

def _resolve(self, upstream_path: str):
import importlib

module_name, attr = upstream_path.split(":")
return getattr(importlib.import_module(module_name), attr)

def test_upstream_keeps_agentsruntime_default(self, upstream_path):
# make sure model rebinds are not breaking the agenthub_config
import uipath_langchain.chat # noqa: F401

upstream_cls = self._resolve(upstream_path)
llm = upstream_cls(model="gpt-4.1-mini-2025-04-14")
assert llm.client_settings.agenthub_config == "agentsruntime"
4 changes: 2 additions & 2 deletions tests/chat/test_default_model_name.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,8 +121,8 @@ def test_fireworks_raises_without_model(self):


class TestReExportedClassIdentity:
def test_uipath_chat_is_upstream_class(self):
assert UiPathChat is _UpstreamUiPathChat
def test_uipath_chat_is_subclass_of_upstream(self):
assert issubclass(UiPathChat, _UpstreamUiPathChat)

def test_uipath_chat_vertex_alias_matches_google(self):
assert UiPathChatVertex is UiPathChatGoogleGenerativeAI
Expand Down
2 changes: 1 addition & 1 deletion uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading