新增了完整的流式响应处理模块,包含代码围栏解析、分块策略、指令处理、流式草稿循环等核心能力,支持按段落/句子等规则切割响应块,支持进度更新与指令回调,完善流式交互的全流程支持
127 lines
4.6 KiB
Python
127 lines
4.6 KiB
Python
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
|
|
from yuxi.channel.capabilities import ChannelCapabilities
|
|
|
|
STREAMING_MODE_CARD_KIT = "card_kit"
|
|
STREAMING_MODE_BLOCK = "block"
|
|
STREAMING_MODE_C2C_STREAM_API = "c2c_stream_api"
|
|
STREAMING_MODE_RAW = "raw"
|
|
STREAMING_MODE_PREVIEW = "preview"
|
|
|
|
_MODE_TO_STRATEGY: dict[str, str] = {
|
|
"card_kit": STREAMING_MODE_CARD_KIT,
|
|
"block": STREAMING_MODE_BLOCK,
|
|
"c2c_stream_api": STREAMING_MODE_C2C_STREAM_API,
|
|
"raw": STREAMING_MODE_RAW,
|
|
}
|
|
|
|
|
|
@dataclass
|
|
class StreamingStrategy:
|
|
mode: str
|
|
|
|
preview_throttle_ms: int = 160
|
|
preview_min_initial_chars: int = 18
|
|
|
|
block_chunk_min_chars: int = 800
|
|
block_chunk_max_chars: int = 1200
|
|
block_chunk_break_preference: str = "paragraph"
|
|
block_coalesce_min_chars: int | None = None
|
|
block_coalesce_max_chars: int | None = None
|
|
block_coalesce_idle_ms: int = 1000
|
|
|
|
@classmethod
|
|
def from_capabilities(
|
|
cls,
|
|
caps: ChannelCapabilities,
|
|
overrides: dict | None = None,
|
|
) -> StreamingStrategy:
|
|
mode = _MODE_TO_STRATEGY.get(caps.streaming_mode or "", STREAMING_MODE_PREVIEW)
|
|
|
|
strategy = cls(
|
|
mode=mode,
|
|
preview_throttle_ms=caps.preview_stream_throttle_ms,
|
|
preview_min_initial_chars=caps.preview_min_initial_chars,
|
|
block_chunk_min_chars=caps.block_streaming_chunk_min_chars,
|
|
block_chunk_max_chars=caps.block_streaming_chunk_max_chars,
|
|
block_chunk_break_preference=caps.block_streaming_chunk_break_preference,
|
|
block_coalesce_min_chars=caps.block_streaming_coalesce_min_chars,
|
|
block_coalesce_max_chars=caps.block_streaming_coalesce_max_chars,
|
|
block_coalesce_idle_ms=caps.block_streaming_coalesce_idle_ms,
|
|
)
|
|
if overrides:
|
|
for k, v in overrides.items():
|
|
if hasattr(strategy, k) and v is not None:
|
|
setattr(strategy, k, v)
|
|
return strategy
|
|
|
|
@property
|
|
def is_card_kit(self) -> bool:
|
|
return self.mode == STREAMING_MODE_CARD_KIT
|
|
|
|
@property
|
|
def is_block(self) -> bool:
|
|
return self.mode == STREAMING_MODE_BLOCK
|
|
|
|
@property
|
|
def is_raw(self) -> bool:
|
|
return self.mode == STREAMING_MODE_RAW
|
|
|
|
@property
|
|
def is_c2c_stream_api(self) -> bool:
|
|
return self.mode == STREAMING_MODE_C2C_STREAM_API
|
|
|
|
@property
|
|
def is_preview(self) -> bool:
|
|
return self.mode == STREAMING_MODE_PREVIEW
|
|
|
|
def to_dict(self) -> dict:
|
|
result = {"mode": self.mode, "preview_throttle_ms": self.preview_throttle_ms}
|
|
if self.preview_min_initial_chars != 18:
|
|
result["preview_min_initial_chars"] = self.preview_min_initial_chars
|
|
if self.mode in (STREAMING_MODE_BLOCK, STREAMING_MODE_CARD_KIT):
|
|
result.update({
|
|
"block_chunk_min_chars": self.block_chunk_min_chars,
|
|
"block_chunk_max_chars": self.block_chunk_max_chars,
|
|
"block_chunk_break_preference": self.block_chunk_break_preference,
|
|
"block_coalesce_idle_ms": self.block_coalesce_idle_ms,
|
|
})
|
|
if self.block_coalesce_min_chars is not None:
|
|
result["block_coalesce_min_chars"] = self.block_coalesce_min_chars
|
|
if self.block_coalesce_max_chars is not None:
|
|
result["block_coalesce_max_chars"] = self.block_coalesce_max_chars
|
|
return result
|
|
|
|
|
|
class StreamingStrategySelector:
|
|
|
|
@staticmethod
|
|
def select(capabilities: ChannelCapabilities) -> StreamingStrategy:
|
|
return StreamingStrategy.from_capabilities(capabilities)
|
|
|
|
@staticmethod
|
|
def supports_preview(capabilities: ChannelCapabilities) -> bool:
|
|
return capabilities.streaming and (
|
|
capabilities.streaming_mode is None or capabilities.streaming_mode == STREAMING_MODE_RAW
|
|
)
|
|
|
|
@staticmethod
|
|
def supports_block(capabilities: ChannelCapabilities) -> bool:
|
|
return bool(capabilities.block_streaming) and (
|
|
capabilities.streaming_mode == STREAMING_MODE_BLOCK
|
|
or (capabilities.streaming_mode is None and capabilities.block_streaming)
|
|
)
|
|
|
|
@staticmethod
|
|
def supports_card_kit(capabilities: ChannelCapabilities) -> bool:
|
|
return capabilities.streaming_mode == STREAMING_MODE_CARD_KIT
|
|
|
|
@staticmethod
|
|
def supports_c2c_stream(capabilities: ChannelCapabilities) -> bool:
|
|
return capabilities.streaming_mode == STREAMING_MODE_C2C_STREAM_API
|
|
|
|
@staticmethod
|
|
def get_config(capabilities: ChannelCapabilities) -> dict:
|
|
return StreamingStrategy.from_capabilities(capabilities).to_dict() |