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>
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user