Ctrl K
ComunidadGuías…

butler/transport/fusion_client.py

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

Archivo original ↗Preguntar a KiBot
"""Trusted-model router for semantic fusion (skill/experience merge).

Distinct from ``auxiliary_client``: fusion tasks need a capable model
(``auxiliary.fusion`` or butler role stack), not the cheap compression path.
"""

from __future__ import annotations

import logging
from typing import Any, Callable

from butler.configuration.settings import ModelConfig, get_butler_settings
from butler.transport.llm_client import LLMClient

logger = logging.getLogger(__name__)

_FUSION_SYSTEM = (
    "You merge overlapping knowledge records for a personal assistant. "
    "Preserve factual pointers (skill:, tool:, mcp:). Output only valid JSON when asked."
)


def resolve_fusion_config() -> ModelConfig:
    """``auxiliary.fusion`` → butler effective model → provider default."""
    settings = get_butler_settings()
    cfg = settings.get_auxiliary_task_config("fusion")
    if not cfg.is_empty():
        return cfg

    from butler.model_resolve import resolve_effective_model

    butler_cfg = resolve_effective_model("butler", settings=settings).config
    if not butler_cfg.is_empty():
        return butler_cfg

    preferred = (settings.default_provider or "minimax").strip()
    if preferred and (pc := settings.providers.get(preferred)):
        return ModelConfig(provider=preferred, model=pc.model or "")

    from butler.defaults.model_defaults import AUXILIARY_SCAN_PROVIDERS

    for name in AUXILIARY_SCAN_PROVIDERS:
        if pc := settings.providers.get(name):
            return ModelConfig(provider=name, model=pc.model or "")
    return ModelConfig(provider=preferred or "minimax", model="")


def create_fusion_client(**kwargs: Any) -> LLMClient:
    cfg = resolve_fusion_config()
    settings = get_butler_settings()
    pc = settings.providers.get(cfg.provider) if cfg.provider else None
    return LLMClient(
        provider=cfg.provider,
        model=cfg.model or (pc.model if pc else ""),
        api_key=pc.api_key if pc else None,
        base_url=pc.base_url if pc else None,
        max_tokens=cfg.max_tokens or 4096,
        temperature=cfg.temperature if cfg.temperature is not None else 0.2,
        **kwargs,
    )


def fusion_complete(
    prompt: str,
    *,
    system: str = _FUSION_SYSTEM,
    session_key: str = "",
) -> str:
    """Single-shot trusted-model completion for merge/fusion tasks."""
    client = create_fusion_client()
    resp = client.complete(
        messages=[
            {"role": "system", "content": system},
            {"role": "user", "content": prompt},
        ],
    )
    sk = session_key or _current_session_key()
    if sk and resp.usage:
        from butler.transport.fusion_client_ops import record_fusion_llm_cost_safe

        record_fusion_llm_cost_safe(sk, resp.usage)
    return resp.content or ""


def _current_session_key() -> str:
    from butler.transport.fusion_client_ops import current_fusion_session_key_safe

    return str(current_fusion_session_key_safe())


def make_fusion_llm_fn() -> Callable[[str], str]:
    """Sync callable for ``SkillConsolidator`` / experience merge."""
    from butler.transport.fusion_client_ops import fusion_complete_or_raise_unavailable

    def _call(prompt: str) -> str:
        return str(fusion_complete_or_raise_unavailable(fusion_complete, prompt))

    return _call


__all__ = [
    "create_fusion_client",
    "fusion_complete",
    "make_fusion_llm_fn",
    "resolve_fusion_config",
]