1- from collections .abc import AsyncIterator , Iterator
21from typing import Self
32
4- from httpx import Response
3+ from httpx import URL , Request
54from pydantic import Field , SecretStr , model_validator
65from uipath_langchain_client .base_client import UiPathBaseLLMClient
76from uipath_langchain_client .settings import UiPathAPIConfig
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-
15020class 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