Skip to content

Commit d1e0b46

Browse files
authored
Merge pull request #352 from UiPath/fix/llm_headers
fix(llm): add headers for agenthub config
2 parents 760dd20 + 3ac0a14 commit d1e0b46

9 files changed

Lines changed: 228 additions & 74 deletions

File tree

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,3 +180,5 @@ cython_debug/
180180
**/__uipath/
181181
**/.langgraph_api
182182
**/testcases/**/uipath.json
183+
184+
/playground.py

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "uipath-langchain"
3-
version = "0.1.31"
3+
version = "0.1.32"
44
description = "Python SDK that enables developers to build and deploy LangGraph agents to the UiPath Cloud Platform"
55
readme = { file = "README.md", content-type = "text/markdown" }
66
requires-python = ">=3.11"

src/uipath_langchain/_utils/_request_mixin.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,8 @@ class UiPathRequestMixin(BaseModel):
137137
max_tokens: int | None = 1000
138138
frequency_penalty: float | None = None
139139
presence_penalty: float | None = None
140+
agenthub_config: str | None = None
141+
byo_connection_id: str | None = None
140142

141143
logger: logging.Logger | None = None
142144
max_retries: int | None = 5
@@ -748,6 +750,12 @@ def auth_headers(self) -> dict[str, str]:
748750
"Authorization": f"Bearer {self.access_token}",
749751
"X-UiPath-LlmGateway-TimeoutSeconds": str(self.default_request_timeout),
750752
}
753+
if self.agenthub_config:
754+
self._auth_headers["X-UiPath-AgentHub-Config"] = self.agenthub_config
755+
if self.byo_connection_id:
756+
self._auth_headers["X-UiPath-LlmGateway-ByoIsConnectionId"] = (
757+
self.byo_connection_id
758+
)
751759
if self.is_normalized and self.model_name:
752760
self._auth_headers["X-UiPath-LlmGateway-NormalizedApi-ModelName"] = (
753761
self.model_name

src/uipath_langchain/chat/bedrock.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,14 @@ def __init__(
4848
model: str,
4949
token: str,
5050
api_flavor: str,
51+
agenthub_config: Optional[str] = None,
52+
byo_connection_id: Optional[str] = None,
5153
):
5254
self.model = model
5355
self.token = token
5456
self.api_flavor = api_flavor
57+
self.agenthub_config = agenthub_config
58+
self.byo_connection_id = byo_connection_id
5559
self._vendor = "awsbedrock"
5660
self._url: Optional[str] = None
5761

@@ -101,6 +105,10 @@ def _modify_request(self, request, **kwargs):
101105
"X-UiPath-Streaming-Enabled": streaming,
102106
}
103107

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
104112
job_key = os.getenv("UIPATH_JOB_KEY")
105113
process_key = os.getenv("UIPATH_PROCESS_KEY")
106114
if job_key:
@@ -118,6 +126,8 @@ def __init__(
118126
tenant_id: Optional[str] = None,
119127
token: Optional[str] = None,
120128
model_name: str = BedrockModels.anthropic_claude_haiku_4_5,
129+
agenthub_config: Optional[str] = None,
130+
byo_connection_id: Optional[str] = None,
121131
**kwargs,
122132
):
123133
org_id = org_id or os.getenv("UIPATH_ORGANIZATION_ID")
@@ -141,6 +151,8 @@ def __init__(
141151
model=model_name,
142152
token=token,
143153
api_flavor="converse",
154+
agenthub_config=agenthub_config,
155+
byo_connection_id=byo_connection_id,
144156
)
145157

146158
client = passthrough_client.get_client()
@@ -156,6 +168,8 @@ def __init__(
156168
tenant_id: Optional[str] = None,
157169
token: Optional[str] = None,
158170
model_name: str = BedrockModels.anthropic_claude_haiku_4_5,
171+
agenthub_config: Optional[str] = None,
172+
byo_connection_id: Optional[str] = None,
159173
**kwargs,
160174
):
161175
org_id = org_id or os.getenv("UIPATH_ORGANIZATION_ID")
@@ -179,6 +193,8 @@ def __init__(
179193
model=model_name,
180194
token=token,
181195
api_flavor="invoke",
196+
agenthub_config=agenthub_config,
197+
byo_connection_id=byo_connection_id,
182198
)
183199

