byteclaw/internal/agent/agent_test.go

93 lines
3 KiB
Go

package agent
import (
"context"
"path/filepath"
"strings"
"testing"
"time"
"github.com/crypto/openclaw/internal/llm"
"github.com/crypto/openclaw/internal/store"
"github.com/crypto/openclaw/internal/tools"
)
// scripted provider returns queued responses and records requests.
type scripted struct {
resps []llm.Response
reqs []llm.Request
}
func (s *scripted) Chat(_ context.Context, req llm.Request, d llm.DeltaFunc) (llm.Response, error) {
s.reqs = append(s.reqs, req)
r := s.resps[0]
s.resps = s.resps[1:]
if d != nil && r.Content != "" {
d(r.Content)
}
return r, nil
}
func newStore(t *testing.T) *store.Store {
s, err := store.Open(filepath.Join(t.TempDir(), "a.db"))
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { s.Close() })
return s
}
func TestToolLoop(t *testing.T) {
st := newStore(t)
ss, _ := st.CreateSession(context.Background(), "")
p := &scripted{resps: []llm.Response{
{ToolCalls: []llm.ToolCall{{ID: "c1", Name: "time_now", Arguments: `{"timezone":"UTC"}`}}},
{Content: "It is noon."},
}}
fixed := time.Date(2026, 5, 5, 12, 0, 0, 0, time.UTC)
a := &Agent{LLM: p, Store: st, Tools: tools.NewRegistry(tools.TimeNow{Now: func() time.Time { return fixed }}), SystemPrompt: "sys", MaxIterations: 4}
var events []Event
out, err := a.Run(context.Background(), ss.ID, "what time?", func(e Event) { events = append(events, e) })
if err != nil || out != "It is noon." {
t.Fatalf("%q %v", out, err)
}
second := p.reqs[1].Messages
if second[0].Role != llm.RoleSystem || second[len(second)-1].Role != llm.RoleTool || !strings.HasPrefix(second[len(second)-1].Content, "2026-05-05T12:00:00") {
t.Fatalf("bad second request: %+v", second)
}
types := []string{}
for _, e := range events {
types = append(types, e.Type)
}
if strings.Join(types, ",") != "tool_call,tool_result,delta" {
t.Fatalf("events %v", types)
}
msgs, _ := st.Messages(context.Background(), ss.ID, 0)
if len(msgs) != 4 {
t.Fatalf("stored %d msgs", len(msgs))
}
}
func TestMaxIterations(t *testing.T) {
st := newStore(t)
ss, _ := st.CreateSession(context.Background(), "")
call := llm.Response{ToolCalls: []llm.ToolCall{{ID: "c", Name: "time_now", Arguments: "{}"}}}
a := &Agent{LLM: &scripted{resps: []llm.Response{call, call}}, Store: st, Tools: tools.NewRegistry(tools.TimeNow{}), MaxIterations: 2}
if _, err := a.Run(context.Background(), ss.ID, "loop", nil); err == nil {
t.Fatal("expected iteration error")
}
}
func TestHistoryDropsOrphanToolMessages(t *testing.T) {
st := newStore(t)
ctx := context.Background()
ss, _ := st.CreateSession(ctx, "")
st.AppendMessage(ctx, ss.ID, llm.Message{Role: llm.RoleAssistant, ToolCalls: []llm.ToolCall{{ID: "x", Name: "t"}}})
st.AppendMessage(ctx, ss.ID, llm.Message{Role: llm.RoleTool, ToolCallID: "x", Content: "r"})
st.AppendMessage(ctx, ss.ID, llm.Message{Role: llm.RoleAssistant, Content: "ok"})
a := &Agent{Store: st, HistoryLimit: 2}
h, _ := a.history(ctx, ss.ID)
if len(h) != 1 || h[0].Content != "ok" {
t.Fatalf("%+v", h)
}
}