Files
Neo-ZQYY/apps/backend/app/trace/sse_wrapper.py
Neo 6f8f12314f feat: 累积功能变更 — 聊天集成、租户管理、小程序更新、ETL 增强、迁移脚本
包含多个会话的累积代码变更:
- backend: AI 聊天服务、触发器调度、认证增强、WebSocket、调度器最小间隔
- admin-web: ETL 状态页、任务管理、调度配置、登录优化
- miniprogram: 看板页面、聊天集成、UI 组件、导航更新
- etl: DWS 新任务(finance_area_daily/board_cache)、连接器增强
- tenant-admin: 项目初始化
- db: 19 个迁移脚本(etl_feiqiu 11 + zqyy_app 8)
- packages/shared: 枚举和工具函数更新
- tools: 数据库工具、报表生成、健康检查
- docs: PRD/架构/部署/合约文档更新

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-06 00:03:48 +08:00

150 lines
4.5 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.
# -*- coding: utf-8 -*-
"""
SSE 流式响应追踪辅助函数
提供一组轻量函数,在 SSE event_generator 内部调用,
追踪流式响应全过程SSE_START → AI_CALL → SSE_EVENT → SSE_END / AI_ERROR
所有函数在无活跃 TraceContext 时静默跳过,不影响业务逻辑。
SSE_EVENT span 每 10 个 token 记录一次,避免 span 爆炸。
"""
from __future__ import annotations
from datetime import datetime
from app.trace.context import (
SpanType,
TraceSpan,
TraceType,
get_current_trace,
)
def record_sse_start(
endpoint: str,
user_id: int | None = None,
chat_id: str = "",
) -> None:
"""记录 SSE_START span流开始。
同时将 trace_type 切换为 "sse"
"""
ctx = get_current_trace()
if ctx is None:
return
# 切换 trace 类型为 SSE
ctx.trace_type = TraceType.SSE
ctx.add_span(TraceSpan(
span_type=SpanType.SSE_START,
module="trace.sse_wrapper",
function="record_sse_start",
description_zh=f"SSE 流开始: endpoint={endpoint}, chat_id={chat_id}",
description_en=f"SSE stream started: endpoint={endpoint}, chat_id={chat_id}",
params={"endpoint": endpoint, "user_id": user_id, "chat_id": chat_id},
result_summary="",
duration_ms=0.0,
timestamp=datetime.now().isoformat(),
))
def record_ai_call(
app_id: str,
prompt_length: int,
session_id: str = "",
) -> None:
"""记录 AI_CALL spanDashScope API 调用。"""
ctx = get_current_trace()
if ctx is None:
return
ctx.add_span(TraceSpan(
span_type=SpanType.AI_CALL,
module="trace.sse_wrapper",
function="record_ai_call",
description_zh=f"AI 调用: app_id={app_id}, prompt 长度={prompt_length}",
description_en=f"AI call: app_id={app_id}, prompt_length={prompt_length}",
params={
"app_id": app_id,
"prompt_length": prompt_length,
"session_id": session_id,
},
result_summary="",
duration_ms=0.0,
timestamp=datetime.now().isoformat(),
))
def record_sse_token(token_count: int, total_tokens: int) -> None:
"""记录 SSE_EVENT span每 10 个 token 记录一次,避免 span 爆炸。
仅当 total_tokens 是 10 的倍数时才记录 span。
"""
ctx = get_current_trace()
if ctx is None:
return
# 每 10 个 token 记录一次
if total_tokens % 10 != 0:
return
ctx.add_span(TraceSpan(
span_type=SpanType.SSE_EVENT,
module="trace.sse_wrapper",
function="record_sse_token",
description_zh=f"SSE token 流: 本批 {token_count} token, 累计 {total_tokens}",
description_en=f"SSE token stream: batch {token_count}, cumulative {total_tokens}",
params={"token_count": token_count, "total_tokens": total_tokens},
result_summary=f"cumulative={total_tokens}",
duration_ms=0.0,
timestamp=datetime.now().isoformat(),
))
def record_sse_end(
total_tokens: int,
total_duration_ms: float,
completed: bool = True,
) -> None:
"""记录 SSE_END span流结束。"""
ctx = get_current_trace()
if ctx is None:
return
ctx.add_span(TraceSpan(
span_type=SpanType.SSE_END,
module="trace.sse_wrapper",
function="record_sse_end",
description_zh=f"SSE 流结束: 总 token={total_tokens}, 耗时={total_duration_ms:.0f}ms, 完成={completed}",
description_en=f"SSE stream ended: total_tokens={total_tokens}, duration={total_duration_ms:.0f}ms, completed={completed}",
params={
"total_tokens": total_tokens,
"total_duration_ms": total_duration_ms,
"completed": completed,
},
result_summary=f"tokens={total_tokens}, completed={completed}",
duration_ms=total_duration_ms,
timestamp=datetime.now().isoformat(),
))
def record_ai_error(error_type: str, message: str) -> None:
"""记录 AI_ERROR spanAI 调用失败。"""
ctx = get_current_trace()
if ctx is None:
return
ctx.add_span(TraceSpan(
span_type=SpanType.AI_ERROR,
module="trace.sse_wrapper",
function="record_ai_error",
description_zh=f"AI 调用失败: {error_type} - {message}",
description_en=f"AI call failed: {error_type} - {message}",
params={"error_type": error_type, "message": message},
result_summary=f"{error_type}: {message}",
duration_ms=0.0,
timestamp=datetime.now().isoformat(),
))