This repository has been archived on 2026-08-16. You can view files and clone it, but cannot push or open issues or pull requests.
ollie-core/pkg/agent/loop_texttool_test.go

126 lines
4.0 KiB
Go

package agent
import (
"context"
"encoding/json"
"testing"
"ollie/pkg/backend"
)
// TestTextToolCall_ParsedAndExecuted verifies that when a model emits a tool
// call as plain text (no API-level tool calls) the loop parses it and executes
// it as a normal tool call.
func TestTextToolCall_ParsedAndExecuted(t *testing.T) {
be := defaultBE()
be.respond = func(_ context.Context, msgs []backend.Message, _ []backend.Tool, _ backend.GenerationParams) (<-chan backend.StreamEvent, error) {
// First call: model emits text tool call
for _, m := range msgs {
if m.Role == "tool" {
// Second call after tool result: respond normally
return textStream("done"), nil
}
}
return textStream(`call_tool: {"calls":[{"tool":"file_read","args":["/tmp/test.go"]}]}`), nil
}
var execCalled bool
c := newCore(t, be, nil)
c.runtime.Tools = []backend.Tool{
{Name: "call_tool", Description: "run tools"},
{Name: "execute_code", Description: "run code"},
}
c.runtime.Exec = func(_ context.Context, name string, args json.RawMessage) (string, error) {
execCalled = true
if name != "call_tool" {
t.Errorf("expected tool name 'call_tool', got %q", name)
}
return "file contents here", nil
}
evs := collectEvents(context.Background(), c, "read a file")
if !execCalled {
t.Fatal("expected Exec to be called for text-based tool call")
}
errEvs := byRole(evs, "error")
if len(errEvs) != 0 {
t.Errorf("unexpected error events: %v", errEvs)
}
}
// TestTextToolCall_RelaxedJSON verifies parsing of relaxed JSON (unquoted keys).
func TestTextToolCall_RelaxedJSON(t *testing.T) {
be := defaultBE()
be.respond = func(_ context.Context, msgs []backend.Message, _ []backend.Tool, _ backend.GenerationParams) (<-chan backend.StreamEvent, error) {
for _, m := range msgs {
if m.Role == "tool" {
return textStream("done"), nil
}
}
return textStream(`execute_code: steps=[{code: "date"}]`), nil
}
var execCalled bool
c := newCore(t, be, nil)
c.runtime.Tools = []backend.Tool{
{Name: "execute_code", Description: "run code"},
{Name: "call_tool", Description: "run tools"},
}
c.runtime.Exec = func(_ context.Context, name string, args json.RawMessage) (string, error) {
execCalled = true
if name != "execute_code" {
t.Errorf("expected tool name 'execute_code', got %q", name)
}
return "Sat May 23 11:00:00 UTC 2026", nil
}
collectEvents(context.Background(), c, "what time is it")
if !execCalled {
t.Fatal("expected Exec to be called for relaxed-JSON text tool call")
}
}
// TestTextToolCall_NoFalsePositive verifies that a normal text response that
// does not contain a tool-call pattern is not mistakenly parsed.
func TestTextToolCall_NoFalsePositive(t *testing.T) {
be := defaultBE()
be.respond = func(_ context.Context, _ []backend.Message, _ []backend.Tool, _ backend.GenerationParams) (<-chan backend.StreamEvent, error) {
return textStream("Here is how execute_code works in general."), nil
}
c := newCore(t, be, nil)
c.runtime.Tools = []backend.Tool{{Name: "execute_code", Description: "run code"}}
evs := collectEvents(context.Background(), c, "explain tools")
errEvs := byRole(evs, "error")
if len(errEvs) != 0 {
t.Errorf("unexpected error events: %v", errEvs)
}
assistEvs := byRole(evs, "assistant")
if len(assistEvs) == 0 {
t.Error("expected assistant response; got none")
}
}
// TestTextToolCall_NoToolsConfigured verifies that when no tools are configured
// the detection does not fire (nothing to detect against).
func TestTextToolCall_NoToolsConfigured(t *testing.T) {
be := defaultBE()
be.respond = func(_ context.Context, _ []backend.Message, _ []backend.Tool, _ backend.GenerationParams) (<-chan backend.StreamEvent, error) {
return textStream(`execute_code: steps=[{code: "print('hi')"}]`), nil
}
c := newCore(t, be, nil)
// No tools configured.
evs := collectEvents(context.Background(), c, "run something")
errEvs := byRole(evs, "error")
if len(errEvs) != 0 {
t.Errorf("unexpected error with no tools configured: %v", errEvs)
}
}