Adam
view release on metacpan or search on metacpan
ex/ai-bot.pl view on Meta::CPAN
sub _schedule_pending_buffers {
my ($self) = @_;
for my $ch (keys %{$self->_msg_buffer}) {
next unless @{$self->_msg_buffer->{$ch} || []};
next if $self->_buffer_timers->{$ch}; # already scheduled
my $id = POE::Kernel->alarm_set( _process_buffer => time() + $BUFFER_DELAY, $ch );
$self->_buffer_timers->{$ch} = $id;
}
}
my @BRAINFREEZE = (
'*brainfreeze*',
'*buffering...*',
'*hamster needs a breather*',
'*neurons recharging*',
'*getty forgot to pay the electricity bill again*',
'*thinking intensifies... slowly*',
'*basement WiFi acting up*',
);
sub _do_raid {
my ($self) = @_;
my $pending = $self->_pending_raid;
return unless $pending;
my $input = $pending->{input};
my $channel = $pending->{channel};
my $messages = $pending->{messages};
my $answer = eval {
my $result = $self->_raider->raid($input);
"$result";
};
if ($@ && $@ =~ /429|rate.limit/i) {
my $total_wait = $self->_rate_limit_wait;
my $err_channel = $self->_default_channel;
if ($total_wait == 0) {
# First hit â show brainfreeze (only in main channel)
my $msg = $BRAINFREEZE[rand @BRAINFREEZE];
$self->_send_to_channel($err_channel, $msg);
}
my $wait = $total_wait < 70 ? (70 - $total_wait) : 60;
$self->_rate_limit_wait($total_wait + $wait);
$self->info("Rate limited, total wait: " . $self->_rate_limit_wait . "s, next retry in ${wait}s");
# Show another message every ~3 minutes of waiting
if ($total_wait > 0 && int($total_wait / 180) != int($self->_rate_limit_wait / 180)) {
my $msg = $BRAINFREEZE[rand @BRAINFREEZE];
$self->_send_to_channel($err_channel, $msg);
}
POE::Kernel->delay( _retry_raid => $wait );
return;
}
# Reset rate limit state
$self->_rate_limit_wait(0);
$self->_pending_raid(undef);
if ($@) {
$self->error("Raider error: $@");
# Show error only in main channel
$self->_send_to_channel($self->_default_channel,
"Something broke in my brain. Getty probably forgot to feed the hamster that powers my GPU.");
$self->_processing(0);
$self->_schedule_pending_buffers;
return;
}
# Log rate limit info
eval {
my $engine = $self->_raider->active_engine;
if ($engine->has_rate_limit) {
my $rl = $engine->rate_limit;
$self->info(sprintf "Rate limit: %s requests remaining, %s tokens remaining",
$rl->requests_remaining // '?', $rl->tokens_remaining // '?');
}
};
$self->_processing(0);
# Check for silence
if ($answer =~ /__SILENT__/) {
$self->info("Bert chose to stay silent");
$self->_schedule_pending_buffers;
return;
}
# Clean up AI output
$answer =~ s/^<\s*\@?\s*(\w+)\s*>:?\s*/$1: /mg; # line start <@nick> â Nick:
$answer =~ s/<\s*\@?\s*(\w+)\s*>/$1/g; # mid-text <nick> â Nick
$answer =~ s/<\/?\w+>//g; # strip remaining XML tags
# Strip lines where the AI narrates its tool usage
$answer =~ s/^\*?\s*(save_note|recall_notes|update_note|delete_note|recall_history|stay_silent|set_alarm|whois|send_private_message)\b[^\n]*\n?//mg;
# Check for lines too long
my @lines = grep { length } map { s/^\s+//r =~ s/\s+$//r } split(/\n/, $answer);
my $too_long = grep { length($_) > $MAX_LINE } @lines;
if ($too_long) {
$self->info("Response too long, asking to shorten");
$answer = eval {
my $retry = $self->_raider->raid(
"Your last response had lines over $MAX_LINE characters. "
. "Rewrite it shorter. Every line must be under $MAX_LINE chars."
);
"$retry";
} || $answer;
}
# Store conversations
for my $m (@$messages) {
$self->memory->store_conversation(
nick => $m->{nick}, message => $m->{msg},
response => $answer, channel => $m->{channel},
);
}
$self->_send_to_channel($channel, $answer);
# Process any messages that arrived while we were thinking
$self->_schedule_pending_buffers;
}
( run in 0.510 second using v1.01-cache-2.11-cpan-2398b32b56e )