splinter-keep/tools/engine_lib/llm.py
2026-06-30 20:03:53 +02:00

64 lines
1.9 KiB
Python

#!/usr/bin/env python3
"""
llm.py — LLM interaction layer for The Chaos engine.
Provides the low-level call_llm function and environment variable setup
for provider-specific auth.
"""
from __future__ import annotations
import os
from .state import append_llm_log
def set_llm_env(model: str, api_key: str | None, api_base: str | None) -> None:
"""Set provider-specific env vars for litellm."""
prefix = model.split("/")[0].upper()
key = api_key or "sk-placeholder"
os.environ[f"{prefix}_API_KEY"] = key
if api_base:
os.environ[f"{prefix}_API_BASE"] = api_base
def call_llm(
messages: list[dict],
*,
model: str,
temperature: float,
timeout: int,
max_tokens: int,
label: str = "",
on_debug: callable = None,
) -> str | None:
"""Make a single LLM call. Returns content text or None on error."""
try:
import litellm
except ImportError:
if on_debug:
on_debug("llm_error", {"label": label, "error": "litellm not installed"})
return None
try:
response = litellm.completion(
model=model,
messages=messages,
temperature=temperature,
stream=False,
timeout=timeout,
max_tokens=max_tokens,
)
content = getattr(response.choices[0].message, 'content', None) or ""
reasoning = getattr(response.choices[0].message, 'reasoning_content', None) or ""
if reasoning and reasoning not in content:
append_llm_log(f"\n--- {label} [reasoning] ---\n{reasoning}")
text = content or reasoning
append_llm_log(f"\n--- {label} ---\n{text}")
return text
except Exception as e:
err_msg = f"{type(e).__name__}: {e}"
append_llm_log(f"\n--- LLM ERROR ({label}) ---\n{err_msg}")
if on_debug:
on_debug("llm_error", {"label": label, "error": err_msg})
return None