- DB: segments 用户输入文本(Alembic 0002) - Chat: 阶段检测/阶段提示/回复限制,编排与访谈/画像 prompts 调整 - Memoir: 忠实度检查 agent,叙事与分类等链路更新 - Core: agent 日志、Alembic 启动、LangChain/日志/配置等 - Story: time_hints;Memory 检索与相关测试 - Expo: 助手头像、会话页与消息拆分、实时会话与文案/i18n - Docs/scripts/tests: 迁移脚本、LLM JSON/记忆检索文档、新增单测
168 lines
8.2 KiB
Python
168 lines
8.2 KiB
Python
"""
|
||
统一配置:所有环境变量通过此模块的 Settings 单点读取。
|
||
业务代码只允许 import settings,禁止散落 os.getenv() / load_dotenv()。
|
||
|
||
本地开发时由 api/development.sh 在启动前将 .env.development 同步为 .env(每次启动覆盖)。
|
||
Docker / 服务端由镜像与 compose 注入进程环境;此处仅固定读取工作目录下的 .env 作为默认值来源。
|
||
进程环境变量(容器 environment、export)覆盖 .env 同名项。
|
||
"""
|
||
|
||
import secrets
|
||
|
||
from pydantic import Field, field_validator
|
||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||
|
||
|
||
class Settings(BaseSettings):
|
||
model_config = SettingsConfigDict(
|
||
env_file=".env",
|
||
env_file_encoding="utf-8",
|
||
case_sensitive=False,
|
||
extra="ignore",
|
||
)
|
||
|
||
# ── Database ──────────────────────────────────────────────
|
||
database_url: str = "postgresql://postgres:postgres@localhost:5432/life_echo"
|
||
# 启动时是否执行 Alembic(main.py lifespan);测试或仅读场景可关
|
||
alembic_run_on_startup: bool = True
|
||
# True:迁移失败则进程退出(生产推荐)。False:仅打错误日志并继续(本地无 DB 时)
|
||
alembic_startup_fail_fast: bool = False
|
||
alembic_startup_max_retries: int = Field(default=3, ge=1, le=10)
|
||
alembic_startup_retry_base_seconds: float = Field(default=1.0, ge=0.1, le=60.0)
|
||
|
||
# ── Redis ─────────────────────────────────────────────────
|
||
redis_url: str = "redis://localhost:6379/0"
|
||
redis_session_ttl: int = 86400
|
||
|
||
# ── Auth / JWT ────────────────────────────────────────────
|
||
secret_key: str = Field(default_factory=lambda: secrets.token_urlsafe(32))
|
||
algorithm: str = "HS256"
|
||
access_token_expire_minutes: int = 120
|
||
refresh_token_expire_days: int = 30
|
||
|
||
# ── LLM / DeepSeek ───────────────────────────────────────
|
||
deepseek_api_key: str = ""
|
||
deepseek_base_url: str = "https://api.deepseek.com"
|
||
deepseek_model: str = "deepseek-chat"
|
||
llm_api_key: str = ""
|
||
llm_base_url: str = ""
|
||
llm_model: str = ""
|
||
llm_temperature: float = 0.7
|
||
|
||
# ── Chat 访谈(短回复:token 上限 + 代码截断,见 reply_limits)──
|
||
chat_interview_max_tokens: int = 320
|
||
chat_interview_max_segments: int = 2
|
||
chat_interview_max_chars_per_segment: int = 220
|
||
chat_opening_max_tokens: int = 256
|
||
chat_profile_followup_max_tokens: int = 280
|
||
chat_era_context_enabled: bool = True
|
||
# 访谈:每轮用 LLM 判定用户主人生阶段并更新 MemoirState.current_stage;False 时仅用关键词
|
||
chat_stage_detection_enabled: bool = True
|
||
chat_stage_detection_max_tokens: int = 128
|
||
|
||
# ── Memoir 叙事忠实度检查(FidelityCheckAgent)────────────────
|
||
memoir_fidelity_check_enabled: bool = True
|
||
memoir_fidelity_check_max_tokens: int = 512
|
||
|
||
# ── ASR ───────────────────────────────────────────────────
|
||
asr_provider: str = "whisper"
|
||
asr_model_size: str = "small"
|
||
asr_device: str = "auto"
|
||
asr_compute_type: str = "auto"
|
||
asr_model_cache_dir: str = ""
|
||
|
||
# ── Tencent SMS ──────────────────────────────────────────
|
||
tencent_sms_secret_id: str = ""
|
||
tencent_sms_secret_key: str = ""
|
||
tencent_sms_sdk_app_id: str = ""
|
||
tencent_sms_sign_name: str = ""
|
||
tencent_sms_template_id: str = ""
|
||
tencent_sms_template_param_count: int = 2
|
||
|
||
# ── Tencent ASR / TTS(共用 Secret;与短信、COS 密钥独立)────────────────
|
||
tencent_secret_id: str = ""
|
||
tencent_secret_key: str = ""
|
||
|
||
# ── TTS (openai | tencent),与 ASR 独立:仅控制回复侧语音合成 ──
|
||
enable_tts: bool = True
|
||
tts_provider: str = "tencent"
|
||
openai_api_key: str = ""
|
||
tts_voice_type: int = 502001 # Tencent 音色 ID,见 https://cloud.tencent.com/document/product/1073/92668
|
||
tts_codec: str = "mp3"
|
||
|
||
# ── WeChat Pay ───────────────────────────────────────────
|
||
wechat_pay_app_id: str = ""
|
||
wechat_pay_mch_id: str = ""
|
||
wechat_pay_api_v3_key: str = ""
|
||
wechat_pay_private_key_path: str = "certs/apiclient_key.pem"
|
||
wechat_pay_private_key: str = "" # PEM 内容,与 private_key_path 二选一
|
||
wechat_pay_cert_serial_no: str = ""
|
||
wechat_pay_notify_url: str = ""
|
||
wechat_pay_platform_public_key: str = ""
|
||
wechat_pay_platform_public_key_path: str = ""
|
||
wechat_pay_platform_public_key_id: str = ""
|
||
|
||
# ── Alipay ───────────────────────────────────────────────
|
||
alipay_app_id: str = ""
|
||
alipay_private_key: str = ""
|
||
alipay_public_key: str = ""
|
||
alipay_notify_url: str = ""
|
||
alipay_sign_type: str = "RSA2"
|
||
alipay_under_development: str = "true" # "1"/"true"/"yes" 视为开发中不可用
|
||
|
||
# ── Logging ──────────────────────────────────────────────
|
||
# 环境变量 LOG_LEVEL;控制 loguru sink 最低级别(TRACE/DEBUG/INFO/…)
|
||
log_level: str = "INFO"
|
||
# LOG_AGENT_VERBOSE:为 True 时额外输出 Agent 单行 INFO 摘要(耗时、规模),无需全局 DEBUG
|
||
log_agent_verbose: bool = False
|
||
# AGENT_LOG_MAX_CHARS:DEBUG 下记录 prompt/响应预览时的最大字符数
|
||
agent_log_max_chars: int = Field(default=4096, ge=256, le=100_000)
|
||
# 第三方 stdlib logging(空=自动:LOG_LEVEL 为 DEBUG/TRACE 时 Celery→INFO、httpx/httpcore→WARNING)
|
||
celery_log_level: str = ""
|
||
httpx_log_level: str = ""
|
||
|
||
@field_validator("log_agent_verbose", mode="before")
|
||
@classmethod
|
||
def _coerce_log_agent_verbose(cls, v: object) -> bool:
|
||
if isinstance(v, bool):
|
||
return v
|
||
if v is None:
|
||
return False
|
||
return str(v).strip().lower() in ("1", "true", "yes", "on")
|
||
|
||
# ── Misc ─────────────────────────────────────────────────
|
||
enable_test_subscription: int = 0
|
||
enable_test_plan: str = "" # "1" / "true" / "yes" 为 True
|
||
enable_docs: bool = True
|
||
|
||
# ── Memoir Image ─────────────────────────────────────────
|
||
memoir_image_enabled: bool = False
|
||
memoir_image_poll_interval: int = 3
|
||
memoir_image_max_attempts: int = 20
|
||
memoir_image_provider: str = "liblib"
|
||
memoir_image_style_default: str = "watercolor"
|
||
memoir_image_size_default: str = "1280x720"
|
||
memoir_image_download_hosts: str = ""
|
||
# Story 正文至少多少字才创建主图 intent / 调图(0 表示不限制)
|
||
story_image_min_body_chars: int = 800
|
||
# 叙事输出相对口述过短则回退为口述原文(比例与下限)
|
||
memoir_narrative_fallback_body_ratio: float = 0.5
|
||
memoir_narrative_fallback_min_chars: int = 20
|
||
|
||
# ── Liblib ───────────────────────────────────────────────
|
||
liblib_access_key: str = ""
|
||
liblib_secret_key: str = ""
|
||
liblib_base_url: str = "https://openapi.liblibai.cloud"
|
||
liblib_template_uuid: str = ""
|
||
|
||
# ── Tencent COS ──────────────────────────────────────────
|
||
tencent_cos_secret_id: str = ""
|
||
tencent_cos_secret_key: str = ""
|
||
tencent_cos_region: str = "ap-shanghai"
|
||
tencent_cos_bucket: str = ""
|
||
tencent_cos_base_url: str = ""
|
||
tencent_cos_token: str = ""
|
||
|
||
|
||
settings = Settings()
|