Skip to content

Commit c2a793a

Browse files
committed
more fixes
1 parent 20f5381 commit c2a793a

29 files changed

Lines changed: 793 additions & 458 deletions

File tree

packages/uipath_langchain_client/src/uipath_langchain_client/base_client.py

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ def uipath_async_client(self) -> UiPathHttpxAsyncClient:
189189

190190
def uipath_request(
191191
self,
192-
method: str = "POST",
192+
method: Literal["POST", "GET"] = "POST",
193193
url: URL | str = "/",
194194
*,
195195
request_body: dict[str, Any] | None = None,
@@ -199,16 +199,18 @@ def uipath_request(
199199
"""Make a synchronous HTTP request to the UiPath API.
200200
201201
Args:
202-
method: HTTP method (GET, POST, etc.). Defaults to "POST".
202+
method: HTTP method (POST or GET). Defaults to "POST".
203203
url: Request URL path. Defaults to "/".
204204
request_body: JSON request body to send.
205+
raise_status_error: If True, raises UiPathAPIError on non-2xx responses.
205206
**kwargs: Additional arguments passed to httpx.Client.request().
206207
207208
Returns:
208209
httpx.Response: The HTTP response from the API.
209210
210211
Raises:
211-
UiPathAPIError: On HTTP 4xx/5xx responses (raised by transport layer).
212+
UiPathAPIError: On HTTP 4xx/5xx responses when raise_status_error is True,
213+
or raised by the transport layer.
212214
"""
213215
response = self.uipath_sync_client.request(method, url, json=request_body, **kwargs)
214216
if raise_status_error:
@@ -224,7 +226,22 @@ async def uipath_arequest(
224226
raise_status_error: bool = False,
225227
**kwargs: Any,
226228
) -> Response:
227-
"""Make an asynchronous HTTP request to the UiPath API."""
229+
"""Make an asynchronous HTTP request to the UiPath API.
230+
231+
Args:
232+
method: HTTP method (POST or GET). Defaults to "POST".
233+
url: Request URL path. Defaults to "/".
234+
request_body: JSON request body to send.
235+
raise_status_error: If True, raises UiPathAPIError on non-2xx responses.
236+
**kwargs: Additional arguments passed to httpx.AsyncClient.request().
237+
238+
Returns:
239+
httpx.Response: The HTTP response from the API.
240+
241+
Raises:
242+
UiPathAPIError: On HTTP 4xx/5xx responses when raise_status_error is True,
243+
or raised by the transport layer.
244+
"""
228245
response = await self.uipath_async_client.request(method, url, json=request_body, **kwargs)
229246
if raise_status_error:
230247
response.raise_for_status()
@@ -251,6 +268,7 @@ def uipath_stream(
251268
- "bytes": Yield raw byte chunks
252269
- "lines": Yield complete lines (default, best for SSE)
253270
- "raw": Yield raw response data
271+
raise_status_error: If True, raises UiPathAPIError on non-2xx responses.
254272
**kwargs: Additional arguments passed to httpx.Client.stream().
255273
256274
Yields:
@@ -294,6 +312,7 @@ async def uipath_astream(
294312
- "bytes": Yield raw byte chunks
295313
- "lines": Yield complete lines (default, best for SSE)
296314
- "raw": Yield raw response data
315+
raise_status_error: If True, raises UiPathAPIError on non-2xx responses.
297316
**kwargs: Additional arguments passed to httpx.AsyncClient.stream().
298317
299318
Yields:

packages/uipath_langchain_client/src/uipath_langchain_client/clients/anthropic/chat_models.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -152,13 +152,13 @@ def _async_anthropic_client(
152152
raise ValueError("Anthropic models are currently not hosted on any other provider")
153153

154154
@override
155-
def _create(self, payload: dict) -> Any:
155+
def _create(self, payload: dict[str, Any]) -> Any:
156156
if "betas" in payload:
157157
return self._anthropic_client.beta.messages.create(**payload)
158158
return self._anthropic_client.messages.create(**payload)
159159

160160
@override
161-
async def _acreate(self, payload: dict) -> Any:
161+
async def _acreate(self, payload: dict[str, Any]) -> Any:
162162
if "betas" in payload:
163163
return await self._async_anthropic_client.beta.messages.create(**payload)
164164
return await self._async_anthropic_client.messages.create(**payload)

packages/uipath_langchain_client/src/uipath_langchain_client/clients/azure/chat_models.py

Lines changed: 8 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
from typing import Self
22

3-
from httpx import URL, Request
3+
from httpx import Request
44
from pydantic import Field, model_validator
55

66
from uipath_langchain_client.base_client import UiPathBaseChatModel
7+
from uipath_langchain_client.clients.openai.utils import fix_url_and_api_flavor_header
78
from uipath_langchain_client.settings import (
8-
ApiFlavor,
99
ApiType,
1010
RoutingMode,
1111
UiPathAPIConfig,
@@ -42,24 +42,14 @@ class UiPathAzureAIChatCompletionsModel(UiPathBaseChatModel, AzureAIOpenAIApiCha
4242
def setup_uipath_client(self) -> Self:
4343
base_url = str(self.uipath_sync_client.base_url).rstrip("/")
4444

45-
def fix_url_and_api_flavor_header(request: Request):
46-
url_suffix = str(request.url).split(base_url)[-1]
47-
if "responses" in url_suffix:
48-
request.headers["X-UiPath-LlmGateway-ApiFlavor"] = ApiFlavor.RESPONSES.value
49-
else:
50-
request.headers["X-UiPath-LlmGateway-ApiFlavor"] = ApiFlavor.CHAT_COMPLETIONS.value
51-
request.url = URL(base_url)
45+
def on_request(request: Request) -> None:
46+
fix_url_and_api_flavor_header(base_url, request)
5247

53-
async def fix_url_and_api_flavor_header_async(request: Request):
54-
url_suffix = str(request.url).split(base_url)[-1]
55-
if "responses" in url_suffix:
56-
request.headers["X-UiPath-LlmGateway-ApiFlavor"] = ApiFlavor.RESPONSES.value
57-
else:
58-
request.headers["X-UiPath-LlmGateway-ApiFlavor"] = ApiFlavor.CHAT_COMPLETIONS.value
59-
request.url = URL(base_url)
48+
async def on_request_async(request: Request) -> None:
49+
fix_url_and_api_flavor_header(base_url, request)
6050

61-
self.uipath_sync_client.event_hooks["request"].append(fix_url_and_api_flavor_header)
62-
self.uipath_async_client.event_hooks["request"].append(fix_url_and_api_flavor_header_async)
51+
self.uipath_sync_client.event_hooks["request"].append(on_request)
52+
self.uipath_async_client.event_hooks["request"].append(on_request_async)
6353

6454
self.root_client = OpenAI(
6555
api_key="PLACEHOLDER",

packages/uipath_langchain_client/src/uipath_langchain_client/clients/fireworks/embeddings.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
from uipath_langchain_client.base_client import UiPathBaseEmbeddings
66
from uipath_langchain_client.settings import (
7-
ApiFlavor,
87
ApiType,
98
RoutingMode,
109
UiPathAPIConfig,
@@ -26,7 +25,6 @@ class UiPathFireworksEmbeddings(UiPathBaseEmbeddings, FireworksEmbeddings):
2625
api_type=ApiType.EMBEDDINGS,
2726
routing_mode=RoutingMode.PASSTHROUGH,
2827
vendor_type=VendorType.OPENAI,
29-
api_flavor=ApiFlavor.CHAT_COMPLETIONS,
3028
api_version="2025-03-01-preview",
3129
freeze_base_url=True,
3230
)

packages/uipath_langchain_client/src/uipath_langchain_client/clients/normalized/embeddings.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
1-
from langchain_core.embeddings import Embeddings
2-
31
from uipath_langchain_client.base_client import UiPathBaseEmbeddings
42
from uipath_langchain_client.settings import ApiType, RoutingMode, UiPathAPIConfig
53

64

7-
class UiPathEmbeddings(UiPathBaseEmbeddings, Embeddings):
5+
class UiPathEmbeddings(UiPathBaseEmbeddings):
86
"""LangChain embeddings using the UiPath's normalized embeddings API.
97
108
Provides a consistent interface for generating text embeddings across all

packages/uipath_langchain_client/src/uipath_langchain_client/clients/openai/chat_models.py

Lines changed: 18 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
from collections.abc import Awaitable, Callable
22
from typing import Self
33

4-
from httpx import URL, Request
4+
from httpx import Request
55
from pydantic import Field, SecretStr, model_validator
66

77
from uipath_langchain_client.base_client import UiPathBaseChatModel
8+
from uipath_langchain_client.clients.openai.utils import fix_url_and_api_flavor_header
89
from uipath_langchain_client.settings import (
9-
ApiFlavor,
1010
ApiType,
1111
RoutingMode,
1212
UiPathAPIConfig,
@@ -41,24 +41,14 @@ class UiPathChatOpenAI(UiPathBaseChatModel, ChatOpenAI): # type: ignore[overrid
4141
def setup_uipath_client(self) -> Self:
4242
base_url = str(self.uipath_sync_client.base_url).rstrip("/")
4343

44-
def fix_url_and_api_flavor_header(request: Request):
45-
url_suffix = str(request.url).split(base_url)[-1]
46-
if "responses" in url_suffix:
47-
request.headers["X-UiPath-LlmGateway-ApiFlavor"] = ApiFlavor.RESPONSES.value
48-
else:
49-
request.headers["X-UiPath-LlmGateway-ApiFlavor"] = ApiFlavor.CHAT_COMPLETIONS.value
50-
request.url = URL(base_url)
51-
52-
async def fix_url_and_api_flavor_header_async(request: Request):
53-
url_suffix = str(request.url).split(base_url)[-1]
54-
if "responses" in url_suffix:
55-
request.headers["X-UiPath-LlmGateway-ApiFlavor"] = ApiFlavor.RESPONSES.value
56-
else:
57-
request.headers["X-UiPath-LlmGateway-ApiFlavor"] = ApiFlavor.CHAT_COMPLETIONS.value
58-
request.url = URL(base_url)
59-
60-
self.uipath_sync_client.event_hooks["request"].append(fix_url_and_api_flavor_header)
61-
self.uipath_async_client.event_hooks["request"].append(fix_url_and_api_flavor_header_async)
44+
def on_request(request: Request) -> None:
45+
fix_url_and_api_flavor_header(base_url, request)
46+
47+
async def on_request_async(request: Request) -> None:
48+
fix_url_and_api_flavor_header(base_url, request)
49+
50+
self.uipath_sync_client.event_hooks["request"].append(on_request)
51+
self.uipath_async_client.event_hooks["request"].append(on_request_async)
6252

6353
self.root_client = OpenAI(
6454
api_key="PLACEHOLDER",
@@ -95,24 +85,14 @@ class UiPathAzureChatOpenAI(UiPathBaseChatModel, AzureChatOpenAI): # type: igno
9585
def setup_uipath_client(self) -> Self:
9686
base_url = str(self.uipath_sync_client.base_url).rstrip("/")
9787

98-
def fix_url_and_api_flavor_header(request: Request):
99-
url_suffix = str(request.url).split(base_url)[-1]
100-
if "responses" in url_suffix:
101-
request.headers["X-UiPath-LlmGateway-ApiFlavor"] = ApiFlavor.RESPONSES.value
102-
else:
103-
request.headers["X-UiPath-LlmGateway-ApiFlavor"] = ApiFlavor.CHAT_COMPLETIONS.value
104-
request.url = URL(base_url)
105-
106-
async def fix_url_and_api_flavor_header_async(request: Request):
107-
url_suffix = str(request.url).split(base_url)[-1]
108-
if "responses" in url_suffix:
109-
request.headers["X-UiPath-LlmGateway-ApiFlavor"] = ApiFlavor.RESPONSES.value
110-
else:
111-
request.headers["X-UiPath-LlmGateway-ApiFlavor"] = ApiFlavor.CHAT_COMPLETIONS.value
112-
request.url = URL(base_url)
113-
114-
self.uipath_sync_client.event_hooks["request"].append(fix_url_and_api_flavor_header)
115-
self.uipath_async_client.event_hooks["request"].append(fix_url_and_api_flavor_header_async)
88+
def on_request(request: Request) -> None:
89+
fix_url_and_api_flavor_header(base_url, request)
90+
91+
async def on_request_async(request: Request) -> None:
92+
fix_url_and_api_flavor_header(base_url, request)
93+
94+
self.uipath_sync_client.event_hooks["request"].append(on_request)
95+
self.uipath_async_client.event_hooks["request"].append(on_request_async)
11696

11797
self.root_client = AzureOpenAI(
11898
azure_endpoint="PLACEHOLDER",
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
"""Shared utilities for UiPath LangChain provider clients."""
2+
3+
from httpx import URL, Request
4+
5+
from uipath_langchain_client.settings import ApiFlavor
6+
7+
8+
def fix_url_and_api_flavor_header(base_url: str, request: Request) -> None:
9+
"""Detect API flavor from URL suffix and rewrite the URL to the base gateway URL.
10+
11+
Inspects the outgoing request URL to determine whether it targets the
12+
OpenAI *responses* or *chat completions* endpoint and sets the
13+
``X-UiPath-LlmGateway-ApiFlavor`` header accordingly. The request URL
14+
is then collapsed back to *base_url* so that the gateway receives a
15+
clean path.
16+
17+
Args:
18+
base_url: The UiPath gateway base URL to rewrite the request to.
19+
request: The outgoing httpx request (mutated in place).
20+
"""
21+
url_suffix = str(request.url).split(base_url)[-1]
22+
if "responses" in url_suffix:
23+
request.headers["X-UiPath-LlmGateway-ApiFlavor"] = ApiFlavor.RESPONSES.value
24+
else:
25+
request.headers["X-UiPath-LlmGateway-ApiFlavor"] = ApiFlavor.CHAT_COMPLETIONS.value
26+
request.url = URL(base_url)

packages/uipath_langchain_client/src/uipath_langchain_client/factory.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
UiPathBaseEmbeddings,
2828
)
2929
from uipath_langchain_client.settings import (
30-
_API_FLAVOR_TO_VENDOR_TYPE,
30+
API_FLAVOR_TO_VENDOR_TYPE,
3131
ApiFlavor,
3232
RoutingMode,
3333
UiPathBaseSettings,
@@ -73,7 +73,7 @@ def _get_model_info(
7373

7474
if not matching_models:
7575
raise ValueError(
76-
f"Model {model_name} not found in available models the available models are: {[m['modelName'] for m in available_models]}"
76+
f"Model {model_name} not found. Available models are: {[m['modelName'] for m in available_models]}"
7777
)
7878

7979
return matching_models[0]
@@ -143,7 +143,7 @@ def get_chat_model(
143143
discovered_vendor_type = model_info.get("vendor", None)
144144
discovered_api_flavor = model_info.get("apiFlavor", None)
145145
if discovered_vendor_type is None and discovered_api_flavor is not None:
146-
discovered_vendor_type = _API_FLAVOR_TO_VENDOR_TYPE.get(discovered_api_flavor, None)
146+
discovered_vendor_type = API_FLAVOR_TO_VENDOR_TYPE.get(discovered_api_flavor, None)
147147
if discovered_vendor_type is None:
148148
raise ValueError("No vendor type or api flavor found in model info")
149149
discovered_vendor_type = discovered_vendor_type.lower()
@@ -299,6 +299,10 @@ def get_embedding_model(
299299
)
300300

301301
discovered_vendor_type = model_info.get("vendor")
302+
if discovered_vendor_type is None:
303+
discovered_api_flavor = model_info.get("apiFlavor")
304+
if discovered_api_flavor is not None:
305+
discovered_vendor_type = API_FLAVOR_TO_VENDOR_TYPE.get(discovered_api_flavor)
302306
if discovered_vendor_type is None:
303307
raise ValueError(
304308
f"No vendor type found in model info for embedding model '{model_name}'. "

packages/uipath_langchain_client/src/uipath_langchain_client/settings.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
get_default_client_settings,
2424
)
2525
from uipath.llm_client.settings.constants import (
26-
_API_FLAVOR_TO_VENDOR_TYPE,
26+
API_FLAVOR_TO_VENDOR_TYPE,
2727
ApiFlavor,
2828
ApiType,
2929
RoutingMode,
@@ -40,5 +40,5 @@
4040
"RoutingMode",
4141
"ApiFlavor",
4242
"VendorType",
43-
"_API_FLAVOR_TO_VENDOR_TYPE",
43+
"API_FLAVOR_TO_VENDOR_TYPE",
4444
]

src/uipath/llm_client/__init__.py

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,22 @@
99
- uipath_llamaindex_client: LlamaIndex-compatible models
1010
1111
Quick Start:
12-
>>> from uipath.llm_client import UiPathBaseLLMClient, UiPathAPIConfig
13-
>>> from uipath.llm_client.settings import get_default_client_settings
12+
>>> from uipath.llm_client import UiPathHttpxClient
13+
>>> from uipath.llm_client.settings import get_default_client_settings, UiPathAPIConfig
14+
>>> from uipath.llm_client.settings.constants import ApiType, RoutingMode
1415
>>>
1516
>>> settings = get_default_client_settings()
16-
>>> client = UiPathBaseLLMClient(
17-
... model="gpt-4o-2024-11-20",
18-
... api_config=UiPathAPIConfig(
19-
... api_type=ApiType.COMPLETIONS,
20-
... routing_mode=RoutingMode.PASSTHROUGH,
21-
... vendor_type="openai",
22-
... ),
23-
... settings=settings,
17+
>>> api_config = UiPathAPIConfig(
18+
... api_type=ApiType.COMPLETIONS,
19+
... routing_mode=RoutingMode.PASSTHROUGH,
20+
... vendor_type="openai",
21+
... )
22+
>>> client = UiPathHttpxClient(
23+
... model_name="gpt-4o-2024-11-20",
24+
... api_config=api_config,
25+
... base_url=settings.build_base_url(model_name="gpt-4o-2024-11-20", api_config=api_config),
26+
... auth=settings.build_auth_pipeline(),
2427
... )
25-
>>> response = client.uipath_request(request_body={...})
2628
"""
2729

2830
from uipath.llm_client.__version__ import __version__

0 commit comments

Comments
 (0)