Files
steve 76ecf0e49e feat: skills — additive instruction+tool bundles, clock + calc examples
Phase 6: skill.New constructor satisfying the agent.Skill contract;
instruction-only skills; ordered additive composition; skill/clock
(injectable-clock time tools) and skill/calc (recursive-descent arithmetic
evaluator) as ready-made examples with full test suites incl. an
agent-loop round trip. ADR-0013; README skills section + matrix synced.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 13:13:07 +02:00

79 lines
1.9 KiB
Go

package calc
import (
"context"
"encoding/json"
"strings"
"testing"
"gitea.stevedudenhoeffer.com/steve/majordomo/agent"
"gitea.stevedudenhoeffer.com/steve/majordomo/llm"
"gitea.stevedudenhoeffer.com/steve/majordomo/provider/fake"
)
func TestEval(t *testing.T) {
tests := []struct {
expr string
want float64
}{
{"1+2", 3},
{"2 * 3 + 4", 10},
{"2 + 3 * 4", 14},
{"(2 + 3) * 4", 20},
{"10 / 4", 2.5},
{"-3 + 5", 2},
{"--3", 3},
{"2^10", 1024},
{"2^3^2", 512}, // right-associative
{"-2^2", -4}, // unary binds looser than power
{"7 % 3", 1},
{"1.5e2 + 1", 151},
{" ( 1 + 1 ) * ( 2 + 2 ) ", 8},
}
for _, tt := range tests {
got, err := Eval(tt.expr)
if err != nil {
t.Errorf("Eval(%q): %v", tt.expr, err)
continue
}
if got != tt.want {
t.Errorf("Eval(%q) = %v, want %v", tt.expr, got, tt.want)
}
}
}
func TestEvalErrors(t *testing.T) {
for _, expr := range []string{"", "1/0", "5 % 0", "2 +", "(1+2", "1 + abc", "1 2", "2^99999"} {
if _, err := Eval(expr); err == nil {
t.Errorf("Eval(%q) should error", expr)
}
}
}
// TestSkillInAgentLoop: an agent actually invokes calculate and answers
// from its result.
func TestSkillInAgentLoop(t *testing.T) {
fp := fake.New("fp")
fp.Enqueue("m",
fake.ReplyWith(llm.Response{
ToolCalls: []llm.ToolCall{{ID: "c1", Name: "calculate", Arguments: json.RawMessage(`{"expression":"(2+3)*4"}`)}},
FinishReason: llm.FinishToolCalls,
}),
fake.Reply("the answer is 20"),
)
m, _ := fp.Model("m")
a := agent.New(m, "Math helper.", agent.WithSkill(New()))
res, err := a.Run(context.Background(), "what is (2+3)*4?")
if err != nil {
t.Fatalf("Run: %v", err)
}
if res.Output != "the answer is 20" {
t.Errorf("output = %q", res.Output)
}
result := res.Steps[0].Results[0]
if result.IsError || !strings.Contains(result.Content, `"result":20`) {
t.Errorf("tool result = %+v", result)
}
}