feat: add action to call other agents (#60)

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
This commit is contained in:
Ettore Di Giacinto
2025-03-19 22:58:35 +01:00
committed by GitHub
parent 8e694f70ec
commit 08785e2908
3 changed files with 108 additions and 5 deletions

View File

@@ -28,7 +28,7 @@ type AgentPool struct {
managers map[string]sse.Manager managers map[string]sse.Manager
agentStatus map[string]*Status agentStatus map[string]*Status
apiURL, defaultModel, defaultMultimodalModel, localRAGAPI, apiKey string apiURL, defaultModel, defaultMultimodalModel, localRAGAPI, apiKey string
availableActions func(*AgentConfig) func(ctx context.Context) []Action availableActions func(*AgentConfig) func(ctx context.Context, pool *AgentPool) []Action
connectors func(*AgentConfig) []Connector connectors func(*AgentConfig) []Connector
promptBlocks func(*AgentConfig) []PromptBlock promptBlocks func(*AgentConfig) []PromptBlock
timeout string timeout string
@@ -68,7 +68,7 @@ func loadPoolFromFile(path string) (*AgentPoolData, error) {
func NewAgentPool( func NewAgentPool(
defaultModel, defaultMultimodalModel, apiURL, apiKey, directory string, defaultModel, defaultMultimodalModel, apiURL, apiKey, directory string,
LocalRAGAPI string, LocalRAGAPI string,
availableActions func(*AgentConfig) func(ctx context.Context) []agent.Action, availableActions func(*AgentConfig) func(ctx context.Context, pool *AgentPool) []agent.Action,
connectors func(*AgentConfig) []Connector, connectors func(*AgentConfig) []Connector,
promptBlocks func(*AgentConfig) []PromptBlock, promptBlocks func(*AgentConfig) []PromptBlock,
timeout string, timeout string,
@@ -185,7 +185,7 @@ func (a *AgentPool) startAgentWithConfig(name string, config *AgentConfig) error
connectors := a.connectors(config) connectors := a.connectors(config)
promptBlocks := a.promptBlocks(config) promptBlocks := a.promptBlocks(config)
actions := a.availableActions(config)(ctx) actions := a.availableActions(config)(ctx, a)
stateFile, characterFile := a.stateFiles(name) stateFile, characterFile := a.stateFiles(name)
@@ -458,9 +458,21 @@ func (a *AgentPool) save() error {
return os.WriteFile(a.file, data, 0644) return os.WriteFile(a.file, data, 0644)
} }
func (a *AgentPool) GetAgent(name string) *Agent { func (a *AgentPool) GetAgent(name string) *Agent {
a.Lock()
defer a.Unlock()
return a.agents[name] return a.agents[name]
} }
func (a *AgentPool) AllAgents() []string {
a.Lock()
defer a.Unlock()
var agents []string
for agent := range a.agents {
agents = append(agents, agent)
}
return agents
}
func (a *AgentPool) GetConfig(name string) *AgentConfig { func (a *AgentPool) GetConfig(name string) *AgentConfig {
a.Lock() a.Lock()
defer a.Unlock() defer a.Unlock()

View File

@@ -31,6 +31,7 @@ const (
ActionSendMail = "send_mail" ActionSendMail = "send_mail"
ActionGenerateImage = "generate_image" ActionGenerateImage = "generate_image"
ActionCounter = "counter" ActionCounter = "counter"
ActionCallAgents = "call_agents"
) )
var AvailableActions = []string{ var AvailableActions = []string{
@@ -51,10 +52,11 @@ var AvailableActions = []string{
ActionGenerateImage, ActionGenerateImage,
ActionTwitterPost, ActionTwitterPost,
ActionCounter, ActionCounter,
ActionCallAgents,
} }
func Actions(a *state.AgentConfig) func(ctx context.Context) []agent.Action { func Actions(a *state.AgentConfig) func(ctx context.Context, pool *state.AgentPool) []agent.Action {
return func(ctx context.Context) []agent.Action { return func(ctx context.Context, pool *state.AgentPool) []agent.Action {
allActions := []agent.Action{} allActions := []agent.Action{}
for _, a := range a.Actions { for _, a := range a.Actions {
@@ -104,6 +106,8 @@ func Actions(a *state.AgentConfig) func(ctx context.Context) []agent.Action {
allActions = append(allActions, actions.NewPostTweet(config)) allActions = append(allActions, actions.NewPostTweet(config))
case ActionCounter: case ActionCounter:
allActions = append(allActions, actions.NewCounter(config)) allActions = append(allActions, actions.NewCounter(config))
case ActionCallAgents:
allActions = append(allActions, actions.NewCallAgent(config, pool))
} }
} }

View File

@@ -0,0 +1,87 @@
package actions
import (
"context"
"fmt"
"github.com/mudler/LocalAgent/core/action"
"github.com/mudler/LocalAgent/core/agent"
"github.com/mudler/LocalAgent/core/state"
"github.com/sashabaranov/go-openai"
"github.com/sashabaranov/go-openai/jsonschema"
)
func NewCallAgent(config map[string]string, pool *state.AgentPool) *CallAgentAction {
return &CallAgentAction{
pool: pool,
}
}
type CallAgentAction struct {
pool *state.AgentPool
}
func (a *CallAgentAction) Run(ctx context.Context, params action.ActionParams) (action.ActionResult, error) {
result := struct {
AgentName string `json:"agent_name"`
Message string `json:"message"`
}{}
err := params.Unmarshal(&result)
if err != nil {
fmt.Printf("error: %v", err)
return action.ActionResult{}, err
}
ag := a.pool.GetAgent(result.AgentName)
if ag == nil {
return action.ActionResult{}, fmt.Errorf("agent '%s' not found", result.AgentName)
}
resp := ag.Ask(
agent.WithConversationHistory(
[]openai.ChatCompletionMessage{
{
Role: "user",
Content: result.Message,
},
},
),
)
if resp.Error != nil {
return action.ActionResult{}, err
}
return action.ActionResult{Result: resp.Response}, nil
}
func (a *CallAgentAction) Definition() action.ActionDefinition {
allAgents := a.pool.AllAgents()
description := "Use this tool to call another agent. Available agents and their roles are:"
for _, agent := range allAgents {
agentConfig := a.pool.GetConfig(agent)
if agentConfig == nil {
continue
}
description += fmt.Sprintf("\n- %s: %s", agent, agentConfig.Description)
}
return action.ActionDefinition{
Name: "call_agent",
Description: description,
Properties: map[string]jsonschema.Definition{
"agent_name": {
Type: jsonschema.String,
Description: "The name of the agent to call.",
Enum: allAgents,
},
"message": {
Type: jsonschema.String,
Description: "The message to send to the agent.",
},
},
Required: []string{"agent_name", "message"},
}
}