Adam
view release on metacpan or search on metacpan
ex/ai-bot.pl view on Meta::CPAN
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;
}
event _retry_raid => sub {
my ($self) = $_[OBJECT];
$self->info("Retrying raid...");
$self->_do_raid;
};
event _alarm_fired => sub {
my ( $self, $channel, $reason ) = @_[ OBJECT, ARG0, ARG1 ];
$self->info("Alarm fired: $reason");
$self->_buffer_message($channel, 'system',
"ALARM FIRED: $reason â You set this alarm earlier. Decide what to do now.");
};
event _idle_check => sub {
my ($self) = $_[OBJECT];
my $idle_secs = time() - $self->_last_activity;
if ($idle_secs >= $IDLE_PING && !$self->_processing) {
my $idle_mins = int($idle_secs / 60);
$self->info("Idle ping after ${idle_mins}m");
# Ping first channel only (idle is a global concept)
my $channel = $self->_default_channel;
$self->_buffer_message($channel, 'system',
"No activity for $idle_mins minutes. You can say something if you want, or stay_silent.");
}
POE::Kernel->delay( _idle_check => $IDLE_PING );
};
( run in 1.837 second using v1.01-cache-2.11-cpan-5837b0d9d2c )