# 配置 SSOT(TOML + .env) Life Echo API 配置分为两层,避免 env 膨胀,同时保留密钥安全边界。 ## 两层 SSOT | 层 | 来源 | 改什么 | 如何生效 | |----|------|--------|----------| | **Secrets / bootstrap** | [`.env.example`](../.env.example) → `.env.development` / staging / production | `DATABASE_URL`、`SECRET_KEY`、API 密钥、支付/Liblib 私钥 | 改 env,重启进程 | | **非密钥配置** | [`config/default.toml`](../config/default.toml) + `config/{APP_ENV}.toml` | 功能开关、token 过期、SMS 模板 ID、Chat/Memoir 调参、OTel 等 | 改 TOML,**发版**(随镜像/代码部署) | `APP_ENV` 必须在 `.env` 中设置,用于选择 overlay:`development` / `staging` / `production`。 加载逻辑:[`app/core/app_config_loader.py`](../app/core/app_config_loader.py) 深合并 `default.toml` 与 `{APP_ENV}.toml`,经 Pydantic 校验后暴露为 [`app_config`](../app/core/app_config.py)。 ## 文件说明 ``` api/config/ default.toml # 全量默认值(所有 section) development.toml # 本地差异(OTel、mock 登录、dev COS 等) staging.toml # 预发差异(CORS、SMS/COS 业务 ID 等) production.toml # 生产差异 ``` TOML 顶层 section 与代码模块对应: | Section | 用途 | |---------|------| | `[deploy]` | 原 Settings 非密钥项(开关、SMS 模板、CORS、OTel endpoint 等) | | `[chat]` | 访谈 / 对话 | | `[memoir]` | 回忆录流水线 | | `[memory]` | Memory 富化 / compaction | | `[story]` | Story / 章节 | | `[eval]` | 内网评测 / judge | | `[llm]` `[asr]` `[tts]` `[celery]` `[alembic]` `[agent_log]` `[otel]` `[misc]` | 运行时默认 | 业务代码仍可通过原有 import 读取(薄 re-export): - `from app.features.conversation.constants import chat` - `from app.core.runtime_constants import llm_defaults` - `settings.enable_tts` 等 deploy 字段通过 [`SettingsFacade`](../app/core/config.py) 代理到 TOML ## 常见操作 **改访谈温度** ```toml # config/default.toml 或 config/staging.toml [chat] interview_temperature = 0.7 ``` **改生产 CORS** ```toml # config/production.toml [deploy] api_cors_origins = "https://your-domain.com" ``` **改 DeepSeek API Key** — 仍在 `.env`: ```env DEEPSEEK_API_KEY=sk-... ``` ## 旧 env 键对照(节选) | 旧 env 键 | 新位置 | |-----------|--------| | `CHAT_INTERVIEW_PERSONA` | `[chat] interview_persona` | | `CHAT_INTERVIEW_TEMPERATURE` | `[chat] interview_temperature` | | `MEMOIR_ORAL_NORMALIZE_MODE` | `[memoir] oral_normalize_mode` | | `MEMORY_COMPACTION_ENABLED` | `[memory] compaction_enabled` | | `EVAL_JUDGE_MODEL` | `[eval] judge_model` | | `DEEPSEEK_BASE_URL` | `[llm] deepseek_base_url` | | `ENABLE_TTS` | `[deploy] enable_tts` | | `TENCENT_SMS_SDK_APP_ID` | `[deploy] tencent_sms_sdk_app_id` | | `OTEL_ENABLED` | `[deploy] otel_enabled` | | `SECRET_KEY` | 仍在 `.env` | ## 测试 - 设置 `CONFIG_DIR` 指向 fixture 目录可隔离加载:见 [`tests/test_app_config_loader.py`](../tests/test_app_config_loader.py) - `tests/test_settings_allowlist.py` 防止 Settings 字段反弹 ## Docker `config/` 随 `COPY . .` 打入镜像;compose 仍 `env_file: .env` 注入密钥。确保容器内 `APP_ENV` 与目标 overlay 文件名一致。 ### Redis / Celery 分离 | 用途 | 默认 DB | 环境变量 | |------|---------|----------| | 会话、对话历史、task tracker 等业务 key | DB/0 | `REDIS_URL` | | Celery broker + result backend | DB/1 | `CELERY_REDIS_URL`(compose 显式注入;未设时由 `REDIS_URL` 自动 +1) | **升级注意:** 启用 DB 分离后,旧版在 DB/0 上未消费的 Celery 消息会被丢弃(一次性 cutover,无需迁移脚本)。 若 `REDIS_URL` 使用 DB/15,必须显式设置 `CELERY_REDIS_URL`(Redis 仅支持 logical DB 0–15,无法 auto +1)。 ## Shell 脚本与 TOML | 脚本 | 仍读 `.env` | 已改读 TOML | |------|-------------|-------------| | `development.sh` | `APP_ENV`、密钥、`OTEL_ENABLED`(legacy 覆盖) | `deploy.otel_enabled` → 是否起 Grafana 栈;`eval.internal_enable_docs` → 是否打印 `/docs` 链接 | | `deploy.sh` | 密钥、`DATABASE_URL` 等 | SMS 模板等不再检查 env(在 `config/production.toml`) | | `verify_observability_metrics.sh` | — | 提示文案指向 `deploy.otel_enabled` | **勿再依赖** `.env` 中的 `OTEL_ENABLED`、`MOCK_SMS_LOGIN_ENABLED`、`INTERNAL_EVAL_ENABLE_DOCS` 等(应用运行时已从 TOML 读取);测试里 `os.environ["OTEL_ENABLED"]` 亦已无效,见 `tests/conftest.py`。