|
|
"""Chat Client Factory for unified provider selection.""" |
|
|
|
|
|
from typing import Any |
|
|
|
|
|
import structlog |
|
|
from agent_framework import BaseChatClient |
|
|
from agent_framework.openai import OpenAIChatClient |
|
|
|
|
|
from src.clients.huggingface import HuggingFaceChatClient |
|
|
from src.utils.config import settings |
|
|
|
|
|
logger = structlog.get_logger() |
|
|
|
|
|
|
|
|
def get_chat_client( |
|
|
provider: str | None = None, |
|
|
api_key: str | None = None, |
|
|
model_id: str | None = None, |
|
|
**kwargs: Any, |
|
|
) -> BaseChatClient: |
|
|
""" |
|
|
Factory for creating chat clients. |
|
|
|
|
|
Auto-detection priority: |
|
|
1. Explicit provider parameter |
|
|
2. API key prefix detection (sk- β OpenAI, sk-ant- β Anthropic) |
|
|
3. OpenAI key from env (Best Function Calling) |
|
|
4. Gemini key from env (Best Context/Cost) |
|
|
5. HuggingFace (Free Fallback) |
|
|
|
|
|
Args: |
|
|
provider: Force specific provider ("openai", "gemini", "huggingface") |
|
|
api_key: Override API key for the provider (auto-detects provider from prefix) |
|
|
model_id: Override default model ID |
|
|
**kwargs: Additional arguments for the client |
|
|
|
|
|
Returns: |
|
|
Configured BaseChatClient instance (Namespace Neutral) |
|
|
|
|
|
Raises: |
|
|
ValueError: If an unsupported provider is explicitly requested |
|
|
NotImplementedError: If Gemini or Anthropic is requested (not yet implemented) |
|
|
""" |
|
|
|
|
|
normalized = provider.lower() if provider is not None else None |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if normalized is None and api_key: |
|
|
if api_key.startswith("sk-ant-"): |
|
|
normalized = "anthropic" |
|
|
elif api_key.startswith("sk-"): |
|
|
normalized = "openai" |
|
|
|
|
|
|
|
|
|
|
|
valid_providers = (None, "openai", "anthropic", "gemini", "huggingface") |
|
|
if normalized not in valid_providers: |
|
|
raise ValueError(f"Unsupported provider: {provider!r}") |
|
|
|
|
|
|
|
|
if normalized == "openai" or (normalized is None and settings.has_openai_key): |
|
|
logger.info("Using OpenAI Chat Client") |
|
|
return OpenAIChatClient( |
|
|
model_id=model_id or settings.openai_model, |
|
|
api_key=api_key or settings.openai_api_key, |
|
|
**kwargs, |
|
|
) |
|
|
|
|
|
|
|
|
if normalized == "anthropic": |
|
|
|
|
|
raise NotImplementedError( |
|
|
"Anthropic client not yet implemented. " |
|
|
"Use OpenAI key (sk-...) or leave empty for free HuggingFace tier." |
|
|
) |
|
|
|
|
|
|
|
|
if normalized == "gemini": |
|
|
|
|
|
raise NotImplementedError("Gemini client not yet implemented (Planned Phase 4)") |
|
|
|
|
|
if normalized is None and settings.has_gemini_key: |
|
|
|
|
|
logger.warning("Gemini key detected but client not yet implemented; falling back") |
|
|
|
|
|
|
|
|
|
|
|
logger.info("Using HuggingFace Chat Client (Free Tier)") |
|
|
return HuggingFaceChatClient( |
|
|
model_id=model_id or settings.huggingface_model, |
|
|
api_key=api_key or settings.hf_token, |
|
|
**kwargs, |
|
|
) |
|
|
|