Skip to content

Commit a8706ab

Browse files
committed
fix(core): Add recursive loop detection and move loop detection
Signed-off-by: Richard Palethorpe <io@richiejp.com>
1 parent bd1b06f commit a8706ab

2 files changed

Lines changed: 34 additions & 26 deletions

File tree

core/agent/actions.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -480,13 +480,18 @@ func (a *Agent) pickAction(job *types.Job, templ string, messages []openai.ChatC
480480
}, c...)
481481
}
482482

483+
reasoningAction := action.NewReasoning()
483484
thought, err := a.decision(job,
484485
c,
485-
types.Actions{action.NewReasoning()}.ToTools(),
486-
action.NewReasoning().Definition().Name.String(), maxRetries)
486+
types.Actions{reasoningAction}.ToTools(),
487+
reasoningAction.Definition().Name.String(), maxRetries)
487488
if err != nil {
488489
return nil, nil, "", err
489490
}
491+
if thought.actioName != "" && thought.actioName != reasoningAction.Definition().Name.String() {
492+
return nil, nil, "", fmt.Errorf("Expected reasoning action not: %s", thought.actioName)
493+
}
494+
490495
originalReasoning := ""
491496
response := &action.ReasoningResponse{}
492497
if thought.actionParams != nil {

core/agent/agent.go

Lines changed: 27 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -490,13 +490,18 @@ func (a *Agent) processUserInputs(job *types.Job, role string, conv Messages) Me
490490
return conv
491491
}
492492

493-
func (a *Agent) consumeJob(job *types.Job, role string) {
493+
func (a *Agent) consumeJob(job *types.Job, role string, retries int) {
494494

495495
if err := job.GetContext().Err(); err != nil {
496496
job.Result.Finish(fmt.Errorf("expired"))
497497
return
498498
}
499499

500+
if retries < 1 {
501+
job.Result.Finish(fmt.Errorf("Exceeded recursive retries"))
502+
return
503+
}
504+
500505
a.Lock()
501506
paused := a.pause
502507
a.Unlock()
@@ -559,7 +564,7 @@ func (a *Agent) consumeJob(job *types.Job, role string) {
559564
xlog.Error("Error generating parameters, trying again", "error", err)
560565
// try again
561566
job.SetNextAction(&chosenAction, nil, reasoning)
562-
a.consumeJob(job, role)
567+
a.consumeJob(job, role, retries - 1)
563568
return
564569
}
565570
actionParams = p.actionParams
@@ -577,24 +582,6 @@ func (a *Agent) consumeJob(job *types.Job, role string) {
577582
}
578583
}
579584

580-
// check if the agent is looping over the same action
581-
// if so, we need to stop it
582-
if a.options.loopDetectionSteps > 0 && len(job.GetPastActions()) > 0 {
583-
count := map[string]int{}
584-
for i := len(job.GetPastActions()) - 1; i >= 0; i-- {
585-
pastAction := job.GetPastActions()[i]
586-
if pastAction.Action.Definition().Name == chosenAction.Definition().Name &&
587-
pastAction.Params.String() == actionParams.String() {
588-
count[chosenAction.Definition().Name.String()]++
589-
}
590-
}
591-
if count[chosenAction.Definition().Name.String()] > a.options.loopDetectionSteps {
592-
xlog.Info("Loop detected, stopping agent", "agent", a.Character.Name, "action", chosenAction.Definition().Name)
593-
a.reply(job, role, conv, actionParams, chosenAction, reasoning)
594-
return
595-
}
596-
}
597-
598585
//xlog.Debug("Picked action", "agent", a.Character.Name, "action", chosenAction.Definition().Name, "reasoning", reasoning)
599586
if chosenAction == nil {
600587
// If no action was picked up, the reasoning is the message returned by the assistant
@@ -648,7 +635,7 @@ func (a *Agent) consumeJob(job *types.Job, role string) {
648635
xlog.Error("Error generating parameters, trying again", "error", err)
649636
// try again
650637
job.SetNextAction(&chosenAction, nil, reasoning)
651-
a.consumeJob(job, role)
638+
a.consumeJob(job, role, retries - 1)
652639
return
653640
}
654641
actionParams = params.actionParams
@@ -668,6 +655,22 @@ func (a *Agent) consumeJob(job *types.Job, role string) {
668655
return
669656
}
670657

658+
if a.options.loopDetectionSteps > 0 && len(job.GetPastActions()) > 0 {
659+
count := 0
660+
for _, pastAction := range job.GetPastActions() {
661+
if pastAction.Action.Definition().Name == chosenAction.Definition().Name &&
662+
pastAction.Params.String() == actionParams.String() {
663+
count++
664+
}
665+
}
666+
if count > a.options.loopDetectionSteps {
667+
xlog.Info("Loop detected, stopping agent", "agent", a.Character.Name, "action", chosenAction.Definition().Name)
668+
a.reply(job, role, conv, actionParams, chosenAction, reasoning)
669+
return
670+
}
671+
xlog.Debug("Checked for loops", "action", chosenAction.Definition().Name, "count", count)
672+
}
673+
671674
job.AddPastAction(chosenAction, &actionParams)
672675

673676
if !job.Callback(types.ActionCurrentState{
@@ -781,7 +784,7 @@ func (a *Agent) consumeJob(job *types.Job, role string) {
781784
// The agent decided to do another action
782785
// call ourselves again
783786
job.SetNextAction(&followingAction, &followingParams, reasoning)
784-
a.consumeJob(job, role)
787+
a.consumeJob(job, role, retries)
785788
return
786789
}
787790

@@ -966,7 +969,7 @@ func (a *Agent) periodicallyRun(timer *time.Timer) {
966969
types.WithReasoningCallback(a.options.reasoningCallback),
967970
types.WithResultCallback(a.options.resultCallback),
968971
)
969-
a.consumeJob(whatNext, SystemRole)
972+
a.consumeJob(whatNext, SystemRole, 5)
970973

971974
xlog.Info("STOP -- Periodically run is done", "agent", a.Character.Name)
972975

@@ -1050,7 +1053,7 @@ func (a *Agent) run(timer *time.Timer) error {
10501053
<-timer.C
10511054
}
10521055
xlog.Debug("Agent is consuming a job", "agent", a.Character.Name, "job", job)
1053-
a.consumeJob(job, UserRole)
1056+
a.consumeJob(job, UserRole, 5)
10541057
timer.Reset(a.options.periodicRuns)
10551058
case <-a.context.Done():
10561059
// Agent has been canceled, return error

0 commit comments

Comments
 (0)