diff --git a/packages/uipath_langchain_client/CHANGELOG.md b/packages/uipath_langchain_client/CHANGELOG.md index 372b957..12fafbb 100644 --- a/packages/uipath_langchain_client/CHANGELOG.md +++ b/packages/uipath_langchain_client/CHANGELOG.md @@ -2,6 +2,11 @@ All notable changes to `uipath_langchain_client` will be documented in this file. +## [1.10.1] - 2026-05-08 + +### Added +- `agenthub_config` kwarg on `get_chat_model` and `get_embedding_model`. When set, overrides `client_settings.agenthub_config` for that call via `model_copy`, so the caller's settings instance is not mutated. Lets callers (e.g. low-code agent runtimes) pass the per-execution AgentHub config (`agentsruntime`, `agentsplayground`, `agentsevals`, …) without rebuilding settings. + ## [1.10.0] - 2026-04-23 ### Added diff --git a/packages/uipath_langchain_client/src/uipath_langchain_client/__version__.py b/packages/uipath_langchain_client/src/uipath_langchain_client/__version__.py index afec5ef..cce9163 100644 --- a/packages/uipath_langchain_client/src/uipath_langchain_client/__version__.py +++ b/packages/uipath_langchain_client/src/uipath_langchain_client/__version__.py @@ -1,3 +1,3 @@ __title__ = "UiPath LangChain Client" __description__ = "A Python client for interacting with UiPath's LLM services via LangChain." -__version__ = "1.10.0" +__version__ = "1.10.1" diff --git a/packages/uipath_langchain_client/src/uipath_langchain_client/factory.py b/packages/uipath_langchain_client/src/uipath_langchain_client/factory.py index f0e0576..ee18854 100644 --- a/packages/uipath_langchain_client/src/uipath_langchain_client/factory.py +++ b/packages/uipath_langchain_client/src/uipath_langchain_client/factory.py @@ -48,6 +48,7 @@ def get_chat_model( vendor_type: VendorType | str | None = None, api_flavor: ApiFlavor | str | None = None, custom_class: type[UiPathBaseChatModel] | None = None, + agenthub_config: str | None = None, **model_kwargs: Any, ) -> UiPathBaseChatModel: """Factory function to create the appropriate LangChain chat model for a given model name. @@ -70,6 +71,9 @@ def get_chat_model( custom_class: A custom class to use for instantiating the chat model instead of the auto-detected one. Must be a subclass of UiPathBaseChatModel. When provided, the factory skips vendor detection and uses this class directly. + agenthub_config: AgentHub config header value to send with requests. When set, + overrides ``client_settings.agenthub_config`` for this call. Returns a copy + of ``client_settings`` so the caller's instance is not mutated. **model_kwargs: Additional keyword arguments to pass to the model constructor. Returns: @@ -79,6 +83,8 @@ def get_chat_model( ValueError: If the model is not found in available models or vendor is not supported. """ client_settings = client_settings or get_default_client_settings() + if agenthub_config is not None and hasattr(client_settings, "agenthub_config"): + client_settings = client_settings.model_copy(update={"agenthub_config": agenthub_config}) model_info = client_settings.get_model_info( model_name, byo_connection_id=byo_connection_id, @@ -238,6 +244,7 @@ def get_embedding_model( routing_mode: RoutingMode | str = RoutingMode.PASSTHROUGH, vendor_type: VendorType | str | None = None, custom_class: type[UiPathBaseEmbeddings] | None = None, + agenthub_config: str | None = None, **model_kwargs: Any, ) -> UiPathBaseEmbeddings: """Factory function to create the appropriate LangChain embeddings model. @@ -255,6 +262,9 @@ def get_embedding_model( custom_class: A custom class to use for instantiating the embedding model instead of the auto-detected one. Must be a subclass of UiPathBaseEmbeddings. When provided, the factory skips vendor detection and uses this class directly. + agenthub_config: AgentHub config header value to send with requests. When set, + overrides ``client_settings.agenthub_config`` for this call. Returns a copy + of ``client_settings`` so the caller's instance is not mutated. **model_kwargs: Additional arguments passed to the embeddings constructor. Returns: @@ -269,6 +279,8 @@ def get_embedding_model( >>> vectors = embeddings.embed_documents(["Hello world"]) """ client_settings = client_settings or get_default_client_settings() + if agenthub_config is not None and hasattr(client_settings, "agenthub_config"): + client_settings = client_settings.model_copy(update={"agenthub_config": agenthub_config}) model_info = client_settings.get_model_info( model_name, byo_connection_id=byo_connection_id, diff --git a/tests/langchain/features/test_factory_function.py b/tests/langchain/features/test_factory_function.py index 3677157..fa35014 100644 --- a/tests/langchain/features/test_factory_function.py +++ b/tests/langchain/features/test_factory_function.py @@ -128,3 +128,82 @@ def test_openai_chat_respects_discovered_byom_chat_completions( }, ) assert captured["api_flavor"] == ApiFlavor.CHAT_COMPLETIONS + + +class TestFactoryAgentHubConfig: + """The ``agenthub_config`` factory kwarg overrides ``client_settings.agenthub_config`` + via ``model_copy`` so the caller's instance is not mutated.""" + + def _capture_settings( + self, + monkeypatch: pytest.MonkeyPatch, + model_info: dict, + original_settings, + **factory_kwargs, + ): + captured: dict = {} + + class _StubModel: + def __init__(self, **kwargs): + captured.update(kwargs) + + monkeypatch.setattr( + "uipath_langchain_client.clients.openai.chat_models.UiPathAzureChatOpenAI", + _StubModel, + ) + get_chat_model( + model_name=model_info["modelName"], + client_settings=original_settings, + **factory_kwargs, + ) + return captured + + def _make_settings(self, agenthub_config: str | None): + settings = MagicMock() + settings.get_model_info.return_value = { + "modelName": "gpt-4o", + "vendor": "OpenAi", + "apiFlavor": None, + "modelFamily": "OpenAi", + } + settings.agenthub_config = agenthub_config + + def _model_copy(*, update): + copied = MagicMock() + copied.get_model_info.return_value = settings.get_model_info.return_value + copied.agenthub_config = update.get("agenthub_config", agenthub_config) + return copied + + settings.model_copy.side_effect = _model_copy + return settings + + def test_kwarg_overrides_settings_value(self, monkeypatch: pytest.MonkeyPatch): + original = self._make_settings(agenthub_config="agentsruntime") + captured = self._capture_settings( + monkeypatch, + original.get_model_info.return_value, + original, + agenthub_config="agentsplayground", + ) + assert captured["settings"].agenthub_config == "agentsplayground" + original.model_copy.assert_called_once_with(update={"agenthub_config": "agentsplayground"}) + + def test_caller_settings_not_mutated(self, monkeypatch: pytest.MonkeyPatch): + original = self._make_settings(agenthub_config="agentsruntime") + self._capture_settings( + monkeypatch, + original.get_model_info.return_value, + original, + agenthub_config="agentsplayground", + ) + assert original.agenthub_config == "agentsruntime" + + def test_no_kwarg_keeps_settings_value(self, monkeypatch: pytest.MonkeyPatch): + original = self._make_settings(agenthub_config="agentsruntime") + captured = self._capture_settings( + monkeypatch, + original.get_model_info.return_value, + original, + ) + assert captured["settings"] is original + original.model_copy.assert_not_called()