__ __ __
| .---.-.-----.-----.-----.----| |_| |--.---.-.
| | _ | | _ | -__| _| _| | _ |
|__|___._|__|__|___ |_____|__| |____|__|__|___._|
---------------|_____|----------------------------
The clan of fierce vikings with axes and shields to AId your rAId
Langertha is a unified Perl interface for LLM APIs. One API, many providers. Supports chat, streaming, embeddings, transcription, MCP tool calling, autonomous agents, observability, and dynamic model discovery.
Supported Providers
| Provider | Chat | Streaming | Tools (MCP) | Embeddings | Transcription | Models API | |----------|:----:|:---------:|:-----------:|:----------:|:-------------:|:----------:| | OpenAI :us: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | | Anthropic :us: | :white_check_mark: | :white_check_mark: | :white_check_mark: | | | :white_check_mark: | | Gemini :us: | :white_check_mark: | :white_check_mark: | :white_check_mark: | | | :white_check_mark: | | Ollama | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | | :white_check_mark: | | Groq :us: | :white_check_mark: | :white_check_mark: | :white_check_mark: | | :white_check_mark: | :white_check_mark: | | Mistral :eu: | :white_check_mark: | :white_check_mark: | :white_check_mark: | | | :white_check_mark: | | DeepSeek :cn: | :white_check_mark: | :white_check_mark: | :white_check_mark: | | | :white_check_mark: | | Perplexity :us: | :white_check_mark: | :white_check_mark: | | | | | | Nous Research :us: | :white_check_mark: | :white_check_mark: | :white_check_mark: | | | :white_check_mark: | | vLLM | :white_check_mark: | :white_check_mark: | :white_check_mark: | | | | | AKI.IO :eu: | :white_check_mark: | :white_check_mark: | :white_check_mark: | | | :white_check_mark: | | Whisper | | | | | :white_check_mark: | |
Quick Start
cpanm Langertha
use Langertha::Engine::OpenAI;
my $openai = Langertha::Engine::OpenAI->new(
api_key => $ENV{OPENAI_API_KEY},
model => 'gpt-4o-mini',
);
print $openai->simple_chat('Hello from Perl!');
Usage Examples
Cloud APIs
use Langertha::Engine::Anthropic;
my $claude = Langertha::Engine::Anthropic->new(
api_key => $ENV{ANTHROPIC_API_KEY},
model => 'claude-sonnet-4-6',
);
print $claude->simple_chat('Generate Perl Moose classes for GeoJSON.');
use Langertha::Engine::Gemini;
my $gemini = Langertha::Engine::Gemini->new(
api_key => $ENV{GEMINI_API_KEY},
model => 'gemini-2.5-flash',
);
print $gemini->simple_chat('Explain quantum computing.');
Local Models with Ollama
use Langertha::Engine::Ollama;
my $ollama = Langertha::Engine::Ollama->new(
url => 'http://localhost:11434',
model => 'llama3.3',
);
print $ollama->simple_chat('Do you wanna build a snowman?');
AKI.IO :eu: European AI Infrastructure
AKI.IO is a European AI model hub based in Germany. All inference runs on EU-based infrastructure, making it a strong choice for GDPR-compliant and data-sovereignty-sensitive applications. No data leaves the EU.
use Langertha::Engine::AKI;
my $aki = Langertha::Engine::AKI->new(
api_key => $ENV{AKI_API_KEY},
model => 'llama3_8b_chat',
);
print $aki->simple_chat('Hello!');
# Or use the OpenAI-compatible API for streaming & tool calling
my $aki_openai = $aki->openai;
Self-hosted with vLLM
use Langertha::Engine::vLLM;
my $vllm = Langertha::Engine::vLLM->new(
url => $ENV{VLLM_URL},
model => 'meta-llama/Llama-3.3-70B-Instruct',
);
print $vllm->simple_chat('Hello!');
Streaming
Real-time token streaming with callbacks, iterators, or async/await:
# Callback
$engine->simple_chat_stream(sub {
print shift->content;
}, 'Write a poem about Perl');
# Iterator
my $iter = $engine->simple_chat_stream_iterator('Tell me a story');
while (my $chunk = $iter->next) {
print $chunk->content;
}
# Async/await with real-time streaming
use Future::AsyncAwait;
my ($content, $chunks) = await $engine->simple_chat_stream_realtime_f(
sub { print shift->content },
'Explain monads'
);
MCP Tool Calling
Langertha integrates with MCP (Model Context Protocol) servers via Net::Async::MCP. LLMs can discover and invoke tools automatically.
use IO::Async::Loop;
use Net::Async::MCP;
use Future::AsyncAwait;
my $loop = IO::Async::Loop->new;
# Connect to an MCP server (in-process, stdio, or HTTP)
my $mcp = Net::Async::MCP->new(
command => ['npx', '@anthropic/mcp-server-web-search'],
);
$loop->add($mcp);
await $mcp->initialize;
# Any engine, same API
my $engine = Langertha::Engine::Anthropic->new(
api_key => $ENV{ANTHROPIC_API_KEY},
model => 'claude-sonnet-4-6',
mcp_servers => [$mcp],
);
my $response = await $engine->chat_with_tools_f(
'Search the web for Perl MCP modules'
);
say $response;
The tool-calling loop runs automatically:
- Gathers available tools from all configured MCP servers
- Sends chat request with tool definitions to the LLM
- If the LLM returns tool calls, executes them via MCP
- Feeds tool results back to the LLM and repeats
- Returns the final text response
Works with all engines that support tool calling (see table above).
Hermes-Native Tool Calling
For models that support the Hermes tool calling format (via <tool_call> XML tags) but lack API-level tool support, enable hermes_tools:
# NousResearch has hermes_tools enabled by default
my $nous = Langertha::Engine::NousResearch->new(
api_key => $ENV{NOUSRESEARCH_API_KEY},
mcp_servers => [$mcp],
);
# Any OpenAI-compatible API or Ollama model can opt in
my $ollama = Langertha::Engine::Ollama->new(
url => 'http://localhost:11434',
model => 'hermes3',
hermes_tools => 1,
mcp_servers => [$mcp],
);
Tools are injected into the system prompt and <tool_call> tags are parsed from the model's text output. The tool prompt template is customizable via hermes_tool_prompt.
Response Metadata
simple_chat returns Langertha::Response objects with full metadata — token usage, model, finish reason, timing. Backward-compatible: stringifies to the text content, so existing code works unchanged.
my $r = $engine->simple_chat('Hello!');
print $r; # prints the text (stringification)
say $r->model; # gpt-4o-mini
say $r->prompt_tokens; # 12
say $r->completion_tokens; # 8
say $r->total_tokens; # 20
say $r->finish_reason; # stop
Works across all engines. Each provider's token counts and metadata are normalized automatically.
Raider — Autonomous Agent
Langertha::Raider is a stateful agent with conversation history and MCP tool calling. It maintains context across multiple interactions ("raids").
use Langertha::Raider;
my $raider = Langertha::Raider->new(
engine => $engine, # any engine with mcp_servers
mission => 'You are a code explorer.',
);
# First raid — tools are called automatically, history is saved
my $r1 = await $raider->raid_f('What files are in lib/?');
say $r1;
# Second raid — has context from the first conversation
my $r2 = await $raider->raid_f('Read the main module.');
say $r2;
# Metrics across all raids
say $raider->metrics->{tool_calls}; # cumulative
$raider->clear_history; # start fresh
Key features: persistent history, mission (system prompt), cumulative metrics (raids, iterations, tool_calls, time_ms), Hermes tool calling support. Langfuse observability is automatic when enabled on the engine.
Observability with Langfuse
Every engine has Langfuse observability built in. Just set env vars — zero code changes:
export LANGFUSE_PUBLIC_KEY=pk-lf-...
export LANGFUSE_SECRET_KEY=sk-lf-...
export LANGFUSE_URL=http://localhost:3000 # optional, defaults to cloud
my $engine = Langertha::Engine::OpenAI->new(
api_key => $ENV{OPENAI_API_KEY},
);
$engine->simple_chat('Hello!'); # auto-traced
$engine->langfuse_flush; # send events to Langfuse
simple_chat calls are auto-instrumented with traces and generations (including token usage and timing). Raider raids are also traced with per-iteration generation events.
Disabled by default — active only when both keys are set. A Kubernetes manifest for self-hosted Langfuse is included: kubectl apply -f ex/langfuse-k8s.yaml
Async/Await
All operations have async variants via Future::AsyncAwait:
use Future::AsyncAwait;
async sub main {
my $response = await $engine->simple_chat_f('Hello!');
say $response;
}
main()->get;
Embeddings
use Langertha::Engine::OpenAI;
my $openai = Langertha::Engine::OpenAI->new(
api_key => $ENV{OPENAI_API_KEY},
);
my $embedding = $openai->simple_embedding('Some text to embed');
# Returns arrayref of floats
Also supported by Ollama (e.g. mxbai-embed-large).
Transcription (Whisper)
use Langertha::Engine::Whisper;
my $whisper = Langertha::Engine::Whisper->new(
url => $ENV{WHISPER_URL},
);
print $whisper->simple_transcription('recording.ogg');
OpenAI and Groq also support transcription via their Whisper endpoints:
my $openai = Langertha::Engine::OpenAI->new(
api_key => $ENV{OPENAI_API_KEY},
);
print $openai->simple_transcription('recording.ogg');
Dynamic Model Discovery
Query available models from any provider API:
my $models = $engine->list_models;
# Returns: ['gpt-4o', 'gpt-4o-mini', 'o1', ...]
my $models = $engine->list_models(full => 1); # Full metadata
my $models = $engine->list_models(force_refresh => 1); # Bypass cache
Results are cached for 1 hour (configurable via models_cache_ttl).
Testing
# Run all unit tests
prove -l t/
# Run mock tool calling tests (no API keys needed)
prove -l -It/lib t/64_tool_calling_ollama_mock.t
prove -l -It/lib t/66_tool_calling_hermes.t
# Run live integration tests
TEST_LANGERTHA_OPENAI_API_KEY=... \
TEST_LANGERTHA_ANTHROPIC_API_KEY=... \
TEST_LANGERTHA_GEMINI_API_KEY=... \
prove -l t/80_live_tool_calling.t
# Ollama with multiple models
TEST_LANGERTHA_OLLAMA_URL=http://localhost:11434 \
TEST_LANGERTHA_OLLAMA_MODELS=qwen3:8b,llama3.2:3b \
prove -l t/80_live_tool_calling.t
# NousResearch (Hermes-native tool calling via <tool_call> tags)
TEST_LANGERTHA_NOUSRESEARCH_API_KEY=... \
prove -l t/80_live_tool_calling.t
# vLLM (requires --enable-auto-tool-choice and --tool-call-parser on server)
TEST_LANGERTHA_VLLM_URL=http://localhost:8000/v1 \
TEST_LANGERTHA_VLLM_MODEL=Qwen/Qwen2.5-3B-Instruct \
TEST_LANGERTHA_VLLM_TOOL_CALL_PARSER=hermes \
prove -l t/80_live_tool_calling.t
Examples
See the ex/ directory for runnable examples:
| Example | Description |
|---------|-------------|
| synopsis.pl | Basic usage with multiple engines |
| response.pl | Response metadata (tokens, model, timing) |
| streaming_callback.pl | Real-time streaming with callbacks |
| streaming_iterator.pl | Streaming with iterator pattern |
| streaming_future.pl | Async streaming with Futures |
| async_await.pl | Async/await patterns |
| mcp_inprocess.pl | MCP tool calling with in-process server |
| mcp_stdio.pl | MCP tool calling with stdio server |
| hermes_tools.pl | Hermes-native tool calling with NousResearch |
| raider.pl | Autonomous agent with MCP tools and history |
| langfuse.pl | Langfuse observability tracing |
| langfuse-k8s.yaml | Kubernetes manifest for self-hosted Langfuse |
| embedding.pl | Text embeddings |
| transcription.pl | Audio transcription with Whisper |
| structured_output.pl | Structured/JSON output |
Community
- CPAN: Langertha on MetaCPAN
- GitHub: Getty/langertha - Issues & PRs welcome
- Discord: Join the community
- IRC:
irc://irc.perl.org/ai
License
This is free software licensed under the same terms as Perl itself (Artistic License / GPL).
THIS API IS WORK IN PROGRESS