Ctrl K
ComunidadGuías…

butler/transport/thinking_headers.py

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
"""Provider-specific thinking / beta API headers (主线 G 深化)."""

from __future__ import annotations

import json
import logging
import os
from typing import Any

from butler.defaults.env_defaults import (
    THINKING_BETA_HEADER_DEFAULT,
    THINKING_BETA_MATRIX_DEFAULT,
)
from butler.transport.model_capabilities import model_supports_thinking
from butler.transport.thinking_protocol import thinking_protocol_enabled

logger = logging.getLogger(__name__)

# (provider, model_substring) → anthropic-beta header value (empty = skip)
_DEFAULT_MATRIX: list[tuple[str, str, str]] = [
    ("anthropic", "", "interleaved-thinking-2025-05-14"),
    ("anthropic", "claude-opus-4", "interleaved-thinking-2025-05-14"),
    ("anthropic", "claude-sonnet-4", "interleaved-thinking-2025-05-14"),
]


def _load_env_matrix() -> list[tuple[str, str, str]]:
    raw = os.getenv("BUTLER_THINKING_BETA_MATRIX", THINKING_BETA_MATRIX_DEFAULT).strip()
    if not raw:
        return []
    try:
        data = json.loads(raw)
    except json.JSONDecodeError:
        return []
    if not isinstance(data, list):
        return []
    rows: list[tuple[str, str, str]] = []
    for item in data:
        if not isinstance(item, dict):
            continue
        prov = str(item.get("provider") or "").strip().lower()
        model = str(item.get("model_contains") or item.get("model") or "").strip().lower()
        beta = str(item.get("anthropic_beta") or item.get("beta") or "").strip()
        if prov and beta:
            rows.append((prov, model, beta))
    return rows


def resolve_thinking_beta_value(*, provider: str, model: str = "") -> str:
    """Return anthropic-beta header value or empty string."""
    if not thinking_protocol_enabled():
        return ""
    if not model_supports_thinking(provider, model):
        return ""
    override = os.getenv("BUTLER_THINKING_BETA_HEADER", THINKING_BETA_HEADER_DEFAULT).strip()
    if override:
        return override
    prov = str(provider or "").strip().lower()
    mlow = str(model or "").strip().lower()
    matrix = _load_env_matrix() or _DEFAULT_MATRIX
    best = ""
    best_len = -1
    for p, substr, beta in matrix:
        if p != prov:
            continue
        if substr and substr not in mlow:
            continue
        key_len = len(substr)
        if key_len >= best_len:
            best_len = key_len
            best = beta
    return best


def merge_thinking_request_kwargs(
    api_kwargs: dict[str, Any],
    *,
    provider: str,
    model: str = "",
) -> dict[str, Any]:
    """Attach ``extra_headers`` for Anthropic thinking beta when configured."""
    beta = resolve_thinking_beta_value(provider=provider, model=model)
    if not beta:
        return api_kwargs
    out = dict(api_kwargs)
    headers = dict(out.get("extra_headers") or {})
    headers["anthropic-beta"] = beta
    out["extra_headers"] = headers
    return out


__all__ = [
    "merge_thinking_request_kwargs",
    "resolve_thinking_beta_value",
]