package skill_test 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" "gitea.stevedudenhoeffer.com/steve/majordomo/skill" ) // The skill package must satisfy the agent contract. var _ agent.Skill = (*skill.Skill)(nil) func TestSkillConstruction(t *testing.T) { tool := llm.Tool{Name: "t1"} sk := skill.New("research", skill.WithInstructions("Cite sources."), skill.WithTools(tool), ) if sk.Name() != "research" || sk.Instructions() != "Cite sources." { t.Errorf("skill = %q / %q", sk.Name(), sk.Instructions()) } if sk.Tools() == nil || len(sk.Tools().Tools()) != 1 { t.Errorf("tools = %+v", sk.Tools()) } if sk.Tools().Name() != "research" { t.Errorf("toolbox name = %q, want skill name", sk.Tools().Name()) } } func TestInstructionOnlySkill(t *testing.T) { sk := skill.New("tone", skill.WithInstructions("Be kind.")) if sk.Tools() != nil { t.Error("instruction-only skill must have nil toolbox") } } // TestSkillsAttachToAnyAgent: the same skill instance layers onto multiple // agents, on demand, extending prompt and toolset additively. func TestSkillsAttachToAnyAgent(t *testing.T) { echo := llm.Tool{ Name: "echo", Handler: func(_ context.Context, args json.RawMessage) (any, error) { return string(args), nil }, } sk := skill.New("echoer", skill.WithInstructions("Echo when asked."), skill.WithTools(echo), ) for _, base := range []string{"Agent one.", "Agent two."} { fp := fake.New("fp") fp.Enqueue("m", fake.Reply("done")) m, _ := fp.Model("m") a := agent.New(m, base) a.AddSkill(sk) if _, err := a.Run(context.Background(), "hi"); err != nil { t.Fatalf("Run: %v", err) } req := fp.Calls()[0].Request if !strings.HasPrefix(req.System, base) || !strings.Contains(req.System, "Echo when asked.") { t.Errorf("system = %q", req.System) } if len(req.Tools) != 1 || req.Tools[0].Name != "echo" { t.Errorf("tools = %+v", req.Tools) } } } // TestMultipleSkillsCompose: instructions append in order; toolsets merge. func TestMultipleSkillsCompose(t *testing.T) { fp := fake.New("fp") fp.Enqueue("m", fake.Reply("ok")) m, _ := fp.Model("m") a := agent.New(m, "Base.", agent.WithSkill(skill.New("one", skill.WithInstructions("First."), skill.WithTools(llm.Tool{Name: "t1"}))), agent.WithSkill(skill.New("two", skill.WithInstructions("Second."), skill.WithTools(llm.Tool{Name: "t2"}))), ) if _, err := a.Run(context.Background(), "go"); err != nil { t.Fatalf("Run: %v", err) } req := fp.Calls()[0].Request if req.System != "Base.\n\nFirst.\n\nSecond." { t.Errorf("system = %q", req.System) } if len(req.Tools) != 2 { t.Errorf("tools = %+v", req.Tools) } }