UpworkAutomation / app /models /provider_models.py
ananyajoshi's picture
Deploy app: Docker Space, session-only API keys, cloud file-upload dossier
6303ae6 verified
Raw
History Blame Contribute Delete
6.29 kB
"""Static reference list of supported LLM providers and common model names.
This module is a *reference / helper* layer, **not** an enforcement layer. It
maps each provider the app supports (see :data:`app.config.PROVIDER_OPTIONS`)
to a curated set of plausible, commonly used model names — split into text and
vision-capable models — plus a short human-readable note per provider.
Why this exists
---------------
The Setup screen already lets operators pick any provider and type any model
name, and the runtime routes calls based on the resolved ``Settings`` object
(see ``app.config`` and ``app.services.llm_client``). **Nothing here changes
that flow.** This list is intended for *future* use by:
* the Setup dropdowns (offer suggested model names instead of a free-text box),
* documentation (the README "Supported Provider Model Reference" section), and
* optional, non-blocking validation / hints.
Suggestions, not rules
----------------------
These model names are *suggestions*. A model that is not listed here is **not**
rejected — if the operator's provider account supports it, the app will still
use it. Strict validation (hard-blocking unknown models) is intentionally not
implemented; a caller that wants it can layer it on top of the
``is_supported_*`` helpers below.
Vision support
--------------
``vision_models`` lists models that can accept image input (used to read job
screenshots). Groq is treated as text-only in this app — its ``vision_models``
list is empty — mirroring ``app.config._SUPPORTED_VISION_PROVIDERS``.
"""
from __future__ import annotations
# Curated, commonly-used model names per provider. Reference data only — see
# the module docstring. Keys are the lowercase provider identifiers used
# everywhere else in the app (matching ``app.config.PROVIDER_OPTIONS``).
SUPPORTED_PROVIDER_MODELS: dict[str, dict[str, object]] = {
"openai": {
"text_models": [
"gpt-4.1",
"gpt-4.1-mini",
"gpt-4o",
"gpt-4o-mini",
],
"vision_models": [
"gpt-4o",
"gpt-4.1",
"gpt-4o-mini",
],
"notes": (
"OpenAI offers both text and vision-capable models. The 4o and "
"4.1 families accept image input and can read job screenshots."
),
},
"anthropic": {
"text_models": [
"claude-sonnet-4-6",
"claude-3-5-sonnet-latest",
"claude-3-5-haiku-latest",
],
"vision_models": [
"claude-sonnet-4-6",
"claude-3-5-sonnet-latest",
],
"notes": (
"Anthropic Claude models support both text and vision. "
"Sonnet-class models are recommended for screenshot extraction."
),
},
"groq": {
"text_models": [
"llama-3.3-70b-versatile",
"llama-3.1-8b-instant",
"mixtral-8x7b-32768",
"gemma2-9b-it",
],
"vision_models": [],
"notes": (
"Groq is treated as text-only in this app unless vision support "
"is added later."
),
},
"gemini": {
"text_models": [
"gemini-1.5-pro",
"gemini-1.5-flash",
"gemini-2.0-flash",
"gemini-2.0-flash-lite",
],
"vision_models": [
"gemini-1.5-pro",
"gemini-1.5-flash",
"gemini-2.0-flash",
],
"notes": (
"Gemini models are multimodal and support both text and vision "
"(image) input."
),
},
}
def _normalize_provider(provider: str | None) -> str:
"""Lowercase, whitespace-trimmed provider key (``""`` when not a string)."""
if not isinstance(provider, str):
return ""
return provider.strip().lower()
def _normalize_model(model: str | None) -> str:
"""Whitespace-trimmed model name (``""`` when not a string)."""
if not isinstance(model, str):
return ""
return model.strip()
def get_supported_providers() -> list[str]:
"""Return the provider identifiers in the reference list, in display order."""
return list(SUPPORTED_PROVIDER_MODELS.keys())
def is_supported_provider(provider: str) -> bool:
"""True when ``provider`` (case-insensitive) is in the reference list."""
return _normalize_provider(provider) in SUPPORTED_PROVIDER_MODELS
def get_text_models(provider: str) -> list[str]:
"""Suggested text model names for ``provider`` (``[]`` when unknown).
Returns a fresh copy so callers cannot mutate the reference data.
"""
entry = SUPPORTED_PROVIDER_MODELS.get(_normalize_provider(provider))
if not entry:
return []
return list(entry.get("text_models", []))
def get_vision_models(provider: str) -> list[str]:
"""Suggested vision model names for ``provider``.
Returns ``[]`` when the provider is unknown *or* text-only (e.g. Groq).
Returns a fresh copy so callers cannot mutate the reference data.
"""
entry = SUPPORTED_PROVIDER_MODELS.get(_normalize_provider(provider))
if not entry:
return []
return list(entry.get("vision_models", []))
def provider_supports_vision(provider: str) -> bool:
"""True when ``provider`` has at least one suggested vision model."""
return bool(get_vision_models(provider))
def is_supported_text_model(provider: str, model: str) -> bool:
"""True when ``model`` is a suggested *text* model for ``provider``.
Matching is case-insensitive and whitespace-tolerant. This is a reference
check only — a ``False`` result does **not** mean the model is unusable,
only that it is not in the curated suggestion list.
"""
target = _normalize_model(model).lower()
if not target:
return False
return any(target == m.lower() for m in get_text_models(provider))
def is_supported_vision_model(provider: str, model: str) -> bool:
"""True when ``model`` is a suggested *vision* model for ``provider``.
Same matching and "reference only" semantics as
:func:`is_supported_text_model`.
"""
target = _normalize_model(model).lower()
if not target:
return False
return any(target == m.lower() for m in get_vision_models(provider))