* add staging ios app build script * feat(api): add OpenTelemetry LGTM stack for local observability Wire OTel traces, metrics, and logs through a collector to Tempo, Prometheus, and Loki, with custom LLM instrumentation, dev compose overlay, Grafana provisioning, env templates, and development.sh auto-start. * feat: expand observability, harden dev tooling, and fix expo staging UX Add business and LLM Prometheus metrics with Grafana dashboards, alerting, and a metrics verification script. Wire telemetry through adapters and core LLM paths, and document the local LGTM workflow. Fix development.sh for macOS bash 3.2, open Grafana and eval-web in Chrome, and repair eval-web auto-open (unbound EVAL_WEB_BROWSER_SCHEDULED). Merge internal-eval into the main dev script with improved compose handling. Require EXPO_PUBLIC_* at build time, improve iOS HTTP ATS for staging IPs, show memoir empty state instead of load errors when no chapters exist, and add jest env setup plus chapter list response normalization. * chore: enable Grafana Assistant Cursor plugin * fix: memoir empty state and repair withdrawn 0020_chapters_book_id stamp Show empty memoir UI when the chapter list succeeds with no items; treat auth/404 as non-fatal. Extend alembic revision repair so local dev DBs stamped with the removed 0020_chapters_book_id migration can roll back and upgrade to 0019. --------- Co-authored-by: Kevin <kevin@brighteng.org> Co-authored-by: Cursor <cursoragent@cursor.com>
287 lines
15 KiB
Plaintext
287 lines
15 KiB
Plaintext
# =============================================================================
|
||
# Life Echo API — production(生产)
|
||
#
|
||
# 仓库维护本文件;production 发布时 workflow 会上传并复制为运行时 .env。
|
||
# 若仓库可被非授权人员访问,请不要在此文件中保留真实密钥。
|
||
# =============================================================================
|
||
|
||
# =============================================================================
|
||
# Docker Compose(宿主机独立 Caddy 反代到本 API)
|
||
# =============================================================================
|
||
# 映射到宿主机的端口,默认 8000;与同机其它项目冲突时改为未占用端口,并在独立 Caddy 的 Caddyfile 中 reverse_proxy 到 127.0.0.1:该端口。
|
||
# LIFE_ECHO_API_HOST_PORT=8000
|
||
# 若 Caddy 跑在独立容器且非 host 网络,不要用 127.0.0.1,应把 Caddy 加入与本 compose 相同的 Docker 网络,并对 http://life-echo-api-prod:8000 做 reverse_proxy。
|
||
|
||
# =============================================================================
|
||
# Logging(loguru sink 最低级别:TRACE / DEBUG / INFO / WARNING / ERROR / CRITICAL)
|
||
# =============================================================================
|
||
# 生产默认 INFO;勿长期 DEBUG。排障 Agent 耗时可短时 LOG_AGENT_VERBOSE=1。
|
||
LOG_LEVEL=INFO
|
||
# Agent 单行 INFO 摘要;与 LOG_LEVEL 独立,便于生产短暂排查
|
||
# LOG_AGENT_VERBOSE=0
|
||
# DEBUG 下预览上限(默认 4096);0=全文
|
||
# AGENT_LOG_MAX_CHARS=4096
|
||
# DEBUG 下 *.prompt:preview | hash_only
|
||
# AGENT_LOG_PROMPT_MODE=preview
|
||
# AGENT_LOG_PROMPT_DEDUP=0
|
||
# DEBUG 下访谈/资料:省略 SystemMessage 正文(仅 total_len+sha12);0/false=打出全文
|
||
# AGENT_LOG_OMIT_SYSTEM_MESSAGE_BODY=1
|
||
# DEBUG 下超长单段 *.prompt:先跳过前 N 字符再预览
|
||
# AGENT_LOG_JSON_PROMPT_PREFIX_CHARS=0
|
||
# AGENT_LOG_JSON_PROMPT_PREFIX_ONLY_IF_LEN_GT=4000
|
||
# 第三方 stdlib logging(空=自动:LOG_LEVEL 为 DEBUG/TRACE 时 Celery→INFO、httpx/httpcore→WARNING,减少刷屏)
|
||
# CELERY_LOG_LEVEL=
|
||
# HTTPX_LOG_LEVEL=
|
||
|
||
# =============================================================================
|
||
# OpenTelemetry(生产;第二阶段 compose profile 接入后设 OTEL_ENABLED=true,见 docs/observability.md)
|
||
# 容器内 API/Celery → http://otel-collector:4317;勿用 localhost
|
||
# =============================================================================
|
||
OTEL_ENABLED=false
|
||
OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4317
|
||
OTEL_EXPORTER_OTLP_INSECURE=true
|
||
OTEL_SERVICE_NAME=life-echo-api
|
||
OTEL_TRACES_SAMPLER=parentbased_traceidratio
|
||
OTEL_TRACES_SAMPLER_ARG=0.1
|
||
# OTEL_METRIC_EXPORT_INTERVAL_MS=10000
|
||
|
||
# =============================================================================
|
||
# LLM / DeepSeek
|
||
# =============================================================================
|
||
DEEPSEEK_API_KEY=sk-09f17fb61c5a4299a3afc2a01de7af75
|
||
DEEPSEEK_BASE_URL=https://api.deepseek.com
|
||
DEEPSEEK_MODEL=deepseek-v4-flash
|
||
|
||
# =============================================================================
|
||
# Memory 向量(智谱 BigModel 国内 embedding-3;与 DeepSeek/OpenAI 用途分离)
|
||
# 文档:https://docs.bigmodel.cn/cn/guide/models/embedding/embedding-3
|
||
# 本期固定 1024 维;库表经迁移与 MEMORY_EMBEDDING_DIMENSION 一致。
|
||
# =============================================================================
|
||
ZHIPU_API_KEY=524eda18eb3848e881eefe4c7ef17ec2.xBmGUabYDEa44m3M
|
||
# 默认国内通用端点(与 ZhipuAiClient 一致)
|
||
# EMBEDDING_BASE_URL=https://open.bigmodel.cn/api/paas/v4
|
||
EMBEDDING_MODEL=embedding-3
|
||
|
||
# Chat 访谈:每轮根据用户内容判定主人生阶段(关则仅用关键词,省一次 LLM)
|
||
# CHAT_STAGE_DETECTION_ENABLED=true
|
||
# CHAT_STAGE_DETECTION_MAX_TOKENS=128
|
||
# 访谈者体验(覆盖 config 默认值;与 api/.env.development 对齐时可减少文风漂移与记忆噪声)
|
||
CHAT_ERA_CONTEXT_ENABLED=true
|
||
CHAT_INTERVIEW_PERSONA=warm_listener
|
||
CHAT_INTERVIEW_TEMPERATURE=0.65
|
||
# 访谈:是否按本轮用户话检索记忆并注入提示词(关则不调 retrieve)
|
||
# CHAT_MEMORY_RETRIEVAL_ENABLED=true
|
||
CHAT_MEMORY_TOP_K=4
|
||
CHAT_MEMORY_EVIDENCE_MAX_CHARS=1400
|
||
CHAT_REPLY_PLANNER_LLM_ENABLED=true
|
||
# 访谈回复长度档位(brief/standard/expanded)联动:极短输入 / 默认 / 长段+新细节
|
||
# CHAT_INTERVIEW_BRIEF_MAX_TOKENS=240
|
||
# CHAT_INTERVIEW_BRIEF_MAX_CHARS_PER_SEGMENT=180
|
||
# CHAT_INTERVIEW_EXPANDED_MAX_TOKENS=400
|
||
# CHAT_INTERVIEW_EXPANDED_MAX_CHARS_PER_SEGMENT=300
|
||
|
||
# Memoir:批处理/抽取更新 slot 时是否允许改写 MemoirState.current_stage(默认 false,访谈 switch_stage 仍可推进)
|
||
# True 时仅当 proposed 与 existing 在同一 chat_bucket 才对齐 current_stage
|
||
# MEMOIR_EXTRACTION_UPDATES_CURRENT_STAGE=false
|
||
|
||
# Memoir:叙事前口述归一(segment 原文仍落库;仅 story 流水线派生输入)
|
||
MEMOIR_ORAL_NORMALIZE_ENABLED=true
|
||
# off | rules | llm(llm 为先规则再 LLM 纠错,失败回退规则结果)
|
||
MEMOIR_ORAL_NORMALIZE_MODE=llm
|
||
MEMOIR_ORAL_NORMALIZE_LLM_MAX_TOKENS=512
|
||
MEMOIR_ORAL_NORMALIZE_LLM_MAX_INPUT_CHARS=8000
|
||
|
||
# Chat:模型消费净稿(segment 原文仍落库;访谈编排层归一后注入 Agent / 记忆检索)
|
||
# CHAT_INPUT_NORMALIZE_ENABLED=true
|
||
# off | rules | llm(llm 为先规则再 LLM;失败回退规则;编排层已带 LLM 时不重复在 Agent 调)
|
||
# CHAT_INPUT_NORMALIZE_MODE=rules
|
||
# CHAT_INPUT_NORMALIZE_LLM_MAX_TOKENS=512
|
||
# CHAT_INPUT_NORMALIZE_LLM_MAX_INPUT_CHARS=8000
|
||
|
||
# =============================================================================
|
||
# Database
|
||
# =============================================================================
|
||
# 本地开发:
|
||
# DATABASE_URL=postgresql://postgres:postgres@localhost:5432/life_echo
|
||
# Docker / 服务端(主机名一般为 compose 服务名 postgres):
|
||
# DATABASE_URL=postgresql://postgres:postgres@postgres:5432/life_echo
|
||
DATABASE_URL=postgresql://postgres:postgres@postgres:5432/life_echo
|
||
# 启动时 Alembic(main.py);生产可设 ALEMBIC_STARTUP_FAIL_FAST=true,迁移失败则拒绝启动
|
||
# ALEMBIC_RUN_ON_STARTUP=true
|
||
# ALEMBIC_STARTUP_FAIL_FAST=false
|
||
# ALEMBIC_STARTUP_MAX_RETRIES=3
|
||
# ALEMBIC_STARTUP_RETRY_BASE_SECONDS=1.0
|
||
|
||
# =============================================================================
|
||
# Redis
|
||
# =============================================================================
|
||
# 本地开发:
|
||
# REDIS_URL=redis://localhost:6379/0
|
||
# Docker / 服务端:
|
||
# REDIS_URL=redis://redis:6379/0
|
||
REDIS_URL=redis://redis:6379/0
|
||
REDIS_SESSION_TTL=86400
|
||
|
||
# Celery:ingest 后 Memory LLM 富化任务投递队列(须被 worker 消费;见 README)
|
||
# CELERY_MEMORY_ENRICHMENT_QUEUE=memory_idle
|
||
|
||
# =============================================================================
|
||
# Memory compaction(近重复 memory chunk 软排除;Celery + Redis 防抖)
|
||
# 与 .env.example / .env.development 一致默认开启;需 running:celery worker + celery-beat(见 docker-compose.yml)。
|
||
# =============================================================================
|
||
MEMORY_COMPACTION_ENABLED=true
|
||
# MEMORY_COMPACTION_DEBOUNCE_SECONDS=105
|
||
# MEMORY_COMPACTION_LOCK_TTL_SECONDS=600
|
||
# MEMORY_COMPACTION_CHUNK_SIMILARITY_THRESHOLD=0.92
|
||
# MEMORY_COMPACTION_MIN_LAYERS_FOR_EXCLUDE=2
|
||
# MEMORY_COMPACTION_MAX_CHUNKS_PER_RUN=200
|
||
# MEMORY_COMPACTION_MAX_EXCLUDES_PER_RUN=50
|
||
# MEMORY_COMPACTION_MAX_NEIGHBORS_PER_CHUNK=25
|
||
# MEMORY_COMPACTION_TEXT_JACCARD_MIN=0.55
|
||
# MEMORY_COMPACTION_METADATA_EVENT_YEAR_WINDOW=1
|
||
# MEMORY_COMPACTION_SWEEP_RECENT_HOURS=24
|
||
|
||
# =============================================================================
|
||
# Story 流水线(post-commit、章节物化、append 上限、evidence 检索)
|
||
# =============================================================================
|
||
# STORY_IMAGE_ENQUEUE_DEDUP_TTL=300
|
||
# RECOMPOSE_CHAPTER_DELAY_SECONDS=8
|
||
# CHAPTER_PIPELINE_LOCK_TTL_SECONDS=120
|
||
# STORY_APPEND_MAX_CANONICAL_CHARS=12000
|
||
# STORY_APPEND_MAX_VERSIONS=20
|
||
# EVIDENCE_TOP_K_DEFAULT=10
|
||
# EVIDENCE_TOP_K_LARGE_BATCH=5
|
||
# EVIDENCE_LARGE_BATCH_THRESHOLD=3
|
||
|
||
# =============================================================================
|
||
# Auth
|
||
# =============================================================================
|
||
# 建议使用: openssl rand -hex 32
|
||
SECRET_KEY=cf47555c7ecbe5ddb7fd2113c59e08a8bcb110810c42f7c644e06a5acc898608
|
||
ALGORITHM=HS256
|
||
ACCESS_TOKEN_EXPIRE_MINUTES=120
|
||
|
||
# =============================================================================
|
||
# Tencent Cloud — 短信
|
||
# =============================================================================
|
||
# 短信、一句话 ASR/TTS、COS 为不同产品;同一主账号可共用同一对 SecretId/SecretKey(分别填三处)。
|
||
TENCENT_SMS_SECRET_ID=AKIDa2ILCwUr56uVt31oU0JOHxPfGhvvkLiq
|
||
TENCENT_SMS_SECRET_KEY=xiFbjlZ9XheS2NWYLvHRPAh2A5nGYcR2
|
||
# 短信应用 SDK AppID
|
||
TENCENT_SMS_SDK_APP_ID=1401010099
|
||
# 短信签名内容(不包含【】符号)
|
||
TENCENT_SMS_SIGN_NAME=上海华嘎科技有限公司
|
||
# 短信模板 ID
|
||
TENCENT_SMS_TEMPLATE_ID=2592163
|
||
# 短信模板参数数量(1=仅验证码,2=验证码+过期时间)
|
||
# 若遇 TemplateParamSetNotMatchApprovedTemplate,请对照控制台模板配置
|
||
TENCENT_SMS_TEMPLATE_PARAM_COUNT=1
|
||
|
||
# =============================================================================
|
||
# ASR Provider(whisper | tencent)
|
||
# =============================================================================
|
||
ASR_PROVIDER=tencent
|
||
|
||
# =============================================================================
|
||
# Whisper ASR(ASR_PROVIDER=whisper 时使用)
|
||
# =============================================================================
|
||
ASR_MODEL_SIZE=small
|
||
ASR_DEVICE=cpu
|
||
ASR_COMPUTE_TYPE=int8
|
||
|
||
# GPU 环境(示例,按需启用)
|
||
# ASR_MODEL_SIZE=medium
|
||
# ASR_DEVICE=cuda
|
||
# ASR_COMPUTE_TYPE=float16
|
||
|
||
# =============================================================================
|
||
# Tencent Cloud — 一句话 ASR + TTS(ASR_PROVIDER=tencent 或 TTS_PROVIDER=tencent)
|
||
# =============================================================================
|
||
TENCENT_SECRET_ID=AKIDa2ILCwUr56uVt31oU0JOHxPfGhvvkLiq
|
||
TENCENT_SECRET_KEY=xiFbjlZ9XheS2NWYLvHRPAh2A5nGYcR2
|
||
|
||
# =============================================================================
|
||
# TTS(文字转语音,Agent 回复朗读)— 与 ASR 独立
|
||
# =============================================================================
|
||
# ENABLE_TTS:是否启用「助手回复朗读」服务端能力(TTS 适配器与密钥配置)。关则永远不合成。
|
||
# 每轮是否实际合成:由客户端在 WebSocket `text` / `audio_segment` / `audio_message` 的 `data.tts_this_turn` 控制(未传或 false 仅返回文字)。
|
||
# 若 ENABLE_TTS=true 且该轮 `tts_this_turn=true`:每一段助手文案先下发 `tts_audio`,再下发对应段的 `agent_response`。
|
||
ENABLE_TTS=true
|
||
TTS_PROVIDER=tencent
|
||
# 仅 TTS_PROVIDER=openai 时需要(填控制台密钥;勿在注释行写 =your_* 以免旧版 CI 误匹配)
|
||
# OPENAI_API_KEY=
|
||
# 音色 ID 见 https://cloud.tencent.com/document/product/1073/92668
|
||
TTS_VOICE_TYPE=501004
|
||
TTS_CODEC=mp3
|
||
|
||
# =============================================================================
|
||
# WeChat Pay
|
||
# =============================================================================
|
||
WECHAT_PAY_APP_ID=wx1df508452e06cfb8
|
||
WECHAT_PAY_MCH_ID=1662979099
|
||
WECHAT_PAY_API_V3_KEY=xjvGSJLGJAJfjgskfjslafjsajsdjals
|
||
# 商户私钥:推荐使用文件路径,避免 .env 中长 PEM 转义问题
|
||
WECHAT_PAY_PRIVATE_KEY_PATH=certs/apiclient_key.pem
|
||
# 若不用文件,可配置 WECHAT_PAY_PRIVATE_KEY(PEM,换行用 \n)
|
||
# WECHAT_PAY_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----"
|
||
WECHAT_PAY_CERT_SERIAL_NO=1AA82328AC1456C6F115B014606F22CD621D2032
|
||
WECHAT_PAY_NOTIFY_URL=https://lifecho.worldsplats.com/api/payment/notify/wechat
|
||
# 平台公钥模式(仅当无法走平台证书自动拉取时使用);勿填商户私钥路径
|
||
# WECHAT_PAY_PLATFORM_PUBLIC_KEY_PATH=certs/wechat_platform_public_key.pem
|
||
# WECHAT_PAY_PLATFORM_PUBLIC_KEY_ID=PUB_KEY_ID_0116629790992026020700181671002400
|
||
|
||
# =============================================================================
|
||
# Alipay(未接入时保持空字符串,与 Settings 默认一致)
|
||
# =============================================================================
|
||
ALIPAY_APP_ID=
|
||
ALIPAY_PRIVATE_KEY=
|
||
ALIPAY_PUBLIC_KEY=
|
||
ALIPAY_NOTIFY_URL=https://lifecho.worldsplats.com/api/payment/notify/alipay
|
||
|
||
# =============================================================================
|
||
# Misc
|
||
# =============================================================================
|
||
ENABLE_TEST_SUBSCRIPTION=1
|
||
|
||
# =============================================================================
|
||
# Memoir image generation(Story 主图等;轮询 Liblib 任务)
|
||
# =============================================================================
|
||
MEMOIR_IMAGE_ENABLED=true
|
||
MEMOIR_IMAGE_POLL_INTERVAL=3
|
||
MEMOIR_IMAGE_MAX_ATTEMPTS=20
|
||
MEMOIR_IMAGE_PROVIDER=liblib
|
||
MEMOIR_IMAGE_STYLE_DEFAULT=watercolor
|
||
MEMOIR_IMAGE_SIZE_DEFAULT=1280x720
|
||
# 章节正文内至少多少张 asset:// 插图才生成/展示章节封面(≥1 即有一张图可出封面)
|
||
MEMOIR_MIN_INLINE_IMAGES_FOR_CHAPTER_COVER=1
|
||
# Story 正文至少多少字才生成主图 intent / 调图(0=不限制)
|
||
STORY_IMAGE_MIN_BODY_CHARS=800
|
||
# 叙事模型输出相对口述过短则回退为口述原文
|
||
MEMOIR_NARRATIVE_FALLBACK_BODY_RATIO=0.5
|
||
MEMOIR_NARRATIVE_FALLBACK_MIN_CHARS=20
|
||
# 回忆录 segment 入队:累计 strip 后字数未达此值则暂缓提交 Celery(0=关闭字数门闸,仅静默防抖后提交)
|
||
# MEMOIR_SEGMENT_BATCH_MIN_CHARS=50
|
||
# 本批首条入队起最长等待(秒),超时仍提交;测试可调低,生产可调高
|
||
# MEMOIR_SEGMENT_BATCH_MAX_WAIT_SECONDS=60
|
||
# 可选,Liblib 返回图片域名不在默认白名单时(逗号分隔)
|
||
# MEMOIR_IMAGE_DOWNLOAD_HOSTS=liblib.cloud,liblibai.cloud
|
||
|
||
# =============================================================================
|
||
# Liblib image provider
|
||
# =============================================================================
|
||
LIBLIB_ACCESS_KEY=zrDp6quCOKlLwcewOEfrog
|
||
LIBLIB_SECRET_KEY=iTVHo5Nf3KA-xpC1Mja80bC93u6chJem
|
||
LIBLIB_BASE_URL=https://openapi.liblibai.cloud
|
||
LIBLIB_TEMPLATE_UUID=5d7e67009b344550bc1aa6ccbfa1d7f4
|
||
|
||
# =============================================================================
|
||
# Tencent Cloud — COS(回忆录图片存储)
|
||
# =============================================================================
|
||
TENCENT_COS_SECRET_ID=AKIDa2ILCwUr56uVt31oU0JOHxPfGhvvkLiq
|
||
TENCENT_COS_SECRET_KEY=xiFbjlZ9XheS2NWYLvHRPAh2A5nGYcR2
|
||
TENCENT_COS_REGION=ap-shanghai
|
||
TENCENT_COS_BUCKET=life-echo-prod-1319381411
|
||
TENCENT_COS_BASE_URL=https://life-echo-prod-1319381411.cos.ap-shanghai.myqcloud.com
|
||
# 可选临时凭证
|
||
# TENCENT_COS_TOKEN=
|