Adam
view release on metacpan or search on metacpan
ex/ai-bot.pl view on Meta::CPAN
#!/usr/bin/env perl
# ABSTRACT: AI agent IRC bot with Langertha::Raider, MCP tools, and conversation memory
#
# Environment:
# ENGINE=Groq Engine class (default: Groq)
# MODEL=llama-3.3-70b-versatile Model name
# API_KEY=gsk_... API key (or LANGERTHA_<ENGINE>_API_KEY)
# IRC_SERVER=irc.perl.org IRC server (default: irc.perl.org)
# IRC_NICKNAME=Bert Bot nickname (default: random from a fun list)
# OWNER=Getty Bot owner name for personality (default: $USER)
# IRC_CHANNELS=#ai Channels to join
# DB_FILE=ai-bot.db SQLite database path
# MAX_LINE_LENGTH=400 Max IRC line length (default: 400)
# BUFFER_DELAY=1.5 Seconds to buffer messages before processing (default: 1.5)
# LINE_DELAY=1.5 Delay between outgoing IRC lines (default: 1.5)
# IDLE_PING=1800 Seconds of silence before idle ping (default: 1800)
# SYSTEM_PROMPT=... Additional text appended to the system prompt
use strict;
use warnings;
my @BOT_NAMES = qw(
Botsworth Clanky Sparky Fizz Gizmo Pixel Blip Rusty Ziggy Turbo
Sprocket Widget Noodle Bleep Chomp Dingle Wobble Clunk Zippy Quirk
);
my $BOT_NICK = $ENV{IRC_NICKNAME} || $BOT_NAMES[rand @BOT_NAMES] . int(rand(999));
my $OWNER = $ENV{OWNER} || $ENV{USER} || 'unknown';
my $MAX_LINE = $ENV{MAX_LINE_LENGTH} || 400;
my $BUFFER_DELAY = $ENV{BUFFER_DELAY} || 1.5;
my $LINE_DELAY = $ENV{LINE_DELAY} || 3;
my $IDLE_PING = $ENV{IDLE_PING} || 1800;
# --- Conversation memory (SQLite) ---
package MemoryStore {
use Moose;
use DBI;
has db_file => ( is => 'ro', default => sub { $ENV{DB_FILE} || 'ai-bot.db' } );
has _dbh => ( is => 'ro', lazy => 1, builder => '_build_dbh' );
sub _build_dbh {
my ($self) = @_;
my $dbh = DBI->connect('dbi:SQLite:dbname=' . $self->db_file, '', '', {
RaiseError => 1, sqlite_unicode => 1,
});
$dbh->do('CREATE TABLE IF NOT EXISTS conversations (
id INTEGER PRIMARY KEY, nick TEXT, message TEXT, response TEXT,
channel TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP
)');
$dbh->do('CREATE TABLE IF NOT EXISTS notes (
id INTEGER PRIMARY KEY, nick TEXT, content TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
)');
return $dbh;
}
sub store_conversation {
my ($self, %a) = @_;
$self->_dbh->do(
'INSERT INTO conversations (nick, message, response, channel) VALUES (?,?,?,?)',
undef, @a{qw(nick message response channel)},
);
}
sub recall {
my ($self, $query, $limit) = @_;
$limit //= 5;
my $rows = $self->_dbh->selectall_arrayref(
'SELECT nick, message, response FROM conversations WHERE message LIKE ? OR response LIKE ? ORDER BY id DESC LIMIT ?',
{ Slice => {} }, "%$query%", "%$query%", $limit,
);
return join("\n---\n", map { "<$_->{nick}> $_->{message}\n$_->{response}" } @$rows);
}
sub save_note {
my ($self, $nick, $content) = @_;
$self->_dbh->do('INSERT INTO notes (nick, content) VALUES (?,?)', undef, $nick, $content);
}
sub recall_notes {
my ($self, $nick, $query, $limit) = @_;
$limit //= 10;
my $rows;
if ($nick) {
$rows = $self->_dbh->selectall_arrayref(
'SELECT id, nick, content FROM notes WHERE nick = ? AND content LIKE ? ORDER BY id DESC LIMIT ?',
{ Slice => {} }, $nick, "%$query%", $limit,
);
} else {
$rows = $self->_dbh->selectall_arrayref(
'SELECT id, nick, content FROM notes WHERE content LIKE ? ORDER BY id DESC LIMIT ?',
{ Slice => {} }, "%$query%", $limit,
);
}
return join("\n", map { "#$_->{id} [$_->{nick}] $_->{content}" } @$rows);
}
sub update_note {
my ($self, $id, $content) = @_;
my $rows = $self->_dbh->do('UPDATE notes SET content = ? WHERE id = ?', undef, $content, $id);
return $rows > 0;
}
sub delete_note {
( run in 0.424 second using v1.01-cache-2.11-cpan-cdf2f3d4e48 )