config.py 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. import os
  2. from dataclasses import dataclass
  3. from typing import Optional, Tuple
  4. from dotenv import load_dotenv
  5. # 加载环境变量
  6. load_dotenv()
  7. @dataclass
  8. class AppConfig:
  9. """应用配置数据类"""
  10. # API配置
  11. api_key: str
  12. app_id: str
  13. api_endpoint: str
  14. # 应用配置
  15. secret_key: str
  16. port: int
  17. debug: bool
  18. # 数据库配置
  19. db_host: str
  20. db_port: int
  21. db_name: str
  22. db_user: str
  23. db_password: str
  24. db_charset: str
  25. # 安全配置
  26. max_message_length: int
  27. max_conversation_history: int
  28. rate_limit_requests: int
  29. rate_limit_window: int
  30. # 日志配置
  31. log_level: str
  32. log_format: str
  33. @classmethod
  34. def from_env(cls) -> 'AppConfig':
  35. """从环境变量创建配置"""
  36. return cls(
  37. # API配置
  38. api_key=os.getenv("API_KEY") or "",
  39. app_id=os.getenv("APP_ID") or "",
  40. api_endpoint=os.getenv("API_ENDPOINT") or "",
  41. # 应用配置
  42. secret_key=os.getenv("SECRET_KEY") or os.urandom(24).hex(),
  43. port=int(os.getenv("PORT", "8000")),
  44. debug=os.getenv("FLASK_DEBUG", "false").lower() == "true",
  45. # 数据库配置
  46. db_host=os.getenv("DB_HOST", "localhost"),
  47. db_port=int(os.getenv("DB_PORT", "3306")),
  48. db_name=os.getenv("DB_NAME", "ai_writing_db"),
  49. db_user=os.getenv("DB_USER", "root"),
  50. db_password=os.getenv("DB_PASSWORD", ""),
  51. db_charset=os.getenv("DB_CHARSET", "utf8mb4"),
  52. # 安全配置
  53. max_message_length=int(os.getenv("MAX_MESSAGE_LENGTH", "20000")),
  54. max_conversation_history=int(os.getenv("MAX_CONVERSATION_HISTORY", "50")),
  55. rate_limit_requests=int(os.getenv("RATE_LIMIT_REQUESTS", "100")),
  56. rate_limit_window=int(os.getenv("RATE_LIMIT_WINDOW", "3600")),
  57. # 日志配置
  58. log_level=os.getenv("LOG_LEVEL", "INFO"),
  59. log_format=os.getenv("LOG_FORMAT", "%(asctime)s - %(name)s - %(levelname)s - %(message)s")
  60. )
  61. def validate(self) -> None:
  62. """验证配置的有效性"""
  63. if not self.api_key:
  64. raise ValueError("API_KEY 环境变量必须设置")
  65. # 允许通过基础 APP_ID 或 按智能体区分的 APP_ID_{AGENT} 提供
  66. has_base_app_id = bool(self.app_id)
  67. has_any_agent_app_id = any(k.startswith("APP_ID_") and os.getenv(k) for k in os.environ.keys())
  68. if not (has_base_app_id or has_any_agent_app_id):
  69. raise ValueError("必须设置 APP_ID,或通过 APP_ID_{AGENT} 之一提供,例如 APP_ID_KUAISHOU")
  70. if not self.api_endpoint:
  71. raise ValueError("API_ENDPOINT 环境变量必须设置")
  72. if self.max_message_length <= 0:
  73. raise ValueError("MAX_MESSAGE_LENGTH 必须大于0")
  74. if self.max_conversation_history <= 0:
  75. raise ValueError("MAX_CONVERSATION_HISTORY 必须大于0")
  76. if self.rate_limit_requests <= 0:
  77. raise ValueError("RATE_LIMIT_REQUESTS 必须大于0")
  78. if self.rate_limit_window <= 0:
  79. raise ValueError("RATE_LIMIT_WINDOW 必须大于0")
  80. # 新增:根据智能体名称获取 APP_ID(默认快手),带来源信息
  81. def get_app_id_for_agent_with_source(self, agent_name: Optional[str]) -> Tuple[str, str]:
  82. """
  83. 根据前端传入的智能体标识,从环境变量读取对应的 APP_ID,并返回使用的环境变量键名。
  84. 优先匹配:
  85. 其次再尝试 APP_ID_{ALIAS} 以及其它别名;最后回退 APP_ID(通用)与 APP_ID_KUAISHOU。
  86. 返回: (app_id, source_env_key)
  87. 若找不到返回 ("", "")
  88. """
  89. normalized = (agent_name or "kuaishou").strip().lower()
  90. # 1) 按你的明确映射优先
  91. primary_map = {
  92. 'kuaishou': ["APP_ID"],
  93. 'toutiao': ["TOUTIAO_APP_ID"],
  94. 'pathology': ["PATHOLOGY_APP_ID"], #病理分析
  95. 'planning': ["PLANNING_APP_ID"], #策划大师
  96. 'diagnosis': ["DIAGNOSIS_APP_ID"], #投放诊断
  97. }
  98. # 2) 兼容性别名(作为次优先)
  99. alias_map = {
  100. 'kuaishou': ["APP_ID_KUAISHOU", "APP_ID_KS",],
  101. 'toutiao': ["APP_ID_TOUTIAO", "APP_ID_TT", ],
  102. 'planning': ["APP_ID_PLANNING", "APP_ID_PL",], #病理分析
  103. 'pathology': ["APP_ID_PATHOLOGY",], #策划大师
  104. 'diagnosis': ["DIAGNOSIS_APP_ID",], #投放诊断
  105. }
  106. for key in primary_map.get(normalized, []):
  107. val = os.getenv(key)
  108. if val:
  109. return val, key
  110. # 生成 APP_ID_{AGENT.upper()} 形式作为额外兼容
  111. generic_key = f"APP_ID_{normalized.upper()}"
  112. if generic_key not in alias_map.get(normalized, []):
  113. alias_candidates = [generic_key] + alias_map.get(normalized, [])
  114. else:
  115. alias_candidates = alias_map.get(normalized, [])
  116. for key in alias_candidates:
  117. val = os.getenv(key)
  118. if val:
  119. return val, key
  120. # 3) 通用 APP_ID 兜底(非快手也可作为兜底)
  121. if self.app_id:
  122. return self.app_id, "APP_ID"
  123. fallback = os.getenv("APP_ID_KUAISHOU")
  124. if fallback:
  125. return fallback, "APP_ID_KUAISHOU"
  126. return "", ""
  127. # 兼容旧方法:仅返回 APP_ID
  128. def get_app_id_for_agent(self, agent_name: Optional[str]) -> str:
  129. app_id, _ = self.get_app_id_for_agent_with_source(agent_name)
  130. return app_id
  131. def get_database_url(self) -> str:
  132. """获取数据库连接URL"""
  133. return f"mysql+pymysql://{self.db_user}:{self.db_password}@{self.db_host}:{self.db_port}/{self.db_name}?charset={self.db_charset}"
  134. # 创建全局配置实例
  135. config = AppConfig.from_env()
  136. # 验证配置
  137. try:
  138. config.validate()
  139. except ValueError as e:
  140. exit(1)