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
118 lines
4.3 KiB
Python
118 lines
4.3 KiB
Python
"""Engine configuration, read from ``LEANCTX_*`` environment variables.
|
|
|
|
The ``compression.*`` block in Hermes' ``config.yaml`` is specific to the
|
|
built-in ``ContextCompressor``; per the plugin guide each engine defines its
|
|
own config. We use a dedicated ``LEANCTX_*`` namespace (analogous to
|
|
``LCM_*``) so the two never collide.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
from dataclasses import dataclass
|
|
from typing import Optional
|
|
|
|
# Default port of the lean-ctx HTTP tools API (`lean-ctx serve`). The plugin
|
|
# speaks the `/v1/tools/call` REST contract, which is served by `lean-ctx serve`
|
|
# (and the daemon's IPC socket) — NOT by the LLM proxy on the 4444+ port, whose
|
|
# router 404s `/v1/tools/call`. So the default targets the serve port; set
|
|
# ``LEANCTX_BASE_URL`` (or ``LEANCTX_HTTP_PORT``) for non-default binds.
|
|
_DEFAULT_HTTP_PORT = 8080
|
|
# Sensible default context window when the host has not called update_model().
|
|
_DEFAULT_CONTEXT_LENGTH = 200_000
|
|
|
|
|
|
def _default_base_url() -> str:
|
|
raw = os.environ.get("LEANCTX_HTTP_PORT")
|
|
port = _DEFAULT_HTTP_PORT
|
|
if raw and raw.strip():
|
|
try:
|
|
port = int(raw.strip())
|
|
except ValueError:
|
|
port = _DEFAULT_HTTP_PORT
|
|
return f"http://127.0.0.1:{port}"
|
|
|
|
|
|
def _env_int(name: str, default: int) -> int:
|
|
raw = os.environ.get(name)
|
|
if raw is None or not raw.strip():
|
|
return default
|
|
try:
|
|
return int(raw.strip())
|
|
except ValueError:
|
|
return default
|
|
|
|
|
|
def _env_float(name: str, default: float) -> float:
|
|
raw = os.environ.get(name)
|
|
if raw is None or not raw.strip():
|
|
return default
|
|
try:
|
|
return float(raw.strip())
|
|
except ValueError:
|
|
return default
|
|
|
|
|
|
def _env_str(name: str, default: Optional[str] = None) -> Optional[str]:
|
|
raw = os.environ.get(name)
|
|
if raw is None:
|
|
return default
|
|
raw = raw.strip()
|
|
return raw or default
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class LeanCtxConfig:
|
|
"""Immutable engine configuration."""
|
|
|
|
base_url: str
|
|
token: Optional[str] = None
|
|
timeout: float = 30.0
|
|
workspace_id: Optional[str] = None
|
|
channel_id: Optional[str] = None
|
|
|
|
context_length: int = _DEFAULT_CONTEXT_LENGTH
|
|
# Fraction of the context window at which compaction fires.
|
|
threshold_fraction: float = 0.75
|
|
# Recent context to always keep verbatim (the "fresh tail").
|
|
protect_fraction: float = 0.25
|
|
protect_min_messages: int = 6
|
|
protect_min_tokens: int = 2_000
|
|
|
|
# Expose lean-ctx recall/intelligence tools natively to the agent.
|
|
enable_tools: bool = True
|
|
# Prefer the daemon's `ctx_transcript_compact` core tool (Single Source of
|
|
# Truth) over the local Python compaction. Falls back automatically when the
|
|
# daemon is unavailable or the tool is missing (older daemon).
|
|
use_core_compaction: bool = True
|
|
|
|
def threshold_tokens(self) -> int:
|
|
return max(1, int(self.context_length * self.threshold_fraction))
|
|
|
|
def protect_tokens(self) -> int:
|
|
return max(self.protect_min_tokens, int(self.context_length * self.protect_fraction))
|
|
|
|
def with_context_length(self, context_length: int) -> "LeanCtxConfig":
|
|
"""Return a copy with an updated context window (on model switch)."""
|
|
from dataclasses import replace
|
|
|
|
return replace(self, context_length=max(1, int(context_length)))
|
|
|
|
@classmethod
|
|
def from_env(cls) -> "LeanCtxConfig":
|
|
return cls(
|
|
base_url=_env_str("LEANCTX_BASE_URL") or _default_base_url(),
|
|
token=_env_str("LEANCTX_TOKEN"),
|
|
timeout=_env_float("LEANCTX_TIMEOUT", 30.0),
|
|
workspace_id=_env_str("LEANCTX_WORKSPACE_ID"),
|
|
channel_id=_env_str("LEANCTX_CHANNEL_ID"),
|
|
context_length=_env_int("LEANCTX_CONTEXT_LENGTH", _DEFAULT_CONTEXT_LENGTH),
|
|
threshold_fraction=_env_float("LEANCTX_THRESHOLD_FRACTION", 0.75),
|
|
protect_fraction=_env_float("LEANCTX_PROTECT_FRACTION", 0.25),
|
|
protect_min_messages=_env_int("LEANCTX_PROTECT_MIN_MESSAGES", 6),
|
|
protect_min_tokens=_env_int("LEANCTX_PROTECT_MIN_TOKENS", 2_000),
|
|
enable_tools=_env_str("LEANCTX_ENABLE_TOOLS", "1") not in {"0", "false", "no", "off"},
|
|
use_core_compaction=_env_str("LEANCTX_CORE_COMPACTION", "1")
|
|
not in {"0", "false", "no", "off"},
|
|
)
|