uipath-langchain 0.1.24__py3-none-any.whl → 0.1.34__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- uipath_langchain/_utils/_request_mixin.py +8 -0
- uipath_langchain/_utils/_settings.py +3 -2
- uipath_langchain/agent/guardrails/__init__.py +0 -16
- uipath_langchain/agent/guardrails/actions/__init__.py +2 -0
- uipath_langchain/agent/guardrails/actions/base_action.py +1 -0
- uipath_langchain/agent/guardrails/actions/block_action.py +2 -1
- uipath_langchain/agent/guardrails/actions/escalate_action.py +243 -35
- uipath_langchain/agent/guardrails/actions/filter_action.py +55 -0
- uipath_langchain/agent/guardrails/actions/log_action.py +2 -1
- uipath_langchain/agent/guardrails/guardrail_nodes.py +186 -22
- uipath_langchain/agent/guardrails/guardrails_factory.py +200 -4
- uipath_langchain/agent/guardrails/types.py +0 -12
- uipath_langchain/agent/guardrails/utils.py +146 -0
- uipath_langchain/agent/react/agent.py +25 -8
- uipath_langchain/agent/react/constants.py +1 -2
- uipath_langchain/agent/{guardrails → react/guardrails}/guardrails_subgraph.py +94 -19
- uipath_langchain/agent/react/llm_node.py +41 -10
- uipath_langchain/agent/react/router.py +48 -37
- uipath_langchain/agent/react/types.py +15 -1
- uipath_langchain/agent/react/utils.py +1 -1
- uipath_langchain/agent/tools/__init__.py +2 -0
- uipath_langchain/agent/tools/mcp_tool.py +86 -0
- uipath_langchain/chat/__init__.py +4 -0
- uipath_langchain/chat/bedrock.py +16 -0
- uipath_langchain/chat/openai.py +57 -26
- uipath_langchain/chat/supported_models.py +9 -0
- uipath_langchain/chat/vertex.py +271 -0
- uipath_langchain/embeddings/embeddings.py +18 -12
- uipath_langchain/runtime/schema.py +116 -23
- {uipath_langchain-0.1.24.dist-info → uipath_langchain-0.1.34.dist-info}/METADATA +9 -6
- {uipath_langchain-0.1.24.dist-info → uipath_langchain-0.1.34.dist-info}/RECORD +34 -31
- uipath_langchain/chat/gemini.py +0 -330
- {uipath_langchain-0.1.24.dist-info → uipath_langchain-0.1.34.dist-info}/WHEEL +0 -0
- {uipath_langchain-0.1.24.dist-info → uipath_langchain-0.1.34.dist-info}/entry_points.txt +0 -0
- {uipath_langchain-0.1.24.dist-info → uipath_langchain-0.1.34.dist-info}/licenses/LICENSE +0 -0
uipath_langchain/chat/bedrock.py
CHANGED
|
@@ -48,10 +48,14 @@ class AwsBedrockCompletionsPassthroughClient:
|
|
|
48
48
|
model: str,
|
|
49
49
|
token: str,
|
|
50
50
|
api_flavor: str,
|
|
51
|
+
agenthub_config: Optional[str] = None,
|
|
52
|
+
byo_connection_id: Optional[str] = None,
|
|
51
53
|
):
|
|
52
54
|
self.model = model
|
|
53
55
|
self.token = token
|
|
54
56
|
self.api_flavor = api_flavor
|
|
57
|
+
self.agenthub_config = agenthub_config
|
|
58
|
+
self.byo_connection_id = byo_connection_id
|
|
55
59
|
self._vendor = "awsbedrock"
|
|
56
60
|
self._url: Optional[str] = None
|
|
57
61
|
|
|
@@ -101,6 +105,10 @@ class AwsBedrockCompletionsPassthroughClient:
|
|
|
101
105
|
"X-UiPath-Streaming-Enabled": streaming,
|
|
102
106
|
}
|
|
103
107
|
|
|
108
|
+
if self.agenthub_config:
|
|
109
|
+
headers["X-UiPath-AgentHub-Config"] = self.agenthub_config
|
|
110
|
+
if self.byo_connection_id:
|
|
111
|
+
headers["X-UiPath-LlmGateway-ByoIsConnectionId"] = self.byo_connection_id
|
|
104
112
|
job_key = os.getenv("UIPATH_JOB_KEY")
|
|
105
113
|
process_key = os.getenv("UIPATH_PROCESS_KEY")
|
|
106
114
|
if job_key:
|
|
@@ -118,6 +126,8 @@ class UiPathChatBedrockConverse(ChatBedrockConverse):
|
|
|
118
126
|
tenant_id: Optional[str] = None,
|
|
119
127
|
token: Optional[str] = None,
|
|
120
128
|
model_name: str = BedrockModels.anthropic_claude_haiku_4_5,
|
|
129
|
+
agenthub_config: Optional[str] = None,
|
|
130
|
+
byo_connection_id: Optional[str] = None,
|
|
121
131
|
**kwargs,
|
|
122
132
|
):
|
|
123
133
|
org_id = org_id or os.getenv("UIPATH_ORGANIZATION_ID")
|
|
@@ -141,6 +151,8 @@ class UiPathChatBedrockConverse(ChatBedrockConverse):
|
|
|
141
151
|
model=model_name,
|
|
142
152
|
token=token,
|
|
143
153
|
api_flavor="converse",
|
|
154
|
+
agenthub_config=agenthub_config,
|
|
155
|
+
byo_connection_id=byo_connection_id,
|
|
144
156
|
)
|
|
145
157
|
|
|
146
158
|
client = passthrough_client.get_client()
|
|
@@ -156,6 +168,8 @@ class UiPathChatBedrock(ChatBedrock):
|
|
|
156
168
|
tenant_id: Optional[str] = None,
|
|
157
169
|
token: Optional[str] = None,
|
|
158
170
|
model_name: str = BedrockModels.anthropic_claude_haiku_4_5,
|
|
171
|
+
agenthub_config: Optional[str] = None,
|
|
172
|
+
byo_connection_id: Optional[str] = None,
|
|
159
173
|
**kwargs,
|
|
160
174
|
):
|
|
161
175
|
org_id = org_id or os.getenv("UIPATH_ORGANIZATION_ID")
|
|
@@ -179,6 +193,8 @@ class UiPathChatBedrock(ChatBedrock):
|
|
|
179
193
|
model=model_name,
|
|
180
194
|
token=token,
|
|
181
195
|
api_flavor="invoke",
|
|
196
|
+
agenthub_config=agenthub_config,
|
|
197
|
+
byo_connection_id=byo_connection_id,
|
|
182
198
|
)
|
|
183
199
|
|
|
184
200
|
client = passthrough_client.get_client()
|
uipath_langchain/chat/openai.py
CHANGED
|
@@ -4,6 +4,7 @@ from typing import Optional
|
|
|
4
4
|
|
|
5
5
|
import httpx
|
|
6
6
|
from langchain_openai import AzureChatOpenAI
|
|
7
|
+
from uipath._utils._ssl_context import get_httpx_client_kwargs
|
|
7
8
|
from uipath.utils import EndpointManager
|
|
8
9
|
|
|
9
10
|
from .supported_models import OpenAIModels
|
|
@@ -11,21 +12,41 @@ from .supported_models import OpenAIModels
|
|
|
11
12
|
logger = logging.getLogger(__name__)
|
|
12
13
|
|
|
13
14
|
|
|
15
|
+
def _rewrite_openai_url(
|
|
16
|
+
original_url: str, params: httpx.QueryParams
|
|
17
|
+
) -> httpx.URL | None:
|
|
18
|
+
"""Rewrite OpenAI URLs to UiPath gateway completions endpoint.
|
|
19
|
+
|
|
20
|
+
Handles three URL patterns:
|
|
21
|
+
- responses: false -> .../openai/deployments/.../chat/completions?api-version=...
|
|
22
|
+
- responses: true -> .../openai/responses?api-version=...
|
|
23
|
+
- responses API base -> .../{model}?api-version=... (no /openai/ path)
|
|
24
|
+
|
|
25
|
+
All are rewritten to .../completions
|
|
26
|
+
"""
|
|
27
|
+
if "/openai/deployments/" in original_url:
|
|
28
|
+
base_url = original_url.split("/openai/deployments/")[0]
|
|
29
|
+
elif "/openai/responses" in original_url:
|
|
30
|
+
base_url = original_url.split("/openai/responses")[0]
|
|
31
|
+
else:
|
|
32
|
+
# Handle base URL case (no /openai/ path appended yet)
|
|
33
|
+
# Strip query string to get base URL
|
|
34
|
+
base_url = original_url.split("?")[0]
|
|
35
|
+
|
|
36
|
+
new_url_str = f"{base_url}/completions"
|
|
37
|
+
if params:
|
|
38
|
+
return httpx.URL(new_url_str, params=params)
|
|
39
|
+
return httpx.URL(new_url_str)
|
|
40
|
+
|
|
41
|
+
|
|
14
42
|
class UiPathURLRewriteTransport(httpx.AsyncHTTPTransport):
|
|
15
43
|
def __init__(self, verify: bool = True, **kwargs):
|
|
16
44
|
super().__init__(verify=verify, **kwargs)
|
|
17
45
|
|
|
18
46
|
async def handle_async_request(self, request: httpx.Request) -> httpx.Response:
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
base_url = original_url.split("/openai/deployments/")[0]
|
|
23
|
-
query_string = request.url.params
|
|
24
|
-
new_url_str = f"{base_url}/completions"
|
|
25
|
-
if query_string:
|
|
26
|
-
request.url = httpx.URL(new_url_str, params=query_string)
|
|
27
|
-
else:
|
|
28
|
-
request.url = httpx.URL(new_url_str)
|
|
47
|
+
new_url = _rewrite_openai_url(str(request.url), request.url.params)
|
|
48
|
+
if new_url:
|
|
49
|
+
request.url = new_url
|
|
29
50
|
|
|
30
51
|
return await super().handle_async_request(request)
|
|
31
52
|
|
|
@@ -35,16 +56,9 @@ class UiPathSyncURLRewriteTransport(httpx.HTTPTransport):
|
|
|
35
56
|
super().__init__(verify=verify, **kwargs)
|
|
36
57
|
|
|
37
58
|
def handle_request(self, request: httpx.Request) -> httpx.Response:
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
base_url = original_url.split("/openai/deployments/")[0]
|
|
42
|
-
query_string = request.url.params
|
|
43
|
-
new_url_str = f"{base_url}/completions"
|
|
44
|
-
if query_string:
|
|
45
|
-
request.url = httpx.URL(new_url_str, params=query_string)
|
|
46
|
-
else:
|
|
47
|
-
request.url = httpx.URL(new_url_str)
|
|
59
|
+
new_url = _rewrite_openai_url(str(request.url), request.url.params)
|
|
60
|
+
if new_url:
|
|
61
|
+
request.url = new_url
|
|
48
62
|
|
|
49
63
|
return super().handle_request(request)
|
|
50
64
|
|
|
@@ -57,6 +71,9 @@ class UiPathChatOpenAI(AzureChatOpenAI):
|
|
|
57
71
|
api_version: str = "2024-12-01-preview",
|
|
58
72
|
org_id: Optional[str] = None,
|
|
59
73
|
tenant_id: Optional[str] = None,
|
|
74
|
+
agenthub_config: Optional[str] = None,
|
|
75
|
+
extra_headers: Optional[dict[str, str]] = None,
|
|
76
|
+
byo_connection_id: Optional[str] = None,
|
|
60
77
|
**kwargs,
|
|
61
78
|
):
|
|
62
79
|
org_id = org_id or os.getenv("UIPATH_ORGANIZATION_ID")
|
|
@@ -80,18 +97,24 @@ class UiPathChatOpenAI(AzureChatOpenAI):
|
|
|
80
97
|
self._vendor = "openai"
|
|
81
98
|
self._model_name = model_name
|
|
82
99
|
self._url: Optional[str] = None
|
|
100
|
+
self._agenthub_config = agenthub_config
|
|
101
|
+
self._byo_connection_id = byo_connection_id
|
|
102
|
+
self._extra_headers = extra_headers or {}
|
|
103
|
+
|
|
104
|
+
client_kwargs = get_httpx_client_kwargs()
|
|
105
|
+
verify = client_kwargs.get("verify", True)
|
|
83
106
|
|
|
84
107
|
super().__init__(
|
|
85
108
|
azure_endpoint=self._build_base_url(),
|
|
86
109
|
model_name=model_name,
|
|
87
110
|
default_headers=self._build_headers(token),
|
|
88
111
|
http_async_client=httpx.AsyncClient(
|
|
89
|
-
transport=UiPathURLRewriteTransport(verify=
|
|
90
|
-
|
|
112
|
+
transport=UiPathURLRewriteTransport(verify=verify),
|
|
113
|
+
**client_kwargs,
|
|
91
114
|
),
|
|
92
115
|
http_client=httpx.Client(
|
|
93
|
-
transport=UiPathSyncURLRewriteTransport(verify=
|
|
94
|
-
|
|
116
|
+
transport=UiPathSyncURLRewriteTransport(verify=verify),
|
|
117
|
+
**client_kwargs,
|
|
95
118
|
),
|
|
96
119
|
api_key=token,
|
|
97
120
|
api_version=api_version,
|
|
@@ -104,10 +127,18 @@ class UiPathChatOpenAI(AzureChatOpenAI):
|
|
|
104
127
|
"X-UiPath-LlmGateway-ApiFlavor": "auto",
|
|
105
128
|
"Authorization": f"Bearer {token}",
|
|
106
129
|
}
|
|
130
|
+
|
|
131
|
+
if self._agenthub_config:
|
|
132
|
+
headers["X-UiPath-AgentHub-Config"] = self._agenthub_config
|
|
133
|
+
if self._byo_connection_id:
|
|
134
|
+
headers["X-UiPath-LlmGateway-ByoIsConnectionId"] = self._byo_connection_id
|
|
107
135
|
if job_key := os.getenv("UIPATH_JOB_KEY"):
|
|
108
136
|
headers["X-UiPath-JobKey"] = job_key
|
|
109
137
|
if process_key := os.getenv("UIPATH_PROCESS_KEY"):
|
|
110
138
|
headers["X-UiPath-ProcessKey"] = process_key
|
|
139
|
+
|
|
140
|
+
# Allow extra_headers to override defaults
|
|
141
|
+
headers.update(self._extra_headers)
|
|
111
142
|
return headers
|
|
112
143
|
|
|
113
144
|
@property
|
|
@@ -116,9 +147,9 @@ class UiPathChatOpenAI(AzureChatOpenAI):
|
|
|
116
147
|
formatted_endpoint = vendor_endpoint.format(
|
|
117
148
|
vendor=self._vendor,
|
|
118
149
|
model=self._model_name,
|
|
119
|
-
api_version=self._openai_api_version,
|
|
120
150
|
)
|
|
121
|
-
|
|
151
|
+
base_endpoint = formatted_endpoint.replace("/completions", "")
|
|
152
|
+
return f"{base_endpoint}?api-version={self._openai_api_version}"
|
|
122
153
|
|
|
123
154
|
def _build_base_url(self) -> str:
|
|
124
155
|
if not self._url:
|
|
@@ -21,14 +21,21 @@ class OpenAIModels:
|
|
|
21
21
|
# GPT-5.1 models
|
|
22
22
|
gpt_5_1_2025_11_13 = "gpt-5.1-2025-11-13"
|
|
23
23
|
|
|
24
|
+
# GPT-5.2 models
|
|
25
|
+
gpt_5_2_2025_12_11 = "gpt-5.2-2025-12-11"
|
|
26
|
+
|
|
24
27
|
|
|
25
28
|
class GeminiModels:
|
|
26
29
|
"""Supported Google Gemini model identifiers."""
|
|
27
30
|
|
|
31
|
+
# Gemini 2 models
|
|
28
32
|
gemini_2_5_pro = "gemini-2.5-pro"
|
|
29
33
|
gemini_2_5_flash = "gemini-2.5-flash"
|
|
30
34
|
gemini_2_0_flash_001 = "gemini-2.0-flash-001"
|
|
31
35
|
|
|
36
|
+
# Gemini 3 models
|
|
37
|
+
gemini_3_pro_preview = "gemini-3-pro-preview"
|
|
38
|
+
|
|
32
39
|
|
|
33
40
|
class BedrockModels:
|
|
34
41
|
"""Supported AWS Bedrock model identifiers."""
|
|
@@ -38,5 +45,7 @@ class BedrockModels:
|
|
|
38
45
|
|
|
39
46
|
# Claude 4 models
|
|
40
47
|
anthropic_claude_sonnet_4 = "anthropic.claude-sonnet-4-20250514-v1:0"
|
|
48
|
+
|
|
49
|
+
# Claude 4.5 models
|
|
41
50
|
anthropic_claude_sonnet_4_5 = "anthropic.claude-sonnet-4-5-20250929-v1:0"
|
|
42
51
|
anthropic_claude_haiku_4_5 = "anthropic.claude-haiku-4-5-20251001-v1:0"
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from typing import Any, Optional
|
|
3
|
+
|
|
4
|
+
import httpx
|
|
5
|
+
from uipath._utils._ssl_context import get_httpx_client_kwargs
|
|
6
|
+
from uipath.utils import EndpointManager
|
|
7
|
+
|
|
8
|
+
from .supported_models import GeminiModels
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _check_genai_dependencies() -> None:
|
|
12
|
+
"""Check if required dependencies for UiPathChatVertex are installed."""
|
|
13
|
+
import importlib.util
|
|
14
|
+
|
|
15
|
+
missing_packages = []
|
|
16
|
+
|
|
17
|
+
if importlib.util.find_spec("langchain_google_genai") is None:
|
|
18
|
+
missing_packages.append("langchain-google-genai")
|
|
19
|
+
|
|
20
|
+
if importlib.util.find_spec("google.genai") is None:
|
|
21
|
+
missing_packages.append("google-genai")
|
|
22
|
+
|
|
23
|
+
if missing_packages:
|
|
24
|
+
packages_str = ", ".join(missing_packages)
|
|
25
|
+
raise ImportError(
|
|
26
|
+
f"The following packages are required to use UiPathChatVertex: {packages_str}\n"
|
|
27
|
+
"Please install them using one of the following methods:\n\n"
|
|
28
|
+
" # Using pip:\n"
|
|
29
|
+
f" pip install uipath-langchain[vertex]\n\n"
|
|
30
|
+
" # Using uv:\n"
|
|
31
|
+
f" uv add 'uipath-langchain[vertex]'\n\n"
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
_check_genai_dependencies()
|
|
36
|
+
|
|
37
|
+
import google.genai
|
|
38
|
+
from google.genai import types as genai_types
|
|
39
|
+
from langchain_google_genai import ChatGoogleGenerativeAI
|
|
40
|
+
from pydantic import PrivateAttr
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _rewrite_vertex_url(original_url: str, gateway_url: str) -> httpx.URL | None:
|
|
44
|
+
"""Rewrite Google GenAI URLs to UiPath gateway endpoint.
|
|
45
|
+
|
|
46
|
+
Handles URL patterns containing generateContent or streamGenerateContent.
|
|
47
|
+
Returns the gateway URL, or None if no rewrite needed.
|
|
48
|
+
"""
|
|
49
|
+
if "generateContent" in original_url or "streamGenerateContent" in original_url:
|
|
50
|
+
return httpx.URL(gateway_url)
|
|
51
|
+
return None
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class _UrlRewriteTransport(httpx.HTTPTransport):
|
|
55
|
+
"""Transport that rewrites URLs to redirect to UiPath gateway."""
|
|
56
|
+
|
|
57
|
+
def __init__(self, gateway_url: str, verify: bool = True):
|
|
58
|
+
super().__init__(verify=verify)
|
|
59
|
+
self.gateway_url = gateway_url
|
|
60
|
+
|
|
61
|
+
def handle_request(self, request: httpx.Request) -> httpx.Response:
|
|
62
|
+
original_url = str(request.url)
|
|
63
|
+
new_url = _rewrite_vertex_url(original_url, self.gateway_url)
|
|
64
|
+
if new_url:
|
|
65
|
+
# Set streaming header based on original URL before modifying
|
|
66
|
+
is_streaming = "alt=sse" in original_url
|
|
67
|
+
request.headers["X-UiPath-Streaming-Enabled"] = (
|
|
68
|
+
"true" if is_streaming else "false"
|
|
69
|
+
)
|
|
70
|
+
# Update host header to match the new URL
|
|
71
|
+
request.headers["host"] = new_url.host
|
|
72
|
+
request.url = new_url
|
|
73
|
+
return super().handle_request(request)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class _AsyncUrlRewriteTransport(httpx.AsyncHTTPTransport):
|
|
77
|
+
"""Async transport that rewrites URLs to redirect to UiPath gateway."""
|
|
78
|
+
|
|
79
|
+
def __init__(self, gateway_url: str, verify: bool = True):
|
|
80
|
+
super().__init__(verify=verify)
|
|
81
|
+
self.gateway_url = gateway_url
|
|
82
|
+
|
|
83
|
+
async def handle_async_request(self, request: httpx.Request) -> httpx.Response:
|
|
84
|
+
original_url = str(request.url)
|
|
85
|
+
new_url = _rewrite_vertex_url(original_url, self.gateway_url)
|
|
86
|
+
if new_url:
|
|
87
|
+
# Set streaming header based on original URL before modifying
|
|
88
|
+
is_streaming = "alt=sse" in original_url
|
|
89
|
+
request.headers["X-UiPath-Streaming-Enabled"] = (
|
|
90
|
+
"true" if is_streaming else "false"
|
|
91
|
+
)
|
|
92
|
+
# Update host header to match the new URL
|
|
93
|
+
request.headers["host"] = new_url.host
|
|
94
|
+
request.url = new_url
|
|
95
|
+
return await super().handle_async_request(request)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class UiPathChatVertex(ChatGoogleGenerativeAI):
|
|
99
|
+
"""UiPath Vertex AI Chat model that routes requests through UiPath's LLM Gateway."""
|
|
100
|
+
|
|
101
|
+
_vendor: str = PrivateAttr(default="vertexai")
|
|
102
|
+
_model_name: str = PrivateAttr()
|
|
103
|
+
_uipath_token: str = PrivateAttr()
|
|
104
|
+
_uipath_llmgw_url: Optional[str] = PrivateAttr(default=None)
|
|
105
|
+
_agenthub_config: Optional[str] = PrivateAttr(default=None)
|
|
106
|
+
_byo_connection_id: Optional[str] = PrivateAttr(default=None)
|
|
107
|
+
|
|
108
|
+
def __init__(
|
|
109
|
+
self,
|
|
110
|
+
org_id: Optional[str] = None,
|
|
111
|
+
tenant_id: Optional[str] = None,
|
|
112
|
+
token: Optional[str] = None,
|
|
113
|
+
model_name: str = GeminiModels.gemini_2_5_flash,
|
|
114
|
+
temperature: Optional[float] = None,
|
|
115
|
+
agenthub_config: Optional[str] = None,
|
|
116
|
+
byo_connection_id: Optional[str] = None,
|
|
117
|
+
**kwargs: Any,
|
|
118
|
+
):
|
|
119
|
+
org_id = org_id or os.getenv("UIPATH_ORGANIZATION_ID")
|
|
120
|
+
tenant_id = tenant_id or os.getenv("UIPATH_TENANT_ID")
|
|
121
|
+
token = token or os.getenv("UIPATH_ACCESS_TOKEN")
|
|
122
|
+
|
|
123
|
+
if not org_id:
|
|
124
|
+
raise ValueError(
|
|
125
|
+
"UIPATH_ORGANIZATION_ID environment variable or org_id parameter is required"
|
|
126
|
+
)
|
|
127
|
+
if not tenant_id:
|
|
128
|
+
raise ValueError(
|
|
129
|
+
"UIPATH_TENANT_ID environment variable or tenant_id parameter is required"
|
|
130
|
+
)
|
|
131
|
+
if not token:
|
|
132
|
+
raise ValueError(
|
|
133
|
+
"UIPATH_ACCESS_TOKEN environment variable or token parameter is required"
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
uipath_url = self._build_base_url(model_name)
|
|
137
|
+
headers = self._build_headers(token, agenthub_config, byo_connection_id)
|
|
138
|
+
|
|
139
|
+
client_kwargs = get_httpx_client_kwargs()
|
|
140
|
+
verify = client_kwargs.get("verify", True)
|
|
141
|
+
|
|
142
|
+
http_options = genai_types.HttpOptions(
|
|
143
|
+
httpx_client=httpx.Client(
|
|
144
|
+
transport=_UrlRewriteTransport(uipath_url, verify=verify),
|
|
145
|
+
headers=headers,
|
|
146
|
+
**client_kwargs,
|
|
147
|
+
),
|
|
148
|
+
httpx_async_client=httpx.AsyncClient(
|
|
149
|
+
transport=_AsyncUrlRewriteTransport(uipath_url, verify=verify),
|
|
150
|
+
headers=headers,
|
|
151
|
+
**client_kwargs,
|
|
152
|
+
),
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
if temperature is None and (
|
|
156
|
+
"gemini-3" in model_name or "gemini-2" in model_name
|
|
157
|
+
):
|
|
158
|
+
temperature = 1.0
|
|
159
|
+
|
|
160
|
+
super().__init__(
|
|
161
|
+
model=model_name,
|
|
162
|
+
google_api_key="uipath-gateway",
|
|
163
|
+
temperature=temperature,
|
|
164
|
+
**kwargs,
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
custom_client = google.genai.Client(
|
|
168
|
+
api_key="uipath-gateway",
|
|
169
|
+
http_options=http_options,
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
object.__setattr__(self, "client", custom_client)
|
|
173
|
+
|
|
174
|
+
self._model_name = model_name
|
|
175
|
+
self._uipath_token = token
|
|
176
|
+
self._uipath_llmgw_url = uipath_url
|
|
177
|
+
self._agenthub_config = agenthub_config
|
|
178
|
+
self._byo_connection_id = byo_connection_id
|
|
179
|
+
|
|
180
|
+
if self.temperature is not None and not 0 <= self.temperature <= 2.0:
|
|
181
|
+
raise ValueError("temperature must be in the range [0.0, 2.0]")
|
|
182
|
+
|
|
183
|
+
if self.top_p is not None and not 0 <= self.top_p <= 1:
|
|
184
|
+
raise ValueError("top_p must be in the range [0.0, 1.0]")
|
|
185
|
+
|
|
186
|
+
if self.top_k is not None and self.top_k <= 0:
|
|
187
|
+
raise ValueError("top_k must be positive")
|
|
188
|
+
|
|
189
|
+
additional_headers = self.additional_headers or {}
|
|
190
|
+
self.default_metadata = tuple(additional_headers.items())
|
|
191
|
+
|
|
192
|
+
@staticmethod
|
|
193
|
+
def _build_headers(
|
|
194
|
+
token: str,
|
|
195
|
+
agenthub_config: Optional[str] = None,
|
|
196
|
+
byo_connection_id: Optional[str] = None,
|
|
197
|
+
) -> dict[str, str]:
|
|
198
|
+
"""Build HTTP headers for UiPath Gateway requests."""
|
|
199
|
+
headers = {
|
|
200
|
+
"Authorization": f"Bearer {token}",
|
|
201
|
+
}
|
|
202
|
+
if agenthub_config:
|
|
203
|
+
headers["X-UiPath-AgentHub-Config"] = agenthub_config
|
|
204
|
+
if byo_connection_id:
|
|
205
|
+
headers["X-UiPath-LlmGateway-ByoIsConnectionId"] = byo_connection_id
|
|
206
|
+
if job_key := os.getenv("UIPATH_JOB_KEY"):
|
|
207
|
+
headers["X-UiPath-JobKey"] = job_key
|
|
208
|
+
if process_key := os.getenv("UIPATH_PROCESS_KEY"):
|
|
209
|
+
headers["X-UiPath-ProcessKey"] = process_key
|
|
210
|
+
return headers
|
|
211
|
+
|
|
212
|
+
@staticmethod
|
|
213
|
+
def _build_base_url(model_name: str) -> str:
|
|
214
|
+
"""Build the full URL for the UiPath LLM Gateway."""
|
|
215
|
+
env_uipath_url = os.getenv("UIPATH_URL")
|
|
216
|
+
|
|
217
|
+
if not env_uipath_url:
|
|
218
|
+
raise ValueError("UIPATH_URL environment variable is required")
|
|
219
|
+
|
|
220
|
+
vendor_endpoint = EndpointManager.get_vendor_endpoint()
|
|
221
|
+
formatted_endpoint = vendor_endpoint.format(
|
|
222
|
+
vendor="vertexai",
|
|
223
|
+
model=model_name,
|
|
224
|
+
)
|
|
225
|
+
return f"{env_uipath_url.rstrip('/')}/{formatted_endpoint}"
|
|
226
|
+
|
|
227
|
+
def _stream(self, messages, stop=None, run_manager=None, **kwargs):
|
|
228
|
+
"""Streaming fallback - calls _generate and yields single response."""
|
|
229
|
+
from langchain_core.messages import AIMessageChunk
|
|
230
|
+
from langchain_core.outputs import ChatGenerationChunk
|
|
231
|
+
|
|
232
|
+
result = self._generate(messages, stop=stop, run_manager=run_manager, **kwargs)
|
|
233
|
+
|
|
234
|
+
if result.generations:
|
|
235
|
+
message = result.generations[0].message
|
|
236
|
+
chunk = AIMessageChunk(
|
|
237
|
+
content=message.content,
|
|
238
|
+
additional_kwargs=message.additional_kwargs,
|
|
239
|
+
response_metadata=getattr(message, "response_metadata", {}),
|
|
240
|
+
id=message.id,
|
|
241
|
+
tool_calls=getattr(message, "tool_calls", []),
|
|
242
|
+
tool_call_chunks=getattr(message, "tool_call_chunks", []),
|
|
243
|
+
)
|
|
244
|
+
if hasattr(message, "usage_metadata") and message.usage_metadata:
|
|
245
|
+
chunk.usage_metadata = message.usage_metadata
|
|
246
|
+
|
|
247
|
+
yield ChatGenerationChunk(message=chunk)
|
|
248
|
+
|
|
249
|
+
async def _astream(self, messages, stop=None, run_manager=None, **kwargs):
|
|
250
|
+
"""Async streaming fallback - calls _agenerate and yields single response."""
|
|
251
|
+
from langchain_core.messages import AIMessageChunk
|
|
252
|
+
from langchain_core.outputs import ChatGenerationChunk
|
|
253
|
+
|
|
254
|
+
result = await self._agenerate(
|
|
255
|
+
messages, stop=stop, run_manager=run_manager, **kwargs
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
if result.generations:
|
|
259
|
+
message = result.generations[0].message
|
|
260
|
+
chunk = AIMessageChunk(
|
|
261
|
+
content=message.content,
|
|
262
|
+
additional_kwargs=message.additional_kwargs,
|
|
263
|
+
response_metadata=getattr(message, "response_metadata", {}),
|
|
264
|
+
id=message.id,
|
|
265
|
+
tool_calls=getattr(message, "tool_calls", []),
|
|
266
|
+
tool_call_chunks=getattr(message, "tool_call_chunks", []),
|
|
267
|
+
)
|
|
268
|
+
if hasattr(message, "usage_metadata") and message.usage_metadata:
|
|
269
|
+
chunk.usage_metadata = message.usage_metadata
|
|
270
|
+
|
|
271
|
+
yield ChatGenerationChunk(message=chunk)
|
|
@@ -4,6 +4,7 @@ from typing import Any
|
|
|
4
4
|
import httpx
|
|
5
5
|
from langchain_openai.embeddings import AzureOpenAIEmbeddings, OpenAIEmbeddings
|
|
6
6
|
from pydantic import Field
|
|
7
|
+
from uipath._utils._ssl_context import get_httpx_client_kwargs
|
|
7
8
|
from uipath.utils import EndpointManager
|
|
8
9
|
|
|
9
10
|
from uipath_langchain._utils._request_mixin import UiPathRequestMixin
|
|
@@ -26,19 +27,24 @@ class UiPathAzureOpenAIEmbeddings(UiPathRequestMixin, AzureOpenAIEmbeddings):
|
|
|
26
27
|
)
|
|
27
28
|
|
|
28
29
|
def __init__(self, **kwargs):
|
|
30
|
+
default_client_kwargs = get_httpx_client_kwargs()
|
|
31
|
+
client_kwargs = {
|
|
32
|
+
**default_client_kwargs,
|
|
33
|
+
"event_hooks": {
|
|
34
|
+
"request": [self._log_request_duration],
|
|
35
|
+
"response": [self._log_response_duration],
|
|
36
|
+
},
|
|
37
|
+
}
|
|
38
|
+
aclient_kwargs = {
|
|
39
|
+
**default_client_kwargs,
|
|
40
|
+
"event_hooks": {
|
|
41
|
+
"request": [self._alog_request_duration],
|
|
42
|
+
"response": [self._alog_response_duration],
|
|
43
|
+
},
|
|
44
|
+
}
|
|
29
45
|
super().__init__(
|
|
30
|
-
http_client=httpx.Client(
|
|
31
|
-
|
|
32
|
-
"request": [self._log_request_duration],
|
|
33
|
-
"response": [self._log_response_duration],
|
|
34
|
-
}
|
|
35
|
-
),
|
|
36
|
-
http_async_client=httpx.AsyncClient(
|
|
37
|
-
event_hooks={
|
|
38
|
-
"request": [self._alog_request_duration],
|
|
39
|
-
"response": [self._alog_response_duration],
|
|
40
|
-
}
|
|
41
|
-
),
|
|
46
|
+
http_client=httpx.Client(**client_kwargs),
|
|
47
|
+
http_async_client=httpx.AsyncClient(**aclient_kwargs),
|
|
42
48
|
**kwargs,
|
|
43
49
|
)
|
|
44
50
|
# Monkey-patch the OpenAI client to use your custom methods
|