e4dcfc49aa
Tests / Import Check (Python 3.13) (push) Has been cancelled
Tests / Import Check (Python 3.14) (push) Has been cancelled
Tests / Python Tests (Python 3.11) (push) Has been cancelled
Tests / Python Tests (Python 3.12) (push) Has been cancelled
Tests / Python Tests (Python 3.14) (push) Has been cancelled
Tests / Test Summary (push) Has been cancelled
Tests / Lint and Format (push) Has been cancelled
Tests / Web Node Tests (push) Has been cancelled
Tests / Import Check (Python 3.11) (push) Has been cancelled
Tests / Import Check (Python 3.12) (push) Has been cancelled
Tests / Python Tests (Python 3.13) (push) Has been cancelled
215 lines
6.8 KiB
Python
215 lines
6.8 KiB
Python
"""Detect or suggest a model context window during settings diagnostics."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from collections.abc import Callable, Iterable, Mapping
|
|
from dataclasses import dataclass
|
|
from datetime import datetime, timezone
|
|
import logging
|
|
from typing import Any
|
|
|
|
import aiohttp
|
|
|
|
from deeptutor.services.llm.config import LLMConfig
|
|
from deeptutor.services.llm.context_window import (
|
|
coerce_positive_int,
|
|
default_context_window_for_model,
|
|
)
|
|
from deeptutor.services.llm.openai_http_client import disable_ssl_verify_enabled
|
|
from deeptutor.services.llm.utils import build_auth_headers
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
_CONTEXT_WINDOW_KEYS = (
|
|
"context_window",
|
|
"context_window_tokens",
|
|
"context_length",
|
|
"max_context_tokens",
|
|
"max_input_tokens",
|
|
"input_token_limit",
|
|
"max_prompt_tokens",
|
|
"max_model_len",
|
|
"max_sequence_length",
|
|
)
|
|
|
|
_KNOWN_CONTEXT_WINDOWS: tuple[tuple[str, int], ...] = (("deepseek-v4", 1_000_000),)
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class ContextWindowDetectionResult:
|
|
"""Structured context-window detection output."""
|
|
|
|
context_window: int
|
|
source: str
|
|
detail: str
|
|
detected_at: str
|
|
|
|
|
|
def _model_aliases(model: str) -> set[str]:
|
|
value = (model or "").strip().lower()
|
|
if not value:
|
|
return set()
|
|
aliases = {value}
|
|
if "/" in value:
|
|
aliases.add(value.split("/", 1)[1])
|
|
if ":" in value:
|
|
aliases.add(value.split(":", 1)[1])
|
|
return {item for item in aliases if item}
|
|
|
|
|
|
def _record_identities(item: Mapping[str, Any]) -> set[str]:
|
|
aliases: set[str] = set()
|
|
for key in ("id", "model", "name"):
|
|
aliases.update(_model_aliases(str(item.get(key, "") or "")))
|
|
return aliases
|
|
|
|
|
|
def _known_context_window(model: str) -> int | None:
|
|
normalized = (model or "").strip().lower()
|
|
if not normalized:
|
|
return None
|
|
for pattern, context_window in _KNOWN_CONTEXT_WINDOWS:
|
|
if pattern in normalized:
|
|
return context_window
|
|
return None
|
|
|
|
|
|
def _recursive_context_window(value: Any) -> int | None:
|
|
if isinstance(value, Mapping):
|
|
for key in _CONTEXT_WINDOW_KEYS:
|
|
parsed = coerce_positive_int(value.get(key))
|
|
if parsed is not None:
|
|
return parsed
|
|
for nested in value.values():
|
|
parsed = _recursive_context_window(nested)
|
|
if parsed is not None:
|
|
return parsed
|
|
elif isinstance(value, list):
|
|
for nested in value:
|
|
parsed = _recursive_context_window(nested)
|
|
if parsed is not None:
|
|
return parsed
|
|
return None
|
|
|
|
|
|
def _iter_model_records(payload: Any) -> Iterable[Mapping[str, Any]]:
|
|
if isinstance(payload, list):
|
|
for item in payload:
|
|
if isinstance(item, Mapping):
|
|
yield item
|
|
return
|
|
if not isinstance(payload, Mapping):
|
|
return
|
|
for key in ("data", "models", "result", "items"):
|
|
items = payload.get(key)
|
|
if isinstance(items, list):
|
|
for item in items:
|
|
if isinstance(item, Mapping):
|
|
yield item
|
|
|
|
|
|
def _extract_context_window_from_payload(payload: Any, model: str) -> int | None:
|
|
target_aliases = _model_aliases(model)
|
|
if not target_aliases:
|
|
return None
|
|
|
|
exact_matches: list[Mapping[str, Any]] = []
|
|
partial_matches: list[Mapping[str, Any]] = []
|
|
for item in _iter_model_records(payload):
|
|
identities = _record_identities(item)
|
|
if not identities:
|
|
continue
|
|
if identities & target_aliases:
|
|
exact_matches.append(item)
|
|
continue
|
|
if any(
|
|
item_identity.endswith(f"/{alias}") or alias.endswith(f"/{item_identity}")
|
|
for item_identity in identities
|
|
for alias in target_aliases
|
|
):
|
|
partial_matches.append(item)
|
|
|
|
for item in [*exact_matches, *partial_matches]:
|
|
parsed = _recursive_context_window(item)
|
|
if parsed is not None:
|
|
return parsed
|
|
return None
|
|
|
|
|
|
async def _detect_from_models_endpoint(
|
|
llm_config: LLMConfig,
|
|
*,
|
|
on_log: Callable[[str], None] | None = None,
|
|
) -> int | None:
|
|
base_url = str(llm_config.base_url or llm_config.effective_url or "").strip()
|
|
if not base_url:
|
|
return None
|
|
|
|
url = f"{base_url.rstrip('/')}/models"
|
|
headers = build_auth_headers(llm_config.api_key, llm_config.binding)
|
|
headers.pop("Content-Type", None)
|
|
|
|
timeout = aiohttp.ClientTimeout(total=12)
|
|
connector = aiohttp.TCPConnector(ssl=False) if disable_ssl_verify_enabled() else None
|
|
try:
|
|
async with aiohttp.ClientSession(
|
|
timeout=timeout,
|
|
connector=connector,
|
|
trust_env=True,
|
|
) as session:
|
|
async with session.get(url, headers=headers) as response:
|
|
if response.status != 200:
|
|
if on_log is not None:
|
|
on_log(
|
|
f"`GET {url}` returned HTTP {response.status}; skipping metadata detection."
|
|
)
|
|
return None
|
|
payload = await response.json()
|
|
except Exception as exc:
|
|
logger.debug("Context-window metadata request failed for %s: %s", url, exc)
|
|
if on_log is not None:
|
|
on_log(f"Could not read `{url}` for context-window metadata: {exc}")
|
|
return None
|
|
|
|
return _extract_context_window_from_payload(payload, llm_config.model)
|
|
|
|
|
|
async def detect_context_window(
|
|
llm_config: LLMConfig,
|
|
*,
|
|
on_log: Callable[[str], None] | None = None,
|
|
) -> ContextWindowDetectionResult:
|
|
"""Detect the current model's context window or fall back to the runtime default."""
|
|
detected_at = datetime.now(timezone.utc).isoformat()
|
|
metadata_window = await _detect_from_models_endpoint(llm_config, on_log=on_log)
|
|
if metadata_window is not None:
|
|
return ContextWindowDetectionResult(
|
|
context_window=metadata_window,
|
|
source="metadata",
|
|
detail="Detected from provider `/models` metadata.",
|
|
detected_at=detected_at,
|
|
)
|
|
|
|
known_window = _known_context_window(llm_config.model)
|
|
if known_window is not None:
|
|
return ContextWindowDetectionResult(
|
|
context_window=known_window,
|
|
source="known_model",
|
|
detail="Matched built-in context-window metadata for this model family.",
|
|
detected_at=detected_at,
|
|
)
|
|
|
|
fallback = default_context_window_for_model(
|
|
model=llm_config.model,
|
|
max_tokens=llm_config.max_tokens,
|
|
)
|
|
return ContextWindowDetectionResult(
|
|
context_window=fallback,
|
|
source="default",
|
|
detail="Provider metadata did not expose a window; using the runtime fallback.",
|
|
detected_at=detected_at,
|
|
)
|
|
|
|
|
|
__all__ = ["ContextWindowDetectionResult", "detect_context_window"]
|