Ctrl K
ComunidadGuías…

butler/transport/interruptible_client.py

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

Archivo original ↗Preguntar a KiBot
"""Interruptible LLM calls with stale-timeout (Hermes run_agent L7166+)."""

from __future__ import annotations

import logging
import threading
import time
from typing import Any, Callable, TypeVar, cast

logger = logging.getLogger(__name__)

T = TypeVar("T")


class StaleApiCallError(TimeoutError):
    """Raised when an API call exceeds stale threshold without completing."""


def run_interruptible(
    fn: Callable[[], T],
    *,
    check_interrupt: Callable[[], bool] | None = None,
    stale_timeout: float = 90.0,
    poll_interval: float = 0.3,
) -> T:
    """Run fn in a worker thread; poll for interrupt or stale timeout."""
    result: dict[str, Any] = {"value": None, "error": None}

    def _worker() -> None:
        from butler.transport.interruptible_client_ops import run_interruptible_worker_safe

        run_interruptible_worker_safe(fn, result)

    start = time.monotonic()
    thread = threading.Thread(target=_worker, daemon=True)
    thread.start()

    while thread.is_alive():
        thread.join(timeout=poll_interval)
        if check_interrupt and check_interrupt():
            logger.info("API call interrupted by user")
            raise InterruptedError("interrupted")
        elapsed = time.monotonic() - start
        if elapsed > stale_timeout:
            logger.warning("API call stale after %.0fs (limit %.0fs)", elapsed, stale_timeout)
            raise StaleApiCallError(
                f"No response from provider after {int(elapsed)}s"
            )

    if result["error"] is not None:
        raise result["error"]
    return cast(T, result["value"])