Standardize ASR on Tencent's dialect-capable engine across all environments, drop faster-whisper from dependencies and deployment images, and add an expo-sqlite iOS vendor sync plus pod install in prebuild to prevent native build failures after npm install. Co-authored-by: Cursor <cursoragent@cursor.com>
317 lines
11 KiB
Python
317 lines
11 KiB
Python
"""Pydantic models for TOML-backed application configuration (non-secret SSOT)."""
|
|
|
|
from typing import Literal
|
|
|
|
from pydantic import BaseModel, ConfigDict, Field
|
|
|
|
|
|
class DeployConfig(BaseModel):
|
|
model_config = ConfigDict(extra="forbid")
|
|
|
|
alembic_startup_fail_fast: bool = False
|
|
access_token_expire_minutes: int = 120
|
|
refresh_token_expire_days: int = 30
|
|
refresh_token_reuse_grace_seconds: int = Field(default=30, ge=0, le=300)
|
|
mock_sms_login_enabled: bool = False
|
|
tencent_sms_sdk_app_id: str = ""
|
|
tencent_sms_sign_name: str = ""
|
|
tencent_sms_template_id: str = ""
|
|
tencent_cos_bucket: str = ""
|
|
tencent_cos_base_url: str = ""
|
|
enable_tts: bool = True
|
|
memoir_image_enabled: bool = False
|
|
enable_docs: bool = True
|
|
wechat_pay_app_id: str = ""
|
|
wechat_pay_mch_id: str = ""
|
|
wechat_pay_private_key_path: str = "certs/apiclient_key.pem"
|
|
wechat_pay_cert_serial_no: str = ""
|
|
wechat_pay_notify_url: str = ""
|
|
wechat_pay_platform_public_key_path: str = ""
|
|
wechat_pay_platform_public_key_id: str = ""
|
|
alipay_app_id: str = ""
|
|
alipay_notify_url: str = ""
|
|
liblib_template_uuid: str = ""
|
|
log_level: str = "INFO"
|
|
otel_enabled: bool = False
|
|
otel_exporter_otlp_endpoint: str = "http://localhost:48317"
|
|
api_cors_origins: str = ""
|
|
|
|
|
|
class ChatConfig(BaseModel):
|
|
model_config = ConfigDict(extra="forbid")
|
|
|
|
interview_max_tokens: int = 512
|
|
interview_max_segments: int = 2
|
|
interview_max_chars_per_segment: int = 380
|
|
opening_max_tokens: int = 380
|
|
profile_followup_max_tokens: int = 280
|
|
history_max_pairs: int = 15
|
|
history_max_chars: int = 6000
|
|
era_context_enabled: bool = True
|
|
stage_detection_enabled: bool = True
|
|
stage_detection_max_tokens: int = 128
|
|
interview_persona: str = "default"
|
|
interview_temperature: float = 0.93
|
|
memory_retrieval_enabled: bool = True
|
|
memory_top_k: int = 8
|
|
memory_evidence_max_chars: int = 4096
|
|
memory_safe_evidence_format_enabled: bool = True
|
|
reply_planner_llm_enabled: bool = False
|
|
reply_planner_max_tokens: int = 256
|
|
reply_planner_temperature: float = 0.2
|
|
re_greeting_enabled: bool = True
|
|
re_greeting_idle_hours: float = 6.0
|
|
topic_chips_enabled: bool = True
|
|
topic_chips_max: int = 4
|
|
input_normalize_enabled: bool = True
|
|
input_normalize_mode: str = "rules"
|
|
input_normalize_llm_max_tokens: int = 512
|
|
input_normalize_llm_max_input_chars: int = 8000
|
|
input_normalize_llm_voice_only: bool = True
|
|
profile_max_turns: int = 8
|
|
profile_extract_max_tokens: int = 512
|
|
|
|
|
|
class MemoirConfig(BaseModel):
|
|
model_config = ConfigDict(extra="forbid")
|
|
|
|
fidelity_check_enabled: bool = True
|
|
fidelity_check_max_tokens: int = 512
|
|
oral_normalize_enabled: bool = True
|
|
oral_normalize_mode: str = "rules"
|
|
oral_normalize_llm_max_tokens: int = 512
|
|
oral_normalize_llm_max_input_chars: int = 8000
|
|
phase1_batch_llm_enabled: bool = True
|
|
phase1_batch_llm_max_tokens: int = 4096
|
|
phase1_batch_llm_chunk_size: int = 24
|
|
pipeline_run_ttl_seconds: int = 172_800
|
|
extraction_max_tokens: int = 1024
|
|
classification_max_tokens: int = 256
|
|
narrative_max_tokens: int = 4096
|
|
narrative_merge_max_tokens: int = 8192
|
|
title_max_tokens: int = 256
|
|
story_route_max_tokens: int = 1024
|
|
story_batch_plan_max_tokens: int = 4096
|
|
segment_batch_min_chars: int = 50
|
|
segment_batch_max_wait_seconds: float = 60.0
|
|
narrative_immediate_char_threshold: int = 50
|
|
narrative_batch_min_segments: int = 3
|
|
narrative_batch_min_chars: int = 80
|
|
narrative_batch_max_wait_seconds: float = 120.0
|
|
extraction_updates_current_stage: bool = False
|
|
fidelity_fail_open_on_parse_error: bool = False
|
|
narrative_evidence_overlap_min_chars: int = 14
|
|
evidence_scene_anchor_check_enabled: bool = True
|
|
title_slots_require_body_or_oral_match: bool = True
|
|
title_hay_grounding_strict_phrases_enabled: bool = True
|
|
recompose_retry_on_lock_contention: bool = True
|
|
phase2_singleflight_immediate: bool = True
|
|
route_defer_enabled: bool = True
|
|
route_defer_seconds: float = 120.0
|
|
route_defer_max_attempts: int = 3
|
|
quality_pass_enabled: bool = True
|
|
quality_pass_delay_seconds: int = 5
|
|
story_route_append_guardrail_oral_chars: int = 1800
|
|
min_inline_images_for_chapter_cover: int = 1
|
|
image_poll_interval: int = 3
|
|
image_max_attempts: int = 20
|
|
image_provider: str = "liblib"
|
|
image_style_default: str = "watercolor"
|
|
image_size_default: str = "1280x720"
|
|
image_download_hosts: str = ""
|
|
image_prompt_fallback_disabled: bool = False
|
|
|
|
|
|
class MemoryConfig(BaseModel):
|
|
model_config = ConfigDict(extra="forbid")
|
|
|
|
enrichment_enabled: bool = True
|
|
enrichment_max_chars: int = 12000
|
|
compaction_enabled: bool = True
|
|
compaction_debounce_seconds: int = 105
|
|
compaction_lock_ttl_seconds: int = 600
|
|
compaction_chunk_similarity_threshold: float = 0.92
|
|
compaction_min_layers_for_exclude: int = 2
|
|
compaction_max_chunks_per_run: int = 200
|
|
compaction_max_excludes_per_run: int = 50
|
|
compaction_max_neighbors_per_chunk: int = 25
|
|
compaction_text_jaccard_min: float = 0.55
|
|
compaction_metadata_event_year_window: int = 1
|
|
compaction_sweep_recent_hours: int = 24
|
|
|
|
|
|
class StoryConfig(BaseModel):
|
|
model_config = ConfigDict(extra="forbid")
|
|
|
|
image_min_body_chars: int = 400
|
|
image_enqueue_dedup_ttl: int = 300
|
|
recompose_chapter_delay_seconds: int = 8
|
|
chapter_pipeline_lock_ttl_seconds: int = 360
|
|
append_max_canonical_chars: int = 12000
|
|
append_max_versions: int = 20
|
|
route_candidate_body_max_chars: int = 2200
|
|
route_candidate_total_max_chars: int = 20_000
|
|
route_long_body_head_chars: int = 700
|
|
route_long_body_tail_chars: int = 700
|
|
route_summary_min_chars: int = 30
|
|
route_index_preview_chars: int = 140
|
|
title_min_body_chars: int = 60
|
|
evidence_top_k_default: int = 10
|
|
evidence_top_k_large_batch: int = 5
|
|
evidence_large_batch_threshold: int = 3
|
|
|
|
|
|
class EvalConfig(BaseModel):
|
|
model_config = ConfigDict(extra="forbid")
|
|
|
|
judge_base_url: str = "https://open.bigmodel.cn/api/paas/v4"
|
|
judge_model: str = "glm-5"
|
|
judge_temperature: float = 0.3
|
|
judge_deepseek_model: str = "deepseek-v4-flash"
|
|
judge_deepseek_thinking_enabled: bool = False
|
|
judge_deepseek_context_window_tokens: int = 64_000
|
|
judge_context_window_tokens: int = 200_000
|
|
judge_completion_reserve_tokens: int = 4096
|
|
judge_prompt_budget_safety_tokens: int = 2048
|
|
judge_approx_tokens_per_char: float = 1.0
|
|
judge_max_transcript_chars: int = 0
|
|
judge_max_compare_transcript_chars_each: int = 0
|
|
judge_compare_prompt_overhead_chars: int = 10_000
|
|
judge_memoir_chapter_concurrency: int = 4
|
|
judge_memoir_body_max_chars: int = 36_000
|
|
judge_memoir_evidence_max_chars: int = 32_000
|
|
judge_memoir_completion_max_tokens: int = 3072
|
|
candidate_temperature: float = 0.7
|
|
gate_protected_regression_threshold: float = 2.0
|
|
execution_enabled: bool = True
|
|
internal_enable_docs: bool = False
|
|
internal_cors_origins: str = ""
|
|
|
|
|
|
class LlmConfig(BaseModel):
|
|
model_config = ConfigDict(extra="forbid")
|
|
|
|
deepseek_base_url: str = "https://api.deepseek.com"
|
|
deepseek_model: str = "deepseek-v4-flash"
|
|
deepseek_thinking_enabled: bool = False
|
|
temperature: float = 0.7
|
|
fast_model: str = ""
|
|
embedding_base_url: str = "https://open.bigmodel.cn/api/paas/v4"
|
|
embedding_model: str = "embedding-3"
|
|
|
|
|
|
class AsrConfig(BaseModel):
|
|
model_config = ConfigDict(extra="forbid")
|
|
|
|
provider: Literal["tencent"] = "tencent"
|
|
engine_type: Literal["16k_zh_large"] = "16k_zh_large"
|
|
|
|
|
|
class TtsConfig(BaseModel):
|
|
model_config = ConfigDict(extra="forbid")
|
|
|
|
provider: str = "tencent"
|
|
voice_type: int = 501004
|
|
voice_type_en: int = 501004
|
|
codec: str = "mp3"
|
|
|
|
|
|
class RedisConfig(BaseModel):
|
|
model_config = ConfigDict(extra="forbid")
|
|
|
|
socket_timeout_seconds: float = 5.0
|
|
socket_connect_timeout_seconds: float = 2.0
|
|
health_check_interval_seconds: int = 30
|
|
task_tracker_ttl_seconds: int = 86400
|
|
|
|
|
|
class CeleryConfig(BaseModel):
|
|
model_config = ConfigDict(extra="forbid")
|
|
|
|
memory_enrichment_queue: str = "memory_idle"
|
|
broker_pool_limit: int = 10
|
|
broker_connection_retry_on_startup: bool = True
|
|
memoir_soft_time_limit: int = 1800
|
|
memoir_hard_time_limit: int = 2400
|
|
image_soft_time_limit: int = 600
|
|
image_hard_time_limit: int = 900
|
|
compaction_sweep_soft_time_limit: int = 300
|
|
compaction_sweep_hard_time_limit: int = 600
|
|
enrichment_soft_time_limit: int = 660
|
|
enrichment_hard_time_limit: int = 960
|
|
|
|
|
|
class AlembicConfig(BaseModel):
|
|
model_config = ConfigDict(extra="forbid")
|
|
|
|
run_on_startup: bool = True
|
|
max_retries: int = 3
|
|
retry_base_seconds: float = 1.0
|
|
|
|
|
|
class AgentLogConfig(BaseModel):
|
|
model_config = ConfigDict(extra="forbid")
|
|
|
|
agent_verbose: bool = False
|
|
max_chars: int = 4096
|
|
omit_system_message_body: bool = True
|
|
json_prompt_prefix_chars: int = 0
|
|
json_prompt_prefix_only_if_len_gt: int = 4000
|
|
prompt_mode: str = "preview"
|
|
prompt_dedup: bool = False
|
|
celery_log_level: str = ""
|
|
httpx_log_level: str = ""
|
|
log_json_file: str = ""
|
|
|
|
|
|
class OtelConfig(BaseModel):
|
|
model_config = ConfigDict(extra="forbid")
|
|
|
|
exporter_insecure: bool = True
|
|
service_name: str = "life-echo-api"
|
|
metric_export_interval_ms: int = 10_000
|
|
|
|
def traces_sampler(self, app_environment: str) -> str:
|
|
env = (app_environment or "").strip().lower()
|
|
if env in ("production", "staging"):
|
|
return "parentbased_traceidratio"
|
|
return "always_on"
|
|
|
|
def traces_sampler_arg(self, app_environment: str) -> float | None:
|
|
env = (app_environment or "").strip().lower()
|
|
if env in ("production", "staging"):
|
|
return 0.1
|
|
return None
|
|
|
|
|
|
class MiscConfig(BaseModel):
|
|
model_config = ConfigDict(extra="forbid")
|
|
|
|
algorithm: str = "HS256"
|
|
redis_session_ttl: int = 86400
|
|
tencent_sms_template_param_count: int = 2
|
|
tencent_cos_region: str = "ap-shanghai"
|
|
liblib_base_url: str = "https://openapi.liblibai.cloud"
|
|
alipay_sign_type: str = "RSA2"
|
|
alipay_under_development: str = "true"
|
|
|
|
|
|
class AppConfig(BaseModel):
|
|
model_config = ConfigDict(extra="forbid")
|
|
|
|
deploy: DeployConfig = Field(default_factory=DeployConfig)
|
|
chat: ChatConfig = Field(default_factory=ChatConfig)
|
|
memoir: MemoirConfig = Field(default_factory=MemoirConfig)
|
|
memory: MemoryConfig = Field(default_factory=MemoryConfig)
|
|
story: StoryConfig = Field(default_factory=StoryConfig)
|
|
eval: EvalConfig = Field(default_factory=EvalConfig)
|
|
llm: LlmConfig = Field(default_factory=LlmConfig)
|
|
asr: AsrConfig = Field(default_factory=AsrConfig)
|
|
tts: TtsConfig = Field(default_factory=TtsConfig)
|
|
celery: CeleryConfig = Field(default_factory=CeleryConfig)
|
|
redis: RedisConfig = Field(default_factory=RedisConfig)
|
|
alembic: AlembicConfig = Field(default_factory=AlembicConfig)
|
|
agent_log: AgentLogConfig = Field(default_factory=AgentLogConfig)
|
|
otel: OtelConfig = Field(default_factory=OtelConfig)
|
|
misc: MiscConfig = Field(default_factory=MiscConfig)
|