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" | \
|
$(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}") stable" | \
|
||||||
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
|
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
|
||||||
sudo apt-get update
|
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 version
|
||||||
|
|
||||||
docker run --rm hello-world
|
docker run --rm hello-world
|
||||||
- uses: actions/setup-go@v5
|
- uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: '>=1.17.0'
|
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
|
- name: Run tests
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update && sudo apt-get install -y make
|
|
||||||
make tests
|
make tests
|
||||||
#sudo mv coverage/coverage.txt coverage.txt
|
#sudo mv coverage/coverage.txt coverage.txt
|
||||||
#sudo chmod 777 coverage.txt
|
#sudo chmod 777 coverage.txt
|
||||||
|
|||||||
@@ -480,13 +480,18 @@ func (a *Agent) pickAction(job *types.Job, templ string, messages []openai.ChatC
|
|||||||
}, c...)
|
}, c...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
reasoningAction := action.NewReasoning()
|
||||||
thought, err := a.decision(job,
|
thought, err := a.decision(job,
|
||||||
c,
|
c,
|
||||||
types.Actions{action.NewReasoning()}.ToTools(),
|
types.Actions{reasoningAction}.ToTools(),
|
||||||
action.NewReasoning().Definition().Name.String(), maxRetries)
|
reasoningAction.Definition().Name.String(), maxRetries)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, "", err
|
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 := ""
|
originalReasoning := ""
|
||||||
response := &action.ReasoningResponse{}
|
response := &action.ReasoningResponse{}
|
||||||
if thought.actionParams != nil {
|
if thought.actionParams != nil {
|
||||||
|
|||||||
@@ -492,13 +492,18 @@ func (a *Agent) processUserInputs(job *types.Job, role string, conv Messages) Me
|
|||||||
return conv
|
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 {
|
if err := job.GetContext().Err(); err != nil {
|
||||||
job.Result.Finish(fmt.Errorf("expired"))
|
job.Result.Finish(fmt.Errorf("expired"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if retries < 1 {
|
||||||
|
job.Result.Finish(fmt.Errorf("Exceeded recursive retries"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
a.Lock()
|
a.Lock()
|
||||||
paused := a.pause
|
paused := a.pause
|
||||||
a.Unlock()
|
a.Unlock()
|
||||||
@@ -561,7 +566,7 @@ func (a *Agent) consumeJob(job *types.Job, role string) {
|
|||||||
xlog.Error("Error generating parameters, trying again", "error", err)
|
xlog.Error("Error generating parameters, trying again", "error", err)
|
||||||
// try again
|
// try again
|
||||||
job.SetNextAction(&chosenAction, nil, reasoning)
|
job.SetNextAction(&chosenAction, nil, reasoning)
|
||||||
a.consumeJob(job, role)
|
a.consumeJob(job, role, retries - 1)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
actionParams = p.actionParams
|
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)
|
//xlog.Debug("Picked action", "agent", a.Character.Name, "action", chosenAction.Definition().Name, "reasoning", reasoning)
|
||||||
if chosenAction == nil {
|
if chosenAction == nil {
|
||||||
// If no action was picked up, the reasoning is the message returned by the assistant
|
// 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)
|
xlog.Error("Error generating parameters, trying again", "error", err)
|
||||||
// try again
|
// try again
|
||||||
job.SetNextAction(&chosenAction, nil, reasoning)
|
job.SetNextAction(&chosenAction, nil, reasoning)
|
||||||
a.consumeJob(job, role)
|
a.consumeJob(job, role, retries - 1)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
actionParams = params.actionParams
|
actionParams = params.actionParams
|
||||||
@@ -670,6 +657,22 @@ func (a *Agent) consumeJob(job *types.Job, role string) {
|
|||||||
return
|
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)
|
job.AddPastAction(chosenAction, &actionParams)
|
||||||
|
|
||||||
if !job.Callback(types.ActionCurrentState{
|
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
|
// The agent decided to do another action
|
||||||
// call ourselves again
|
// call ourselves again
|
||||||
job.SetNextAction(&followingAction, &followingParams, reasoning)
|
job.SetNextAction(&followingAction, &followingParams, reasoning)
|
||||||
a.consumeJob(job, role)
|
a.consumeJob(job, role, retries)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -988,7 +991,7 @@ func (a *Agent) periodicallyRun(timer *time.Timer) {
|
|||||||
types.WithReasoningCallback(a.options.reasoningCallback),
|
types.WithReasoningCallback(a.options.reasoningCallback),
|
||||||
types.WithResultCallback(a.options.resultCallback),
|
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)
|
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
|
<-timer.C
|
||||||
}
|
}
|
||||||
xlog.Debug("Agent is consuming a job", "agent", a.Character.Name, "job", job)
|
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)
|
timer.Reset(a.options.periodicRuns)
|
||||||
case <-a.context.Done():
|
case <-a.context.Done():
|
||||||
// Agent has been canceled, return error
|
// Agent has been canceled, return error
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ func defaultOptions() *options {
|
|||||||
return &options{
|
return &options{
|
||||||
parallelJobs: 1,
|
parallelJobs: 1,
|
||||||
periodicRuns: 15 * time.Minute,
|
periodicRuns: 15 * time.Minute,
|
||||||
|
loopDetectionSteps: 10,
|
||||||
LLMAPI: llmOptions{
|
LLMAPI: llmOptions{
|
||||||
APIURL: "http://localhost:8080",
|
APIURL: "http://localhost:8080",
|
||||||
Model: "gpt-4",
|
Model: "gpt-4",
|
||||||
|
|||||||
Reference in New Issue
Block a user