File size: 3,856 Bytes
fa1140b | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 | """流式辅助:增量释放 warmup/guard 双缓冲 + safe_sse_stream 安全包装。
**warmup/guard 双缓冲**(参考 cursor2api streaming-text):先缓冲预热文本,确认不是应拦截
的前缀(如拒绝文本)再开始释放;释放时永远保留尾部 guard 窗口,给跨 chunk 的清洗规则留
上下文;超过 guard 强制放行。避免「拒绝文本泄漏给客户端」。
**safe_sse_stream**(参考 grok2api):把 SSE 流中途异常转译成 error chunk,避免 ASGI 在
响应已开始后崩溃(连接已关则吞掉二次错误)。
"""
from __future__ import annotations
from collections.abc import AsyncIterator, Callable
DEFAULT_WARMUP = 96
DEFAULT_GUARD = 256
class IncrementalStreamer:
"""增量释放器:warmup 预热 + guard 尾部缓冲。
- :meth:`push` 返回当前可安全释放给客户端的文本。
- :meth:`finish` 返回剩余全部(含 guard 窗口)。
- ``is_blocked`` 回调在 warmup 阶段判断文本是否为应拦截的前缀(如拒绝)。
"""
def __init__(
self,
*,
warmup: int = DEFAULT_WARMUP,
guard: int = DEFAULT_GUARD,
is_blocked: Callable[[str], bool] | None = None,
) -> None:
self._warmup = warmup
self._guard = guard
self._is_blocked = is_blocked or (lambda _: False)
self._buf: list[str] = []
self._buflen = 0
self._unlocked = False # warmup 通过后置 True
def push(self, chunk: str) -> str:
"""喂入一段文本,返回可释放的部分(可能为空,因 warmup/guard 缓冲)。"""
if not chunk:
return ""
self._buf.append(chunk)
self._buflen += len(chunk)
if not self._unlocked:
if self._buflen < self._warmup:
return ""
head = "".join(self._buf)
if self._is_blocked(head):
# 命中拦截前缀(拒绝):丢弃已缓冲,不再释放
self._buf.clear()
self._buflen = 0
return ""
self._unlocked = True
# 释放时保留尾部 guard 窗口
full = "".join(self._buf)
if len(full) <= self._guard:
return ""
release_len = len(full) - self._guard
self._buf = [full[release_len:]]
self._buflen = len(self._buf[0])
return full[:release_len]
def finish(self) -> str:
"""收尾:返回剩余全部文本(若全程未过 warmup,最后再判一次拦截前缀)。"""
if not self._unlocked:
head = "".join(self._buf)
if head and self._is_blocked(head):
self._buf.clear()
self._buflen = 0
return ""
self._unlocked = True
out = "".join(self._buf)
self._buf.clear()
self._buflen = 0
return out
def safe_sse_stream(
stream: AsyncIterator[str],
*,
on_error: Callable[[BaseException], list[str]] | None = None,
) -> AsyncIterator[str]:
"""包装 SSE 流:正常 yield;中途异常 → on_error 产出的 chunk(默认通用 OpenAI 风格 error + [DONE])。
各 adapter 可传入自定义 ``on_error`` 以产出符合自家协议的 error 帧。
"""
async def _gen() -> AsyncIterator[str]:
try:
async for chunk in stream:
yield chunk
except Exception as e: # noqa: BLE001
chunks = on_error(e) if on_error else [
f'data: {{"error":{{"message":"{str(e)[:200]}"}}}}\n\n',
"data: [DONE]\n\n",
]
for c in chunks:
try:
yield c
except Exception: # noqa: BLE001 连接已断,吞掉二次错误
break
return _gen()
|