184200
client = passthrough_client.get_client()

src/uipath_langchain/chat/openai.py

Lines changed: 56 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -12,21 +12,41 @@
1212
logger = logging.getLogger(__name__)
1313

1414

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+
1542
class UiPathURLRewriteTransport(httpx.AsyncHTTPTransport):
1643
def __init__(self, verify: bool = True, **kwargs):
1744
super().__init__(verify=verify, **kwargs)
1845

1946
async def handle_async_request(self, request: httpx.Request) -> httpx.Response:
20-
original_url = str(request.url)
21-
22-
if "/openai/deployments/" in original_url:
23-
base_url = original_url.split("/openai/deployments/")[0]
24-
query_string = request.url.params
25-
new_url_str = f"{base_url}/completions"
26-
if query_string:
27-
request.url = httpx.URL(new_url_str, params=query_string)
28-
else:
29-
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
3050

3151
return await super().handle_async_request(request)
3252

@@ -36,16 +56,9 @@ def __init__(self, verify: bool = True, **kwargs):
3656
super().__init__(verify=verify, **kwargs)
3757

3858
def handle_request(self, request: httpx.Request) -> httpx.Response:
39-
original_url = str(request.url)
40-
41-
if "/openai/deployments/" in original_url:
42-
base_url = original_url.split("/openai/deployments/")[0]
43-
query_string = request.url.params
44-
new_url_str = f"{base_url}/completions"
45-
if query_string:
46-
request.url = httpx.URL(new_url_str, params=query_string)
47-
else:
48-
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
4962

5063
return super().handle_request(request)
5164

@@ -58,6 +71,9 @@ def __init__(
5871
api_version: str = "2024-12-01-preview",
5972
org_id: Optional[str] = None,
6073
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,
6177
**kwargs,
6278
):
6379
org_id = org_id or os.getenv("UIPATH_ORGANIZATION_ID")
@@ -81,18 +97,24 @@ def __init__(
8197
self._vendor = "openai"
8298
self._model_name = model_name
8399
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)
84106

85107
super().__init__(
86108
azure_endpoint=self._build_base_url(),
87109
model_name=model_name,
88110
default_headers=self._build_headers(token),
89111
http_async_client=httpx.AsyncClient(
90-
transport=UiPathURLRewriteTransport(verify=True),
91-
**get_httpx_client_kwargs(),
112+
transport=UiPathURLRewriteTransport(verify=verify),
113+
**client_kwargs,
92114
),
93115
http_client=httpx.Client(
94-
transport=UiPathSyncURLRewriteTransport(verify=True),
95-
**get_httpx_client_kwargs(),
116+
transport=UiPathSyncURLRewriteTransport(verify=verify),
117+
**client_kwargs,
96118
),
97119
api_key=token,
98120
api_version=api_version,
@@ -105,10 +127,18 @@ def _build_headers(self, token: str) -> dict[str, str]:
105127
"X-UiPath-LlmGateway-ApiFlavor": "auto",
106128
"Authorization": f"Bearer {token}",
107129
}
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
108135
if job_key := os.getenv("UIPATH_JOB_KEY"):
109136
headers["X-UiPath-JobKey"] = job_key
110137
if process_key := os.getenv("UIPATH_PROCESS_KEY"):
111138
headers["X-UiPath-ProcessKey"] = process_key
139+
140+
# Allow extra_headers to override defaults
141+
headers.update(self._extra_headers)
112142
return headers
113143

114144
@property
@@ -117,9 +147,9 @@ def endpoint(self) -> str:
117147
formatted_endpoint = vendor_endpoint.format(
118148
vendor=self._vendor,
119149
model=self._model_name,
120-
api_version=self._openai_api_version,
121150
)
122-
return formatted_endpoint.replace("/completions", "")
151+
base_endpoint = formatted_endpoint.replace("/completions", "")
152+
return f"{base_endpoint}?api-version={self._openai_api_version}"
123153

124154
def _build_base_url(self) -> str:
125155
if not self._url:

0 commit comments

Comments
 (0)