ForcePilot/backend/package/yuxi/channels/contract/dtos/streaming.py

181 lines
6.1 KiB
Python
Raw Normal View History

"""流式 DTO。
定义流式输出相关的不可变值对象包括分块模式枚举流式分块分块
结果流式完成结果与流式能力所有 DTO 均为
``dataclass(frozen=True)``仅依赖标准库与契约层内部类型用于
流式输出的分块投递结果同步与能力声明FR-13
"""
from __future__ import annotations
from dataclasses import dataclass
from enum import StrEnum
from yuxi.channels.contract.errors import ValidationError
class ChunkMode(StrEnum):
"""分块模式。
标识流式输出的分块模式用于适配器按模式渲染分块继承
``str, Enum`` 以支持 JSON 序列化与字符串比较
取值
NONE: 无流式走完整消息
TEXT: 文本追加模式
BLOCK: 块模式每分块作为新消息块
FULL_UPDATE: 卡片整体更新
PARTIAL_UPDATE: 卡片局部更新
"""
NONE = "none"
TEXT = "text"
BLOCK = "block"
FULL_UPDATE = "full_update" # 卡片整体更新
PARTIAL_UPDATE = "partial_update" # 卡片局部更新
class RenderStrategy(StrEnum):
"""卡片渲染策略。
继承 ``str, Enum`` 以支持 JSON 序列化用于 ``updatePartialCard``
方法声明局部更新的渲染策略
取值:
APPEND: 追加到现有内容
REPLACE: 替换现有内容
MERGE: 合并到现有内容
"""
APPEND = "append" # 追加到现有内容
REPLACE = "replace" # 替换现有内容
MERGE = "merge" # 合并到现有内容
@dataclass(frozen=True)
class StreamChunk:
"""流式分块。
描述流式输出的单个分块包括内容序号与是否末块标记用于
适配器按序投递分块
字段
content: 分块内容
sequence: 序号
is_final: 是否末块默认 False
"""
content: str
sequence: int
is_final: bool = False
def __post_init__(self) -> None:
"""校验 content 非空与 sequence 非负。
``content`` 必须非空``sequence`` 必须非负在构造时即抛出
``ValidationError``避免空分块或负序号破坏流式分块顺序INV-8
"""
if not self.content:
raise ValidationError("content", "must not be empty")
if self.sequence < 0:
raise ValidationError("sequence", "must not be negative")
@dataclass(frozen=True)
class ChunkResult:
"""分块结果。
描述单个分块投递的结果包括分块本身是否成功与可选错误信息
用于流式投递的状态同步与错误处理
字段
chunk: 流式分块
success: 是否成功
error: 错误信息可选
"""
chunk: StreamChunk
success: bool
error: str | None = None
@dataclass(frozen=True)
class StreamingCompleted:
"""流式完成结果。
描述流式输出完成后的汇总结果包括总分块数与耗时毫秒用于
流式投递的统计与审计
字段
total_chunks: 总分块数
duration_ms: 耗时毫秒
"""
total_chunks: int
duration_ms: int
def __post_init__(self) -> None:
"""校验 total_chunks 为正整数与 duration_ms 非负。
``total_chunks`` 必须为正整数``duration_ms`` 必须非负在构造时
即抛出 ``ValidationError``避免非正分块数或负耗时导致统计失真INV-8
"""
if self.total_chunks <= 0:
raise ValidationError("total_chunks", "must be a positive integer")
if self.duration_ms < 0:
raise ValidationError("duration_ms", "must not be negative")
@dataclass(frozen=True)
class StreamingCapability:
"""流式能力。
描述渠道的流式能力声明包括是否支持流式是否支持输入指示
分块模式最小分块间隔与最大分块长度用于 FR-13 流式能力协商
与降级决策
字段
supports_streaming: 是否支持流式默认 False
supports_typing_indicator: 是否支持输入指示默认 False
chunk_mode: 分块模式默认 NONE
min_chunk_interval_ms: 最小分块间隔毫秒默认 0
max_chunk_length: 最大分块长度默认 0
"""
supports_streaming: bool = False
supports_typing_indicator: bool = False
chunk_mode: ChunkMode = ChunkMode.NONE
min_chunk_interval_ms: int = 0
max_chunk_length: int = 0
@dataclass(frozen=True)
class StreamingConfig:
"""流式配置。
描述流式管道的运行时配置 ConfigPort.getStreamingConfig 读取
用于控制流式管道是否激活输入指示开关最小分块间隔与 TTL 安全阀
FR-13
字段
streaming_enabled: 是否启用流式管道默认 False启用时
``delivery_mode`` 设为 ``streaming``激活 stream-chunk /
truncation-check 等流式阶段
enable_typing: 是否启用输入指示默认 Truetyping-indicator 阶段
据此决定是否调用 ``startTypingIndicator`` / ``stopTypingIndicator``
FR-13关闭时跳过输入指示动画不影响流式分块投递
min_chunk_interval_ms: 最小分块间隔毫秒默认 200stream-chunk
阶段据此在分批发送之间强制等待避免对渠道侧造成压力AC-22
streaming_ttl_ms: 流式 TTL 安全阀毫秒默认 60000stream-chunk
阶段累计耗时超过该值时停止发送分块 typing-stop 阶段强制
结束流式会话并记录告警日志AC-62
typing_ttl_ms: 输入指示 TTL毫秒默认 10000插件可据此控制
输入指示动画的刷新周期避免动画超时停留FR-13
"""
streaming_enabled: bool = False
enable_typing: bool = True
min_chunk_interval_ms: int = 200
streaming_ttl_ms: int = 60000
typing_ttl_ms: int = 10000