tool format hardening

This commit is contained in:
Luxferre
2026-08-09 15:19:36 +03:00
parent 88dd9dfef5
commit accd1fc75c
4 changed files with 92 additions and 21 deletions
+11 -18
View File
@@ -4,13 +4,9 @@
use strict; use warnings; use HTTP::Tiny; use JSON::PP; use POSIX qw(strftime); use File::Path qw(make_path);
$SIG{__WARN__} = sub {
my $m = shift;
return if $m =~ /^Use of uninitialized value \$err in numeric eq \(==\) at .*IO\/Socket\/IP\.pm line \d+/;
warn $m;
};
$SIG{__WARN__} = sub { warn $_[0] unless $_[0] =~ /^Use of uninitialized value \$err in numeric eq \(==\) at .*IO\/Socket\/IP\.pm line \d+/ };
binmode(STDOUT, ':encoding(UTF-8)');binmode(STDERR, ':encoding(UTF-8)');binmode(STDIN, ':encoding(UTF-8)');
binmode(STDOUT, ':encoding(UTF-8)'); binmode(STDERR, ':encoding(UTF-8)'); binmode(STDIN, ':encoding(UTF-8)');
$| = 1; # unbuffered output: live spinner, responsive prompt
@@ -29,7 +25,10 @@ sub sp { my $p = '';
sub T { my ($n, $d, $p) = @_; {type=>'function', function=>{name=>$n, description=>$d, parameters=>{type=>'object', properties=>$p, required=>[keys %$p]}}} }
sub sanitize_msgs { for my $m (@{$_[0]}) { if (ref $m eq 'HASH' && $m->{tool_calls}) { for my $tc (@{$m->{tool_calls}}) { my $a = eval { decode_json($tc->{function}{arguments} // '{}') }; $tc->{function}{arguments} = encode_json({invalid_raw => $tc->{function}{arguments} // ''}) if ref $a ne 'HASH'; } } } }
sub llm { my ($c, $msgs) = @_; # one non-streaming chat completion
sanitize_msgs($msgs);
my $ep = $c->{endpoint}; $ep =~ s{/+$}{};
my $h = {'Content-Type'=>'application/json', 'User-Agent'=>'Mozilla/5.0 (compatible; MicroBantam/1.0)'};
$h->{Authorization} = "Bearer $c->{api_key}" if $c->{api_key} ne '-';
@@ -66,11 +65,9 @@ sub AL { my ($c, $msgs, $sp, $depth) = @_; # the agentic loop: LLM <-> tools unt
my $fn = $tc->{function}{name};
my $a = eval { decode_json($tc->{function}{arguments} // '{}') };
my $res;
if (ref $a ne 'HASH') { $res = "bad JSON args for $fn: $tc->{function}{arguments}"; }
if (ref $a ne 'HASH') { $tc->{function}{arguments} = encode_json({invalid_raw => $tc->{function}{arguments} // ''}); $res = "bad JSON args for $fn: " . ($tc->{function}{arguments} // ''); }
elsif ($fn eq 'shell_exec') { $res = shell_exec($a->{command} // '', $c->{shell_timeout}); }
elsif ($fn eq 'run_subagent') { $res = $depth >= 5 ? '[subagent depth limit (5) reached, child not spawned]'
: last_assistant(AL($c, [{role=>'system', content=>"$sp\n\nImportant: this is a child agent"},
{role=>'user', content=>$a->{prompt} // ''}], $sp, $depth + 1)); }
elsif ($fn eq 'run_subagent') { $res = $depth >= 5 ? '[subagent depth limit (5) reached, child not spawned]' : last_assistant(AL($c, [{role=>'system', content=>"$sp\n\nImportant: this is a child agent"}, {role=>'user', content=>$a->{prompt} // ''}], $sp, $depth + 1)); }
else { $res = "unknown tool: $fn"; }
print "[tool] $fn: $res\n";
push @$msgs, {role=>'tool', tool_call_id=>$tc->{id}, content=>$res};
@@ -86,8 +83,7 @@ sub save { sdir(); my $id = strftime('%Y%m%d-%H%M%S', localtime); my $i = 0;
$id .= '-' . ++$i while -f "$SDIR/$id.json";
open my $f, '>', "$SDIR/$id.json" or die "cannot save: $!";
print $f JSON::PP->new->utf8->pretty->encode({id=>$id, messages=>$_[0]}); close $f; $id; }
sub load { my ($want) = @_; my ($hit) = grep { $_->{id} eq $want } sessions();
die "no session: $want\n" unless $hit; $hit->{messages}; }
sub load { my ($want) = @_; my ($hit) = grep { $_->{id} eq $want } sessions(); die "no session: $want\n" unless $hit; $hit->{messages}; }
sub autosave { sdir(); open my $f, '>', "$SDIR/autosave.json" or return; print $f JSON::PP->new->utf8->pretty->encode({id=>'autosave', messages=>$_[0]}); }
sub list_sessions { map { [$_->{id}, scalar @{$_->{messages} // []}] } sessions() }
@@ -95,14 +91,11 @@ sub main {
my ($c, $sp) = (cfg(), sp());
my $msgs = [{role=>'system', content=>$sp}];
if (@ARGV) { open my $f, '<:encoding(UTF-8)', $ARGV[0] or die "cannot open $ARGV[0]: $!"; # file mode
local $/; push @$msgs, {role=>'user', content=><$f>};
AL($c, $msgs, $sp); autosave($msgs); return; }
local $/; push @$msgs, {role=>'user', content=><$f>}; AL($c, $msgs, $sp); autosave($msgs); return; }
print "MicroBantam ready ($c->{model}). Commands: /quit /clear /save /list /load <id> /help\n";
while (1) {
print "> "; my $u = <STDIN>;
last unless defined $u;
$u =~ s/^\s+|\s+$//g;
next unless length $u;
print "> "; my $u = <STDIN>; last unless defined $u;
$u =~ s/^\s+|\s+$//g; next unless length $u;
if ($u eq '/quit') { last; }
elsif ($u eq '/clear') { $msgs = [{role=>'system', content=>$sp}]; autosave($msgs); }
elsif ($u eq '/save') { print "session saved: ", save($msgs), "\n"; }