NAME

Langertha - The clan of fierce vikings with 🪓 and 🛡️ to AId your rAId

VERSION

version 0.303

SYNOPSIS

my $system_prompt = 'You are a helpful assistant.';

# Local models via Ollama
use Langertha::Engine::Ollama;

my $ollama = Langertha::Engine::Ollama->new(
    url           => 'http://127.0.0.1:11434',
    model         => 'llama3.1',
    system_prompt => $system_prompt,
);
print $ollama->simple_chat('Do you wanna build a snowman?');

# OpenAI
use Langertha::Engine::OpenAI;

my $openai = Langertha::Engine::OpenAI->new(
    api_key       => $ENV{OPENAI_API_KEY},
    model         => 'gpt-4o-mini',
    system_prompt => $system_prompt,
);
print $openai->simple_chat('Do you wanna build a snowman?');

# Anthropic Claude
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 to represent GeoJSON data.');

# Google Gemini
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 the difference between Moose and Moo.');

DESCRIPTION

Langertha provides a unified Perl interface for interacting with various Large Language Model (LLM) APIs. It abstracts away provider-specific differences, giving you a consistent API whether you're using OpenAI, Anthropic Claude, Ollama, Groq, Mistral, or other providers.

THIS API IS WORK IN PROGRESS.

Key Features

  • 20 engines -- unified API across cloud and local LLM providers

  • Chat, streaming, embeddings, transcription, image generation

  • MCP tool calling -- automatic multi-round tool loops via Net::Async::MCP

  • Raider -- autonomous agent with history, compression, and plugins

  • Response metadata -- token usage, model, timing, rate limits

  • Async/await via Future::AsyncAwait, sync via LWP::UserAgent

  • Langfuse observability -- traces, generations, and tool spans

  • Dynamic model discovery -- query provider APIs with caching

  • Chain-of-thought -- native extraction and <think> tag filtering

  • Plugin system for extending Raider, Chat, Embedder, and ImageGen

Class Sugar

Langertha can set up your package as a Raider subclass or Plugin role:

# Build a custom Raider agent
package MyAgent;
use Langertha qw( Raider );
plugin 'Langfuse';

around plugin_before_llm_call => async sub {
    my ($orig, $self, $conversation, $iteration) = @_;
    $conversation = await $self->$orig($conversation, $iteration);
    # ... custom logic ...
    return $conversation;
};

__PACKAGE__->meta->make_immutable;

# Build a custom Plugin
package MyApp::Guardrails;
use Langertha qw( Plugin );

around plugin_before_tool_call => async sub {
    my ($orig, $self, $name, $input) = @_;
    my @result = await $self->$orig($name, $input);
    return unless @result;
    return if $name eq 'dangerous_tool';
    return @result;
};

use Langertha qw( Raider ) imports Moose and Future::AsyncAwait, sets Langertha::Raider as superclass, and provides the plugin function for applying plugins by short name.

use Langertha qw( Plugin ) imports Moose and Future::AsyncAwait, and sets Langertha::Plugin as superclass.

Engine Modules

Roles

Roles provide composable functionality to engines:

Wrapper Classes

These classes wrap an engine with optional overrides and plugin lifecycle hooks:

Plugins

Data Objects

Streaming

All engines that implement Langertha::Role::Chat support streaming. There are several ways to consume a stream:

Synchronous with callback:

$engine->simple_chat_stream(sub {
    my ($chunk) = @_;
    print $chunk->content;
}, 'Tell me a story');

Synchronous with iterator (Langertha::Stream):

my $stream = $engine->simple_chat_stream_iterator('Tell me a story');
while (my $chunk = $stream->next) {
    print $chunk->content;
}

Async with Future (traditional):

my $future = $engine->simple_chat_f('Hello');
my $response = $future->get;

my $future = $engine->simple_chat_stream_f('Tell me a story');
my ($content, $chunks) = $future->get;

Async with Future::AsyncAwait (recommended):

use Future::AsyncAwait;

async sub chat_with_ai {
    my ($engine) = @_;
    my $response = await $engine->simple_chat_f('Hello');
    say "AI says: $response";
    return $response;
}

async sub stream_chat {
    my ($engine) = @_;
    my ($content, $chunks) = await $engine->simple_chat_stream_realtime_f(
        sub { print shift->content },
        'Tell me a story',
    );
    say "\nReceived ", scalar(@$chunks), " chunks";
    return $content;
}

chat_with_ai($engine)->get;
stream_chat($engine)->get;

The _f methods use IO::Async and Net::Async::HTTP internally, loaded lazily only when you call them. See examples/async_await_example.pl for complete working examples.

Using with Mojolicious:

use Mojo::Base -strict;
use Future::Mojo;
use Langertha::Engine::OpenAI;

my $openai = Langertha::Engine::OpenAI->new(
    api_key => $ENV{OPENAI_API_KEY},
    model   => 'gpt-4o-mini',
);

my $future = $openai->simple_chat_stream_realtime_f(
    sub { print shift->content },
    'Hello!',
);
$future->on_done(sub {
    my ($content, $chunks) = @_;
    say "Done: $content";
});
Mojo::IOLoop->start;

Response Metadata

simple_chat returns Langertha::Response objects that stringify to text content (backward compatible) but carry full metadata:

my $r = $engine->simple_chat('Hello!');
print $r;                    # prints the text
say $r->model;               # actual model used
say $r->prompt_tokens;       # input tokens
say $r->completion_tokens;   # output tokens
say $r->total_tokens;        # total
say $r->finish_reason;       # stop, end_turn, tool_calls, ...
say $r->thinking;            # chain-of-thought (if available)

Rate Limiting

Rate limit information from HTTP response headers is extracted automatically into Langertha::RateLimit objects. Available per-response and on the engine:

if ($r->has_rate_limit) {
    say $r->requests_remaining;
    say $r->tokens_remaining;
    say $r->rate_limit->requests_reset;
}

# Engine always reflects the latest response
say $engine->rate_limit->requests_remaining
    if $engine->has_rate_limit;

Supported: OpenAI, Groq, Cerebras, OpenRouter, Replicate, HuggingFace (x-ratelimit-*) and Anthropic (anthropic-ratelimit-*).

MCP Tool Calling

Integrates with Net::Async::MCP for automatic multi-round tool calling:

my $engine = Langertha::Engine::OpenAI->new(
    api_key     => $ENV{OPENAI_API_KEY},
    mcp_servers => [$mcp],
);

my $response = await $engine->chat_with_tools_f('Search for Perl modules');

Works with all engines that support tool calling. See Langertha::Role::Tools.

Raider (Autonomous Agent)

Langertha::Raider is a stateful agent with conversation history, MCP tool calling, context compression, session history, and a plugin system:

my $raider = Langertha::Raider->new(
    engine  => $engine,
    mission => 'You are a code explorer.',
);

my $r1 = await $raider->raid_f('What files are in lib/?');
my $r2 = await $raider->raid_f('Read the main module.');

Langfuse Observability

Every engine has Langfuse observability built in. Set LANGFUSE_PUBLIC_KEY and LANGFUSE_SECRET_KEY env vars to enable auto-instrumented traces and generations. See Langertha::Role::Langfuse.

Extensions

The LangerthaX namespace is reserved for third-party extensions. See LangerthaX.

SUPPORT

Issues

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

CONTRIBUTING

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

AUTHOR

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

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.