picoclaw/pkg/tools/agentic_map_test.go
ZanzyTHEbar 00802f78be feat(tools): add agentic_map and llm_map operators with FlatBuffers persistence
pkg/tools/agentic_map.go + agentic_map_test.go
- agentic_map: batch operator that dispatches each item to a subagent
  worker; supports configurable concurrency, retries, and timeout per item
- Items are processed in parallel up to MaxWorkers; failed items are
  retried up to MaxRetries with exponential backoff
- Results are collected into a structured MapResult with per-item status

pkg/tools/llm_map.go + llm_map_test.go
- llm_map: batch operator that applies a prompt template to each item
  using a direct LLM call (no subagent overhead)
- Supports Jinja-style {{item}} template substitution and structured
  JSON output extraction from LLM responses

pkg/tools/map_runtime.go + map_runtime_integration_test.go
- MapRuntime: manages the lifecycle of a map operator run (create, update,
  complete, fail); persists run state via memory delegate
- Tracks per-item status transitions and aggregates run-level metrics

pkg/tools/map_run_tools.go
- map_run_status: returns the current status and progress of a running
  map operation; enables the agent to monitor long-running batch jobs
- map_run_cancel: cancels an in-progress map run

pkg/tools/map_boundary.go
- MapBoundary: defines the input/output contract for map operators;
  validates item schemas and enforces output format requirements

pkg/tools/map_payloads.fbs
- FlatBuffers schema for MapRunSpec, MapItemInput, MapItemOutput;
  enables zero-copy serialization of map operator payloads

pkg/tools/mapopsfb/MapRunSpec.go
pkg/tools/mapopsfb/MapItemInput.go
pkg/tools/mapopsfb/MapItemOutput.go
- Generated FlatBuffers Go bindings for map operator payload types

pkg/tools/map_flatbuffer_codec.go + map_flatbuffer_codec_test.go
- MapFlatbufferCodec: bidirectional codec between Go map payload structs
  and FlatBuffers wire format; used by MapRuntime for persistence
2026-02-21 18:59:19 +00:00

113 lines
3.6 KiB
Go

package tools
import (
"context"
"fmt"
"testing"
jsonv2 "github.com/go-json-experiment/json"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/ZanzyTHEbar/dragonscale/pkg/bus"
)
func TestAgenticMapTool_Execute_Success(t *testing.T) {
provider := &MockLanguageModel{}
manager := NewSubagentManager(provider, "test-model", "/tmp/test", bus.NewMessageBus())
manager.SetRunLoop(func(_ context.Context, _ ToolLoopConfig, _, userPrompt, _, _ string) (*ToolLoopResult, error) {
return &ToolLoopResult{Content: "processed: " + userPrompt, Iterations: 1}, nil
})
tool := NewAgenticMapTool(manager)
result := tool.Execute(context.Background(), map[string]interface{}{
"items": []interface{}{
map[string]interface{}{"name": "a"},
map[string]interface{}{"name": "b"},
},
"task_template": "Handle item {{index}} => {{item_json}}",
"max_retries": float64(1),
})
require.NotNil(t, result)
require.False(t, result.IsError, result.ForLLM)
var payload struct {
Count int `json:"count"`
Summary struct {
SuccessCount int `json:"success_count"`
FailureCount int `json:"failure_count"`
} `json:"summary"`
}
require.NoError(t, jsonv2.Unmarshal([]byte(result.ForLLM), &payload))
assert.Equal(t, 2, payload.Count)
assert.Equal(t, 2, payload.Summary.SuccessCount)
assert.Equal(t, 0, payload.Summary.FailureCount)
}
func TestAgenticMapTool_Execute_RetriesFailedItems(t *testing.T) {
provider := &MockLanguageModel{}
manager := NewSubagentManager(provider, "test-model", "/tmp/test", bus.NewMessageBus())
callCount := 0
manager.SetRunLoop(func(_ context.Context, _ ToolLoopConfig, _, userPrompt, _, _ string) (*ToolLoopResult, error) {
callCount++
if callCount == 1 {
return nil, fmt.Errorf("transient failure for %s", userPrompt)
}
return &ToolLoopResult{Content: "processed on retry", Iterations: 1}, nil
})
tool := NewAgenticMapTool(manager)
result := tool.Execute(context.Background(), map[string]interface{}{
"items": []interface{}{map[string]interface{}{"name": "retry-me"}},
"task_template": "Retry item {{index}} => {{item_json}}",
"max_retries": float64(2),
})
require.NotNil(t, result)
require.False(t, result.IsError, result.ForLLM)
var payload struct {
Results []struct {
Success bool `json:"success"`
Attempts int `json:"attempts"`
} `json:"results"`
}
require.NoError(t, jsonv2.Unmarshal([]byte(result.ForLLM), &payload))
require.Len(t, payload.Results, 1)
assert.True(t, payload.Results[0].Success)
assert.Equal(t, 2, payload.Results[0].Attempts)
}
func TestAgenticMapTool_Execute_RequiresPlaceholders(t *testing.T) {
provider := &MockLanguageModel{}
manager := NewSubagentManager(provider, "test-model", "/tmp/test", bus.NewMessageBus())
tool := NewAgenticMapTool(manager)
result := tool.Execute(context.Background(), map[string]interface{}{
"items": []interface{}{map[string]interface{}{"name": "x"}},
"task_template": "Handle item without placeholders",
})
require.NotNil(t, result)
require.True(t, result.IsError)
assert.Contains(t, result.ForLLM, "placeholders")
}
func TestAgenticMapTool_Execute_ContextCancelled(t *testing.T) {
provider := &MockLanguageModel{}
manager := NewSubagentManager(provider, "test-model", "/tmp/test", bus.NewMessageBus())
tool := NewAgenticMapTool(manager)
ctx, cancel := context.WithCancel(context.Background())
cancel()
result := tool.Execute(ctx, map[string]interface{}{
"items": []interface{}{map[string]interface{}{"name": "x"}},
"task_template": "Handle {{index}} => {{item_json}}",
})
require.NotNil(t, result)
require.True(t, result.IsError)
assert.Contains(t, result.ForLLM, "cancelled")
}