Skip to content

Commit 283a037

Browse files
committed
gemini fix and toml update
1 parent 795e70a commit 283a037

2 files changed

Lines changed: 10 additions & 154 deletions

File tree

packages/uipath_langchain_client/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ readme = "README.md"
66
requires-python = ">=3.11"
77
dependencies = [
88
"langchain>=1.2.7",
9-
"uipath-llm-client>=1.0.0",
9+
"uipath-llm-client>=1.0.2",
1010
]
1111

1212
[project.optional-dependencies]

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

Lines changed: 9 additions & 153 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
from collections.abc import AsyncIterator, Iterator
21
from typing import Self
32

4-
from httpx import Response
3+
from httpx import URL, Request
54
from pydantic import Field, SecretStr, model_validator
65
from uipath_langchain_client.base_client import UiPathBaseLLMClient
76
from uipath_langchain_client.settings import UiPathAPIConfig
@@ -18,135 +17,6 @@
1817
) from e
1918

2019

21-
def _wrap_iter_lines(original: Iterator[str]) -> Iterator[str]:
22-
"""Wrap iter_lines to extract individual JSON objects from streaming responses.
23-
24-
The LLM Gateway wraps streaming JSON responses in an array like [{...}, {...}].
25-
This extracts each complete JSON object and yields them individually.
26-
Handles multiple JSON objects on a single line (e.g., {...},{...}).
27-
28-
We prefix output with 'data: ' so the SDK's _iter_response_stream bypasses its
29-
broken brace counting (which doesn't handle braces inside strings) and yields
30-
our JSON objects directly.
31-
32-
Temporal Fix until it's fixed in the main package.
33-
"""
34-
buffer = ""
35-
balance = 0
36-
in_string = False
37-
escape_next = False
38-
39-
for line in original:
40-
# Handle data: prefix (SSE format)
41-
if line.startswith("data:"):
42-
line = line[5:].lstrip()
43-
44-
for char in line:
45-
# Handle escape sequences in strings
46-
if escape_next:
47-
buffer += char
48-
escape_next = False
49-
continue
50-
51-
if char == "\\" and in_string:
52-
buffer += char
53-
escape_next = True
54-
continue
55-
56-
if char == '"':
57-
in_string = not in_string
58-
buffer += char
59-
continue
60-
61-
# Only track braces outside of strings
62-
if not in_string:
63-
if char == "{":
64-
balance += 1
65-
buffer += char
66-
elif char == "}":
67-
buffer += char
68-
balance -= 1
69-
if balance == 0 and buffer:
70-
# Complete JSON object found - yield with 'data: ' prefix
71-
# so SDK bypasses its broken brace counting
72-
yield "data: " + buffer
73-
buffer = ""
74-
elif balance == 0:
75-
# Skip characters outside JSON objects (array brackets, commas, whitespace)
76-
continue
77-
else:
78-
buffer += char
79-
else:
80-
buffer += char
81-
82-
# Yield any remaining buffer (handles incomplete streams)
83-
if buffer:
84-
yield "data: " + buffer
85-
86-
87-
async def _wrap_aiter_lines(original: AsyncIterator[str]) -> AsyncIterator[str]:
88-
"""Async version of _wrap_iter_lines.
89-
90-
Extracts individual JSON objects from streaming responses.
91-
Handles multiple JSON objects on a single line (e.g., {...},{...}).
92-
93-
We prefix output with 'data: ' so the SDK's _iter_response_stream bypasses its
94-
broken brace counting (which doesn't handle braces inside strings) and yields
95-
our JSON objects directly.
96-
"""
97-
buffer = ""
98-
balance = 0
99-
in_string = False
100-
escape_next = False
101-
102-
async for line in original:
103-
# Handle data: prefix (SSE format)
104-
if line.startswith("data:"):
105-
line = line[5:].lstrip()
106-
107-
for char in line:
108-
# Handle escape sequences in strings
109-
if escape_next:
110-
buffer += char
111-
escape_next = False
112-
continue
113-
114-
if char == "\\" and in_string:
115-
buffer += char
116-
escape_next = True
117-
continue
118-
119-
if char == '"':
120-
in_string = not in_string
121-
buffer += char
122-
continue
123-
124-
# Only track braces outside of strings
125-
if not in_string:
126-
if char == "{":
127-
balance += 1
128-
buffer += char
129-
elif char == "}":
130-
buffer += char
131-
balance -= 1
132-
if balance == 0 and buffer:
133-
# Complete JSON object found - yield with 'data: ' prefix
134-
# so SDK bypasses its broken brace counting
135-
yield "data: " + buffer
136-
buffer = ""
137-
elif balance == 0:
138-
# Skip characters outside JSON objects (array brackets, commas, whitespace)
139-
continue
140-
else:
141-
buffer += char
142-
else:
143-
buffer += char
144-
145-
# Yield any remaining buffer (handles incomplete streams)
146-
if buffer:
147-
yield "data: " + buffer
148-
149-
15020
class UiPathChatGoogleGenerativeAI(UiPathBaseLLMClient, ChatGoogleGenerativeAI):
15121
api_config: UiPathAPIConfig = UiPathAPIConfig(
15222
api_type="completions",
@@ -163,30 +33,16 @@ class UiPathChatGoogleGenerativeAI(UiPathBaseLLMClient, ChatGoogleGenerativeAI):
16333

16434
@model_validator(mode="after")
16535
def setup_uipath_client(self) -> Self:
166-
def fix_streaming_response(response: Response):
167-
"""Monkey-patch iter_lines to strip JSON array brackets."""
168-
original_iter_lines = response.iter_lines
169-
response.iter_lines = lambda: _wrap_iter_lines(original_iter_lines())
170-
171-
async def fix_streaming_response_async(response: Response):
172-
"""Monkey-patch aiter_lines to strip JSON array brackets."""
173-
original_aiter_lines = response.aiter_lines
174-
response.aiter_lines = lambda: _wrap_aiter_lines(original_aiter_lines())
175-
176-
self.uipath_sync_client.event_hooks["response"].append(fix_streaming_response)
177-
self.uipath_async_client.event_hooks["response"].append(fix_streaming_response_async)
178-
179-
# TODO: in exactly 2 weeks, we need to uncomment this part of the code because it will work, 5 february 2026 is the date.
180-
# def fix_url_for_streaming(request: Request):
181-
# if request.headers.get("X-UiPath-Streaming-Enabled") == "true":
182-
# request.url = URL(request.url).copy_add_param("alt", "sse")
36+
def fix_url_for_streaming(request: Request):
37+
if request.headers.get("X-UiPath-Streaming-Enabled") == "true":
38+
request.url = URL(request.url).copy_add_param("alt", "sse")
18339

184-
# async def fix_url_for_streaming_async(request: Request):
185-
# if request.headers.get("X-UiPath-Streaming-Enabled") == "true":
186-
# request.url = URL(request.url).copy_add_param("alt", "sse")
40+
async def fix_url_for_streaming_async(request: Request):
41+
if request.headers.get("X-UiPath-Streaming-Enabled") == "true":
42+
request.url = URL(request.url).copy_add_param("alt", "sse")
18743

188-
# self.uipath_sync_client.event_hooks["request"].append(fix_url_for_streaming)
189-
# self.uipath_async_client.event_hooks["request"].append(fix_url_for_streaming_async)
44+
self.uipath_sync_client.event_hooks["request"].append(fix_url_for_streaming)
45+
self.uipath_async_client.event_hooks["request"].append(fix_url_for_streaming_async)
19046

19147
self.client = Client(
19248
vertexai=True,

0 commit comments

Comments
 (0)