Files
my-v0-server/app/config.py
T
2026-10-02 08:47:24 -07:00

125 lines
5.1 KiB
Python

import os
from dataclasses import dataclass, field
from pathlib import Path
from dotenv import load_dotenv
load_dotenv(Path(__file__).resolve().parent.parent / ".env")
@dataclass(frozen=True)
class Settings:
# LLM 配置
llm_api_key: str = field(
default_factory=lambda: os.getenv("LLM_API_KEY", ""),
repr=False,
)
llm_model: str = field(default_factory=lambda: os.getenv("LLM_MODEL", ""))
llm_base_url: str | None = field(
default_factory=lambda: os.getenv("LLM_BASE_URL") or None
)
llm_timeout: float = 60.0
llm_max_retries: int = 2
# Jev
typesafe_api_key: str = field(
default_factory=lambda: os.getenv("TYPESAFE_API_KEY", ""), repr=False
)
typesafe_model: str = field(
default_factory=lambda: os.getenv("TYPESAFE_DEFAULT_MODEL") or "jev-latest"
)
typesafe_base_url: str = field(
default_factory=lambda: (
os.getenv("TYPESAFE_BASE_URL") or "https://api.typesafe.ai"
)
)
typesafe_timeout: float = field(
default_factory=lambda: float(os.getenv("TYPESAFE_TIMEOUT", "30"))
)
typesafe_max_retries: int = field(
default_factory=lambda: int(os.getenv("TYPESAFE_MAX_RETRIES", "2"))
)
# 意图门控:Jev 选中 qa 且概率达到阈值才走问答,其余都交给 screen agent;阈值按真实语料校准
intent_qa_min_probability: float = field(
default_factory=lambda: float(os.getenv("INTENT_QA_MIN_PROBABILITY", "0.5"))
)
# screen agent 单次运行的模型调用上限,包括读取 skill、查询物料、提交修正和最终回复
max_agent_steps: int = field(
default_factory=lambda: int(os.getenv("MAX_AGENT_STEPS", "12"))
)
# 单次 MCP 工具调用的超时(秒),服务列表见 mcp_servers.json
mcp_tool_timeout: float = field(
default_factory=lambda: float(os.getenv("MCP_TOOL_TIMEOUT", "5"))
)
# 没有任何对话的空会话保留时长(秒),超时后视为不存在
conversation_empty_ttl: int = field(
default_factory=lambda: int(os.getenv("CONVERSATION_EMPTY_TTL", "7200"))
)
# 每次请求从会话记录读取的最近轮数上限,实际带入多少由 history_max_tokens 决定
conversation_history_turns: int = field(
default_factory=lambda: int(os.getenv("CONVERSATION_HISTORY_TURNS", "20"))
)
# 每次请求带入的会话历史 token 预算(近似计数),从最近一轮往前保留完整的轮次
history_max_tokens: int = field(
default_factory=lambda: int(os.getenv("HISTORY_MAX_TOKENS", "4096"))
)
# MongoDB 连接串,未配置时会话记录和检查点回退为进程内存实现
mongodb_uri: str | None = field(
default_factory=lambda: os.getenv("MONGODB_URI") or None, repr=False
)
mongodb_db: str = field(
default_factory=lambda: os.getenv("MONGODB_DB") or "screen_agent"
)
# 检查点保留时长(秒),超时由 MongoDB TTL 索引清理,0 表示永久保留
mongodb_checkpoint_ttl: int = field(
default_factory=lambda: int(os.getenv("MONGODB_CHECKPOINT_TTL", "604800"))
)
# 用户中心:请求携带的 token 交给它校验,换回 user_id
user_center_url: str = field(
default_factory=lambda: (
os.getenv("USER_CENTER_URL") or "http://127.0.0.1:8100"
).rstrip("/")
)
user_center_timeout_seconds: float = field(
default_factory=lambda: float(os.getenv("USER_CENTER_TIMEOUT_SECONDS", "5"))
)
# 关闭后不校验 token,所有请求视为同一个本地调试用户,仅用于开发
auth_enabled: bool = field(
default_factory=lambda: (
os.getenv("AUTH_ENABLED", "true").strip().lower()
not in ("false", "0", "no", "off")
)
)
# token 校验结果缓存时长(秒),登出或禁用后最多延迟这么久失效,0 表示不缓存
auth_cache_ttl_seconds: int = field(
default_factory=lambda: int(os.getenv("AUTH_CACHE_TTL_SECONDS", "30"))
)
def __post_init__(self):
if self.user_center_timeout_seconds <= 0:
raise ValueError("USER_CENTER_TIMEOUT_SECONDS 必须是正数")
if self.auth_cache_ttl_seconds < 0:
raise ValueError("AUTH_CACHE_TTL_SECONDS 必须是非负整数")
if self.mongodb_checkpoint_ttl < 0:
raise ValueError("MONGODB_CHECKPOINT_TTL 必须是非负整数")
if self.conversation_empty_ttl <= 0:
raise ValueError("CONVERSATION_EMPTY_TTL 必须是正整数")
if self.conversation_history_turns < 0:
raise ValueError("CONVERSATION_HISTORY_TURNS 必须是非负整数")
if self.history_max_tokens < 1:
raise ValueError("HISTORY_MAX_TOKENS 必须是正整数")
if self.max_agent_steps < 1:
raise ValueError("MAX_AGENT_STEPS 必须是正整数")
if self.mcp_tool_timeout <= 0:
raise ValueError("MCP_TOOL_TIMEOUT 必须是正数")
if not 0 <= self.intent_qa_min_probability <= 1:
raise ValueError("INTENT_QA_MIN_PROBABILITY 必须在 0 到 1 之间")
settings = Settings()