NAME

Langertha::Knarr::Config - YAML configuration loader and validator

VERSION

version 1.102

SYNOPSIS

use Langertha::Knarr::Config;

# Load from file
my $config = Langertha::Knarr::Config->new(file => 'knarr.yaml');

# Build from environment (Docker zero-config mode)
my $config = Langertha::Knarr::Config->from_env;

# Validate
my @errors = $config->validate;
die join("\n", @errors) if @errors;

DESCRIPTION

Loads and validates Knarr configuration from a YAML file or from environment variables. All string values in the YAML file support ${ENV_VAR} interpolation.

The attributes below are the configuration file reference: each one names its YAML key and, where there is one, its environment variable. The README has an annotated example, and share/example-config.yaml in the distribution is a commented starting point.

file

Path to the YAML configuration file. Optional — when omitted the config object starts with an empty data hash (useful for testing or for configs built purely from environment variables via "from_env").

data

The raw configuration hashref, loaded from "file" and with all ${ENV_VAR} references expanded. Can be supplied directly to bypass file loading.

engine_catalog

my $defs = Langertha::Knarr::Config->engine_catalog;
for my $def (@$defs) { say $def->{engine}, ": @{$def->{vars}}" }

Class method. Returns an ArrayRef of {engine, vars} hashrefs describing every engine "scan_env" can auto-detect, with its API key environment variable names in priority order. The list is a copy — mutating it does not affect scanning.

default_model_for

my $model = Langertha::Knarr::Config->default_model_for('Anthropic');

Class method. Returns the default model name for an engine, read from the Langertha engine class itself (Langertha::Engine::Anthropic->default_model) so the catalog cannot drift away from the framework. Returns undef when the engine class is not installed, or when it has no default model and no fallback is known. The result is cached per engine name.

from_env

my $config = Langertha::Knarr::Config->from_env(%opts);

Class method. Builds a config object purely from environment variables (zero-config Docker mode). Calls "scan_env" to detect which API keys are set, assigns each detected engine its "default_model_for" model, enables auto_discover and passthrough, and sets OpenAI as the default engine when an OpenAI key is present. Every generated entry, the default engine included, names the variable its key was found in as api_key_env, so OPENAI_API_KEY works as well as LANGERTHA_OPENAI_API_KEY.

Options are passed through to "scan_env" (e.g. include_test).

listen

ArrayRef of host:port strings to listen on (listen:, a list or a single string). Defaults to ['127.0.0.1:8080', '127.0.0.1:11434'], so a config without listen: only answers on loopback. knarr start -p replaces it with the given ports on -H (default 0.0.0.0); the Docker image starts that way.

models

HashRef of model name → model definition hashref from the models: config section. Each definition may include engine, model, api_key_env, api_key, url, system_prompt, temperature, response_size, context_size, and user_agent_timeout (seconds the engine waits for its upstream; defaults to "upstream_timeout", 0 disables it).

context_size is passed to engines that compose Langertha::Role::ContextSize (Ollama, LMStudio native), which send it on the wire (Ollama's num_ctx, LM Studio's context_length) and report it from /api/show. For any other engine it is ignored, with one warning when the models are loaded.

Models found by "auto_discover" inherit the endpoint-level keys of the entry they were discovered through, but not the model-specific model and context_size; see "DESCRIPTION" in Langertha::Knarr::Router for the split.

default_engine

HashRef from the default: config section, or undef if not set. At minimum contains engine. Used as the fallback when a model name is not explicitly configured and no passthrough URL matches -- that is, no "passthrough" upstream exists for the protocol the client speaks (Ollama without an ollama entry, and A2A, ACP and AG-UI always). Without a default engine such a request is answered with 404 in the client protocol's error shape.

A model in this section is what the default engine uses for a request that names no model (A2A always, ACP without agent_name); a model the client names replaces it. The section takes the same keys as a "models" entry, api_key_env or api_key included; without either the engine reads only its own LANGERTHA_*_API_KEY variable.

log_file

Path to a JSONL log file for request logging. Resolved from logging.file in config or KNARR_LOG_FILE environment variable.

log_dir

Path to a directory for per-request JSON log files. Resolved from logging.dir in config or KNARR_LOG_DIR environment variable.

langfuse

HashRef from the langfuse: config section. May contain url, public_key, secret_key, trace_name, transport (see "langfuse_transport") and timeout (see "langfuse_timeout"). Returns an empty hashref when the section is absent.

langfuse_transport

