Files
life-echo/api/app/core/config.py
Kevin e884409410 feat(api): Memory compaction 管线与调度修复,同步环境变量示例
Memory compaction(近重复 chunk 软排除)
- 新增 compaction 调度:Redis debounce、scheduler gate、增量游标;任务结束时 finalize,避免 gate 长期占用并处理运行期新 trigger。
- Celery memory_compaction_run:debounce 未到点则 retry;用户级 Redis 锁;成功路径更新游标并 finalize;异常时释放 scheduler gate 并 self.retry,避免静默卡死调度与瞬时失败不重试。
- compaction_service:多层判定 + canonical 打分;无 embedding 时停止前移游标(awaiting_embeddings);curation details 补全 trigger 等上下文。
- ingest_transcript_sync:同步路径尽力写入 embedding,与异步 ingest 行为对齐,避免 compaction 永远扫不到无向量 chunk。
- repo:新增 update_chunk_embedding_sync。
测试
- 扩展 test_memory_compaction:调度合并、finalize、ingest embedding、无向量游标、异常路径 gate+retry 等回归用
2026-03-30 10:46:35 +08:00

189 lines
9.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
统一配置:所有环境变量通过此模块的 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"
# 启动时是否执行 Alembicmain.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_stageFalse 时仅用关键词
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_CHARSDEBUG 下记录 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 = 400
# 叙事输出相对口述过短则回退为口述原文(比例与下限)
memoir_narrative_fallback_body_ratio: float = 0.5
memoir_narrative_fallback_min_chars: int = 20
# ── Memory 检索与富化 ─────────────────────────────────────
# Truequery 为空时仍返回 rolling 摘要 + 最近事实/时间线(无 chunk FTS
memory_evidence_empty_query_include_rolling: bool = False
# False跳过 ingest 后 LLM 富化(摘要/事实/时间线)
memory_enrichment_enabled: bool = True
memory_enrichment_max_chars: int = Field(default=12000, ge=1000, le=100_000)
# ── Memory compaction近重复 chunk 软排除;事件触发 + Redis 防抖 + 用户锁)──
memory_compaction_enabled: bool = False
memory_compaction_debounce_seconds: int = Field(default=105, ge=10, le=3600)
memory_compaction_lock_ttl_seconds: int = Field(default=600, ge=60, le=7200)
memory_compaction_chunk_similarity_threshold: float = Field(
default=0.92, ge=0.5, le=0.999
)
memory_compaction_min_layers_for_exclude: int = Field(default=2, ge=1, le=3)
memory_compaction_max_chunks_per_run: int = Field(default=200, ge=1, le=10_000)
memory_compaction_max_excludes_per_run: int = Field(default=50, ge=1, le=1000)
memory_compaction_max_neighbors_per_chunk: int = Field(default=25, ge=5, le=100)
memory_compaction_text_jaccard_min: float = Field(default=0.55, ge=0.0, le=1.0)
memory_compaction_metadata_event_year_window: int = Field(default=1, ge=0, le=50)
# ── 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()