mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-08-02 03:28:36 -04:00
b9cafd67a1
* feat(models): define capability schema and readers * fix(models): harden Google catalog probing Restrict native catalog probing to the Gemini host, keep provider keys out of request URLs, filter non-chat model resources, and preserve the manual refresh default in the built-in Google add flow.
163 lines
5.7 KiB
Python
163 lines
5.7 KiB
Python
"""Google AI Studio / Gemini native Models API capability mapping.
|
|
|
|
This module maps already-fetched `models.list` and `models.get` payloads into
|
|
Odysseus' canonical model capability shape. It performs no network I/O and
|
|
does not infer model capabilities from model IDs, display names, or product
|
|
families. Only fields explicitly returned by Google's Model resource are
|
|
mapped here.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from collections.abc import Mapping
|
|
from typing import Any
|
|
|
|
from src import model_capabilities as mc
|
|
from src.model_capability_readers.base import as_list, compact_str, int_limit
|
|
|
|
|
|
METHOD_GENERATE_CONTENT = "generateContent"
|
|
METHOD_GENERATE_MESSAGE = "generateMessage"
|
|
METHOD_GENERATE_TEXT = "generateText"
|
|
METHOD_GENERATE_ANSWER = "generateAnswer"
|
|
METHOD_EMBED_CONTENT = "embedContent"
|
|
METHOD_ASYNC_BATCH_EMBED = "asyncBatchEmbedContent"
|
|
METHOD_PREDICT = "predict"
|
|
METHOD_PREDICT_LONG_RUNNING = "predictLongRunning"
|
|
METHOD_BATCH_GENERATE = "batchGenerateContent"
|
|
METHOD_CREATE_CACHED_CONTENT = "createCachedContent"
|
|
|
|
TEXT_GENERATION_METHODS = frozenset(
|
|
{
|
|
METHOD_GENERATE_CONTENT,
|
|
METHOD_GENERATE_MESSAGE,
|
|
METHOD_GENERATE_TEXT,
|
|
METHOD_GENERATE_ANSWER,
|
|
}
|
|
)
|
|
EMBEDDING_METHODS = frozenset({METHOD_EMBED_CONTENT, METHOD_ASYNC_BATCH_EMBED})
|
|
BATCH_METHODS = frozenset({METHOD_BATCH_GENERATE, METHOD_ASYNC_BATCH_EMBED})
|
|
|
|
MODEL_FIELD_MAP = {
|
|
"name": "vendor resource name",
|
|
"baseModelId": "vendor model id",
|
|
"displayName": "display name",
|
|
"description": "display description only",
|
|
"inputTokenLimit": "limits.input_tokens and limits.context_tokens",
|
|
"outputTokenLimit": "limits.output_tokens",
|
|
"supportedGenerationMethods": "provider method support signal",
|
|
"thinking": "capabilities.reasoning when true",
|
|
"temperature": "deterministic_controls.temperature when present",
|
|
"maxTemperature": "deterministic_controls.temperature when present",
|
|
"topP": "deterministic_controls.top_p when present",
|
|
"topK": "deterministic_controls.top_k when present",
|
|
}
|
|
|
|
|
|
def google_model_id(raw: Mapping[str, Any]) -> str:
|
|
value = compact_str(raw.get("baseModelId")) or compact_str(raw.get("name"))
|
|
return value.removeprefix("models/")
|
|
|
|
|
|
def supported_methods(raw: Mapping[str, Any]) -> frozenset[str]:
|
|
return frozenset(compact_str(method) for method in as_list(raw.get("supportedGenerationMethods")) if method)
|
|
|
|
|
|
def limits_from_model(raw: Mapping[str, Any]) -> dict[str, Any]:
|
|
limits: dict[str, Any] = {}
|
|
input_limit = int_limit(raw.get("inputTokenLimit"))
|
|
output_limit = int_limit(raw.get("outputTokenLimit"))
|
|
if input_limit:
|
|
limits["input_tokens"] = input_limit
|
|
limits["context_tokens"] = input_limit
|
|
if output_limit:
|
|
limits["output_tokens"] = output_limit
|
|
return limits
|
|
|
|
|
|
def _capability(
|
|
*,
|
|
family: str,
|
|
input_modalities: tuple[str, ...],
|
|
output_modalities: tuple[str, ...],
|
|
capabilities: tuple[str, ...] = (),
|
|
limits: Mapping[str, Any] | None = None,
|
|
primary_task: str | None = None,
|
|
source: str = mc.SOURCE_PROVIDER_READER,
|
|
confidence: str = mc.CONFIDENCE_PROVIDER_REPORTED,
|
|
) -> mc.ModelCapability:
|
|
return mc.ModelCapability.build(
|
|
family=family,
|
|
primary_task=primary_task,
|
|
input_modalities=input_modalities,
|
|
output_modalities=output_modalities,
|
|
capabilities=capabilities,
|
|
limits=limits,
|
|
source=source,
|
|
confidence=confidence,
|
|
)
|
|
|
|
|
|
def capability_from_model(raw: Mapping[str, Any]) -> mc.ModelCapability:
|
|
methods = supported_methods(raw)
|
|
capabilities: list[str] = []
|
|
if raw.get("thinking") is True:
|
|
capabilities.append(mc.CAP_REASONING)
|
|
|
|
if methods & EMBEDDING_METHODS and not methods & TEXT_GENERATION_METHODS:
|
|
return _capability(
|
|
family=mc.FAMILY_EMBEDDING,
|
|
input_modalities=(mc.MODALITY_TEXT,),
|
|
output_modalities=(mc.MODALITY_EMBEDDING,),
|
|
capabilities=tuple(capabilities),
|
|
limits=limits_from_model(raw),
|
|
)
|
|
|
|
# `generateContent` proves the model supports Google's content generation
|
|
# method, but the Model resource does not expose input/output modalities.
|
|
# Keep the model unknown instead of guessing chat/image/audio/video from ID.
|
|
if methods & TEXT_GENERATION_METHODS:
|
|
return _capability(
|
|
family=mc.FAMILY_UNKNOWN,
|
|
input_modalities=(),
|
|
output_modalities=(),
|
|
capabilities=tuple(capabilities),
|
|
limits=limits_from_model(raw),
|
|
)
|
|
|
|
capability = mc.unknown_capability(
|
|
source=mc.SOURCE_PROVIDER_READER,
|
|
confidence=mc.CONFIDENCE_UNKNOWN,
|
|
)
|
|
limits = limits_from_model(raw)
|
|
if limits or capabilities:
|
|
return _capability(
|
|
family=mc.FAMILY_UNKNOWN,
|
|
input_modalities=(),
|
|
output_modalities=(),
|
|
capabilities=tuple(capabilities),
|
|
limits=limits,
|
|
)
|
|
return capability
|
|
|
|
|
|
def deterministic_controls_from_model(raw: Mapping[str, Any]) -> tuple[mc.DeterministicControl, ...]:
|
|
methods = supported_methods(raw)
|
|
controls: list[str] = []
|
|
if "temperature" in raw or "maxTemperature" in raw:
|
|
controls.append(mc.CONTROL_TEMPERATURE)
|
|
if "topP" in raw:
|
|
controls.append(mc.CONTROL_TOP_P)
|
|
if raw.get("topK") not in (None, ""):
|
|
controls.append(mc.CONTROL_TOP_K)
|
|
if METHOD_CREATE_CACHED_CONTENT in methods:
|
|
controls.append(mc.CONTROL_PROMPT_CACHING)
|
|
if methods & BATCH_METHODS:
|
|
controls.append(mc.CONTROL_BATCH)
|
|
return mc.deterministic_controls_from_values(
|
|
controls,
|
|
status=mc.ASSERTION_CLAIMED,
|
|
source=mc.SOURCE_PROVIDER_READER,
|
|
confidence=mc.CONFIDENCE_PROVIDER_REPORTED,
|
|
)
|