@@ -489,6 +489,23 @@ func (a *Agent) consumeJob(job *types.Job, role string) {
|
||||
}
|
||||
}
|
||||
|
||||
// check if the agent is looping over the same action
|
||||
// if so, we need to stop it
|
||||
if a.options.loopDetectionSteps > 0 && len(job.GetPastActions()) > 0 {
|
||||
count := map[string]int{}
|
||||
for i := len(job.GetPastActions()) - 1; i >= 0; i-- {
|
||||
pastAction := job.GetPastActions()[i]
|
||||
if pastAction.Action.Definition().Name == chosenAction.Definition().Name &&
|
||||
pastAction.Params.String() == actionParams.String() {
|
||||
count[chosenAction.Definition().Name.String()]++
|
||||
}
|
||||
}
|
||||
if count[chosenAction.Definition().Name.String()] > a.options.loopDetectionSteps {
|
||||
xlog.Info("Loop detected, stopping agent", "agent", a.Character.Name, "action", chosenAction.Definition().Name)
|
||||
chosenAction = nil
|
||||
}
|
||||
}
|
||||
|
||||
//xlog.Debug("Picked action", "agent", a.Character.Name, "action", chosenAction.Definition().Name, "reasoning", reasoning)
|
||||
if chosenAction == nil {
|
||||
// If no action was picked up, the reasoning is the message returned by the assistant
|
||||
@@ -551,6 +568,8 @@ func (a *Agent) consumeJob(job *types.Job, role string) {
|
||||
return
|
||||
}
|
||||
|
||||
job.AddPastAction(chosenAction, &actionParams)
|
||||
|
||||
var err error
|
||||
conv, err = a.handlePlanning(job.GetContext(), job, chosenAction, actionParams, reasoning, pickTemplate, conv)
|
||||
if err != nil {
|
||||
|
||||
@@ -126,6 +126,7 @@ var _ = Describe("Agent test", func() {
|
||||
agent, err := New(
|
||||
WithLLMAPIURL(apiURL),
|
||||
WithModel(testModel),
|
||||
WithLoopDetectionSteps(3),
|
||||
// WithRandomIdentity(),
|
||||
WithActions(&TestAction{response: map[string]string{
|
||||
"boston": testActionResult,
|
||||
|
||||
@@ -28,6 +28,7 @@ type options struct {
|
||||
|
||||
canStopItself bool
|
||||
initiateConversations bool
|
||||
loopDetectionSteps int
|
||||
forceReasoning bool
|
||||
canPlan bool
|
||||
characterfile string
|
||||
@@ -113,6 +114,13 @@ func WithTimeout(timeout string) Option {
|
||||
}
|
||||
}
|
||||
|
||||
func WithLoopDetectionSteps(steps int) Option {
|
||||
return func(o *options) error {
|
||||
o.loopDetectionSteps = steps
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithConversationsPath(path string) Option {
|
||||
return func(o *options) error {
|
||||
o.conversationsPath = path
|
||||
|
||||
@@ -56,6 +56,7 @@ type AgentConfig struct {
|
||||
EnableKnowledgeBase bool `json:"enable_kb" form:"enable_kb"`
|
||||
EnableReasoning bool `json:"enable_reasoning" form:"enable_reasoning"`
|
||||
KnowledgeBaseResults int `json:"kb_results" form:"kb_results"`
|
||||
LoopDetectionSteps int `json:"loop_detection_steps" form:"loop_detection_steps"`
|
||||
CanStopItself bool `json:"can_stop_itself" form:"can_stop_itself"`
|
||||
SystemPrompt string `json:"system_prompt" form:"system_prompt"`
|
||||
LongTermMemory bool `json:"long_term_memory" form:"long_term_memory"`
|
||||
@@ -250,6 +251,15 @@ func NewAgentConfigMeta(
|
||||
HelpText: "Enable agent to explain its reasoning process",
|
||||
Tags: config.Tags{Section: "AdvancedSettings"},
|
||||
},
|
||||
{
|
||||
Name: "loop_detection_steps",
|
||||
Label: "Max Loop Detection Steps",
|
||||
Type: "number",
|
||||
DefaultValue: 5,
|
||||
Min: 1,
|
||||
Step: 1,
|
||||
Tags: config.Tags{Section: "AdvancedSettings"},
|
||||
},
|
||||
},
|
||||
MCPServers: []config.Field{
|
||||
{
|
||||
|
||||
@@ -461,6 +461,10 @@ func (a *AgentPool) startAgentWithConfig(name string, config *AgentConfig) error
|
||||
opts = append(opts, EnableKnowledgeBaseWithResults(config.KnowledgeBaseResults))
|
||||
}
|
||||
|
||||
if config.LoopDetectionSteps > 0 {
|
||||
opts = append(opts, WithLoopDetectionSteps(config.LoopDetectionSteps))
|
||||
}
|
||||
|
||||
xlog.Info("Starting agent", "name", name, "config", config)
|
||||
|
||||
agent, err := New(opts...)
|
||||
|
||||
@@ -20,6 +20,7 @@ type Job struct {
|
||||
UUID string
|
||||
Metadata map[string]interface{}
|
||||
|
||||
pastActions []*ActionRequest
|
||||
nextAction *Action
|
||||
nextActionParams *ActionParams
|
||||
nextActionReasoning string
|
||||
@@ -28,6 +29,11 @@ type Job struct {
|
||||
cancel context.CancelFunc
|
||||
}
|
||||
|
||||
type ActionRequest struct {
|
||||
Action Action
|
||||
Params *ActionParams
|
||||
}
|
||||
|
||||
type JobOption func(*Job)
|
||||
|
||||
func WithConversationHistory(history []openai.ChatCompletionMessage) JobOption {
|
||||
@@ -82,6 +88,17 @@ func (j *Job) SetNextAction(action *Action, params *ActionParams, reasoning stri
|
||||
j.nextActionReasoning = reasoning
|
||||
}
|
||||
|
||||
func (j *Job) AddPastAction(action Action, params *ActionParams) {
|
||||
j.pastActions = append(j.pastActions, &ActionRequest{
|
||||
Action: action,
|
||||
Params: params,
|
||||
})
|
||||
}
|
||||
|
||||
func (j *Job) GetPastActions() []*ActionRequest {
|
||||
return j.pastActions
|
||||
}
|
||||
|
||||
func (j *Job) GetNextAction() (*Action, *ActionParams, string) {
|
||||
return j.nextAction, j.nextActionParams, j.nextActionReasoning
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user