How Langertha::Knarr::Tracing sends traces to Langfuse: ingestion (the default) posts trace and generation events to Langfuse's /api/public/ingestion API; otel exports them as OpenTelemetry spans (OTLP/HTTP, JSON encoded) to /api/public/otel/v1/traces, the path Langfuse now recommends (Langfuse Cloud, or self-hosted v3.22 and later; Langfuse v2 has only the ingestion API). See "OpenTelemetry transport" in Langertha::Knarr::Tracing. Read from langfuse.transport, falling back to the KNARR_LANGFUSE_TRANSPORT environment variable, so it also applies under --from-env. Case does not matter. Any other value croaks when read, and "validate" reports it.

langfuse_timeout

Seconds a trace POST to Langfuse may take before it is given up and logged as a Langfuse flush error warning, for either "langfuse_transport". Default 15; 0 disables it. The POST never holds a request, so this only decides when a slow Langfuse is reported as failed. Read from langfuse.timeout, falling back to the KNARR_LANGFUSE_TIMEOUT environment variable. A value that is not a non-negative number croaks when read, and "validate" reports it.

proxy_api_key

Optional shared secret that clients must present in the Authorization: Bearer or x-api-key header. Falls back to the KNARR_API_KEY environment variable. When not set, the proxy is open (no auth required). It is never forwarded to a passthrough upstream; see "auth_token" in Langertha::Knarr.

public_url

