cascade_risk / src /llm /client.py
Lucasoppem's picture
Sync from GitHub main (part 2)
36f9d47 verified
Raw
History Blame Contribute Delete
2.87 kB
"""Abstract LLM client interface."""
from __future__ import annotations
import os
import re
from abc import ABC, abstractmethod
from pathlib import Path
import yaml
def load_config(config_path: str = "config.yaml") -> dict:
with open(config_path) as f:
return yaml.safe_load(f)
def load_prompt_template(template_path: str) -> str:
return Path(template_path).read_text()
# Strip HTML comments (maintainer-only notes) from knowledge files before
# passing to LLM. Authors put checklists / cross-file-update warnings in
# <!-- ... --> blocks at the top of each knowledge/*.md; those are meta
# about how to edit the file, not content the LLM should read.
_HTML_COMMENT_RE = re.compile(r"<!--.*?-->", re.DOTALL)
def load_expert_knowledge(knowledge_path: str) -> str:
path = Path(knowledge_path)
if not path.exists():
return ""
return _HTML_COMMENT_RE.sub("", path.read_text()).lstrip()
class LLMClient(ABC):
"""Abstract base class for LLM backends."""
@abstractmethod
def call(
self,
prompt_template: str,
variables: dict,
expert_knowledge: str = "",
seed: int | None = None,
) -> str:
"""Call the LLM with a prompt template, variables, and optional expert knowledge.
Args:
prompt_template: The prompt template string with {placeholders}.
variables: Dictionary of variables to fill into the template.
expert_knowledge: Expert knowledge text to inject via {expert_knowledge} placeholder.
seed: Optional integer seed for sampling reproducibility. Local backends
must honor this when temperature > 0; cloud backends (claude /
claude-cli) currently raise NotImplementedError when seed is not None.
Returns:
The LLM's response text.
"""
def call_with_config(
self,
prompt_key: str,
knowledge_key: str,
variables: dict,
config: dict | None = None,
seed: int | None = None,
) -> str:
"""Convenience method: load prompt template and knowledge from config paths, then call.
Args:
prompt_key: Key in config['prompts'] for the prompt template file path.
knowledge_key: Key in config['knowledge'] for the expert knowledge file path.
variables: Dictionary of variables to fill into the template.
config: Config dict. If None, loads from config.yaml.
seed: Optional seed forwarded to the underlying call() (see LLMClient.call).
"""
if config is None:
config = load_config()
template = load_prompt_template(config["prompts"][prompt_key])
knowledge = load_expert_knowledge(config["knowledge"][knowledge_key])
return self.call(template, variables, knowledge, seed=seed)