feat(server): 新增 LLM 集成配置项与 pydantic-settings 嵌套模型修复
This commit is contained in:
@@ -34,6 +34,9 @@ from pydantic_settings import BaseSettings, SettingsConfigDict
|
|||||||
SERVER_DIR: Path = Path(__file__).resolve().parent.parent
|
SERVER_DIR: Path = Path(__file__).resolve().parent.parent
|
||||||
PROJECT_ROOT: Path = SERVER_DIR.parent.parent
|
PROJECT_ROOT: Path = SERVER_DIR.parent.parent
|
||||||
|
|
||||||
|
# 共享 .env 文件路径 (所有嵌套子配置共用)
|
||||||
|
_ENV_FILE = str(SERVER_DIR / ".env")
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# 子配置
|
# 子配置
|
||||||
@@ -43,7 +46,7 @@ PROJECT_ROOT: Path = SERVER_DIR.parent.parent
|
|||||||
class APISettings(BaseSettings):
|
class APISettings(BaseSettings):
|
||||||
"""API 与服务器相关配置。"""
|
"""API 与服务器相关配置。"""
|
||||||
|
|
||||||
model_config = SettingsConfigDict(env_prefix="API_", extra="ignore")
|
model_config = SettingsConfigDict(env_prefix="API_", env_file=_ENV_FILE, extra="ignore")
|
||||||
|
|
||||||
host: str = Field(default="0.0.0.0", description="监听地址")
|
host: str = Field(default="0.0.0.0", description="监听地址")
|
||||||
port: int = Field(default=8000, description="监听端口")
|
port: int = Field(default=8000, description="监听端口")
|
||||||
@@ -56,7 +59,7 @@ class APISettings(BaseSettings):
|
|||||||
class DetectionSettings(BaseSettings):
|
class DetectionSettings(BaseSettings):
|
||||||
"""检测相关全局默认值。"""
|
"""检测相关全局默认值。"""
|
||||||
|
|
||||||
model_config = SettingsConfigDict(env_prefix="DETECTION_", extra="ignore")
|
model_config = SettingsConfigDict(env_prefix="DETECTION_", env_file=_ENV_FILE, extra="ignore")
|
||||||
|
|
||||||
default_confidence: float = Field(default=0.5, ge=0.0, le=1.0)
|
default_confidence: float = Field(default=0.5, ge=0.0, le=1.0)
|
||||||
default_iou: float = Field(default=0.45, ge=0.0, le=1.0)
|
default_iou: float = Field(default=0.45, ge=0.0, le=1.0)
|
||||||
@@ -67,7 +70,7 @@ class DetectionSettings(BaseSettings):
|
|||||||
class ActionDetectionSettings(BaseSettings):
|
class ActionDetectionSettings(BaseSettings):
|
||||||
"""ppTSM 行为识别 (Docker) 服务配置。"""
|
"""ppTSM 行为识别 (Docker) 服务配置。"""
|
||||||
|
|
||||||
model_config = SettingsConfigDict(env_prefix="ACTION_DETECTION_", extra="ignore")
|
model_config = SettingsConfigDict(env_prefix="ACTION_DETECTION_", env_file=_ENV_FILE, extra="ignore")
|
||||||
|
|
||||||
api_url: str = Field(default="http://localhost:8081")
|
api_url: str = Field(default="http://localhost:8081")
|
||||||
timeout: int = Field(default=30, ge=1)
|
timeout: int = Field(default=30, ge=1)
|
||||||
@@ -76,7 +79,7 @@ class ActionDetectionSettings(BaseSettings):
|
|||||||
class EventEngineSettings(BaseSettings):
|
class EventEngineSettings(BaseSettings):
|
||||||
"""事件决策 + 聚合 + 规则引擎配置。"""
|
"""事件决策 + 聚合 + 规则引擎配置。"""
|
||||||
|
|
||||||
model_config = SettingsConfigDict(env_prefix="EVENT_", extra="ignore")
|
model_config = SettingsConfigDict(env_prefix="EVENT_", env_file=_ENV_FILE, extra="ignore")
|
||||||
|
|
||||||
# 时间窗口去重 (秒),同一 (source_id, event_type, track_id) 在窗口内只产生一条
|
# 时间窗口去重 (秒),同一 (source_id, event_type, track_id) 在窗口内只产生一条
|
||||||
dedup_window_seconds: float = Field(default=30.0, ge=0.0)
|
dedup_window_seconds: float = Field(default=30.0, ge=0.0)
|
||||||
@@ -89,7 +92,7 @@ class EventEngineSettings(BaseSettings):
|
|||||||
class RTSPSettings(BaseSettings):
|
class RTSPSettings(BaseSettings):
|
||||||
"""RTSP 流接入相关配置 (MVP-2)。"""
|
"""RTSP 流接入相关配置 (MVP-2)。"""
|
||||||
|
|
||||||
model_config = SettingsConfigDict(env_prefix="RTSP_", extra="ignore")
|
model_config = SettingsConfigDict(env_prefix="RTSP_", env_file=_ENV_FILE, extra="ignore")
|
||||||
|
|
||||||
max_streams: int = Field(default=16, ge=1, description="最大同时接入流数量")
|
max_streams: int = Field(default=16, ge=1, description="最大同时接入流数量")
|
||||||
buffer_capacity: int = Field(default=300, ge=1, description="每路流帧缓冲区容量")
|
buffer_capacity: int = Field(default=300, ge=1, description="每路流帧缓冲区容量")
|
||||||
@@ -105,7 +108,7 @@ class RTSPSettings(BaseSettings):
|
|||||||
class MQTTSettings(BaseSettings):
|
class MQTTSettings(BaseSettings):
|
||||||
"""MQTT 预警发布相关配置 (MVP-2 / D16-D18)。"""
|
"""MQTT 预警发布相关配置 (MVP-2 / D16-D18)。"""
|
||||||
|
|
||||||
model_config = SettingsConfigDict(env_prefix="MQTT_", extra="ignore")
|
model_config = SettingsConfigDict(env_prefix="MQTT_", env_file=_ENV_FILE, extra="ignore")
|
||||||
|
|
||||||
enabled: bool = Field(default=False, description="是否启用 MQTT 发布")
|
enabled: bool = Field(default=False, description="是否启用 MQTT 发布")
|
||||||
broker_host: str = Field(default="localhost", description="MQTT broker 主机")
|
broker_host: str = Field(default="localhost", description="MQTT broker 主机")
|
||||||
@@ -126,7 +129,7 @@ class MQTTSettings(BaseSettings):
|
|||||||
class TrackingSettings(BaseSettings):
|
class TrackingSettings(BaseSettings):
|
||||||
"""目标跟踪 (ByteTrack) 配置 (MVP-2 / D19)。"""
|
"""目标跟踪 (ByteTrack) 配置 (MVP-2 / D19)。"""
|
||||||
|
|
||||||
model_config = SettingsConfigDict(env_prefix="TRACKING_", extra="ignore")
|
model_config = SettingsConfigDict(env_prefix="TRACKING_", env_file=_ENV_FILE, extra="ignore")
|
||||||
|
|
||||||
enabled: bool = Field(default=True, description="是否启用目标跟踪")
|
enabled: bool = Field(default=True, description="是否启用目标跟踪")
|
||||||
track_thresh: float = Field(default=0.5, ge=0.0, le=1.0, description="跟踪置信度阈值")
|
track_thresh: float = Field(default=0.5, ge=0.0, le=1.0, description="跟踪置信度阈值")
|
||||||
@@ -139,7 +142,7 @@ class TrackingSettings(BaseSettings):
|
|||||||
class AggregatorSettings(BaseSettings):
|
class AggregatorSettings(BaseSettings):
|
||||||
"""事件聚合器扩展配置 (MVP-2 / D20)。"""
|
"""事件聚合器扩展配置 (MVP-2 / D20)。"""
|
||||||
|
|
||||||
model_config = SettingsConfigDict(env_prefix="AGGREGATOR_", extra="ignore")
|
model_config = SettingsConfigDict(env_prefix="AGGREGATOR_", env_file=_ENV_FILE, extra="ignore")
|
||||||
|
|
||||||
enable_spatial_merge: bool = Field(default=True, description="是否启用空间邻近合并")
|
enable_spatial_merge: bool = Field(default=True, description="是否启用空间邻近合并")
|
||||||
spatial_iou_threshold: float = Field(
|
spatial_iou_threshold: float = Field(
|
||||||
@@ -154,10 +157,101 @@ class AggregatorSettings(BaseSettings):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class LLMSettings(BaseSettings):
|
||||||
|
"""LLM 二次判断服务配置 (MVP-3 / D29)。
|
||||||
|
|
||||||
|
支持 OpenAI 兼容协议 (GPT-4V / Qwen-VL / GLM-4V 等),
|
||||||
|
通过 ``provider=mock`` 在无 API Key 环境下保持离线可用。
|
||||||
|
"""
|
||||||
|
|
||||||
|
model_config = SettingsConfigDict(env_prefix="LLM_", env_file=_ENV_FILE, extra="ignore")
|
||||||
|
|
||||||
|
enabled: bool = Field(default=False, description="是否启用 LLM 二次判断")
|
||||||
|
provider: str = Field(
|
||||||
|
default="mock",
|
||||||
|
description="LLM provider: openai / qwen / glm / mock",
|
||||||
|
)
|
||||||
|
api_base: Optional[str] = Field(default=None, description="API base URL (OpenAI 兼容)")
|
||||||
|
api_key: Optional[str] = Field(default=None, description="API Key")
|
||||||
|
model: str = Field(default="gpt-4o", description="LLM 模型名称")
|
||||||
|
timeout: float = Field(default=15.0, ge=1.0, description="单次调用超时(秒)")
|
||||||
|
max_retries: int = Field(default=2, ge=0, description="失败重试次数")
|
||||||
|
max_tokens: int = Field(default=512, ge=64, description="最大返回 token 数")
|
||||||
|
temperature: float = Field(default=0.0, ge=0.0, le=2.0, description="采样温度")
|
||||||
|
max_concurrency: int = Field(default=2, ge=1, description="最大并发调用数")
|
||||||
|
image_max_side: int = Field(
|
||||||
|
default=768, ge=128, description="图像编码前最大边长 (节省 token)"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class LLMTriggerSettings(BaseSettings):
|
||||||
|
"""LLM 触发器配置 (MVP-3 / D26-D28)。"""
|
||||||
|
|
||||||
|
model_config = SettingsConfigDict(env_prefix="LLM_TRIGGER_", env_file=_ENV_FILE, extra="ignore")
|
||||||
|
|
||||||
|
enabled: bool = Field(default=False, description="是否启用 LLM 触发")
|
||||||
|
window_seconds: float = Field(
|
||||||
|
default=3.0, ge=0.5, description="多帧累积时间窗口(秒)"
|
||||||
|
)
|
||||||
|
min_consecutive_hits: int = Field(
|
||||||
|
default=3, ge=1, description="触发所需的最小连续命中帧数"
|
||||||
|
)
|
||||||
|
min_avg_confidence: float = Field(
|
||||||
|
default=0.55, ge=0.0, le=1.0, description="累积平均置信度阈值"
|
||||||
|
)
|
||||||
|
cooldown_seconds: float = Field(
|
||||||
|
default=20.0, ge=0.0, description="同目标 LLM 冷却时间(秒)"
|
||||||
|
)
|
||||||
|
max_track_capacity: int = Field(
|
||||||
|
default=2000, ge=1, description="累积器最大跟踪条目"
|
||||||
|
)
|
||||||
|
severity_bypass: List[str] = Field(
|
||||||
|
default_factory=lambda: ["critical"],
|
||||||
|
description="无需累积、立即触发的严重性级别",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class FusionSettings(BaseSettings):
|
||||||
|
"""LLM/YOLO 结果融合配置 (MVP-3 / D30)。"""
|
||||||
|
|
||||||
|
model_config = SettingsConfigDict(env_prefix="FUSION_", env_file=_ENV_FILE, extra="ignore")
|
||||||
|
|
||||||
|
strategy: str = Field(
|
||||||
|
default="weighted",
|
||||||
|
description="融合策略: weighted / conservative / llm_priority",
|
||||||
|
)
|
||||||
|
yolo_weight: float = Field(default=0.4, ge=0.0, le=1.0)
|
||||||
|
llm_weight: float = Field(default=0.6, ge=0.0, le=1.0)
|
||||||
|
# conservative 策略下,LLM 判定为否时直接抑制预警
|
||||||
|
suppress_on_llm_negative: bool = Field(default=True)
|
||||||
|
# LLM 不可用时是否回退到 YOLO 结果
|
||||||
|
fallback_to_yolo: bool = Field(default=True)
|
||||||
|
|
||||||
|
|
||||||
|
class LLMCostSettings(BaseSettings):
|
||||||
|
"""LLM 成本追踪 + 降级策略配置 (MVP-3 / D34)。"""
|
||||||
|
|
||||||
|
model_config = SettingsConfigDict(env_prefix="LLM_COST_", env_file=_ENV_FILE, extra="ignore")
|
||||||
|
|
||||||
|
daily_budget_usd: float = Field(
|
||||||
|
default=0.0, ge=0.0, description="日预算 (USD),0=不限"
|
||||||
|
)
|
||||||
|
history_days: int = Field(default=7, ge=1, description="保留多少天的日级统计")
|
||||||
|
recent_window: int = Field(
|
||||||
|
default=20, ge=3, description="错误率统计窗口 (最近 N 次调用)"
|
||||||
|
)
|
||||||
|
error_rate_threshold: float = Field(
|
||||||
|
default=0.5, gt=0.0, le=1.0, description="触发熔断的错误率阈值"
|
||||||
|
)
|
||||||
|
cooldown_seconds: float = Field(
|
||||||
|
default=60.0, ge=0.0, description="熔断冷却时间 (秒)"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class LoggingSettings(BaseSettings):
|
class LoggingSettings(BaseSettings):
|
||||||
"""日志配置。"""
|
"""日志配置。"""
|
||||||
|
|
||||||
model_config = SettingsConfigDict(env_prefix="LOG_", extra="ignore")
|
model_config = SettingsConfigDict(env_prefix="LOG_", env_file=_ENV_FILE, extra="ignore")
|
||||||
|
|
||||||
level: str = Field(default="INFO")
|
level: str = Field(default="INFO")
|
||||||
json_format: bool = Field(default=False)
|
json_format: bool = Field(default=False)
|
||||||
@@ -217,6 +311,10 @@ class Settings(BaseSettings):
|
|||||||
mqtt: MQTTSettings = Field(default_factory=MQTTSettings)
|
mqtt: MQTTSettings = Field(default_factory=MQTTSettings)
|
||||||
tracking: TrackingSettings = Field(default_factory=TrackingSettings)
|
tracking: TrackingSettings = Field(default_factory=TrackingSettings)
|
||||||
aggregator: AggregatorSettings = Field(default_factory=AggregatorSettings)
|
aggregator: AggregatorSettings = Field(default_factory=AggregatorSettings)
|
||||||
|
llm: LLMSettings = Field(default_factory=LLMSettings)
|
||||||
|
llm_trigger: LLMTriggerSettings = Field(default_factory=LLMTriggerSettings)
|
||||||
|
fusion: FusionSettings = Field(default_factory=FusionSettings)
|
||||||
|
llm_cost: LLMCostSettings = Field(default_factory=LLMCostSettings)
|
||||||
logging: LoggingSettings = Field(default_factory=LoggingSettings)
|
logging: LoggingSettings = Field(default_factory=LoggingSettings)
|
||||||
paths: PathSettings = Field(default_factory=PathSettings)
|
paths: PathSettings = Field(default_factory=PathSettings)
|
||||||
|
|
||||||
@@ -243,6 +341,10 @@ __all__ = [
|
|||||||
"MQTTSettings",
|
"MQTTSettings",
|
||||||
"TrackingSettings",
|
"TrackingSettings",
|
||||||
"AggregatorSettings",
|
"AggregatorSettings",
|
||||||
|
"LLMSettings",
|
||||||
|
"LLMTriggerSettings",
|
||||||
|
"FusionSettings",
|
||||||
|
"LLMCostSettings",
|
||||||
"LoggingSettings",
|
"LoggingSettings",
|
||||||
"PathSettings",
|
"PathSettings",
|
||||||
"get_settings",
|
"get_settings",
|
||||||
|
|||||||
Reference in New Issue
Block a user