Ctrl K
ComunidadGuías…

butler/transport/usage_normalize.py

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

Archivo original ↗Preguntar a KiBot
"""Normalize LLM usage across OpenAI / Anthropic / MiniMax transports (Langflow subset)."""

from __future__ import annotations

from typing import Any

from butler.transport.types import Usage


def _int_field(raw: Any, *keys: str) -> int:
    if raw is None:
        return 0
    if isinstance(raw, dict):
        for key in keys:
            if key in raw and raw[key] is not None:
                try:
                    return max(0, int(raw[key]))
                except (TypeError, ValueError):
                    continue
        return 0
    for key in keys:
        val = getattr(raw, key, None)
        if val is not None:
            try:
                return max(0, int(val))
            except (TypeError, ValueError):
                continue
    return 0


def normalize_usage(
    raw: Any,
    *,
    provider: str = "",
) -> Usage | None:
    """Map provider-specific usage blobs to :class:`Usage`."""
    if raw is None:
        return None

    prompt = _int_field(
        raw,
        "prompt_tokens",
        "input_tokens",
        "prompt_token_count",
    )
    completion = _int_field(
        raw,
        "completion_tokens",
        "output_tokens",
        "completion_token_count",
    )
    total = _int_field(raw, "total_tokens", "total_token_count")
    cached = _int_field(
        raw,
        "cached_tokens",
        "cache_read_input_tokens",
        "cache_creation_input_tokens",
    )

    if prompt == 0 and completion == 0 and total == 0 and cached == 0:
        return None

    if total <= 0:
        total = prompt + completion + cached

    return Usage(
        prompt_tokens=prompt,
        completion_tokens=completion,
        total_tokens=total,
        cached_tokens=cached,
    )


def normalize_usage_from_response(
    response: Any,
    *,
    provider: str = "",
) -> Usage | None:
    usage = getattr(response, "usage", None)
    if usage is None and isinstance(response, dict):
        usage = response.get("usage")
    if isinstance(usage, Usage):
        return usage
    return normalize_usage(usage, provider=provider)


def usage_to_diagnostics_fields(usage: Usage | None) -> dict[str, int]:
    if usage is None:
        return {}
    return {
        "last_usage_prompt_tokens": int(usage.prompt_tokens or 0),
        "last_usage_completion_tokens": int(usage.completion_tokens or 0),
        "last_usage_total_tokens": int(usage.total_tokens or 0),
        "last_usage_cached_tokens": int(usage.cached_tokens or 0),
    }