본문 바로가기
C.W.K.
Stream
Lesson 04 of 07 · published

httpx Streaming — async iter_lines + SSE parse

~22 min · httpx-stream, sse-parse

Level 0Tokenizer
0 XP0/54 lessons0/10 achievements
0/120 XP to next level120 XP to go0% complete

async streaming 은 async with client.stream("POST", url, headers=h, json=body) as response: 로 시작해. block 안에서 async for line in response.aiter_lines(): 를 순회하면 SSE 줄이 차례로 와. data: prefix, 빈 줄 경계, [DONE] 종료는 client가 parsing 해야 해.

stream 안에서 전체 body를 읽지 마

response.aread()response.json()은 body 전체가 올 때까지 기다려 streaming을 무효로 만들어. 줄을 순회하거나, 중간 결과가 필요 없다면 처음부터 non-streaming 호출을 선택해.

SSE parsing 순서

  1. 줄 양끝의 불필요한 문자를 정리해.
  2. data: prefix를 떼어.
  3. [DONE]이면 loop를 끝내.
  4. 나머지 payload를 JSON으로 parsing해.
  5. event type에 따라 처리해.

text SSE에는 aiter_lines

aiter_lines는 줄 단위, aiter_bytes는 raw byte chunk 단위야. 줄 기반인 SSE에는 aiter_lines가 맞고, audio download 같은 binary streaming에는 aiter_bytes가 맞아.

Code

client.stream('POST', url, json=...)·python
import os, json, httpx, asyncio
from typing import AsyncIterator

async def stream_chat_async(
    messages: list[dict], model: str = "gpt-4o-mini",
) -> AsyncIterator[str]:
    """Yield text delta strings from a streaming response."""
    url = "https://api.openai.com/v1/chat/completions"
    headers = {
        "Authorization": f"Bearer {os.environ['OPENAI_API_KEY']}",
        "Content-Type": "application/json",
    }
    body = {"model": model, "messages": messages, "stream": True}
    async with httpx.AsyncClient(timeout=None) as client:
        async with client.stream("POST", url, headers=headers, json=body) as resp:
            resp.raise_for_status()
            async for line in resp.aiter_lines():
                if not line or line == "data: [DONE]":
                    continue
                if line.startswith("data: "):
                    try:
                        chunk = json.loads(line[len("data: "):])
                        content = chunk["choices"][0]["delta"].get("content", "")
                        if content:
                            yield content
                    except (json.JSONDecodeError, KeyError, IndexError):
                        pass

async def main():
    async for token in stream_chat_async([{"role": "user", "content": "Count to 5."}]):
        print(token, end="", flush=True)
    print()

asyncio.run(main())

External links

Exercise

raw httpx로 Responses 호출을 streaming하고 SSE frame을 직접 parsing해. response.output_text.delta event 수, 조립한 text, 관찰한 모든 non-text event를 출력해.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.