26382a7ac6
CI / Clippy (push) Failing after 15m13s
CI / Test (ubuntu-latest) (push) Failing after 16m1s
CI / Test (macos-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / Build (no embeddings / no ORT) (push) Has been cancelled
CI / Format (push) Has been cancelled
CI / Cookbook (Node) (push) Has been cancelled
CI / Pi Extension (Node) (push) Has been cancelled
CI / Rust SDK (lean-ctx-client) (push) Has been cancelled
CI / Embed SDK (lean-ctx-sdk) (push) Has been cancelled
CI / Python SDK (leanctx) (push) Has been cancelled
CI / Hermes Plugin (Python) (push) Has been cancelled
CI / SDK Conformance Matrix (push) Has been cancelled
CI / Coverage (push) Has been cancelled
CI / cargo-deny (push) Has been cancelled
CI / Adversarial Safety (push) Has been cancelled
CI / Benchmarks (push) Has been cancelled
CI / Output-Quality Gate (eval A/B) (push) Has been cancelled
CI / Documentation (push) Has been cancelled
CI / CI Green (push) Has been cancelled
JetBrains Plugin / Actionlint (push) Has been cancelled
CodeQL / Analyze (actions) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
CodeQL / Analyze (rust) (push) Has been cancelled
JetBrains Plugin / Validation (push) Has been cancelled
JetBrains Plugin / Build (push) Has been cancelled
JetBrains Plugin / Test (push) Has been cancelled
Security Check / Security Scan (push) Has been cancelled
106 lines
3.3 KiB
Python
106 lines
3.3 KiB
Python
"""LiteLLM integration for lean-ctx.
|
|
|
|
Compresses the ``messages`` of an outbound LiteLLM request through the local
|
|
proxy before it is sent to the provider. Two entry points:
|
|
|
|
* :func:`compress_request_data` — framework-agnostic helper that rewrites a
|
|
request ``dict`` in place (testable without LiteLLM installed).
|
|
* :class:`LeanCtxLiteLLMHandler` — a LiteLLM ``CustomLogger`` that hooks
|
|
``async_pre_call_hook`` for use with LiteLLM Proxy or ``litellm.callbacks``.
|
|
|
|
Register programmatically::
|
|
|
|
import litellm
|
|
from lean_ctx.litellm import LeanCtxLiteLLMHandler
|
|
|
|
litellm.callbacks = [LeanCtxLiteLLMHandler(model="gpt-4o")]
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
from typing import Any, Dict, Optional
|
|
|
|
from .errors import LeanCtxError
|
|
from .proxy import ProxyClient
|
|
|
|
_PRECALL_TYPES = ("completion", "text_completion")
|
|
|
|
|
|
def compress_request_data(
|
|
data: Dict[str, Any],
|
|
*,
|
|
client: Optional[ProxyClient] = None,
|
|
model: Optional[str] = None,
|
|
raise_on_error: bool = False,
|
|
) -> Dict[str, Any]:
|
|
"""Compress ``data["messages"]`` in place and return ``data``.
|
|
|
|
Mirrors the LiteLLM/OpenAI request shape. On a proxy failure the messages are
|
|
left untouched — a compaction hiccup must never block the LLM call — unless
|
|
``raise_on_error`` is set.
|
|
"""
|
|
messages = data.get("messages")
|
|
if not isinstance(messages, list) or not messages:
|
|
return data
|
|
proxy = client or ProxyClient()
|
|
try:
|
|
data["messages"] = proxy.compress(messages, model=model or data.get("model")).messages
|
|
except LeanCtxError:
|
|
if raise_on_error:
|
|
raise
|
|
return data
|
|
|
|
|
|
try: # pragma: no cover - import wiring, exercised by presence/absence of litellm
|
|
from litellm.integrations.custom_logger import CustomLogger
|
|
|
|
_BASE: type = CustomLogger
|
|
_LITELLM_AVAILABLE = True
|
|
except Exception: # noqa: BLE001 - any litellm import failure means "not available"
|
|
_BASE = object
|
|
_LITELLM_AVAILABLE = False
|
|
|
|
|
|
class LeanCtxLiteLLMHandler(_BASE): # type: ignore[misc,valid-type]
|
|
"""LiteLLM ``CustomLogger`` that compresses requests in ``async_pre_call_hook``.
|
|
|
|
The synchronous (urllib) :class:`ProxyClient` call is dispatched to a thread
|
|
so it never blocks the proxy event loop.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
*,
|
|
model: Optional[str] = None,
|
|
raise_on_error: bool = False,
|
|
base_url: Optional[str] = None,
|
|
token: Optional[str] = None,
|
|
) -> None:
|
|
if not _LITELLM_AVAILABLE:
|
|
raise ImportError("litellm is required: pip install litellm")
|
|
super().__init__()
|
|
self._client = ProxyClient(base_url=base_url, token=token)
|
|
self._model = model
|
|
self._raise_on_error = raise_on_error
|
|
|
|
async def async_pre_call_hook(
|
|
self,
|
|
user_api_key_dict: Any,
|
|
cache: Any,
|
|
data: Dict[str, Any],
|
|
call_type: str,
|
|
) -> Dict[str, Any]:
|
|
if call_type in _PRECALL_TYPES and isinstance(data, dict):
|
|
loop = asyncio.get_running_loop()
|
|
await loop.run_in_executor(
|
|
None,
|
|
lambda: compress_request_data(
|
|
data,
|
|
client=self._client,
|
|
model=self._model,
|
|
raise_on_error=self._raise_on_error,
|
|
),
|
|
)
|
|
return data
|