Optional public base URL of this Knarr (e.g. https://knarr.example), published in the provider manifest at /.well-known/langertha.json. Falls back to the KNARR_PUBLIC_URL environment variable. When not set, the manifest takes the base URL from each request.

has_proxy_api_key

if ($config->has_proxy_api_key) { ... }

Returns true when a "proxy_api_key" is configured.

ollama_compat_version

Optional Ollama version to report at GET /api/version (see "ollama_compat_version" in Langertha::Knarr). Falls back to the KNARR_OLLAMA_COMPAT_VERSION environment variable. When not set, Knarr's default applies. A value that is not three dot-separated numbers (0.34.4) croaks when read, and "validate" reports it: Ollama clients parse each part as an integer.

a2a_name

Optional name of the A2A agent card at /.well-known/agent.json (see "agent_name" in Langertha::Knarr::Protocol::A2A). Resolved from a2a.name in config or the KNARR_A2A_NAME environment variable. When not set, the card says Langertha Knarr Agent.

a2a_description

Optional description of the A2A agent card (see "agent_description" in Langertha::Knarr::Protocol::A2A). Resolved from a2a.description in config or the KNARR_A2A_DESCRIPTION environment variable. When not set, Knarr's default description applies.

protocol_args

my $knarr = Langertha::Knarr->new( ..., protocol_args => $config->protocol_args );

Returns the "protocol_args" in Langertha::Knarr HashRef for the configured protocol settings: { A2A => { agent_name => ..., agent_description => ... } }, with only the keys that are set ("a2a_name", "a2a_description").

upstream_timeout

Seconds an upstream may take to answer a non-streaming request. Default 300; 0 disables it. Falls back to the KNARR_UPSTREAM_TIMEOUT environment variable. It is the total time of a raw passthrough request (answered with 504 in the client protocol's error shape when it runs out), and it becomes the user_agent_timeout of every routed engine whose model config sets none -- Langertha applies that as the total time of a plain request and as the time without data of a streaming one. A value that is not a non-negative number croaks when read, and "validate" reports it.

upstream_stall_timeout

Seconds a streaming raw passthrough request may go without data from the upstream, the wait for its response headers included. Default 120; 0 disables it. Falls back to the KNARR_UPSTREAM_STALL_TIMEOUT environment variable. A stream that stalls before its headers is answered with 504; one that stalls later ends with the protocol's error frame. Croaks when read and is reported by "validate" like "upstream_timeout".

probe_capabilities

Boolean, default 1. When true, "start" in Langertha::Knarr asks every routed engine that can read its provider's model metadata (OpenRouter, Mistral, LM Studio, T-Systems, Ollama, llama.cpp; Langertha core's probe_model_capabilities_f) which capabilities its model has, once at startup, after auto-discovery. What it learns (today: whether the model sees images) then shows as vision in POST /api/show and as image_input in the provider manifest. Engines without such metadata and a Langertha too old to probe (0.503) send nothing. A failed probe is logged and changes nothing. Set 0 (or KNARR_PROBE_CAPABILITIES=0) to never send these requests. See "probe_capabilities_f" in Langertha::Knarr::Router.

probe_timeout

Seconds one capability probe (see "probe_capabilities") may take before it is given up and logged. Default 10; 0 leaves only the engine's own user_agent_timeout. Falls back to KNARR_PROBE_TIMEOUT. A value that is not a non-negative number croaks when read, and "validate" reports it.

workers

Number of processes knarr start serves from. Default 1: one process, nothing forked. With more, it forks that many workers on the same listen sockets and supervises them (see "workers" in Langertha::Knarr). Falls back to KNARR_WORKERS, so it also applies under --from-env; knarr start -w N wins over both. A value that is not a whole number of 1 or more croaks when read, and "validate" reports it.

auto_discover

Boolean. When true, Langertha::Knarr::Router asks each configured endpoint (engine, URL and API key variable) for its model list the first time a model is resolved, making all discovered models available without explicit config entries and listing them (/v1/models, /api/tags, the manifest). A discovered model is routed through its engine, except when the "passthrough" upstream of the client's protocol is the endpoint that listed it (same scheme, host, port and path, an engine's trailing /v1 aside, so a gateway's /openai and /groq paths are two upstreams) and the request carries the client's own provider key (Authorization for OpenAI, x-api-key or Authorization for Anthropic, not counting the "proxy_api_key"; Ollama needs none): then it passes through byte for byte like an unknown model, with that key. Without one it is routed through its engine, with the engine's key, and so it is when the upstream refuses that key with 401 (see "raw_passthrough" in Langertha::Knarr). A model under "models" is always routed. Defaults to 0; "from_env" and the knarr init output turn it on.

passthrough

HashRef of format name (openai, anthropic, ollama) → upstream base URL. Empty, and passthrough off, unless the config has a passthrough: key; "from_env" turns it on. passthrough: true in YAML enables openai and anthropic with their default upstream URLs (https://api.openai.com and https://api.anthropic.com); ollama has no default and needs its URL. Per-format URLs can be customised or set to false to disable selectively.

A request for a model that is neither configured nor auto-discovered (or auto-discovered from that very upstream, see "auto_discover") goes to the upstream of its protocol byte for byte, with the client's own headers and key. A protocol without an upstream sends such a request to the "default_engine" instead.

passthrough_url_for

my $url = $config->passthrough_url_for('openai');

Returns the upstream base URL for the given format name (e.g. openai or anthropic), or undef if passthrough is not configured for that format.

validate

my @errors = $config->validate;

Validates the configuration and returns a list of error strings. Returns an empty list when the config is valid. A config file that does not load (not YAML, or not a mapping) is reported alone, and so are the sections models, default, langfuse, logging, a2a and passthrough when one is not a mapping (passthrough may also be true or false), and listen when it is not a string or a list of strings: reading such a section croaks. Otherwise it checks that every model entry is a mapping with an engine key, that the default engine (if set) has an engine key, that at least one model or default engine is configured, that a model's context_size (and the default engine's), when set, is a positive integer, and its user_agent_timeout a non-negative number, and that "ollama_compat_version", when set, is three dot-separated numbers, and that "upstream_timeout", "upstream_stall_timeout" and "probe_timeout" are non-negative numbers, and that "workers" is a whole number of 1 or more.

scan_env

my $found = Langertha::Knarr::Config->scan_env(
  env_files    => ['.env', '.env.local'],
  include_test => 1,
);

Class method. Scans %ENV and optional .env files for known API key environment variables. Returns a HashRef of engine name → {engine, api_key_env} for every engine whose key was found. See "engine_catalog" for the engines and variable names it knows about.

Priority order per engine: LANGERTHA_*_API_KEY beats the bare vendor key (e.g. OPENAI_API_KEY), which beats the TEST_LANGERTHA_*_API_KEY variant.

Options:

  • env_files — ArrayRef of .env file paths to parse (optional)

  • include_test — Include TEST_* variables (default: 1)

generate_config

my $yaml = Langertha::Knarr::Config->generate_config(
  engines => $found,       # from scan_env
  listen  => ['127.0.0.1:8080', '127.0.0.1:11434'],
);

Class method. Generates a YAML configuration string from scan_env results. The generated YAML includes sensible defaults for each detected engine and commented-out stanzas for optional features. Used by knarr init.

Returns a string.

SEE ALSO

SUPPORT

Issues

Please report bugs and feature requests on GitHub at https://github.com/Getty/langertha-knarr/issues.

IRC

Join #langertha on irc.perl.org or message Getty directly.

CONTRIBUTING

Contributions are welcome! Please fork the repository and submit a pull request.

AUTHOR

Torsten Raudssus <torsten@raudssus.de> https://raudssus.de/

COPYRIGHT AND LICENSE

This software is copyright (c) 2026 by Torsten Raudssus.

This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.