diff --git a/.env.example b/.env.example index 7562246..fb46f9d 100644 --- a/.env.example +++ b/.env.example @@ -6,14 +6,21 @@ # --------------------------------------------------------------- # --------------------------------------------------------------- -# AI Model (via litellm — supports any provider) +# AI Models (via litellm — supports any provider) # --------------------------------------------------------------- # Use litellm model strings: "/" -# Examples: -# anthropic/claude-opus-4-6 (default) -# openai/gpt-4o -# gemini/gemini-2.0-flash +# +# AI_MODEL — primary model for conversation with parents (complex reasoning). +# SIMPLE_MODEL — lightweight model for simple tasks, e.g. email-label translation. +# Can be from a different provider than AI_MODEL. +# If unset, falls back to AI_MODEL with a warning in the logs. +# +# Examples — mixing providers: +# AI_MODEL=gemini/gemini-3-pro-preview + SIMPLE_MODEL=gemini/gemini-3-flash +# AI_MODEL=anthropic/claude-opus-4-6 + SIMPLE_MODEL=anthropic/claude-haiku-4-5-20251001 +# AI_MODEL=gemini/gemini-3-pro-preview + SIMPLE_MODEL=anthropic/claude-haiku-4-5-20251001 AI_MODEL=anthropic/claude-opus-4-6 +SIMPLE_MODEL=anthropic/claude-haiku-4-5-20251001 # Extended thinking — Anthropic models only (leave unset to disable). # Enables a reasoning phase before the model's reply, which improves diff --git a/chat_app.py b/chat_app.py index 6026ccb..acbcf17 100644 --- a/chat_app.py +++ b/chat_app.py @@ -58,6 +58,7 @@ _notifier = AdminNotifier( indoor_email=_config.admin_email_indoor, outdoor_email=_config.admin_email_outdoor, cc_emails=[e.strip() for e in _config.admin_email_cc.split(",") if e.strip()], + model=_config.simple_model, ) # --------------------------------------------------------------------------- diff --git a/main.py b/main.py index b434cbe..c432fb8 100644 --- a/main.py +++ b/main.py @@ -61,6 +61,7 @@ def build_components(config: Config): indoor_email=config.admin_email_indoor, outdoor_email=config.admin_email_outdoor, cc_emails=[e.strip() for e in config.admin_email_cc.split(",") if e.strip()], + model=config.simple_model, ) agent = EmailAgent( diff --git a/src/config.py b/src/config.py index fbba30e..b7f3d8f 100644 --- a/src/config.py +++ b/src/config.py @@ -1,9 +1,12 @@ """Configuration loaded from environment variables.""" +import logging import os from dataclasses import dataclass, field from pathlib import Path +logger = logging.getLogger(__name__) + try: from dotenv import load_dotenv load_dotenv() @@ -13,10 +16,17 @@ except ImportError: @dataclass class Config: - # AI model — litellm format, e.g. "anthropic/claude-opus-4-6" or "openai/gpt-4o". + # Primary model for conversation with parents — litellm format. # The matching API key must be set as an env var (ANTHROPIC_API_KEY, OPENAI_API_KEY, …). + # Example: "anthropic/claude-opus-4-6", "google/gemini-2.0-flash", "openai/gpt-4o" ai_model: str = "anthropic/claude-opus-4-6" + # Lightweight model for simple tasks such as email-label translation. + # Can be from a different provider than ai_model. + # If not configured (SIMPLE_MODEL env var unset), falls back to ai_model with a warning. + # Example: "anthropic/claude-haiku-4-5-20251001", "openai/gpt-4o-mini" + simple_model: str = "" + # Email — IMAP (receiving) imap_host: str = "" imap_port: int = 993 @@ -57,8 +67,18 @@ class Config: @classmethod def from_env(cls) -> "Config": + ai_model = os.getenv("AI_MODEL", "anthropic/claude-opus-4-6") + simple_model = os.getenv("SIMPLE_MODEL", "") + if not simple_model: + logger.warning( + "SIMPLE_MODEL not configured — falling back to AI_MODEL (%s) for simple tasks " + "(set SIMPLE_MODEL to a cheaper model, e.g. anthropic/claude-haiku-4-5-20251001)", + ai_model, + ) + simple_model = ai_model return cls( - ai_model=os.getenv("AI_MODEL", "anthropic/claude-opus-4-6"), + ai_model=ai_model, + simple_model=simple_model, imap_host=os.getenv("IMAP_HOST", ""), imap_port=int(os.getenv("IMAP_PORT", "993")), imap_username=os.getenv("IMAP_USERNAME", ""),