Skip to content

aio generate_content_stream raises aiohttp LineTooLong when a chunk exceeds 4 MiB (large image output); sync path works #3244

Open

Description

@jaeminbird

Environment details

  • Programming language: Python
  • OS: Windows 11
  • Language runtime version: 3.14
  • Package version: google-genai 2.33.0, aiohttp 3.14.5 (code unchanged on main)

Symptom

With aiohttp installed (the default async transport), client.aio.models.generate_content_stream fails when a single streamed chunk is larger than 4 MiB, e.g. an image model returning a large inlineData part (base64 of a ~3.1 MB+ image):

aiohttp.http_exceptions.LineTooLong: 400, message: Got more than 4194304 bytes when reading: b'data: {"candidates": [{"content": ... "inlineData" ...'

The text chunk before it is delivered; the image chunk is lost. The sync client (httpx) handles the same response.

Steps to reproduce

Self-contained script with a local SSE server, no API key (below):

$ python repro_genai_aiohttp.py 2000000   # 2.54 MiB base64: sync OK, async OK
$ python repro_genai_aiohttp.py 3500000   # 4.45 MiB base64
sync  (httpx):   ['text', 'inline_data 3500004 bytes']
async (aiohttp): ERR LineTooLong ... | chunks before error: ['text']
repro_genai_aiohttp.py
"""google-genai: async streaming fails when one SSE chunk is > 4 MiB (aiohttp path)."""
import asyncio, base64, json, threading
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from google import genai

IMG = base64.b64encode(b"\x89PNG" + b"\0" * (int(__import__("sys").argv[1]) if len(__import__("sys").argv) > 1 else 3_500_000)).decode()
CHUNKS = [
    {"candidates": [{"content": {"role": "model", "parts": [{"text": "Here is your image."}]}}]},
    {"candidates": [{"content": {"role": "model", "parts": [{"inlineData": {"mimeType": "image/png", "data": IMG}}]}, "finishReason": "STOP"}],
     "usageMetadata": {"promptTokenCount": 5, "candidatesTokenCount": 1290, "totalTokenCount": 1295}},
]
BODY = b"".join(b"data: " + json.dumps(c).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()
opts = {"base_url": f"http://127.0.0.1:{srv.server_port}"}
client = genai.Client(api_key="test", http_options=opts)
print(f"genai {genai.__version__}, base64 image = {len(IMG)/2**20:.2f} MiB")

def summarize(chunks):
    return [("text" if p.text else f"inline_data {len(p.inline_data.data)} bytes") for c in chunks for p in c.candidates[0].content.parts]

try:
    print("sync  (httpx):  ", summarize(list(client.models.generate_content_stream(model="gemini-3-pro-image-preview", contents="draw"))))
except Exception as e:
    print("sync  (httpx):   ERR", type(e).__name__, str(e)[:200])

async def main():
    out = []
    try:
        async for c in await client.aio.models.generate_content_stream(model="gemini-3-pro-image-preview", contents="draw"):
            out.append(c)
        print("async (aiohttp):", summarize(out))
    except Exception as e:
        print("async (aiohttp): ERR", type(e).__name__, str(e)[:200], "| chunks before error:", summarize(out))
asyncio.run(main())

Root cause

google/genai/_api_client.py:587-589 reads each SSE line with content.readline(max_line_length=READ_BUFFER_SIZE), and READ_BUFFER_SIZE = 2**22 (:147). This follows #2248: raising the limit moved the ceiling to 4 MiB but kept it.

Suggested fix

Don't cap SSE line length below what the API can return: accumulate content.iter_chunked() / iter_any() and split lines manually (no per-line cap, matching httpx), or pass a much larger limit. Non-streaming responses aren't affected.

Activity

  1. added a commit that references this issue on Oct 29, 2026
    31a72b6
  2. added
    priority: p2Moderately-important priority. Fix may not be included in next release.
    type: bugError or flaw in code with unintended results or allowing sub-optimal usage patterns.
    on Oct 29, 2026
  3. Venkaiahbabuneelam commented on Oct 30, 2026

    @Venkaiahbabuneelam

    Hi @jaeminbird,

    The issue stems from google/genai/_api_client.py passing max_line_length=READ_BUFFER_SIZE (4 MiB) into aiohttp's readline() during SSE stream processing, triggering aiohttp.http_exceptions.LineTooLong when an inline base64 image chunk exceeds that boundary.

    As a temporary workaround until the streaming buffer handling is updated, you can run the sync client stream via asyncio.to_thread, as httpx does not impose the 4 MiB line limitation:
    python
    import asyncio
    from google import genai

    client = genai.Client()

    def fetch_stream():
    return list(client.models.generate_content_stream(
    model="gemini-3.5-flash",
    contents="...",
    ))

    async def main():
    chunks = await asyncio.to_thread(fetch_stream)
    for chunk in chunks:
    # Process streamed chunks
    pass

    asyncio.run(main())

    
    For API reference on streaming content generation, refer to the [Gemini API generateContent reference](https://ai.google.dev/api/generate-content).
    
    Thanks
    
  4. added 3 commits that reference this issue on Nov 5, 2026
    a3e32f2
    82e6600
    2a2fe39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

priority: p2Moderately-important priority. Fix may not be included in next release.status:awaiting user responsetype: bugError or flaw in code with unintended results or allowing sub-optimal usage patterns.

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions