config.py 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. """集中读取和校验项目配置。
  2. 配置优先从后端目录的 ``.env`` 读取;若某项未配置,则使用字段右侧的默认值。
  3. Pydantic 会自动把环境变量中的字符串转换成声明的 Python 类型。
  4. """
  5. from functools import lru_cache
  6. from pathlib import Path
  7. from typing import Literal
  8. from pydantic import AliasChoices, Field, field_validator
  9. from pydantic_settings import BaseSettings, SettingsConfigDict
  10. def resolve_env_file(start_directory: Path | None = None) -> Path:
  11. """兼容从 backend、项目根目录和可编辑安装环境启动。"""
  12. start = (start_directory or Path.cwd()).resolve()
  13. editable_backend = Path(__file__).resolve().parents[3]
  14. candidates = (
  15. start / ".env",
  16. start / "backend" / ".env",
  17. editable_backend / ".env",
  18. )
  19. for candidate in candidates:
  20. if candidate.is_file():
  21. return candidate
  22. return start / ".env"
  23. ENV_FILE = resolve_env_file()
  24. BACKEND_ROOT = ENV_FILE.parent
  25. class Settings(BaseSettings):
  26. """应用运行时需要的全部配置及其默认值。"""
  27. model_config = SettingsConfigDict(
  28. env_file=ENV_FILE,
  29. env_file_encoding="utf-8",
  30. case_sensitive=False,
  31. extra="ignore",
  32. populate_by_name=True,
  33. )
  34. # Literal 限制只能使用列出的三种环境名称,拼写错误会在启动时直接报错。
  35. app_env: Literal["development", "test", "production"] = "development"
  36. app_host: str = "127.0.0.1"
  37. app_port: int = 8000
  38. app_timezone: str = "Asia/Shanghai"
  39. app_cors_origins: list[str] = [
  40. "http://127.0.0.1:5173",
  41. "http://127.0.0.1:5174",
  42. ]
  43. mysql_host: str = "127.0.0.1"
  44. mysql_port: int = 3306
  45. mysql_user: str = "zbt_s2"
  46. mysql_password: str = ""
  47. mysql_core_database: str = "insurance_s2_core"
  48. mysql_agent_database: str = "insurance_s2_agent"
  49. mysql_analytics_database: str = "insurance_s2_analytics"
  50. redis_url: str = "redis://127.0.0.1:6379/0"
  51. redis_prefix: str = "ins:s2:"
  52. agent_rate_limit: int = Field(default=20, ge=1, le=1000)
  53. agent_rate_window_seconds: int = Field(default=60, ge=1, le=3600)
  54. agent_hot_state_ttl_seconds: int = Field(default=3600, ge=60, le=86400)
  55. agent_event_ttl_seconds: int = Field(default=1800, ge=60, le=86400)
  56. milvus_uri: str = "http://127.0.0.1:19530"
  57. milvus_token: str = ""
  58. milvus_collection_prefix: str = "ins_s2_"
  59. bge_m3_model_path: str = "BAAI/bge-m3"
  60. bge_m3_device: str = "cpu"
  61. bge_reranker_model_path: str = "BAAI/bge-reranker-v2-m3"
  62. bge_reranker_device: str = "cpu"
  63. embedding_dimension: int = Field(default=1024, ge=128, le=4096)
  64. knowledge_candidate_multiplier: int = Field(default=4, ge=2, le=20)
  65. deepseek_api_key: str = ""
  66. deepseek_base_url: str = "https://api.deepseek.com"
  67. deepseek_model: str = "deepseek-chat"
  68. dashscope_api_key: str = Field(
  69. default="",
  70. validation_alias=AliasChoices("DASHSCOPE_API_KEY", "QWEN_API_KEY"),
  71. )
  72. dashscope_base_url: str = Field(
  73. default="https://dashscope.aliyuncs.com/compatible-mode/v1",
  74. validation_alias=AliasChoices("DASHSCOPE_BASE_URL", "QWEN_BASE_URL"),
  75. )
  76. dashscope_vision_model: str = Field(
  77. default="qwen3.5-plus",
  78. validation_alias=AliasChoices("DASHSCOPE_VISION_MODEL", "QWEN_VISION_MODEL"),
  79. )
  80. service_material_storage_path: Path = BACKEND_ROOT / "var" / "service-materials"
  81. langsmith_api_key: str = ""
  82. langsmith_endpoint: str = "https://api.smith.langchain.com"
  83. langsmith_project: str = "智保通-第二阶段-知识服务与数据分析"
  84. langsmith_tracing: bool = True
  85. jwt_access_secret: str = Field(default="development-access-secret-change-me", min_length=32)
  86. jwt_refresh_secret: str = Field(default="development-refresh-secret-change-me", min_length=32)
  87. field_encryption_key: str = Field(default="development-field-key-change-me-now", min_length=32)
  88. dev_fixed_otp: str = Field(default="147258", pattern=r"^\d{6}$")
  89. dev_tools_enabled: bool = True
  90. mock_payment_callback_secret: str = Field(
  91. default="development-mock-payment-secret-change-me",
  92. min_length=32,
  93. )
  94. access_token_minutes: int = 15
  95. refresh_token_days: int = 7
  96. # 以下验证器限制程序只能访问本项目约定的数据库,避免误连或误操作其他库。
  97. @field_validator("mysql_core_database")
  98. @classmethod
  99. def validate_core_database(cls, value: str) -> str:
  100. if value not in {"insurance_s2_core", "insurance_s2_test_core"}:
  101. raise ValueError("第二阶段Core数据库名称不在白名单")
  102. return value
  103. @field_validator("mysql_agent_database")
  104. @classmethod
  105. def validate_agent_database(cls, value: str) -> str:
  106. if value not in {"insurance_s2_agent", "insurance_s2_test_agent"}:
  107. raise ValueError("第二阶段Agent数据库名称不在白名单")
  108. return value
  109. @field_validator("mysql_analytics_database")
  110. @classmethod
  111. def validate_analytics_database(cls, value: str) -> str:
  112. if value not in {"insurance_s2_analytics", "insurance_s2_test_analytics"}:
  113. raise ValueError("第二阶段Analytics数据库名称不在白名单")
  114. return value
  115. @field_validator("redis_prefix")
  116. @classmethod
  117. def validate_redis_prefix(cls, value: str) -> str:
  118. if value != "ins:s2:" and not value.startswith("ins:s2:test:"):
  119. raise ValueError("第二阶段Redis前缀不在白名单")
  120. return value
  121. @field_validator("milvus_collection_prefix")
  122. @classmethod
  123. def validate_milvus_prefix(cls, value: str) -> str:
  124. if not value.startswith("ins_s2_"):
  125. raise ValueError("第二阶段Milvus集合前缀不在白名单")
  126. return value
  127. @lru_cache
  128. def get_settings() -> Settings:
  129. """创建并缓存配置对象,后续调用复用同一个实例。"""
  130. return Settings()