fix(core): Add recursive loop detection and move loop detection (#101)
* fix(core): Add recursive loop detection and move loop detection Signed-off-by: Richard Palethorpe <io@richiejp.com> * fix(ci): Free up space after installation to avoid out of space error Signed-off-by: Richard Palethorpe <io@richiejp.com> --------- Signed-off-by: Richard Palethorpe <io@richiejp.com>
This commit is contained in:
committed by
GitHub
parent
45dd74d27c
commit
8abf5512a4
12
.github/workflows/tests.yml
vendored
12
.github/workflows/tests.yml
vendored
@@ -30,16 +30,24 @@ jobs:
|
||||
$(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}") stable" | \
|
||||
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
|
||||
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin make
|
||||
docker version
|
||||
|
||||
docker run --rm hello-world
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '>=1.17.0'
|
||||
- name: Free up disk space
|
||||
run: |
|
||||
sudo rm -rf /usr/share/dotnet
|
||||
sudo rm -rf /usr/local/lib/android
|
||||
sudo rm -rf /opt/ghc
|
||||
sudo apt-get clean
|
||||
docker system prune -af || true
|
||||
df -h
|
||||
|
||||
- name: Run tests
|
||||
run: |
|
||||
sudo apt-get update && sudo apt-get install -y make
|
||||
make tests
|
||||
#sudo mv coverage/coverage.txt coverage.txt
|
||||
#sudo chmod 777 coverage.txt
|
||||
|
||||
@@ -480,13 +480,18 @@ func (a *Agent) pickAction(job *types.Job, templ string, messages []openai.ChatC
|
||||
}, c...)
|
||||
}
|
||||
|
||||
reasoningAction := action.NewReasoning()
|
||||
thought, err := a.decision(job,
|
||||
c,
|
||||
types.Actions{action.NewReasoning()}.ToTools(),
|
||||
action.NewReasoning().Definition().Name.String(), maxRetries)
|
||||
types.Actions{reasoningAction}.ToTools(),
|
||||
reasoningAction.Definition().Name.String(), maxRetries)
|
||||
if err != nil {
|
||||
return nil, nil, "", err
|
||||
}
|
||||
if thought.actioName != "" && thought.actioName != reasoningAction.Definition().Name.String() {
|
||||
return nil, nil, "", fmt.Errorf("Expected reasoning action not: %s", thought.actioName)
|
||||
}
|
||||
|
||||
originalReasoning := ""
|
||||
response := &action.ReasoningResponse{}
|
||||
if thought.actionParams != nil {
|
||||
|
||||
@@ -492,13 +492,18 @@ func (a *Agent) processUserInputs(job *types.Job, role string, conv Messages) Me
|
||||
return conv
|
||||
}
|
||||
|
||||
func (a *Agent) consumeJob(job *types.Job, role string) {
|
||||
func (a *Agent) consumeJob(job *types.Job, role string, retries int) {
|
||||
|
||||
if err := job.GetContext().Err(); err != nil {
|
||||
job.Result.Finish(fmt.Errorf("expired"))
|
||||
return
|
||||
}
|
||||
|
||||
if retries < 1 {
|
||||
job.Result.Finish(fmt.Errorf("Exceeded recursive retries"))
|
||||
return
|
||||
}
|
||||
|
||||
a.Lock()
|
||||
paused := a.pause
|
||||
a.Unlock()
|
||||
@@ -561,7 +566,7 @@ func (a *Agent) consumeJob(job *types.Job, role string) {
|
||||
xlog.Error("Error generating parameters, trying again", "error", err)
|
||||
// try again
|
||||
job.SetNextAction(&chosenAction, nil, reasoning)
|
||||
a.consumeJob(job, role)
|
||||
a.consumeJob(job, role, retries - 1)
|
||||
return
|
||||
}
|
||||
actionParams = p.actionParams
|
||||
@@ -579,24 +584,6 @@ 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)
|
||||
a.reply(job, role, conv, actionParams, chosenAction, reasoning)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
//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
|
||||
@@ -650,7 +637,7 @@ func (a *Agent) consumeJob(job *types.Job, role string) {
|
||||
xlog.Error("Error generating parameters, trying again", "error", err)
|
||||
// try again
|
||||
job.SetNextAction(&chosenAction, nil, reasoning)
|
||||
a.consumeJob(job, role)
|
||||
a.consumeJob(job, role, retries - 1)
|
||||
return
|
||||
}
|
||||
actionParams = params.actionParams
|
||||
@@ -670,6 +657,22 @@ func (a *Agent) consumeJob(job *types.Job, role string) {
|
||||
return
|
||||
}
|
||||
|
||||
if a.options.loopDetectionSteps > 0 && len(job.GetPastActions()) > 0 {
|
||||
count := 0
|
||||
for _, pastAction := range job.GetPastActions() {
|
||||
if pastAction.Action.Definition().Name == chosenAction.Definition().Name &&
|
||||
pastAction.Params.String() == actionParams.String() {
|
||||
count++
|
||||
}
|
||||
}
|
||||
if count > a.options.loopDetectionSteps {
|
||||
xlog.Info("Loop detected, stopping agent", "agent", a.Character.Name, "action", chosenAction.Definition().Name)
|
||||
a.reply(job, role, conv, actionParams, chosenAction, reasoning)
|
||||
return
|
||||
}
|
||||
xlog.Debug("Checked for loops", "action", chosenAction.Definition().Name, "count", count)
|
||||
}
|
||||
|
||||
job.AddPastAction(chosenAction, &actionParams)
|
||||
|
||||
if !job.Callback(types.ActionCurrentState{
|
||||
@@ -783,7 +786,7 @@ func (a *Agent) consumeJob(job *types.Job, role string) {
|
||||
// The agent decided to do another action
|
||||
// call ourselves again
|
||||
job.SetNextAction(&followingAction, &followingParams, reasoning)
|
||||
a.consumeJob(job, role)
|
||||
a.consumeJob(job, role, retries)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -988,7 +991,7 @@ func (a *Agent) periodicallyRun(timer *time.Timer) {
|
||||
types.WithReasoningCallback(a.options.reasoningCallback),
|
||||
types.WithResultCallback(a.options.resultCallback),
|
||||
)
|
||||
a.consumeJob(whatNext, SystemRole)
|
||||
a.consumeJob(whatNext, SystemRole, a.options.loopDetectionSteps)
|
||||
|
||||
xlog.Info("STOP -- Periodically run is done", "agent", a.Character.Name)
|
||||
|
||||
@@ -1072,7 +1075,7 @@ func (a *Agent) run(timer *time.Timer) error {
|
||||
<-timer.C
|
||||
}
|
||||
xlog.Debug("Agent is consuming a job", "agent", a.Character.Name, "job", job)
|
||||
a.consumeJob(job, UserRole)
|
||||
a.consumeJob(job, UserRole, a.options.loopDetectionSteps)
|
||||
timer.Reset(a.options.periodicRuns)
|
||||
case <-a.context.Done():
|
||||
// Agent has been canceled, return error
|
||||
|
||||
@@ -69,6 +69,7 @@ func defaultOptions() *options {
|
||||
return &options{
|
||||
parallelJobs: 1,
|
||||
periodicRuns: 15 * time.Minute,
|
||||
loopDetectionSteps: 10,
|
||||
LLMAPI: llmOptions{
|
||||
APIURL: "http://localhost:8080",
|
||||
Model: "gpt-4",
|
||||
|
||||
Reference in New Issue
Block a user