generate_content_stream fails with UnknownApiResponseError when a streamed text part contains U+2028 / U+2029 / U+0085 (httpx transport)
Maintainer antworten meist innerhalb von 1 Tag
Bewertung
Dieses Issue wurde noch nicht bewertet.
Beschreibung
Environment details
- Programming language: Python
- OS: Windows 11 (transport-level, not OS-specific)
- Language runtime version: 3.14
- Package version: google-genai 2.33.0 (code unchanged on main)
Symptom
If a streamed chunk's JSON contains a raw U+2028 (LINE SEPARATOR), U+2029 or U+0085 (NEL) inside a string, models.generate_content_stream (sync) and aio.models.generate_content_stream with an httpx client raise:
UnknownApiResponseError: Failed to parse response as JSON. Raw response: {"candidates": [{"content": {"role": "model", "parts": [{"text": "line one
The same bytes parse correctly on the aiohttp async path.
Steps to reproduce
Self-contained script with a local SSE server, no API key (below):
sync (httpx) ERR UnknownApiResponseError ... "text": "line one
async (httpx) ERR UnknownApiResponseError ... "text": "line one
async (aiohttp) ['line one
line two', ' done']
The body is valid JSON (RFC 8259 doesn't require escaping these code points), one data: line per event.
repro_genai_linesep.py
"""google-genai: streamed text containing U+2028 / U+2029 / U+0085 breaks SSE parsing (httpx path)."""
import asyncio, json, sys, threading
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
import httpx
from google import genai
TEXT = sys.argv[1].encode().decode("unicode_escape") if len(sys.argv) > 1 else "line one\u2028line two"
CHUNKS = [
{"candidates": [{"content": {"role": "model", "parts": [{"text": TEXT}]}}]},
{"candidates": [{"content": {"role": "model", "parts": [{"text": " done"}]}, "finishReason": "STOP"}]},
]
# Valid JSON (RFC 8259 does not require escaping these code points); one event per line.
BODY = b"".join(b"data: " + json.dumps(c, ensure_ascii=False).encode() + b"\r\n\r\n" for c in CHUNKS)
class H(BaseHTTPRequestHandler):
def do_POST(self):
self.rfile.read(int(self.headers["Content-Length"]))
self.send_response(200)
self.send_header("Content-Type", "text/event-stream")
self.send_header("Content-Length", str(len(BODY)))
self.end_headers()
self.wfile.write(BODY)
def log_message(self, *a): pass
srv = ThreadingHTTPServer(("127.0.0.1", 0), H)
threading.Thread(target=srv.serve_forever, daemon=True).start()
base = f"http://127.0.0.1:{srv.server_port}"
print(f"genai {genai.__version__}, text={TEXT!r}")
def run_sync():
c = genai.Client(api_key="test", http_options={"base_url": base})
return [ch.text for ch in c.models.generate_content_stream(model="gemini-2.5-flash", contents="hi")]
async def run_async(httpx_client):
opts = {"base_url": base}
if httpx_client:
opts["httpx_async_client"] = httpx.AsyncClient()
c = genai.Client(api_key="test", http_options=opts)
return [ch.text async for ch in await c.aio.models.generate_content_stream(model="gemini-2.5-flash", contents="hi")]
for name, fn in [("sync (httpx) ", run_sync),
("async (httpx) ", lambda: asyncio.run(run_async(True))),
("async (aiohttp)", lambda: asyncio.run(run_async(False)))]:
try:
print(name, fn())
except Exception as e:
print(name, "ERR", type(e).__name__, str(e)[:160].replace("\n", " "))
Root cause
google/genai/_api_client.py:488 (iter_lines()) and :545 (aiter_lines()) use httpx's line decoder, which follows str.splitlines() and also breaks on U+2028/U+2029/U+0085 (and , , -). The SSE spec treats only CRLF, LF and CR as line terminators. The aiohttp branch (:587, readline() on bytes) gets this right.
Suggested fix
Split the byte stream on `
/
/
only (e.g. iterateiter_bytes()/aiter_bytes()` through a small line splitter, as the aiohttp branch effectively does), then decode each line.
- Vorherrschende Sprache
- Python
- Sterne
- 4.1k
- Forks
- 1.1k
- Ø Merge
- 1 T. 20 Std.
- Gemergte PRs (30 T.)
- 108
Entwicklungsumgebung
- Kein Dockerfile und keine Docker-Compose-Datei
- Keine Pull-Request-Vorlage
- Beitragsleitfaden lesen
Erste Schritte
- Lesen Sie das ganze Issue und danach den Beitragsleitfaden des Projekts.
- Schreiben Sie ins Issue, dass Sie es übernehmen — das erspart doppelte Arbeit.
- Forken Sie das Repository und arbeiten Sie in einem Branch.
- Öffnen Sie einen Pull Request, der die Issue-Nummer nennt.
Mehr aus googleapis/python-genai
-
Test import module that doesn't existEvtl. vergeben @Venkaiahbabuneelam hat das vor 24 Tagen übernommen. Offenpriority: p2 type: bug
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 66/100
googleapis/python-genai#3122 · 6 Kommentare · 1 zugewiesene Person ·
Maintainer antworten meist innerhalb von 1 Tag
-
Curated history keeps half a user turn when the model turn is invalidEvtl. wieder frei @Venkaiahbabuneelam hat das vor 33 Tagen übernommen, und es ist kein Pull Request offen. Offenpriority: p2 type: bug
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 62/100
googleapis/python-genai#3051 · 3 Kommentare · 1 zugewiesene Person ·
Maintainer antworten meist innerhalb von 1 Tag
-
[Bug]: Unsubscripted typing.List and typing.Dict crash convert_if_exist_pydantic_model in AFCEvtl. vergeben @Venkaiahbabuneelam hat das vor 33 Tagen übernommen. Offenpriority: p2 type: bug
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 82/100
googleapis/python-genai#3044 · 1 Kommentar · 1 zugewiesene Person ·
Maintainer antworten meist innerhalb von 1 Tag
-
SyntaxWarning on import in 3.0.0 (invalid escape sequence in types.py)Evtl. vergeben @VISHNU0906 hat das heute übernommen. Offen
Schwierigkeit 1/5 Unter einer Stunde Anfängerfreundlichkeit 35/100
googleapis/python-genai#3308 ·
Maintainer antworten meist innerhalb von 1 Tag
-
Function declaration rejected: required naming a property defined only in anyOf ("requires unspecified property")Evtl. vergeben @Venkaiahbabuneelam hat das vor 8 Tagen übernommen. Offenpriority: p2 status:awaiting user response type: bug
Schwierigkeit 4/5 3-5 Tage Anfängerfreundlichkeit 47/100
googleapis/python-genai#3250 · 1 Kommentar · 1 zugewiesene Person ·
Maintainer antworten meist innerhalb von 1 Tag
Alle Issues in googleapis/python-genai
Ähnliche Issues
-
binding:python bug
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 82/100
Maintainer antworten meist innerhalb von 1 Tag
-
The Browser tab fails with a Wayland error when Open Terminal runs from a Wayland desktop sessionOffen
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 78/100
open-webui/open-terminal#210 ·
-
triage/confirmed
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 78/100
agentscope-ai/agentscope#3807 ·
Maintainer antworten meist innerhalb von 2 Tagen
-
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 78/100
NousResearch/hermes-agent#150542 ·
Maintainer antworten meist innerhalb von 1 Tag
-
Schwierigkeit 1/5 Unter einer Stunde Anfängerfreundlichkeit 91/100
triton-inference-server/server#9069 ·
Maintainer antworten meist innerhalb von 1 Tag