Inner streaming function.
()
| 386 | ) |
| 387 | |
| 388 | async def stream_chunks(): |
| 389 | """Inner streaming function.""" |
| 390 | stream = client.create_completion( |
| 391 | messages=messages, |
| 392 | tools=tools, |
| 393 | stream=True, |
| 394 | **kwargs, |
| 395 | ) |
| 396 | |
| 397 | stream_iter = stream.__aiter__() |
| 398 | first_chunk_received = False |
| 399 | |
| 400 | while True: |
| 401 | try: |
| 402 | # Use longer timeout for first chunk |
| 403 | effective_timeout = ( |
| 404 | chunk_timeout if first_chunk_received else first_chunk_timeout |
| 405 | ) |
| 406 | |
| 407 | # Wait for next chunk with timeout |
| 408 | chunk = await asyncio.wait_for( |
| 409 | stream_iter.__anext__(), |
| 410 | timeout=effective_timeout, |
| 411 | ) |
| 412 | first_chunk_received = True |
| 413 | |
| 414 | # Check for interrupt |
| 415 | if self._interrupted: |
| 416 | logger.debug("Stream interrupted by user") |
| 417 | try: |
| 418 | await stream_iter.aclose() |
| 419 | except Exception as e: |
| 420 | logger.debug(f"Error closing stream: {e}") |
| 421 | break |
| 422 | |
| 423 | # Process chunk |
| 424 | await self._process_chunk(chunk) |
| 425 | |
| 426 | # Small yield for smooth UI |
| 427 | await asyncio.sleep(0.0005) |
| 428 | |
| 429 | except StopAsyncIteration: |
| 430 | logger.debug("Stream completed normally") |
| 431 | break |
| 432 | except asyncio.TimeoutError: |
| 433 | effective_timeout = ( |
| 434 | chunk_timeout if first_chunk_received else first_chunk_timeout |
| 435 | ) |
| 436 | logger.warning( |
| 437 | f"Chunk timeout after {effective_timeout}s " |
| 438 | f"(first_chunk={not first_chunk_received}, " |
| 439 | f"after_tools={after_tool_calls})" |
| 440 | ) |
| 441 | if not first_chunk_received and after_tool_calls: |
| 442 | logger.warning( |
| 443 | "Streaming timeout after %.0fs waiting for first response after tool calls. " |
| 444 | "Increase with MCP_STREAMING_FIRST_CHUNK_TIMEOUT=%.0f", |
| 445 | effective_timeout, |
nothing calls this directly
no test coverage detected