Files
Aether/_deprecated_py_src/services/usage/events.py
fawney19 1d9c77522a refactor: 移除 Python 后端源码,全面迁移至 Rust gateway 架构
- 删除全部 Python 源码 (src/) 及 Alembic 迁移脚本,归档至 _deprecated_py_src/
- 重构 Rust gateway ai_pipeline: 拆分 planner/finalize 模块,新增 contracts/adaptation 层
- 重组 handlers 模块为 admin/public/proxy/internal/shared 子模块结构
- 新增 executor 模块,引入 Rust 原生数据库迁移 (aether-data/migrations)
- 简化 CI/Docker 构建流程,移除 base image 二级构建,统一为单一 app image
- 移除 Python 相关基础设施文件 (entrypoint.sh, gunicorn_conf.py, Dockerfile.base)
2026-04-03 16:26:16 +08:00

126 lines
3.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
Usage 事件定义与序列化工具(用于 Redis Streams
"""
from __future__ import annotations
import json
import time
from dataclasses import dataclass
from enum import Enum
from typing import Any
import msgpack
from msgpack.exceptions import OutOfData
USAGE_EVENT_VERSION = 1
class UsageEventType(str, Enum):
STREAMING = "streaming"
COMPLETED = "completed"
FAILED = "failed"
CANCELLED = "cancelled"
def now_ms() -> int:
return int(time.time() * 1000)
def _sanitize_value(value: Any) -> Any:
if value is None or isinstance(value, (str, int, float, bool)):
return value
if isinstance(value, dict):
return {str(k): _sanitize_value(v) for k, v in value.items()}
if isinstance(value, list):
return [_sanitize_value(item) for item in value]
return str(value)
def sanitize_payload(data: dict[str, Any]) -> dict[str, Any]:
return {str(k): _sanitize_value(v) for k, v in data.items()}
def _decode_payload(raw: Any) -> dict[str, Any]:
"""兼容解码:优先 msgpack回退旧 JSON。
统一将输入归一化为 bytes 后走单一解码路径msgpack → JSON fallback。
str 输入来自 decode_responses=True + surrogateescape 的 Redis 客户端,
通过 surrogateescape 可无损还原回原始 bytes。
"""
if isinstance(raw, str):
raw = raw.encode("utf-8", errors="surrogateescape")
if not isinstance(raw, (bytes, bytearray, memoryview)):
raise ValueError("Invalid payload field in usage event")
payload_bytes = bytes(raw)
# 新格式msgpack
try:
payload = msgpack.unpackb(payload_bytes, raw=False)
except (ValueError, OutOfData, TypeError):
# 兼容旧格式JSON bytes含 surrogateescape 还原后的纯 UTF-8 JSON
try:
payload = json.loads(payload_bytes.decode("utf-8"))
except (UnicodeDecodeError, json.JSONDecodeError, TypeError) as exc:
raise ValueError("Invalid payload field in usage event") from exc
if not isinstance(payload, dict):
raise ValueError("Invalid payload field in usage event")
return payload
@dataclass
class UsageEvent:
event_type: UsageEventType
request_id: str
timestamp_ms: int
data: dict[str, Any]
def to_stream_fields(self) -> dict[str, bytes]:
"""序列化为 Redis Stream 字段。
该函数返回 bytes payload要求读写 usage queue 的 Redis 客户端使用
decode_responses=True并以 surrogateescape 做 UTF-8 编解码,
以保证 bytes <-> str 往返无损。
"""
payload = {
"v": USAGE_EVENT_VERSION,
"type": self.event_type.value,
"request_id": self.request_id,
"timestamp_ms": self.timestamp_ms,
# 兜底清洗,避免 metadata 中混入非 JSON 类型导致队列写入失败。
"data": sanitize_payload(self.data),
}
return {"payload": msgpack.packb(payload, use_bin_type=True)}
@classmethod
def from_stream_fields(cls, fields: dict[str, Any]) -> UsageEvent:
raw = fields.get("payload")
if not raw:
raise ValueError("Missing payload field in usage event")
payload = _decode_payload(raw)
event_type = UsageEventType(payload["type"])
return cls(
event_type=event_type,
request_id=payload["request_id"],
timestamp_ms=int(payload.get("timestamp_ms", 0)),
data=payload.get("data", {}) or {},
)
def build_usage_event(
*,
event_type: UsageEventType,
request_id: str,
data: dict[str, Any],
timestamp_ms: int | None = None,
) -> UsageEvent:
return UsageEvent(
event_type=event_type,
request_id=request_id,
timestamp_ms=timestamp_ms or now_ms(),
data=data,
)