mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-10-21 14:22:19 +02:00
feat(models): define capability schema and readers (#2739)
* 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.
This commit is contained in:
@@ -0,0 +1,186 @@
|
||||
"""LM Studio native model metadata reader."""
|
||||
|
||||
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 import generic_openai
|
||||
from src.model_capability_readers.base import (
|
||||
ModelCapabilityRecord,
|
||||
VENDOR_LMSTUDIO,
|
||||
as_list,
|
||||
as_mapping,
|
||||
build_capability,
|
||||
compact_str,
|
||||
int_limit,
|
||||
merge_unique,
|
||||
model_id_from,
|
||||
openai_model_items,
|
||||
stable_model_id_for,
|
||||
)
|
||||
|
||||
|
||||
vendor = VENDOR_LMSTUDIO
|
||||
|
||||
|
||||
def _loaded_instance_contexts(raw: Mapping[str, Any]) -> tuple[int, ...]:
|
||||
contexts: list[int] = []
|
||||
for instance in as_list(raw.get("loaded_instances")):
|
||||
instance_payload = as_mapping(instance)
|
||||
config = as_mapping(instance_payload.get("config"))
|
||||
value = int_limit(instance_payload.get("context_length")) or int_limit(
|
||||
config.get("context_length")
|
||||
)
|
||||
if value:
|
||||
contexts.append(value)
|
||||
return tuple(contexts)
|
||||
|
||||
|
||||
def _limits_from_model(raw: Mapping[str, Any]) -> dict[str, Any]:
|
||||
limits: dict[str, Any] = {}
|
||||
loaded_contexts = _loaded_instance_contexts(raw)
|
||||
loaded_context = int_limit(raw.get("loaded_context_length")) or (
|
||||
min(loaded_contexts) if loaded_contexts else None
|
||||
)
|
||||
configured_context = int_limit(raw.get("context_length")) or int_limit(raw.get("contextLength"))
|
||||
max_context = int_limit(raw.get("max_context_length")) or int_limit(raw.get("maxContextLength"))
|
||||
context_tokens = loaded_context or configured_context or max_context
|
||||
if context_tokens:
|
||||
limits["context_tokens"] = context_tokens
|
||||
if max_context and max_context != context_tokens:
|
||||
limits["max_context_tokens"] = max_context
|
||||
return limits
|
||||
|
||||
|
||||
def _family_from_type(raw: Mapping[str, Any]) -> str:
|
||||
kind = compact_str(raw.get("type") or raw.get("model_type") or raw.get("task")).lower().replace("-", "_")
|
||||
if kind in {"embedding", "embeddings", "text_embedding", "text_embeddings"}:
|
||||
return mc.FAMILY_EMBEDDING
|
||||
if kind in {"llm", "chat", "vlm", "vision", "text_generation"}:
|
||||
return mc.FAMILY_CHAT
|
||||
return mc.FAMILY_UNKNOWN
|
||||
|
||||
|
||||
def _capabilities_from_native_payload(raw: Mapping[str, Any]) -> tuple[str, ...]:
|
||||
capabilities_payload = as_mapping(raw.get("capabilities"))
|
||||
capabilities: list[str] = []
|
||||
if capabilities_payload.get("vision") is True:
|
||||
capabilities.append(mc.CAP_VISION)
|
||||
if (
|
||||
capabilities_payload.get("trained_for_tool_use") is True
|
||||
or capabilities_payload.get("tools") is True
|
||||
or capabilities_payload.get("tool_use") is True
|
||||
):
|
||||
capabilities.append(mc.CAP_TOOL_CALL)
|
||||
if capabilities_payload.get("reasoning"):
|
||||
capabilities.append(mc.CAP_REASONING)
|
||||
return merge_unique(capabilities)
|
||||
|
||||
|
||||
def _unknown_record(
|
||||
raw: Mapping[str, Any],
|
||||
model_id: str,
|
||||
*,
|
||||
endpoint_id: Any = "",
|
||||
base_url: Any = "",
|
||||
) -> ModelCapabilityRecord:
|
||||
return ModelCapabilityRecord(
|
||||
vendor=VENDOR_LMSTUDIO,
|
||||
model_id=model_id,
|
||||
stable_model_id=stable_model_id_for(
|
||||
VENDOR_LMSTUDIO,
|
||||
model_id,
|
||||
endpoint_id=endpoint_id,
|
||||
base_url=base_url,
|
||||
),
|
||||
display_name=compact_str(raw.get("display_name") or raw.get("name")) or model_id,
|
||||
capability=mc.unknown_capability(
|
||||
source=mc.SOURCE_PROVIDER_READER,
|
||||
confidence=mc.CONFIDENCE_UNKNOWN,
|
||||
),
|
||||
raw=raw,
|
||||
)
|
||||
|
||||
|
||||
def record_from_native_model(
|
||||
raw: Mapping[str, Any],
|
||||
*,
|
||||
endpoint_id: Any = "",
|
||||
base_url: Any = "",
|
||||
) -> ModelCapabilityRecord | None:
|
||||
model_id = model_id_from(raw, "key", "id", "model", "name")
|
||||
if not model_id:
|
||||
return None
|
||||
|
||||
family = _family_from_type(raw)
|
||||
capabilities = _capabilities_from_native_payload(raw)
|
||||
|
||||
if family == mc.FAMILY_UNKNOWN and capabilities:
|
||||
family = mc.FAMILY_CHAT
|
||||
|
||||
if family == mc.FAMILY_EMBEDDING:
|
||||
input_modalities = (mc.MODALITY_TEXT,)
|
||||
output_modalities = (mc.MODALITY_EMBEDDING,)
|
||||
elif family == mc.FAMILY_CHAT and mc.CAP_VISION in capabilities:
|
||||
input_modalities = (mc.MODALITY_TEXT, mc.MODALITY_IMAGE)
|
||||
output_modalities = (mc.MODALITY_TEXT,)
|
||||
elif family == mc.FAMILY_CHAT:
|
||||
input_modalities = (mc.MODALITY_TEXT,)
|
||||
output_modalities = (mc.MODALITY_TEXT,)
|
||||
else:
|
||||
return generic_openai.record_from_model(
|
||||
raw,
|
||||
vendor_id=VENDOR_LMSTUDIO,
|
||||
endpoint_id=endpoint_id,
|
||||
base_url=base_url,
|
||||
) or _unknown_record(
|
||||
raw,
|
||||
model_id,
|
||||
endpoint_id=endpoint_id,
|
||||
base_url=base_url,
|
||||
)
|
||||
|
||||
capability = build_capability(
|
||||
family=family,
|
||||
input_modalities=input_modalities,
|
||||
output_modalities=output_modalities,
|
||||
capabilities=capabilities,
|
||||
limits=_limits_from_model(raw),
|
||||
)
|
||||
return ModelCapabilityRecord(
|
||||
vendor=VENDOR_LMSTUDIO,
|
||||
model_id=model_id,
|
||||
stable_model_id=stable_model_id_for(
|
||||
VENDOR_LMSTUDIO,
|
||||
model_id,
|
||||
endpoint_id=endpoint_id,
|
||||
base_url=base_url,
|
||||
),
|
||||
display_name=compact_str(raw.get("display_name") or raw.get("name")) or model_id,
|
||||
capability=capability,
|
||||
raw=raw,
|
||||
)
|
||||
|
||||
|
||||
def records_from_payload(
|
||||
payload: Mapping[str, Any],
|
||||
*,
|
||||
endpoint_id: Any = "",
|
||||
base_url: Any = "",
|
||||
) -> tuple[ModelCapabilityRecord, ...]:
|
||||
records: list[ModelCapabilityRecord] = []
|
||||
for item in openai_model_items(payload):
|
||||
record = record_from_native_model(item, endpoint_id=endpoint_id, base_url=base_url)
|
||||
if record:
|
||||
records.append(record)
|
||||
if records:
|
||||
return tuple(records)
|
||||
for item in as_list(as_mapping(payload).get("models")):
|
||||
if not isinstance(item, Mapping):
|
||||
continue
|
||||
record = record_from_native_model(item, endpoint_id=endpoint_id, base_url=base_url)
|
||||
if record:
|
||||
records.append(record)
|
||||
return tuple(records)
|
||||
Reference in New Issue
Block a user