76ecf0e49e
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>
79 lines
1.9 KiB
Go
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)
|
|
}
|
|
}
|