Files
yvgude--lean-ctx/packages/python-lean-ctx/lean_ctx/litellm.py
T
wehub-resource-sync 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
chore: import upstream snapshot with attribution
2026-07-13 12:35:30 +08:00

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