NAME
Langertha::Skeid - Dynamic routing control-plane for multi-node LLM serving with normalized metrics and cost accounting
VERSION
version 0.001
SYNOPSIS
use Langertha::Skeid;
my $skeid = Langertha::Skeid->new(
config_file => '/etc/skeid/config.yaml',
);
my $cost = $skeid->call_function('metrics.estimate_cost', {
model => 'gpt-4o-mini',
usage => { prompt_tokens => 1000, completion_tokens => 200 },
});
DESCRIPTION
Langertha::Skeid is a routing control-plane for provider-style LLM operations. It keeps a live node table, routes by model/health/capacity, and records normalized token/cost usage.
Skeid is commonly used as one API edge in front of many upstream APIs (cloud + local). With pricing and usage.record/report, you can build tenant billing from one consistent ledger.
Multi-API Billing Flow
1. Define multiple nodes in config (for example OpenAI-compatible cloud APIs and local vLLM/SGLang). 2. Set model pricing via pricing or pricing.set. 3. Forward tenant identity via x-skeid-key-id (or x-api-key-id). 4. Read totals by key/model/time with usage.report.
Engine IDs
nodes[].engine uses lowercased engine class names from Langertha. Examples: OpenAI => openai, OpenAIBase => openaibase, vLLM => vllm. Legacy aliases like openai-compatible are intentionally rejected.
Pluggable Usage Storage
The usage storage layer is pluggable. Built-in backends are jsonlog (recommended, no DBI required), sqlite, and postgresql. You can also replace the storage layer entirely via constructor callbacks or subclass override.
jsonlog backend (recommended — no DBI dependency):
# Directory mode: one JSON file per event (no collision risk)
my $skeid = Langertha::Skeid->new(
usage_store => { backend => 'jsonlog', path => '/var/log/skeid/events/' },
);
# File mode: JSON-lines appended to a single file
my $skeid = Langertha::Skeid->new(
usage_store => { backend => 'jsonlog', path => '/var/log/skeid/usage.jsonl', mode => 'file' },
);
Directory mode is auto-detected when the path is an existing directory or ends with /. It writes one .json file per event, which avoids file-level locking and concurrent-write collisions entirely.
Constructor callbacks (custom backend, no subclassing):
my $skeid = Langertha::Skeid->new(
store_usage_event => sub {
my ($self, $event) = @_;
# $event is a hashref with all 22 normalized columns
publish_to_nats($event);
return { ok => 1 };
},
query_usage_report => sub {
my ($self, $filters) = @_;
# $filters has: since, api_key_id, model, limit
return { ok => 1, enabled => 1, totals => { ... } };
},
);
Option 2 – Subclass override:
package MyApp::Skeid;
use Moo;
extends 'Langertha::Skeid';
sub _store_usage_event {
my ($self, $event) = @_;
...
return { ok => 1 };
}
sub _query_usage_report {
my ($self, $filters) = @_;
...
}
When a callback or override is provided, the DBI default is bypassed entirely and no database connection is created. DBI and DBD::SQLite are recommends dependencies — they are not required when usage is handled externally.
Admin API Key
admin.api_key (or admin_api_key) controls access to proxy admin routes. If empty, admin routes are effectively disabled by returning 404. If set, the proxy expects Authorization: Bearer .... This value can be changed through dynamic config reload.
SUPPORT
Issues
Please report bugs and feature requests on GitHub at https://github.com/Getty/langertha-skeid/issues.
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.