mirror of
https://github.com/Nezumi-2711/astrbot_plugin_qq_group_daily_analysis.git
synced 2026-09-23 04:09:59 +00:00
feat: add trace context and resilience utilities for LLM calls
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
import time
|
||||
import asyncio
|
||||
from typing import Dict
|
||||
from astrbot.api import logger
|
||||
|
||||
|
||||
class CircuitBreaker:
|
||||
"""
|
||||
简单的熔断器实现 (Simple Circuit Breaker)
|
||||
"""
|
||||
|
||||
STATE_CLOSED = "CLOSED"
|
||||
STATE_OPEN = "OPEN"
|
||||
STATE_HALF_OPEN = "HALF_OPEN"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
failure_threshold: int = 5,
|
||||
recovery_timeout: int = 60,
|
||||
name: str = "default",
|
||||
):
|
||||
self.name = name
|
||||
self.failure_threshold = failure_threshold
|
||||
self.recovery_timeout = recovery_timeout
|
||||
|
||||
self.failure_count = 0
|
||||
self.state = self.STATE_CLOSED
|
||||
self.last_failure_time = 0
|
||||
|
||||
def record_failure(self):
|
||||
"""记录一次失败"""
|
||||
self.failure_count += 1
|
||||
if (
|
||||
self.state == self.STATE_CLOSED
|
||||
and self.failure_count >= self.failure_threshold
|
||||
):
|
||||
self._open_circuit()
|
||||
elif self.state == self.STATE_HALF_OPEN:
|
||||
# 在半开状态下,一次失败直接重新打开熔断器
|
||||
self._open_circuit()
|
||||
|
||||
def record_success(self):
|
||||
"""记录一次成功"""
|
||||
if self.state == self.STATE_HALF_OPEN:
|
||||
self._close_circuit()
|
||||
elif self.state == self.STATE_CLOSED:
|
||||
# 成功则重置失败计数 (可选,这里选择连续失败才熔断)
|
||||
self.failure_count = 0
|
||||
|
||||
def allow_request(self) -> bool:
|
||||
"""是否允许请求"""
|
||||
if self.state == self.STATE_OPEN:
|
||||
if time.time() - self.last_failure_time > self.recovery_timeout:
|
||||
self._half_open_circuit()
|
||||
return True
|
||||
return False
|
||||
return True
|
||||
|
||||
def _open_circuit(self):
|
||||
self.state = self.STATE_OPEN
|
||||
self.last_failure_time = time.time()
|
||||
logger.warning(
|
||||
f"CircuitBreaker[{self.name}] 熔断器已打开! 暂停请求 {self.recovery_timeout} 秒。"
|
||||
)
|
||||
|
||||
def _close_circuit(self):
|
||||
self.state = self.STATE_CLOSED
|
||||
self.failure_count = 0
|
||||
logger.info(f"CircuitBreaker[{self.name}] 熔断器已关闭,服务恢复。")
|
||||
|
||||
def _half_open_circuit(self):
|
||||
self.state = self.STATE_HALF_OPEN
|
||||
logger.info(f"CircuitBreaker[{self.name}] 进入半开状态,尝试恢复...")
|
||||
|
||||
|
||||
class GlobalRateLimiter:
|
||||
"""
|
||||
全局限流器 (Global Rate Limiter)
|
||||
使用 asyncio.Semaphore 控制并发数
|
||||
"""
|
||||
|
||||
_instance = None
|
||||
_semaphore = None
|
||||
|
||||
@classmethod
|
||||
def get_instance(cls, max_concurrency: int = 3):
|
||||
if cls._instance is None:
|
||||
cls._instance = cls()
|
||||
cls._semaphore = asyncio.Semaphore(max_concurrency)
|
||||
return cls._instance
|
||||
|
||||
@property
|
||||
def semaphore(self):
|
||||
if self._semaphore is None:
|
||||
# Fallback if accessed before get_instance called with arg
|
||||
self._semaphore = asyncio.Semaphore(3)
|
||||
return self._semaphore
|
||||
|
||||
|
||||
# 默认全局限流实例
|
||||
global_llm_rate_limiter = GlobalRateLimiter.get_instance(max_concurrency=3).semaphore
|
||||
@@ -0,0 +1,50 @@
|
||||
import contextvars
|
||||
import logging
|
||||
import uuid
|
||||
import time
|
||||
|
||||
# 定义 ContextVar
|
||||
_trace_id_ctx = contextvars.ContextVar("trace_id", default="")
|
||||
|
||||
class TraceContext:
|
||||
"""
|
||||
链路追踪上下文管理器
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def set(trace_id: str):
|
||||
"""设置当前上下文的 TraceID"""
|
||||
return _trace_id_ctx.set(trace_id)
|
||||
|
||||
@staticmethod
|
||||
def get() -> str:
|
||||
"""获取当前上下文的 TraceID"""
|
||||
return _trace_id_ctx.get()
|
||||
|
||||
@staticmethod
|
||||
def generate(prefix: str = "") -> str:
|
||||
"""生成一个新的 TraceID (Prefix + Timestamp + UUID前8位)"""
|
||||
timestamp = int(time.time())
|
||||
unique_id = str(uuid.uuid4())[:8]
|
||||
if prefix:
|
||||
return f"{prefix}-{timestamp}-{unique_id}"
|
||||
return f"{timestamp}-{unique_id}"
|
||||
|
||||
@staticmethod
|
||||
def clear():
|
||||
"""清除当前上下文的 TraceID"""
|
||||
_trace_id_ctx.set("")
|
||||
|
||||
class TraceLogFilter(logging.Filter):
|
||||
"""
|
||||
日志过滤器,自动注入 TraceID
|
||||
"""
|
||||
def filter(self, record):
|
||||
trace_id = _trace_id_ctx.get()
|
||||
if trace_id:
|
||||
# 将 trace_id 注入到 record 中,同时也修改 msg 以便在不支持自定义 format 的 logger 中也能看到
|
||||
record.trace_id = trace_id
|
||||
record.msg = f"[{trace_id}] {record.msg}"
|
||||
else:
|
||||
record.trace_id = ""
|
||||
return True
|
||||
Reference in New Issue
Block a user