From dc78050609b80d5822d2be28d9747a6023e0c322 Mon Sep 17 00:00:00 2001 From: Cosmin Maria Date: Thu, 2 Apr 2026 01:15:06 +0300 Subject: [PATCH 01/10] fix: code review bugs + bump both packages to 1.6.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bug fixes (critical): - callbacks.py: add ABC to UiPathDynamicHeadersCallback so @abstractmethod is enforced — previously the class could be instantiated without implementing get_headers(), silently deferring the error to runtime - clients/anthropic/chat_models.py: make vendor_type a required field (no default) and seed api_config with VendorType.VERTEXAI as placeholder; the old default of VendorType.ANTHROPIC caused the model validator to immediately raise ValueError, making the class impossible to construct directly; also removed dead VendorType.ANTHROPIC / VendorType.AZURE branches from _anthropic_client and _async_anthropic_client that could never be reached, and unused imports - clients/bedrock/utils.py: fix json.loads(kwargs.get('body', {})) — passing a dict as default caused TypeError since json.loads requires str/bytes; changed default to '{}' string; also corrected converse() and converse_stream() system parameter type annotation from str|None to list[dict[str,Any]]|None to match the list[dict] value langchain-aws actually passes - clients/normalized/chat_models.py: add 'stream': True to request body in _uipath_stream and _uipath_astream — without it the server returns a regular non-streaming JSON response instead of SSE; fix fragile SSE prefix stripping from chunk.split('data:')[1] (splits on all occurrences) to chunk[len('data:'):].strip(); fix _generate_chunk to set text=content or '' instead of text=original_message (raw JSON wire data) - factory.py: replace bare model_info['vendor'] key access in get_embedding_model with model_info.get('vendor') + explicit ValueError, matching the safe pattern already used in get_chat_model Bug fixes (minor): - base_client.py: correct return type annotations on _astream, _uipath_astream, and uipath_astream from AsyncIterator to AsyncGenerator (these are async generator functions that contain yield, not plain iterables) - demo.py: replace unsafe eval(expression) with an AST-based arithmetic-only evaluator to prevent arbitrary code execution via LLM-generated input Version bumps: - packages/uipath_langchain_client: 1.5.10 -> 1.6.0 - src/uipath/llm_client: 1.5.10 -> 1.6.0 --- packages/uipath_langchain_client/demo.py | 14 ++++- .../uipath_langchain_client/__version__.py | 2 +- .../uipath_langchain_client/base_client.py | 8 +-- .../src/uipath_langchain_client/callbacks.py | 4 +- .../clients/anthropic/chat_models.py | 60 ++++++------------- .../clients/bedrock/utils.py | 16 +++-- .../clients/normalized/chat_models.py | 14 +++-- .../src/uipath_langchain_client/factory.py | 8 ++- src/uipath/llm_client/__version__.py | 2 +- 9 files changed, 65 insertions(+), 63 deletions(-) diff --git a/packages/uipath_langchain_client/demo.py b/packages/uipath_langchain_client/demo.py index be792d9..86c8688 100644 --- a/packages/uipath_langchain_client/demo.py +++ b/packages/uipath_langchain_client/demo.py @@ -136,8 +136,20 @@ def calculate(expression: str) -> str: Args: expression: A mathematical expression to evaluate (e.g., "2 + 2"). """ + import ast try: - result = eval(expression) + # Restrict to a safe subset: only literals and basic arithmetic operators. + # This prevents arbitrary code execution via eval(). + tree = ast.parse(expression, mode="eval") + allowed_node_types = ( + ast.Expression, ast.BinOp, ast.UnaryOp, ast.Constant, + ast.Add, ast.Sub, ast.Mult, ast.Div, ast.FloorDiv, + ast.Mod, ast.Pow, ast.USub, ast.UAdd, + ) + for node in ast.walk(tree): + if not isinstance(node, allowed_node_types): + return f"Error: unsupported operation in expression" + result = eval(compile(tree, "", "eval"), {"__builtins__": {}}) return str(result) except Exception as e: return f"Error: {e}" 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 292aefb..5149f6e 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.5.10" +__version__ = "1.6.0" diff --git a/packages/uipath_langchain_client/src/uipath_langchain_client/base_client.py b/packages/uipath_langchain_client/src/uipath_langchain_client/base_client.py index 2999441..dc6bd17 100644 --- a/packages/uipath_langchain_client/src/uipath_langchain_client/base_client.py +++ b/packages/uipath_langchain_client/src/uipath_langchain_client/base_client.py @@ -25,7 +25,7 @@ import logging from abc import ABC -from collections.abc import AsyncIterator, Iterator, Mapping, Sequence +from collections.abc import AsyncGenerator, AsyncIterator, Iterator, Mapping, Sequence from functools import cached_property from typing import Any, Literal @@ -282,7 +282,7 @@ async def uipath_astream( stream_type: Literal["text", "bytes", "lines", "raw"] = "lines", raise_status_error: bool = False, **kwargs: Any, - ) -> AsyncIterator[str | bytes]: + ) -> AsyncGenerator[str | bytes, None]: """Make an asynchronous streaming HTTP request to the UiPath API. Args: @@ -423,7 +423,7 @@ async def _astream( stop: list[str] | None = None, run_manager: AsyncCallbackManagerForLLMRun | None = None, **kwargs: Any, - ) -> AsyncIterator[ChatGenerationChunk]: + ) -> AsyncGenerator[ChatGenerationChunk, None]: set_captured_response_headers({}) try: first = True @@ -443,7 +443,7 @@ async def _uipath_astream( stop: list[str] | None = None, run_manager: AsyncCallbackManagerForLLMRun | None = None, **kwargs: Any, - ) -> AsyncIterator[ChatGenerationChunk]: + ) -> AsyncGenerator[ChatGenerationChunk, None]: """Override in subclasses to provide the core (non-wrapped) async stream logic.""" async for chunk in super()._astream(messages, stop=stop, run_manager=run_manager, **kwargs): yield chunk diff --git a/packages/uipath_langchain_client/src/uipath_langchain_client/callbacks.py b/packages/uipath_langchain_client/src/uipath_langchain_client/callbacks.py index af99ac1..431b49c 100644 --- a/packages/uipath_langchain_client/src/uipath_langchain_client/callbacks.py +++ b/packages/uipath_langchain_client/src/uipath_langchain_client/callbacks.py @@ -1,6 +1,6 @@ """LangChain callbacks for dynamic per-request header injection.""" -from abc import abstractmethod +from abc import ABC, abstractmethod from typing import Any from langchain_core.callbacks import BaseCallbackHandler @@ -8,7 +8,7 @@ from uipath.llm_client.utils.headers import set_dynamic_request_headers -class UiPathDynamicHeadersCallback(BaseCallbackHandler): +class UiPathDynamicHeadersCallback(BaseCallbackHandler, ABC): """Base callback for injecting dynamic headers into each LLM gateway request. Extend this class and implement ``get_headers()`` to return the headers to diff --git a/packages/uipath_langchain_client/src/uipath_langchain_client/clients/anthropic/chat_models.py b/packages/uipath_langchain_client/src/uipath_langchain_client/clients/anthropic/chat_models.py index 52633df..4283bcc 100644 --- a/packages/uipath_langchain_client/src/uipath_langchain_client/clients/anthropic/chat_models.py +++ b/packages/uipath_langchain_client/src/uipath_langchain_client/clients/anthropic/chat_models.py @@ -15,13 +15,9 @@ try: from anthropic import ( - Anthropic, AnthropicBedrock, - AnthropicFoundry, AnthropicVertex, - AsyncAnthropic, AsyncAnthropicBedrock, - AsyncAnthropicFoundry, AsyncAnthropicVertex, ) from langchain_anthropic.chat_models import ChatAnthropic @@ -33,13 +29,18 @@ class UiPathChatAnthropic(UiPathBaseChatModel, ChatAnthropic): + # api_config.vendor_type is a placeholder here; the model_validator below + # always overwrites it (and api_flavor / api_version) from self.vendor_type. + # VendorType.VERTEXAI is used as the seed so UiPathAPIConfig's own validator + # (which requires vendor_type when routing_mode=PASSTHROUGH) is satisfied. api_config: UiPathAPIConfig = UiPathAPIConfig( api_type=ApiType.COMPLETIONS, routing_mode=RoutingMode.PASSTHROUGH, - vendor_type=VendorType.ANTHROPIC, + vendor_type=VendorType.VERTEXAI, freeze_base_url=True, ) - vendor_type: VendorType = VendorType.ANTHROPIC + # Required — caller must supply VendorType.VERTEXAI or VendorType.AWSBEDROCK. + vendor_type: VendorType @model_validator(mode="after") def setup_api_flavor_and_version(self) -> Self: @@ -52,7 +53,8 @@ def setup_api_flavor_and_version(self) -> Self: self.api_config.api_flavor = ApiFlavor.INVOKE case _: raise ValueError( - "anthropic and azure vendors are currently not supported by UiPath" + f"vendor_type '{self.vendor_type}' is not supported by UiPathChatAnthropic. " + "Use VendorType.VERTEXAI or VendorType.AWSBEDROCK." ) return self @@ -64,24 +66,8 @@ def setup_api_flavor_and_version(self) -> Self: @cached_property def _anthropic_client( self, - ) -> Anthropic | AnthropicVertex | AnthropicBedrock | AnthropicFoundry: + ) -> AnthropicVertex | AnthropicBedrock: match self.vendor_type: - case VendorType.ANTHROPIC: - return Anthropic( - api_key="PLACEHOLDER", - base_url=str(self.uipath_sync_client.base_url), - default_headers=dict(self.uipath_sync_client.headers), - max_retries=0, # handled by the UiPathBaseChatModel - http_client=self.uipath_sync_client, - ) - case VendorType.AZURE: - return AnthropicFoundry( - api_key="PLACEHOLDER", - base_url=str(self.uipath_sync_client.base_url), - default_headers=dict(self.uipath_sync_client.headers), - max_retries=0, # handled by the UiPathBaseChatModel - http_client=self.uipath_sync_client, - ) case VendorType.VERTEXAI: return AnthropicVertex( region="PLACEHOLDER", @@ -103,29 +89,15 @@ def _anthropic_client( http_client=self.uipath_sync_client, ) case _: - raise ValueError("Anthropic models are currently not hosted on any other provider") + raise ValueError( + f"vendor_type '{self.vendor_type}' is not supported by UiPathChatAnthropic." + ) @cached_property def _async_anthropic_client( self, - ) -> AsyncAnthropic | AsyncAnthropicVertex | AsyncAnthropicBedrock | AsyncAnthropicFoundry: + ) -> AsyncAnthropicVertex | AsyncAnthropicBedrock: match self.vendor_type: - case VendorType.ANTHROPIC: - return AsyncAnthropic( - api_key="PLACEHOLDER", - base_url=str(self.uipath_async_client.base_url), - default_headers=dict(self.uipath_async_client.headers), - max_retries=0, # handled by the UiPathBaseChatModel - http_client=self.uipath_async_client, - ) - case VendorType.AZURE: - return AsyncAnthropicFoundry( - api_key="PLACEHOLDER", - base_url=str(self.uipath_async_client.base_url), - default_headers=dict(self.uipath_async_client.headers), - max_retries=0, # handled by the UiPathBaseChatModel - http_client=self.uipath_async_client, - ) case VendorType.VERTEXAI: return AsyncAnthropicVertex( region="PLACEHOLDER", @@ -147,7 +119,9 @@ def _async_anthropic_client( http_client=self.uipath_async_client, ) case _: - raise ValueError("Anthropic models are currently not hosted on any other provider") + raise ValueError( + f"vendor_type '{self.vendor_type}' is not supported by UiPathChatAnthropic." + ) @override def _create(self, payload: dict) -> Any: diff --git a/packages/uipath_langchain_client/src/uipath_langchain_client/clients/bedrock/utils.py b/packages/uipath_langchain_client/src/uipath_langchain_client/clients/bedrock/utils.py index a56f371..e50d396 100644 --- a/packages/uipath_langchain_client/src/uipath_langchain_client/clients/bedrock/utils.py +++ b/packages/uipath_langchain_client/src/uipath_langchain_client/clients/bedrock/utils.py @@ -71,15 +71,19 @@ def invoke_model(self, **kwargs: Any) -> Any: return { "body": self.httpx_client.post( "/", - json=json.loads(kwargs.get("body", {})), + json=json.loads(kwargs.get("body", "{}")), ) } def invoke_model_with_response_stream(self, **kwargs: Any) -> Any: - return {"body": self._stream_generator(json.loads(kwargs.get("body", {})))} + return {"body": self._stream_generator(json.loads(kwargs.get("body", "{}")))} def converse( - self, *, messages: list[dict[str, Any]], system: str | None = None, **params: Any + self, + *, + messages: list[dict[str, Any]], + system: list[dict[str, Any]] | None = None, + **params: Any, ) -> Any: if self.httpx_client is None: raise ValueError("httpx_client is not set") @@ -95,7 +99,11 @@ def converse( ).json() def converse_stream( - self, *, messages: list[dict[str, Any]], system: str | None = None, **params: Any + self, + *, + messages: list[dict[str, Any]], + system: list[dict[str, Any]] | None = None, + **params: Any, ) -> Any: return { "stream": self._stream_generator( diff --git a/packages/uipath_langchain_client/src/uipath_langchain_client/clients/normalized/chat_models.py b/packages/uipath_langchain_client/src/uipath_langchain_client/clients/normalized/chat_models.py index 582181c..7d77d15 100644 --- a/packages/uipath_langchain_client/src/uipath_langchain_client/clients/normalized/chat_models.py +++ b/packages/uipath_langchain_client/src/uipath_langchain_client/clients/normalized/chat_models.py @@ -24,7 +24,7 @@ """ import json -from collections.abc import AsyncIterator, Callable, Iterator, Sequence +from collections.abc import AsyncGenerator, AsyncIterator, Callable, Iterator, Sequence from typing import Any from langchain_core.callbacks import ( @@ -377,10 +377,10 @@ def _generate_chunk( ) return ChatGenerationChunk( - text=original_message, + text=content or "", generation_info=generation_info, message=AIMessageChunk( - content=content, + content=content or "", usage_metadata=usage_metadata, tool_call_chunks=tool_call_chunks, ), @@ -394,12 +394,13 @@ def _uipath_stream( **kwargs: Any, ) -> Iterator[ChatGenerationChunk]: request_body = self._preprocess_request(messages, stop=stop, **kwargs) + request_body["stream"] = True for chunk in self.uipath_stream( request_body=request_body, stream_type="lines", raise_status_error=True ): chunk = str(chunk) if chunk.startswith("data:"): - chunk = chunk.split("data:")[1].strip() + chunk = chunk[len("data:"):].strip() try: json_data = json.loads(chunk) except json.JSONDecodeError: @@ -414,14 +415,15 @@ async def _uipath_astream( stop: list[str] | None = None, run_manager: AsyncCallbackManagerForLLMRun | None = None, **kwargs: Any, - ) -> AsyncIterator[ChatGenerationChunk]: + ) -> AsyncGenerator[ChatGenerationChunk, None]: request_body = self._preprocess_request(messages, stop=stop, **kwargs) + request_body["stream"] = True async for chunk in self.uipath_astream( request_body=request_body, stream_type="lines", raise_status_error=True ): chunk = str(chunk) if chunk.startswith("data:"): - chunk = chunk.split("data:")[1].strip() + chunk = chunk[len("data:"):].strip() try: json_data = json.loads(chunk) except json.JSONDecodeError: 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 9a5804b..2a3b4db 100644 --- a/packages/uipath_langchain_client/src/uipath_langchain_client/factory.py +++ b/packages/uipath_langchain_client/src/uipath_langchain_client/factory.py @@ -298,7 +298,13 @@ def get_embedding_model( **model_kwargs, ) - discovered_vendor_type = model_info["vendor"].lower() + discovered_vendor_type = model_info.get("vendor") + if discovered_vendor_type is None: + raise ValueError( + f"No vendor type found in model info for embedding model '{model_name}'. " + f"Model info returned: {model_info}" + ) + discovered_vendor_type = discovered_vendor_type.lower() match discovered_vendor_type: case VendorType.OPENAI: if is_uipath_owned: diff --git a/src/uipath/llm_client/__version__.py b/src/uipath/llm_client/__version__.py index 7da9182..c5bf7a8 100644 --- a/src/uipath/llm_client/__version__.py +++ b/src/uipath/llm_client/__version__.py @@ -1,3 +1,3 @@ __title__ = "UiPath LLM Client" __description__ = "A Python client for interacting with UiPath's LLM services." -__version__ = "1.5.10" +__version__ = "1.6.0" From 1ae8f0b64f6bce37d19abacc09c70bcd8f1b43d5 Mon Sep 17 00:00:00 2001 From: Cosmin Maria Date: Thu, 2 Apr 2026 01:49:50 +0300 Subject: [PATCH 02/10] fix: bugs #5 and #8 (embeddings model name + error handling) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bug #5 — UiPathFireworksEmbeddings: self.model returned wrong model name Without an explicit field override, Pydantic kept two separate fields: FireworksEmbeddings.model (default 'nomic-ai/nomic-embed-text-v1.5') and UiPathBaseLLMClient.model_name (populated by the caller via alias 'model'). self.model therefore always resolved to the stale FireworksEmbeddings default, silently ignoring whatever model name the caller passed. Fix: add model: str = Field(default='', alias='model_name') — the same override pattern used by every other embedding class — to collapse the two fields into one. Replace self.model with self.model_name in all four embed methods. Bug #8 — UiPathEmbeddings: silent HTTP error swallowing + missing model field Two issues in embed_documents / aembed_documents: 1. uipath_request / uipath_arequest were called without raise_status_error=True, so HTTP 4xx/5xx responses were silently accepted. The subsequent response.json()['data'] access would then raise a confusing KeyError or AttributeError rather than a clear HTTP error. 2. The request body only contained {'input': texts}. The normalized API requires a 'model' field for routing, matching the pattern already used by the normalized chat model (_default_params includes 'model'). Fix: pass raise_status_error=True and add 'model': self.model_name to the request body in both the sync and async paths. --- .../clients/fireworks/embeddings.py | 17 ++++++++++++++--- .../clients/normalized/embeddings.py | 10 ++++++++-- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/packages/uipath_langchain_client/src/uipath_langchain_client/clients/fireworks/embeddings.py b/packages/uipath_langchain_client/src/uipath_langchain_client/clients/fireworks/embeddings.py index b4af2bc..8ded0ea 100644 --- a/packages/uipath_langchain_client/src/uipath_langchain_client/clients/fireworks/embeddings.py +++ b/packages/uipath_langchain_client/src/uipath_langchain_client/clients/fireworks/embeddings.py @@ -1,6 +1,6 @@ from typing import Self -from pydantic import model_validator +from pydantic import Field, model_validator from uipath_langchain_client.base_client import UiPathBaseEmbeddings from uipath_langchain_client.settings import ( @@ -31,6 +31,14 @@ class UiPathFireworksEmbeddings(UiPathBaseEmbeddings, FireworksEmbeddings): freeze_base_url=True, ) + # Override FireworksEmbeddings.model so that Pydantic merges it with + # UiPathBaseLLMClient.model_name (alias="model") into a single field. + # Without this, two separate fields coexist: FireworksEmbeddings.model + # (default "nomic-ai/nomic-embed-text-v1.5") and model_name, causing + # self.model to always return the stale FireworksEmbeddings default + # instead of the model name supplied by the caller. + model: str = Field(default="", alias="model_name") + @model_validator(mode="after") def setup_uipath_client(self) -> Self: self.client = OpenAI( @@ -48,7 +56,8 @@ def setup_uipath_client(self) -> Self: def embed_documents(self, texts: list[str]) -> list[list[float]]: """Embed search docs.""" return [ - i.embedding for i in self.client.embeddings.create(input=texts, model=self.model).data + i.embedding + for i in self.client.embeddings.create(input=texts, model=self.model_name).data ] def embed_query(self, text: str) -> list[float]: @@ -59,7 +68,9 @@ async def aembed_documents(self, texts: list[str]) -> list[list[float]]: """Embed search docs asynchronously.""" return [ i.embedding - for i in (await self.async_client.embeddings.create(input=texts, model=self.model)).data + for i in ( + await self.async_client.embeddings.create(input=texts, model=self.model_name) + ).data ] async def aembed_query(self, text: str) -> list[float]: diff --git a/packages/uipath_langchain_client/src/uipath_langchain_client/clients/normalized/embeddings.py b/packages/uipath_langchain_client/src/uipath_langchain_client/clients/normalized/embeddings.py index 86498a4..007ce0e 100644 --- a/packages/uipath_langchain_client/src/uipath_langchain_client/clients/normalized/embeddings.py +++ b/packages/uipath_langchain_client/src/uipath_langchain_client/clients/normalized/embeddings.py @@ -18,14 +18,20 @@ class UiPathEmbeddings(UiPathBaseEmbeddings, Embeddings): ) def embed_documents(self, texts: list[str]) -> list[list[float]]: - response = self.uipath_request(request_body={"input": texts}) + response = self.uipath_request( + request_body={"input": texts, "model": self.model_name}, + raise_status_error=True, + ) return [r["embedding"] for r in response.json()["data"]] def embed_query(self, text: str) -> list[float]: return self.embed_documents([text])[0] async def aembed_documents(self, texts: list[str]) -> list[list[float]]: - response = await self.uipath_arequest(request_body={"input": texts}) + response = await self.uipath_arequest( + request_body={"input": texts, "model": self.model_name}, + raise_status_error=True, + ) return [r["embedding"] for r in response.json()["data"]] async def aembed_query(self, text: str) -> list[float]: From ffd4ee7fd0818fdf514cc30959089e7a484a5a06 Mon Sep 17 00:00:00 2001 From: Cosmin Maria Date: Thu, 2 Apr 2026 01:55:43 +0300 Subject: [PATCH 03/10] fix: replace Iterator/AsyncIterator with Generator/AsyncGenerator on all generator functions Every function annotated -> Iterator[T] or -> AsyncIterator[T] that contains a yield statement is actually a generator function, not a plain function that returns an iterator. The correct annotations are: sync yield -> Generator[YieldType, SendType, ReturnType] async yield -> AsyncGenerator[YieldType, SendType] The previous async fix (Iterator -> AsyncGenerator) was applied inconsistently: the same issue existed on all sync counterparts and was left behind. This commit fixes all remaining sites for full consistency: base_client.py: uipath_stream Iterator[str | bytes] -> Generator[str | bytes, None, None] _stream Iterator[ChatGenerationChunk] -> Generator[ChatGenerationChunk, None, None] _uipath_stream Iterator[ChatGenerationChunk] -> Generator[ChatGenerationChunk, None, None] clients/normalized/chat_models.py: _uipath_stream Iterator[ChatGenerationChunk] -> Generator[ChatGenerationChunk, None, None] clients/bedrock/utils.py: _stream_generator Iterator[dict[str, Any]] -> Generator[dict[str, Any], None, None] Added Generator to collections.abc imports in all three files. --- .../src/uipath_langchain_client/base_client.py | 8 ++++---- .../src/uipath_langchain_client/clients/bedrock/utils.py | 5 +++-- .../clients/normalized/chat_models.py | 8 ++++---- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/packages/uipath_langchain_client/src/uipath_langchain_client/base_client.py b/packages/uipath_langchain_client/src/uipath_langchain_client/base_client.py index dc6bd17..d0744da 100644 --- a/packages/uipath_langchain_client/src/uipath_langchain_client/base_client.py +++ b/packages/uipath_langchain_client/src/uipath_langchain_client/base_client.py @@ -25,7 +25,7 @@ import logging from abc import ABC -from collections.abc import AsyncGenerator, AsyncIterator, Iterator, Mapping, Sequence +from collections.abc import AsyncGenerator, AsyncIterator, Generator, Iterator, Mapping, Sequence from functools import cached_property from typing import Any, Literal @@ -239,7 +239,7 @@ def uipath_stream( stream_type: Literal["text", "bytes", "lines", "raw"] = "lines", raise_status_error: bool = False, **kwargs: Any, - ) -> Iterator[str | bytes]: + ) -> Generator[str | bytes, None, None]: """Make a synchronous streaming HTTP request to the UiPath API. Args: @@ -393,7 +393,7 @@ def _stream( stop: list[str] | None = None, run_manager: CallbackManagerForLLMRun | None = None, **kwargs: Any, - ) -> Iterator[ChatGenerationChunk]: + ) -> Generator[ChatGenerationChunk, None, None]: set_captured_response_headers({}) try: first = True @@ -413,7 +413,7 @@ def _uipath_stream( stop: list[str] | None = None, run_manager: CallbackManagerForLLMRun | None = None, **kwargs: Any, - ) -> Iterator[ChatGenerationChunk]: + ) -> Generator[ChatGenerationChunk, None, None]: """Override in subclasses to provide the core (non-wrapped) stream logic.""" yield from super()._stream(messages, stop=stop, run_manager=run_manager, **kwargs) diff --git a/packages/uipath_langchain_client/src/uipath_langchain_client/clients/bedrock/utils.py b/packages/uipath_langchain_client/src/uipath_langchain_client/clients/bedrock/utils.py index e50d396..7ba056a 100644 --- a/packages/uipath_langchain_client/src/uipath_langchain_client/clients/bedrock/utils.py +++ b/packages/uipath_langchain_client/src/uipath_langchain_client/clients/bedrock/utils.py @@ -1,6 +1,7 @@ import base64 import json -from typing import Any, Iterator +from typing import Any +from collections.abc import Generator, Iterator from httpx import Client @@ -50,7 +51,7 @@ def __init__(self, httpx_client: Client | None = None, region_name: str = "PLACE self.httpx_client = httpx_client self.meta = _MockClientMeta(region_name=region_name) - def _stream_generator(self, request_body: dict[str, Any]) -> Iterator[dict[str, Any]]: + def _stream_generator(self, request_body: dict[str, Any]) -> Generator[dict[str, Any], None, None]: if self.httpx_client is None: raise ValueError("httpx_client is not set") with self.httpx_client.stream("POST", "/", json=_serialize_bytes(request_body)) as response: diff --git a/packages/uipath_langchain_client/src/uipath_langchain_client/clients/normalized/chat_models.py b/packages/uipath_langchain_client/src/uipath_langchain_client/clients/normalized/chat_models.py index 7d77d15..0fc160a 100644 --- a/packages/uipath_langchain_client/src/uipath_langchain_client/clients/normalized/chat_models.py +++ b/packages/uipath_langchain_client/src/uipath_langchain_client/clients/normalized/chat_models.py @@ -24,7 +24,7 @@ """ import json -from collections.abc import AsyncGenerator, AsyncIterator, Callable, Iterator, Sequence +from collections.abc import AsyncGenerator, Callable, Generator, Iterator, Sequence from typing import Any from langchain_core.callbacks import ( @@ -392,7 +392,7 @@ def _uipath_stream( stop: list[str] | None = None, run_manager: CallbackManagerForLLMRun | None = None, **kwargs: Any, - ) -> Iterator[ChatGenerationChunk]: + ) -> Generator[ChatGenerationChunk, None, None]: request_body = self._preprocess_request(messages, stop=stop, **kwargs) request_body["stream"] = True for chunk in self.uipath_stream( @@ -400,7 +400,7 @@ def _uipath_stream( ): chunk = str(chunk) if chunk.startswith("data:"): - chunk = chunk[len("data:"):].strip() + chunk = chunk[len("data:") :].strip() try: json_data = json.loads(chunk) except json.JSONDecodeError: @@ -423,7 +423,7 @@ async def _uipath_astream( ): chunk = str(chunk) if chunk.startswith("data:"): - chunk = chunk[len("data:"):].strip() + chunk = chunk[len("data:") :].strip() try: json_data = json.loads(chunk) except json.JSONDecodeError: From fde067204b57a122b8c7bde45595a783e5a815e3 Mon Sep 17 00:00:00 2001 From: Cosmin Maria Date: Thu, 2 Apr 2026 01:58:37 +0300 Subject: [PATCH 04/10] fix: ruff and pyright clean pass MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ruff --fix: - demo.py: remove extraneous f-prefix from plain string literal (F541) - base_client.py: remove unused AsyncIterator, Iterator imports (F401) - bedrock/utils.py: remove unused Iterator import (F401); fix import block order (I001) - normalized/chat_models.py: remove unused Iterator import (F401) ruff format: - demo.py: expand tuple literal to one-entry-per-line style - bedrock/utils.py: wrap long _stream_generator signature to fit line-length pyright (--pythonpath .venv/bin/python): - bedrock/chat_models.py: UiPathChatBedrockConverse and UiPathChatBedrock mix UiPathBaseLLMClient (max_retries: int) with ChatBedrockConverse/ChatBedrock (max_retries: int | None); the type narrowing is intentional — our field enforces a non-None default of 0 and the httpx client accepts int | None. Suppress with # type: ignore[override] on both class definition lines, which is the correct pyright mechanism for intentional incompatible base-class field merges in multiple-inheritance mixins. --- packages/uipath_langchain_client/demo.py | 19 +++++++++++++++---- .../uipath_langchain_client/base_client.py | 2 +- .../clients/bedrock/chat_models.py | 4 ++-- .../clients/bedrock/utils.py | 6 ++++-- .../clients/normalized/chat_models.py | 2 +- 5 files changed, 23 insertions(+), 10 deletions(-) diff --git a/packages/uipath_langchain_client/demo.py b/packages/uipath_langchain_client/demo.py index 86c8688..35db0da 100644 --- a/packages/uipath_langchain_client/demo.py +++ b/packages/uipath_langchain_client/demo.py @@ -137,18 +137,29 @@ def calculate(expression: str) -> str: expression: A mathematical expression to evaluate (e.g., "2 + 2"). """ import ast + try: # Restrict to a safe subset: only literals and basic arithmetic operators. # This prevents arbitrary code execution via eval(). tree = ast.parse(expression, mode="eval") allowed_node_types = ( - ast.Expression, ast.BinOp, ast.UnaryOp, ast.Constant, - ast.Add, ast.Sub, ast.Mult, ast.Div, ast.FloorDiv, - ast.Mod, ast.Pow, ast.USub, ast.UAdd, + ast.Expression, + ast.BinOp, + ast.UnaryOp, + ast.Constant, + ast.Add, + ast.Sub, + ast.Mult, + ast.Div, + ast.FloorDiv, + ast.Mod, + ast.Pow, + ast.USub, + ast.UAdd, ) for node in ast.walk(tree): if not isinstance(node, allowed_node_types): - return f"Error: unsupported operation in expression" + return "Error: unsupported operation in expression" result = eval(compile(tree, "", "eval"), {"__builtins__": {}}) return str(result) except Exception as e: diff --git a/packages/uipath_langchain_client/src/uipath_langchain_client/base_client.py b/packages/uipath_langchain_client/src/uipath_langchain_client/base_client.py index d0744da..33454f5 100644 --- a/packages/uipath_langchain_client/src/uipath_langchain_client/base_client.py +++ b/packages/uipath_langchain_client/src/uipath_langchain_client/base_client.py @@ -25,7 +25,7 @@ import logging from abc import ABC -from collections.abc import AsyncGenerator, AsyncIterator, Generator, Iterator, Mapping, Sequence +from collections.abc import AsyncGenerator, Generator, Mapping, Sequence from functools import cached_property from typing import Any, Literal diff --git a/packages/uipath_langchain_client/src/uipath_langchain_client/clients/bedrock/chat_models.py b/packages/uipath_langchain_client/src/uipath_langchain_client/clients/bedrock/chat_models.py index f2349b0..8993522 100644 --- a/packages/uipath_langchain_client/src/uipath_langchain_client/clients/bedrock/chat_models.py +++ b/packages/uipath_langchain_client/src/uipath_langchain_client/clients/bedrock/chat_models.py @@ -48,7 +48,7 @@ def _patched_format_data_content_block(block: dict) -> dict: ) from e -class UiPathChatBedrockConverse(UiPathBaseChatModel, ChatBedrockConverse): +class UiPathChatBedrockConverse(UiPathBaseChatModel, ChatBedrockConverse): # type: ignore[override] api_config: UiPathAPIConfig = UiPathAPIConfig( api_type=ApiType.COMPLETIONS, routing_mode=RoutingMode.PASSTHROUGH, @@ -77,7 +77,7 @@ def setup_uipath_client(self) -> Self: return self -class UiPathChatBedrock(UiPathBaseChatModel, ChatBedrock): +class UiPathChatBedrock(UiPathBaseChatModel, ChatBedrock): # type: ignore[override] api_config: UiPathAPIConfig = UiPathAPIConfig( api_type=ApiType.COMPLETIONS, routing_mode=RoutingMode.PASSTHROUGH, diff --git a/packages/uipath_langchain_client/src/uipath_langchain_client/clients/bedrock/utils.py b/packages/uipath_langchain_client/src/uipath_langchain_client/clients/bedrock/utils.py index 7ba056a..232c085 100644 --- a/packages/uipath_langchain_client/src/uipath_langchain_client/clients/bedrock/utils.py +++ b/packages/uipath_langchain_client/src/uipath_langchain_client/clients/bedrock/utils.py @@ -1,7 +1,7 @@ import base64 import json +from collections.abc import Generator from typing import Any -from collections.abc import Generator, Iterator from httpx import Client @@ -51,7 +51,9 @@ def __init__(self, httpx_client: Client | None = None, region_name: str = "PLACE self.httpx_client = httpx_client self.meta = _MockClientMeta(region_name=region_name) - def _stream_generator(self, request_body: dict[str, Any]) -> Generator[dict[str, Any], None, None]: + def _stream_generator( + self, request_body: dict[str, Any] + ) -> Generator[dict[str, Any], None, None]: if self.httpx_client is None: raise ValueError("httpx_client is not set") with self.httpx_client.stream("POST", "/", json=_serialize_bytes(request_body)) as response: diff --git a/packages/uipath_langchain_client/src/uipath_langchain_client/clients/normalized/chat_models.py b/packages/uipath_langchain_client/src/uipath_langchain_client/clients/normalized/chat_models.py index 0fc160a..0913971 100644 --- a/packages/uipath_langchain_client/src/uipath_langchain_client/clients/normalized/chat_models.py +++ b/packages/uipath_langchain_client/src/uipath_langchain_client/clients/normalized/chat_models.py @@ -24,7 +24,7 @@ """ import json -from collections.abc import AsyncGenerator, Callable, Generator, Iterator, Sequence +from collections.abc import AsyncGenerator, Callable, Generator, Sequence from typing import Any from langchain_core.callbacks import ( From 8ad12aa9d5673ef284b2a9d8d0ae86b23304c4bc Mon Sep 17 00:00:00 2001 From: Cosmin Maria Date: Thu, 2 Apr 2026 02:01:22 +0300 Subject: [PATCH 05/10] fix: remove model_name from normalized API request bodies MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The normalized routing layer resolves the model from the URL/connection context — the caller does not need to send it in the JSON body. - normalized/chat_models.py: remove 'model': self.model_name from _default_params - normalized/embeddings.py: remove 'model': self.model_name from both embed_documents and aembed_documents request bodies --- .../uipath_langchain_client/clients/normalized/chat_models.py | 1 - .../uipath_langchain_client/clients/normalized/embeddings.py | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/uipath_langchain_client/src/uipath_langchain_client/clients/normalized/chat_models.py b/packages/uipath_langchain_client/src/uipath_langchain_client/clients/normalized/chat_models.py index 0913971..2a9a6a9 100644 --- a/packages/uipath_langchain_client/src/uipath_langchain_client/clients/normalized/chat_models.py +++ b/packages/uipath_langchain_client/src/uipath_langchain_client/clients/normalized/chat_models.py @@ -155,7 +155,6 @@ def _default_params(self) -> dict[str, Any]: } return { - "model": self.model_name, **{k: v for k, v in exclude_if_none.items() if v is not None}, **self.model_kwargs, } diff --git a/packages/uipath_langchain_client/src/uipath_langchain_client/clients/normalized/embeddings.py b/packages/uipath_langchain_client/src/uipath_langchain_client/clients/normalized/embeddings.py index 007ce0e..753324e 100644 --- a/packages/uipath_langchain_client/src/uipath_langchain_client/clients/normalized/embeddings.py +++ b/packages/uipath_langchain_client/src/uipath_langchain_client/clients/normalized/embeddings.py @@ -19,7 +19,7 @@ class UiPathEmbeddings(UiPathBaseEmbeddings, Embeddings): def embed_documents(self, texts: list[str]) -> list[list[float]]: response = self.uipath_request( - request_body={"input": texts, "model": self.model_name}, + request_body={"input": texts}, raise_status_error=True, ) return [r["embedding"] for r in response.json()["data"]] @@ -29,7 +29,7 @@ def embed_query(self, text: str) -> list[float]: async def aembed_documents(self, texts: list[str]) -> list[list[float]]: response = await self.uipath_arequest( - request_body={"input": texts, "model": self.model_name}, + request_body={"input": texts}, raise_status_error=True, ) return [r["embedding"] for r in response.json()["data"]] From 822806bafac753daf83dc6587dead44a86614d21 Mon Sep 17 00:00:00 2001 From: Cosmin Maria Date: Thu, 2 Apr 2026 02:06:14 +0300 Subject: [PATCH 06/10] revert: restore UiPathChatAnthropic to original structure - Restore VendorType.ANTHROPIC as default for both api_config.vendor_type and the vendor_type field - Restore all four vendor branches (ANTHROPIC, AZURE, VERTEXAI, AWSBEDROCK) in _anthropic_client and _async_anthropic_client - Restore original imports (Anthropic, AnthropicFoundry, AsyncAnthropic, AsyncAnthropicFoundry) that were removed in the earlier fix - Remove the long explanatory comment on api_config - Keep the original error messages verbatim --- .../clients/anthropic/chat_models.py | 60 +++++++++++++------ 1 file changed, 43 insertions(+), 17 deletions(-) diff --git a/packages/uipath_langchain_client/src/uipath_langchain_client/clients/anthropic/chat_models.py b/packages/uipath_langchain_client/src/uipath_langchain_client/clients/anthropic/chat_models.py index 4283bcc..52633df 100644 --- a/packages/uipath_langchain_client/src/uipath_langchain_client/clients/anthropic/chat_models.py +++ b/packages/uipath_langchain_client/src/uipath_langchain_client/clients/anthropic/chat_models.py @@ -15,9 +15,13 @@ try: from anthropic import ( + Anthropic, AnthropicBedrock, + AnthropicFoundry, AnthropicVertex, + AsyncAnthropic, AsyncAnthropicBedrock, + AsyncAnthropicFoundry, AsyncAnthropicVertex, ) from langchain_anthropic.chat_models import ChatAnthropic @@ -29,18 +33,13 @@ class UiPathChatAnthropic(UiPathBaseChatModel, ChatAnthropic): - # api_config.vendor_type is a placeholder here; the model_validator below - # always overwrites it (and api_flavor / api_version) from self.vendor_type. - # VendorType.VERTEXAI is used as the seed so UiPathAPIConfig's own validator - # (which requires vendor_type when routing_mode=PASSTHROUGH) is satisfied. api_config: UiPathAPIConfig = UiPathAPIConfig( api_type=ApiType.COMPLETIONS, routing_mode=RoutingMode.PASSTHROUGH, - vendor_type=VendorType.VERTEXAI, + vendor_type=VendorType.ANTHROPIC, freeze_base_url=True, ) - # Required — caller must supply VendorType.VERTEXAI or VendorType.AWSBEDROCK. - vendor_type: VendorType + vendor_type: VendorType = VendorType.ANTHROPIC @model_validator(mode="after") def setup_api_flavor_and_version(self) -> Self: @@ -53,8 +52,7 @@ def setup_api_flavor_and_version(self) -> Self: self.api_config.api_flavor = ApiFlavor.INVOKE case _: raise ValueError( - f"vendor_type '{self.vendor_type}' is not supported by UiPathChatAnthropic. " - "Use VendorType.VERTEXAI or VendorType.AWSBEDROCK." + "anthropic and azure vendors are currently not supported by UiPath" ) return self @@ -66,8 +64,24 @@ def setup_api_flavor_and_version(self) -> Self: @cached_property def _anthropic_client( self, - ) -> AnthropicVertex | AnthropicBedrock: + ) -> Anthropic | AnthropicVertex | AnthropicBedrock | AnthropicFoundry: match self.vendor_type: + case VendorType.ANTHROPIC: + return Anthropic( + api_key="PLACEHOLDER", + base_url=str(self.uipath_sync_client.base_url), + default_headers=dict(self.uipath_sync_client.headers), + max_retries=0, # handled by the UiPathBaseChatModel + http_client=self.uipath_sync_client, + ) + case VendorType.AZURE: + return AnthropicFoundry( + api_key="PLACEHOLDER", + base_url=str(self.uipath_sync_client.base_url), + default_headers=dict(self.uipath_sync_client.headers), + max_retries=0, # handled by the UiPathBaseChatModel + http_client=self.uipath_sync_client, + ) case VendorType.VERTEXAI: return AnthropicVertex( region="PLACEHOLDER", @@ -89,15 +103,29 @@ def _anthropic_client( http_client=self.uipath_sync_client, ) case _: - raise ValueError( - f"vendor_type '{self.vendor_type}' is not supported by UiPathChatAnthropic." - ) + raise ValueError("Anthropic models are currently not hosted on any other provider") @cached_property def _async_anthropic_client( self, - ) -> AsyncAnthropicVertex | AsyncAnthropicBedrock: + ) -> AsyncAnthropic | AsyncAnthropicVertex | AsyncAnthropicBedrock | AsyncAnthropicFoundry: match self.vendor_type: + case VendorType.ANTHROPIC: + return AsyncAnthropic( + api_key="PLACEHOLDER", + base_url=str(self.uipath_async_client.base_url), + default_headers=dict(self.uipath_async_client.headers), + max_retries=0, # handled by the UiPathBaseChatModel + http_client=self.uipath_async_client, + ) + case VendorType.AZURE: + return AsyncAnthropicFoundry( + api_key="PLACEHOLDER", + base_url=str(self.uipath_async_client.base_url), + default_headers=dict(self.uipath_async_client.headers), + max_retries=0, # handled by the UiPathBaseChatModel + http_client=self.uipath_async_client, + ) case VendorType.VERTEXAI: return AsyncAnthropicVertex( region="PLACEHOLDER", @@ -119,9 +147,7 @@ def _async_anthropic_client( http_client=self.uipath_async_client, ) case _: - raise ValueError( - f"vendor_type '{self.vendor_type}' is not supported by UiPathChatAnthropic." - ) + raise ValueError("Anthropic models are currently not hosted on any other provider") @override def _create(self, payload: dict) -> Any: From 7ef9dfa38e7f2804b733a09eb53dd538ff947069 Mon Sep 17 00:00:00 2001 From: Cosmin Maria Date: Thu, 2 Apr 2026 02:09:30 +0300 Subject: [PATCH 07/10] fix: remove long comment on UiPathFireworksEmbeddings model field --- .../uipath_langchain_client/clients/fireworks/embeddings.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/packages/uipath_langchain_client/src/uipath_langchain_client/clients/fireworks/embeddings.py b/packages/uipath_langchain_client/src/uipath_langchain_client/clients/fireworks/embeddings.py index 8ded0ea..fb0c3ce 100644 --- a/packages/uipath_langchain_client/src/uipath_langchain_client/clients/fireworks/embeddings.py +++ b/packages/uipath_langchain_client/src/uipath_langchain_client/clients/fireworks/embeddings.py @@ -31,12 +31,6 @@ class UiPathFireworksEmbeddings(UiPathBaseEmbeddings, FireworksEmbeddings): freeze_base_url=True, ) - # Override FireworksEmbeddings.model so that Pydantic merges it with - # UiPathBaseLLMClient.model_name (alias="model") into a single field. - # Without this, two separate fields coexist: FireworksEmbeddings.model - # (default "nomic-ai/nomic-embed-text-v1.5") and model_name, causing - # self.model to always return the stale FireworksEmbeddings default - # instead of the model name supplied by the caller. model: str = Field(default="", alias="model_name") @model_validator(mode="after") From 0de9716c3598b3dbd66ae7810a4bdf9fa34dbc60 Mon Sep 17 00:00:00 2001 From: Cosmin Maria Date: Thu, 2 Apr 2026 02:19:48 +0300 Subject: [PATCH 08/10] fix: vertexai default_headers consistency + demo import path - clients/vertexai/chat_models.py: pass dict(self.uipath_sync_client.headers) and dict(self.uipath_async_client.headers) instead of the raw httpx.Headers objects, matching the pattern used in clients/anthropic/chat_models.py - demo.py: import RoutingMode from the public uipath_langchain_client.settings API instead of the internal uipath.llm_client.settings.constants path; merge with the existing get_default_client_settings import --- packages/uipath_langchain_client/demo.py | 4 +--- .../uipath_langchain_client/clients/vertexai/chat_models.py | 4 ++-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/uipath_langchain_client/demo.py b/packages/uipath_langchain_client/demo.py index 35db0da..e3c887e 100644 --- a/packages/uipath_langchain_client/demo.py +++ b/packages/uipath_langchain_client/demo.py @@ -17,9 +17,7 @@ from langchain_core.messages import HumanMessage, SystemMessage from langchain_core.tools import tool from uipath_langchain_client import get_chat_model, get_embedding_model -from uipath_langchain_client.settings import get_default_client_settings - -from uipath.llm_client.settings.constants import RoutingMode +from uipath_langchain_client.settings import RoutingMode, get_default_client_settings def demo_basic_chat(): diff --git a/packages/uipath_langchain_client/src/uipath_langchain_client/clients/vertexai/chat_models.py b/packages/uipath_langchain_client/src/uipath_langchain_client/clients/vertexai/chat_models.py index 1d3d242..d6f1db6 100644 --- a/packages/uipath_langchain_client/src/uipath_langchain_client/clients/vertexai/chat_models.py +++ b/packages/uipath_langchain_client/src/uipath_langchain_client/clients/vertexai/chat_models.py @@ -41,7 +41,7 @@ def setup_uipath_client(self) -> Self: project_id="PLACEHOLDER", access_token="PLACEHOLDER", base_url=str(self.uipath_sync_client.base_url), - default_headers=self.uipath_sync_client.headers, + default_headers=dict(self.uipath_sync_client.headers), max_retries=0, # handled by the UiPath client http_client=self.uipath_sync_client, ) @@ -50,7 +50,7 @@ def setup_uipath_client(self) -> Self: project_id="PLACEHOLDER", access_token="PLACEHOLDER", base_url=str(self.uipath_async_client.base_url), - default_headers=self.uipath_async_client.headers, + default_headers=dict(self.uipath_async_client.headers), max_retries=0, # handled by the UiPath client http_client=self.uipath_async_client, ) From 2ac6434c85e9fcd266ebf773ebd8874db6e91e7d Mon Sep 17 00:00:00 2001 From: Cosmin Maria Date: Thu, 2 Apr 2026 02:21:04 +0300 Subject: [PATCH 09/10] fix: add ANTHROPIC/AZURE cases to validator + remove unused original_message param - clients/anthropic/chat_models.py: add VendorType.ANTHROPIC and VendorType.AZURE cases to setup_api_flavor_and_version validator so the class can be constructed with all supported vendor types without raising - clients/normalized/chat_models.py: remove the now-unused original_message parameter from _generate_chunk and update both call sites in _uipath_stream and _uipath_astream --- .../clients/anthropic/chat_models.py | 8 +++++--- .../clients/normalized/chat_models.py | 8 +++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/uipath_langchain_client/src/uipath_langchain_client/clients/anthropic/chat_models.py b/packages/uipath_langchain_client/src/uipath_langchain_client/clients/anthropic/chat_models.py index 52633df..df90ede 100644 --- a/packages/uipath_langchain_client/src/uipath_langchain_client/clients/anthropic/chat_models.py +++ b/packages/uipath_langchain_client/src/uipath_langchain_client/clients/anthropic/chat_models.py @@ -45,15 +45,17 @@ class UiPathChatAnthropic(UiPathBaseChatModel, ChatAnthropic): def setup_api_flavor_and_version(self) -> Self: self.api_config.vendor_type = self.vendor_type match self.vendor_type: + case VendorType.ANTHROPIC: + self.api_config.api_flavor = ApiFlavor.CHAT_COMPLETIONS + case VendorType.AZURE: + self.api_config.api_flavor = ApiFlavor.CHAT_COMPLETIONS case VendorType.VERTEXAI: self.api_config.api_flavor = ApiFlavor.ANTHROPIC_CLAUDE self.api_config.api_version = "v1beta1" case VendorType.AWSBEDROCK: self.api_config.api_flavor = ApiFlavor.INVOKE case _: - raise ValueError( - "anthropic and azure vendors are currently not supported by UiPath" - ) + raise ValueError(f"Unsupported vendor_type: {self.vendor_type}") return self # Override fields to avoid typing issues and fix stuff diff --git a/packages/uipath_langchain_client/src/uipath_langchain_client/clients/normalized/chat_models.py b/packages/uipath_langchain_client/src/uipath_langchain_client/clients/normalized/chat_models.py index 2a9a6a9..47e325b 100644 --- a/packages/uipath_langchain_client/src/uipath_langchain_client/clients/normalized/chat_models.py +++ b/packages/uipath_langchain_client/src/uipath_langchain_client/clients/normalized/chat_models.py @@ -328,9 +328,7 @@ async def _uipath_agenerate( response = await self.uipath_arequest(request_body=request_body, raise_status_error=True) return self._postprocess_response(response.json()) - def _generate_chunk( - self, original_message: str, json_data: dict[str, Any] - ) -> ChatGenerationChunk: + def _generate_chunk(self, json_data: dict[str, Any]) -> ChatGenerationChunk: generation_info = { "id": json_data.get("id"), "created": json_data.get("created", ""), @@ -406,7 +404,7 @@ def _uipath_stream( continue if "id" in json_data and not json_data["id"]: continue - yield self._generate_chunk(chunk, json_data) + yield self._generate_chunk(json_data) async def _uipath_astream( self, @@ -429,4 +427,4 @@ async def _uipath_astream( continue if "id" in json_data and not json_data["id"]: continue - yield self._generate_chunk(chunk, json_data) + yield self._generate_chunk(json_data) From 20f53817a7b211bf5ced065fdedc34f859c8afe0 Mon Sep 17 00:00:00 2001 From: Cosmin Maria Date: Thu, 2 Apr 2026 02:24:32 +0300 Subject: [PATCH 10/10] fix: set api_flavor to None for ANTHROPIC and AZURE vendor types These vendors are not yet supported by UiPath's gateway, so no api_flavor routing header should be emitted for them. --- .../uipath_langchain_client/clients/anthropic/chat_models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/uipath_langchain_client/src/uipath_langchain_client/clients/anthropic/chat_models.py b/packages/uipath_langchain_client/src/uipath_langchain_client/clients/anthropic/chat_models.py index df90ede..86a53bc 100644 --- a/packages/uipath_langchain_client/src/uipath_langchain_client/clients/anthropic/chat_models.py +++ b/packages/uipath_langchain_client/src/uipath_langchain_client/clients/anthropic/chat_models.py @@ -46,9 +46,9 @@ def setup_api_flavor_and_version(self) -> Self: self.api_config.vendor_type = self.vendor_type match self.vendor_type: case VendorType.ANTHROPIC: - self.api_config.api_flavor = ApiFlavor.CHAT_COMPLETIONS + self.api_config.api_flavor = None case VendorType.AZURE: - self.api_config.api_flavor = ApiFlavor.CHAT_COMPLETIONS + self.api_config.api_flavor = None case VendorType.VERTEXAI: self.api_config.api_flavor = ApiFlavor.ANTHROPIC_CLAUDE self.api_config.api_version = "v1beta1"