feat: add OCI GenAI streaming agent foundation

This commit is contained in:
2026-10-10 20:55:44 +00:00
parent 50038cca34
commit 088473a0c8
8 changed files with 1873 additions and 1 deletions

View File

@@ -0,0 +1,147 @@
"""
Async bridge for synchronous OCI SSE streams.
The bridge runs blocking SSE iteration in a worker thread and
delivers events to an asynchronous consumer with backpressure.
"""
from __future__ import annotations
import asyncio
import logging
import threading
from collections.abc import AsyncIterator
from typing import Any
logger = logging.getLogger(__name__)
async def stream_sse_events(
stream: Any,
*,
max_queue_size: int = 16,
shutdown_timeout: float = 5.0,
) -> AsyncIterator[str]:
"""
Convert a synchronous SSE stream into asynchronous text events.
The stream must provide:
events() -> Iterator
close() -> None
Cancellation stops delivery, requests stream closure and
waits briefly for the worker to finish.
Worker termination is not guaranteed if the underlying HTTP
read ignores stream closure.
"""
if max_queue_size < 1:
raise ValueError("max_queue_size must be positive.")
if shutdown_timeout <= 0:
raise ValueError("shutdown_timeout must be positive.")
loop = asyncio.get_running_loop()
queue: asyncio.Queue[tuple[str, Any]] = asyncio.Queue(
maxsize=max_queue_size,
)
stop_requested = threading.Event()
def publish(kind: str, value: Any = None) -> bool:
if stop_requested.is_set():
return False
future = asyncio.run_coroutine_threadsafe(
queue.put((kind, value)),
loop,
)
while not stop_requested.is_set():
try:
future.result(timeout=0.1)
return True
except TimeoutError:
continue
except Exception:
future.cancel()
raise
future.cancel()
return False
def worker() -> None:
try:
for event in stream.events():
if stop_requested.is_set():
break
data = getattr(event, "data", None)
if not isinstance(data, str):
continue
if not publish("data", data):
break
except Exception as exc:
if not stop_requested.is_set():
publish("error", exc)
finally:
if not stop_requested.is_set():
publish("done")
worker_task = asyncio.create_task(
asyncio.to_thread(worker)
)
try:
while True:
kind, value = await queue.get()
if kind == "done":
break
if kind == "error":
raise value
yield value
finally:
stop_requested.set()
# Request transport closure outside the event loop.
try:
await asyncio.wait_for(
asyncio.to_thread(stream.close),
timeout=shutdown_timeout,
)
except TimeoutError:
logger.error(
"OCI SSE stream closure timed out."
)
except Exception:
logger.exception(
"Failed to close OCI SSE stream."
)
# Do not assume that closing the stream immediately
# terminates the synchronous reader.
try:
await asyncio.wait_for(
asyncio.shield(worker_task),
timeout=shutdown_timeout,
)
except TimeoutError:
logger.error(
"OCI SSE worker did not terminate after closure."
)
except Exception:
logger.exception(
"OCI SSE worker failed during shutdown."
)