LLM utilities¶
LLM related utils
File: klea_rag/llm.py
Copyright 2026 Ankur Sinha Author: Ankur Sinha <sanjay DOT ankur AT gmail DOT com>
- klea_utils.llm.DEFAULT_MAX_OUTPUT_TOKENS = 4096¶
Fallback max output tokens used when no node/role default provides a value.
- class klea_utils.llm.LLMModel(*, model_name: str = '', instance: Any, role_defaults: dict[str, Any] = {}, provider_defaults: dict[str, dict[str, Any]] = {}, modifiable: bool = True)[source]¶
Bases:
BaseModelContainer for a single LLM model instance and its runtime configuration.
instanceholds the model object (typically a_ConfigurableModelreturned byinit_chat_model).role_defaultsstores role-wide default parameters (e.g.max_tokens,temperature) that apply to every node sharing this role, unless overridden by node or user config.build_config()performs a five-layer merge:Layer 0 —
role_defaults: role-wide parameters (e.g.{"max_tokens": 4096}).Layer 1 —
model_name: the default model identifier from the graph config.Layer 2 —
context_overrides: per-request fields from the API (model,api_key, etc.). Only applied whenmodifiable=True, and skipping any keys frozen by node defaults.Layer 3 —
node_defaults: frozen per-node defaults (always win).Layer 4 —
provider_defaults: per-provider defaults from the graph config (e.g. HuggingFace role budgets), applied after the model string is parsed so the resolved provider is known. Applied withsetdefaultso explicit role/context/node values always win.modifiablecontrols whether the model can be changed at runtime (both the API and web UI reject modifications to locked roles). Set toFalseto lock a role (e.g. guard) against user overrides in managed deployments.- build_config(context_overrides: dict[str, Any] | None = None, node_defaults: dict[str, Any] | None = None) langgraph.types.RunnableConfig[source]¶
Merge up to five layers of model configuration into a
RunnableConfig.Layer order (lowest -> highest priority):
self.role_defaults— role-wide parametersself.model_name— role model from graph configcontext_overrides— per-request user overridesnode_defaults— frozen per-node defaultsself.provider_defaults— per-provider defaults (setdefault)
- Parameters:
context_overrides – Per-request fields from the API (e.g.
model,api_key). Only applied whenself.modifiable is True, and skipping any keys present innode_defaults.node_defaults – Frozen per-node defaults (e.g.
{"temperature": 0.3}). Always win.
- Returns:
A
RunnableConfigwith theconfigurablekey populated.
- model_config = {}¶
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class klea_utils.llm.ParsedModelName(provider: str | None, model_name: str, suffix: str | None)[source]¶
Bases:
NamedTupleParsed components of a model name string.
- klea_utils.llm.add_memory_to_prompt(context_summary: str, messages, num_history_messages) str[source]¶
Add memory to system prompt.
Adds the context summary and recent conversation
- Parameters:
state – agent state
- Returns:
“memory” string to add to the system prompt
- klea_utils.llm.check_model_works(model, timeout=30, retries=5)[source]¶
Check if a model works since it is not tested when loaded
- klea_utils.llm.check_ollama_model(logger, model, exit=False)[source]¶
Check if ollama model is available
- klea_utils.llm.classify_llm_invocation_error(exc: BaseException) LLMInvocationErrorCategory[source]¶
Classify an LLM invocation exception into a category.
Providers report failures inconsistently, so this uses tolerant regex matching over the exception message and its cause chain. Categories are checked in order of specificity (context overflow first), so a message matching several heuristics lands in the most actionable bucket.
- Parameters:
exc – The exception raised by an LLM invocation.
- Returns:
The best-effort
klea_utils.errors.LLMInvocationErrorCategory.
- klea_utils.llm.content_to_str(content: str | list[dict | str] | None) str[source]¶
Normalise an
AIMessage.contentvalue to a plain string.AIMessage.content can be a plain string, a list of content blocks (when the LLM returns tool calls or structured output), or
None. This helper always returns a string suitable for downstream text processing (regex,inchecks, prompt interpolation, etc.).- Parameters:
content – The raw
.contentvalue from an AIMessage.- Returns:
A plain string.
- klea_utils.llm.create_configurable_model(logger: Logger)[source]¶
Set up a configurable chat model.
Creates a
_ConfigurableModelwith no default model. Model, provider, and all other parameters (base_url,api_key,temperature, etc.) are specified per-invoke via theconfig["configurable"]dict passed toainvoke().This enables runtime model switching — each
ainvoke()call creates a fresh underlying model instance for the given provider, so there is no stale configuration leakage between calls.The lookup function
check_model_worksis deliberately not called here — we prefer a “leap before you look” approach so that startup is fast and model availability is checked only at query time.
- klea_utils.llm.estimate_input_tokens(input_chars: int) int[source]¶
Rough token estimate for a prompt’s character count.
Used only to keep the reserved output window within a model’s total budget (input + output <= context). ~4 characters per token is a reasonable average for mixed English/code text; an exact count would require provider-specific tokenizers.
- Parameters:
input_chars – Number of characters in the prompt.
- Returns:
Estimated token count.
- klea_utils.llm.extract_llm_output_content(output: AIMessage | dict) str[source]¶
Extract plain-text content from an LLM output.
Handles both
AIMessage(non-structured output) anddict(structured output withraw/parsedkeys):AIMessage– returnscontent_to_str(message.content).dict– extracts therawAIMessagefrom a structured output response and returns its content; falls back tooutput["parsed"]and finallystr(output).
- Parameters:
output – The raw output from
llm.invoke().- Returns:
A plain-text string.
- klea_utils.llm.format_alert(text: str, level: str = 'warning') str[source]¶
Wrap text as a GitHub-style markdown alert (e.g.
> [!WARNING]).Multi-line text is prefixed per line so the whole thing stays inside the blockquote. Renderers with the markdown2
alertsextra (the NiceGUI speech bubbles) show it as a styled callout; others fall back to a plain blockquote.- Parameters:
text – Alert body text
level – Alert level (note, tip, important, warning, caution)
- Returns:
Markdown alert blockquote
- klea_utils.llm.get_last_n_conversations(all_messages, start: int = 0, stop: int | None = None) tuple[str, list[langchain_core.messages.HumanMessage], list[langchain_core.messages.AIMessage]][source]¶
Get recent converstations between start and stop indices
- Parameters:
all_messages – all the messages
start – start index
stop – stop index
- Returns:
(conversation, list of human messages, list of ai messages)
- klea_utils.llm.get_provider_allowed_fields(provider: str) set[str][source]¶
Return the set of init-param names accepted by a given provider’s model class.
Uses LangChain’s internal provider registry to look up the Pydantic model class and introspect its fields (including aliases so that both
api_keyandopenai_api_keypass through).Falls back to an empty set if the provider is not registered in LangChain’s built-in providers. Raises
ImportErrorif the provider’s integration package is not installed — callers should handle this at configuration time, not silently fall through.The caller should always include
{"model", "model_provider"}on top of the returned set since those are consumed by_ConfigurableModelbefore reaching the model constructor.
- klea_utils.llm.get_token_limit_param(provider: str) str[source]¶
Return the max-output token parameter name for a provider.
Providers disagree on the parameter name for the maximum number of output tokens: Ollama uses
num_predict, while HuggingFace’sChatHuggingFace(which internally maps it tomax_new_tokens) and other OpenAI-compatible providers all usemax_tokens.Note
Known benign warning
When Klea resolves
max_tokensfor HuggingFace, the innerHuggingFaceEndpointconstructed byChatHuggingFace.from_model_id(which declaresmax_new_tokens, notmax_tokens) logsWARNING! max_tokens is not default parameterand shuffles it intomodel_kwargs. This is a false positive: the limit is still delivered correctly asmax_tokenstoInferenceClient.chat_completionvia the outerChatHuggingFace, which is the parameter the HuggingFace Inference API actually accepts. Do not “fix” it by switching tomax_new_tokenshere.- Parameters:
provider – Klea provider id (
huggingface,ollama, …)- Returns:
The token parameter name to send in the invoke config.
- klea_utils.llm.is_output_truncated(output: AIMessage | dict[str, Any]) bool[source]¶
Return True if an LLM output was truncated by the max-token limit.
Providers signal truncation via
finish_reason == "length"on the message metadata. Handles both plainAIMessageoutputs and structured-output dicts ({"raw": AIMessage, ...}), and the list-formfinish_reasonsome providers return.- Parameters:
output – The raw output from
llm.invoke().- Returns:
True when the model stopped because it hit the output cap.
- klea_utils.llm.load_prompt(prompt_name: str, prompt_registry_location: str)[source]¶
Load a prompt from file called prompt_name.md
- Parameters:
str – prompt file name
prompt_registry_location – location of prompts folder/registry
- Returns:
loaded prompt text
- klea_utils.llm.parse_model_name(raw: str) ParsedModelName[source]¶
Split a model name into provider, model identifier, and suffix.
Follows the
provider:model_idconvention. The provider is expected to be explicitly included; no provider inference is done.With three segments the third is treated as a
suffix(provider hint, model tag, base URL, etc.) unless the provider isollama, for which the second and third segments form the model name (model_name:tag).Examples:
ollama:bge-m3:latest-> provider=ollama, model=bge-m3:latest, suffix=Nonehuggingface:org/model:auto-> provider=huggingface, model=org/model, suffix=autocustom:model:https://example.com/v1-> provider=custom, model=model, suffix=https://example.com/v1openai:gpt-4o-> provider=openai, model=gpt-4o, suffix=Nonebge-m3-> provider=None, model=bge-m3, suffix=None
- Parameters:
raw – Model name with optional provider prefix
- Returns:
Parsed model name components
- klea_utils.llm.parse_output_with_thought(message: langchain_core.messages.AIMessage, schema: type[TSchema]) tuple[TSchema, str][source]¶
Parse AI message with thought to a dict based on given schema
- klea_utils.llm.prompt_value_to_messages(prompt: langchain_core.prompt_values.PromptValue) list[dict][source]¶
Convert a
PromptValueto a clean list of message dicts.Each dict has
roleandcontentkeys, suitable for JSON serialisation in the inspector debug panel.- Parameters:
prompt – The LangChain
PromptValue(filled, variables already substituted).- Returns:
A list of
{"role": "...", "content": "..."}dicts.
- klea_utils.llm.resolve_output_token_limit(overrides: dict[str, Any], provider: str, role: str | None = None, input_chars: int | None = None) None[source]¶
Ensure a bounded max-output token param is set in overrides.
HuggingFace-style providers apply a total budget: the reserved output window (
max_new_tokens) is accounted against the model’s context window alongside the input, and an unset value makes them reserve the entire remaining window (causing spurious usage limits and rate limiting). This helper guarantees a finite, clamped value.Resolution precedence:
An explicit provider token param (
max_tokens/max_new_tokens/num_predict) already present inoverrides(user/node/role value).The generic
max_output_tokenskey (provider-agnostic count).The built-in per-role fallback for role.
The resolved value is clamped to
min(value, catalog limit.output)and, when the catalog exposes a context window and input_chars is given, to the remaining budget (context - estimated input tokens) so HuggingFace’s total-budget check is never exceeded.- Parameters:
overrides – The merged
configurabledict to update in place.provider – Klea provider id (
huggingface,ollama, …).role – Model role (e.g.
"chat"), used for the built-in per-role fallback.input_chars – Character count of the prompt, to bound the output within the total budget.