config.py 3.06 KB
"""
Configuration management for ShopAgent
Loads environment variables and provides configuration objects
"""

from pathlib import Path
from typing import Optional

from pydantic_settings import BaseSettings, PydanticBaseSettingsSource, SettingsConfigDict

# config.py sits at app/config.py → parent.parent = project root
PROJECT_ROOT = Path(__file__).resolve().parent.parent


class Settings(BaseSettings):
    """Application settings loaded from environment variables

    All settings can be configured via .env file or environment variables.
    Priority (high → low): init kwargs > .env file > env vars > defaults
    """

    model_config = SettingsConfigDict(
        env_file=str(PROJECT_ROOT / ".env"),
        env_file_encoding="utf-8",
        case_sensitive=False,
        extra="ignore",
    )

    # OpenAI Configuration
    openai_api_key: str
    openai_model: str = "gpt-4o-mini"
    # 图片理解 / 多模态模型(例如 Qwen3-Omni-Flash)
    openai_vision_model: str = "qwen3-omni-flash"
    openai_temperature: float = 0.7
    openai_max_tokens: int = 1000
    # 对话调用大模型时是否开启 thinking:
    # - OpenAI 官方 endpoint(含 api.openai.com base_url):走 Responses API reasoning
    # - DashScope 兼容 endpoint:通过 extra_body.enable_thinking 开启
    openai_use_reasoning: bool = True
    openai_reasoning_effort: str = "medium"  # low | medium | high
    # Base URL for OpenAI-compatible APIs (e.g. Qwen/DashScope)
    # Qwen 北京: https://dashscope.aliyuncs.com/compatible-mode/v1
    openai_api_base_url: Optional[str] = None

    # Search Configuration
    top_k_results: int = 10
    similarity_threshold: float = 0.6
    # 商品搜索 API 单次请求最多返回条数(1–20),search_products 统一使用此配置
    search_products_limit: int = 20

    # Search API (see docs/搜索API对接指南.md)
    search_api_base_url: str = "http://120.76.41.98:6002"
    search_api_tenant_id: str = "170"

    # Application Configuration
    app_host: str = "0.0.0.0"
    app_port: int = 8000
    debug: bool = True
    log_level: str = "INFO"

    # Data Paths
    raw_data_path: str = "./data/raw"
    processed_data_path: str = "./data/processed"
    image_data_path: str = "./data/images"

    @classmethod
    def settings_customise_sources(
        cls,
        settings_cls: type[BaseSettings],
        init_settings: PydanticBaseSettingsSource,
        env_settings: PydanticBaseSettingsSource,
        dotenv_settings: PydanticBaseSettingsSource,
        file_secret_settings: PydanticBaseSettingsSource,
    ) -> tuple[PydanticBaseSettingsSource, ...]:
        """Make .env file take priority over system environment variables.

        Default order:  init > env > dotenv > secrets
        Our order:      init > dotenv > env > secrets
        """
        return init_settings, dotenv_settings, env_settings, file_secret_settings


# Global settings instance
settings = Settings()


def get_absolute_path(relative_path: str) -> str:
    """Convert relative path to absolute path based on project root"""
    return str(PROJECT_ROOT / relative_path)