Replace custom provider abstraction with litellm

Drops the src/providers/ package (base class, AnthropicProvider,
OpenAIProvider, factory) in favour of a single src/llm.py that calls
litellm.completion() directly. litellm handles provider routing,
authentication, and SDK differences for 100+ providers without any
code we need to maintain.

Changes:
- Delete src/providers/ entirely
- Add src/llm.py — one complete() function wrapping litellm
- src/agent/core.py: EmailAgent takes model: str instead of LLMProvider
- src/config.py: ai_provider + api key fields → single ai_model string
  in litellm format (e.g. "anthropic/claude-opus-4-6")
- main.py: remove provider factory wiring; pass config.ai_model to agent
- .env.example: simplify AI section, show litellm model string examples
- pyproject.toml: replace anthropic + openai deps with litellm>=1.0.0
- uv.lock: regenerated

https://claude.ai/code/session_01HaUFs7SaLD5SoiuGCY27Tw
This commit is contained in:
Claude
2026-02-21 07:35:37 +00:00
parent 1ba42f9497
commit 431847a8b7
11 changed files with 972 additions and 226 deletions
+5 -24
View File
@@ -11,10 +11,9 @@ The agent polls the configured IMAP inbox every POLL_INTERVAL seconds,
processes new messages, and replies via SMTP.
Environment variables (see .env.example for full list):
AI_PROVIDER anthropic | openai (default: anthropic)
AI_MODEL Model name override (default: provider default)
ANTHROPIC_API_KEY Required if AI_PROVIDER=anthropic
OPENAI_API_KEY Required if AI_PROVIDER=openai
AI_MODEL litellm model string (default: anthropic/claude-opus-4-6)
ANTHROPIC_API_KEY Required for Anthropic models
OPENAI_API_KEY Required for OpenAI models
IMAP_HOST IMAP server hostname
IMAP_PORT IMAP port (default: 993)
IMAP_USERNAME Email account username
@@ -35,7 +34,6 @@ from src.channels.email_channel import EmailChannel
from src.config import Config
from src.knowledge_base.loader import KnowledgeBase
from src.notifications.notifier import AdminNotifier
from src.providers import create_provider
from src.storage.json_store import ConversationStore
logging.basicConfig(
@@ -48,24 +46,7 @@ logger = logging.getLogger(__name__)
def build_components(config: Config):
"""Instantiate and wire together all agent components."""
# Resolve API key for the chosen provider
if config.ai_provider == "anthropic":
if not config.anthropic_api_key:
logger.error("ANTHROPIC_API_KEY is required when AI_PROVIDER=anthropic")
sys.exit(1)
api_key = config.anthropic_api_key
elif config.ai_provider == "openai":
if not config.openai_api_key:
logger.error("OPENAI_API_KEY is required when AI_PROVIDER=openai")
sys.exit(1)
api_key = config.openai_api_key
else:
logger.error("Unknown AI_PROVIDER '%s'. Choose 'anthropic' or 'openai'.", config.ai_provider)
sys.exit(1)
provider = create_provider(config.ai_provider, api_key, config.ai_model)
logger.info("AI provider: %s / model: %s", config.ai_provider, provider.model_name)
logger.info("AI model: %s", config.ai_model)
kb = KnowledgeBase(config.knowledge_base_dir)
store = ConversationStore(config.data_dir)
@@ -79,7 +60,7 @@ def build_components(config: Config):
from_email=config.registration_email,
)
agent = EmailAgent(provider=provider, kb=kb, store=store, notifier=notifier)
agent = EmailAgent(model=config.ai_model, kb=kb, store=store, notifier=notifier)
channel = EmailChannel(
imap_host=config.imap_host,