feat: add Capabilities field to AgentInfo for capability-based routing
Add Capabilities []string to the full agent chain (config → instance → resolver → multiagent) as required by issue #294 spec: "Define a standard Agent interface that includes Capabilities." - AgentConfig: new capabilities JSON field - AgentInstance: new Capabilities field, populated from config - registryResolver: map Capabilities in GetAgentInfo and ListAgents - AgentInfo: new Capabilities field - FindAgentsByCapability: helper to query agents by capability tag - 4 new tests covering match, multi-match, empty, and nil safety
This commit is contained in:
parent
ef4ee76480
commit
e121c7ca04
5 changed files with 87 additions and 3 deletions
|
|
@ -28,6 +28,7 @@ type AgentInstance struct {
|
|||
Sessions *session.SessionManager
|
||||
ContextBuilder *ContextBuilder
|
||||
Tools *tools.ToolRegistry
|
||||
Capabilities []string
|
||||
Subagents *config.SubagentsConfig
|
||||
SkillsFilter []string
|
||||
Candidates []providers.FallbackCandidate
|
||||
|
|
@ -67,6 +68,7 @@ func NewAgentInstance(
|
|||
agentSystemPrompt := ""
|
||||
var subagents *config.SubagentsConfig
|
||||
var skillsFilter []string
|
||||
var capabilities []string
|
||||
|
||||
if agentCfg != nil {
|
||||
agentID = routing.NormalizeAgentID(agentCfg.ID)
|
||||
|
|
@ -75,6 +77,7 @@ func NewAgentInstance(
|
|||
agentSystemPrompt = agentCfg.SystemPrompt
|
||||
subagents = agentCfg.Subagents
|
||||
skillsFilter = agentCfg.Skills
|
||||
capabilities = agentCfg.Capabilities
|
||||
}
|
||||
|
||||
maxIter := defaults.MaxToolIterations
|
||||
|
|
@ -103,6 +106,7 @@ func NewAgentInstance(
|
|||
Sessions: sessionsManager,
|
||||
ContextBuilder: contextBuilder,
|
||||
Tools: toolsRegistry,
|
||||
Capabilities: capabilities,
|
||||
Subagents: subagents,
|
||||
SkillsFilter: skillsFilter,
|
||||
Candidates: candidates,
|
||||
|
|
|
|||
|
|
@ -99,6 +99,7 @@ func (r *registryResolver) GetAgentInfo(agentID string) *multiagent.AgentInfo {
|
|||
Provider: agent.Provider,
|
||||
Tools: agent.Tools,
|
||||
MaxIter: agent.MaxIterations,
|
||||
Capabilities: agent.Capabilities,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -111,9 +112,10 @@ func (r *registryResolver) ListAgents() []multiagent.AgentInfo {
|
|||
continue
|
||||
}
|
||||
agents = append(agents, multiagent.AgentInfo{
|
||||
ID: agent.ID,
|
||||
Name: agent.Name,
|
||||
Role: agent.Role,
|
||||
ID: agent.ID,
|
||||
Name: agent.Name,
|
||||
Role: agent.Role,
|
||||
Capabilities: agent.Capabilities,
|
||||
})
|
||||
}
|
||||
return agents
|
||||
|
|
|
|||
|
|
@ -109,6 +109,7 @@ type AgentConfig struct {
|
|||
Workspace string `json:"workspace,omitempty"`
|
||||
Model *AgentModelConfig `json:"model,omitempty"`
|
||||
Skills []string `json:"skills,omitempty"`
|
||||
Capabilities []string `json:"capabilities,omitempty"`
|
||||
Subagents *SubagentsConfig `json:"subagents,omitempty"`
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package multiagent
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"slices"
|
||||
|
||||
"github.com/sipeed/picoclaw/pkg/providers"
|
||||
"github.com/sipeed/picoclaw/pkg/tools"
|
||||
|
|
@ -26,6 +27,18 @@ type AgentInfo struct {
|
|||
Provider providers.LLMProvider
|
||||
Tools *tools.ToolRegistry
|
||||
MaxIter int
|
||||
Capabilities []string // optional tags for capability-based routing (e.g. "coding", "research")
|
||||
}
|
||||
|
||||
// FindAgentsByCapability returns agents that advertise the given capability.
|
||||
func FindAgentsByCapability(resolver AgentResolver, capability string) []AgentInfo {
|
||||
var matches []AgentInfo
|
||||
for _, a := range resolver.ListAgents() {
|
||||
if slices.Contains(a.Capabilities, capability) {
|
||||
matches = append(matches, a)
|
||||
}
|
||||
}
|
||||
return matches
|
||||
}
|
||||
|
||||
// HandoffRequest describes a delegation from one agent to another.
|
||||
|
|
|
|||
|
|
@ -224,6 +224,70 @@ func TestListAgentsTool_Empty(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestFindAgentsByCapability(t *testing.T) {
|
||||
resolver := newMockResolver(
|
||||
&AgentInfo{ID: "coder", Name: "Coder", Capabilities: []string{"coding", "review"}},
|
||||
&AgentInfo{ID: "researcher", Name: "Researcher", Capabilities: []string{"research", "web_search"}},
|
||||
&AgentInfo{ID: "generalist", Name: "Generalist"},
|
||||
)
|
||||
|
||||
// Find coding agents
|
||||
matches := FindAgentsByCapability(resolver, "coding")
|
||||
if len(matches) != 1 || matches[0].ID != "coder" {
|
||||
t.Errorf("FindAgentsByCapability(coding) = %v, want [coder]", matches)
|
||||
}
|
||||
|
||||
// Find research agents
|
||||
matches = FindAgentsByCapability(resolver, "research")
|
||||
if len(matches) != 1 || matches[0].ID != "researcher" {
|
||||
t.Errorf("FindAgentsByCapability(research) = %v, want [researcher]", matches)
|
||||
}
|
||||
|
||||
// No match
|
||||
matches = FindAgentsByCapability(resolver, "design")
|
||||
if len(matches) != 0 {
|
||||
t.Errorf("FindAgentsByCapability(design) = %v, want empty", matches)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindAgentsByCapability_Multiple(t *testing.T) {
|
||||
resolver := newMockResolver(
|
||||
&AgentInfo{ID: "a", Capabilities: []string{"coding"}},
|
||||
&AgentInfo{ID: "b", Capabilities: []string{"coding", "review"}},
|
||||
&AgentInfo{ID: "c", Capabilities: []string{"research"}},
|
||||
)
|
||||
|
||||
matches := FindAgentsByCapability(resolver, "coding")
|
||||
if len(matches) != 2 {
|
||||
t.Errorf("expected 2 matches, got %d", len(matches))
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindAgentsByCapability_Empty(t *testing.T) {
|
||||
resolver := newMockResolver()
|
||||
matches := FindAgentsByCapability(resolver, "anything")
|
||||
if len(matches) != 0 {
|
||||
t.Errorf("expected empty, got %v", matches)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAgentInfo_Capabilities(t *testing.T) {
|
||||
agent := &AgentInfo{
|
||||
ID: "coder",
|
||||
Name: "Code Agent",
|
||||
Capabilities: []string{"coding", "review", "testing"},
|
||||
}
|
||||
if len(agent.Capabilities) != 3 {
|
||||
t.Errorf("Capabilities len = %d, want 3", len(agent.Capabilities))
|
||||
}
|
||||
|
||||
// Nil capabilities should not panic
|
||||
agent2 := &AgentInfo{ID: "basic"}
|
||||
if agent2.Capabilities != nil {
|
||||
t.Error("expected nil Capabilities for unset agent")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildHandoffSystemPrompt(t *testing.T) {
|
||||
agent := &AgentInfo{
|
||||
Name: "Code Agent",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue