@@ -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)
|
//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
|
||||||
@@ -551,6 +568,8 @@ func (a *Agent) consumeJob(job *types.Job, role string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
job.AddPastAction(chosenAction, &actionParams)
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
conv, err = a.handlePlanning(job.GetContext(), job, chosenAction, actionParams, reasoning, pickTemplate, conv)
|
conv, err = a.handlePlanning(job.GetContext(), job, chosenAction, actionParams, reasoning, pickTemplate, conv)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -126,6 +126,7 @@ var _ = Describe("Agent test", func() {
|
|||||||
agent, err := New(
|
agent, err := New(
|
||||||
WithLLMAPIURL(apiURL),
|
WithLLMAPIURL(apiURL),
|
||||||
WithModel(testModel),
|
WithModel(testModel),
|
||||||
|
WithLoopDetectionSteps(3),
|
||||||
// WithRandomIdentity(),
|
// WithRandomIdentity(),
|
||||||
WithActions(&TestAction{response: map[string]string{
|
WithActions(&TestAction{response: map[string]string{
|
||||||
"boston": testActionResult,
|
"boston": testActionResult,
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ type options struct {
|
|||||||
|
|
||||||
canStopItself bool
|
canStopItself bool
|
||||||
initiateConversations bool
|
initiateConversations bool
|
||||||
|
loopDetectionSteps int
|
||||||
forceReasoning bool
|
forceReasoning bool
|
||||||
canPlan bool
|
canPlan bool
|
||||||
characterfile string
|
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 {
|
func WithConversationsPath(path string) Option {
|
||||||
return func(o *options) error {
|
return func(o *options) error {
|
||||||
o.conversationsPath = path
|
o.conversationsPath = path
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ type AgentConfig struct {
|
|||||||
EnableKnowledgeBase bool `json:"enable_kb" form:"enable_kb"`
|
EnableKnowledgeBase bool `json:"enable_kb" form:"enable_kb"`
|
||||||
EnableReasoning bool `json:"enable_reasoning" form:"enable_reasoning"`
|
EnableReasoning bool `json:"enable_reasoning" form:"enable_reasoning"`
|
||||||
KnowledgeBaseResults int `json:"kb_results" form:"kb_results"`
|
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"`
|
CanStopItself bool `json:"can_stop_itself" form:"can_stop_itself"`
|
||||||
SystemPrompt string `json:"system_prompt" form:"system_prompt"`
|
SystemPrompt string `json:"system_prompt" form:"system_prompt"`
|
||||||
LongTermMemory bool `json:"long_term_memory" form:"long_term_memory"`
|
LongTermMemory bool `json:"long_term_memory" form:"long_term_memory"`
|
||||||
@@ -250,6 +251,15 @@ func NewAgentConfigMeta(
|
|||||||
HelpText: "Enable agent to explain its reasoning process",
|
HelpText: "Enable agent to explain its reasoning process",
|
||||||
Tags: config.Tags{Section: "AdvancedSettings"},
|
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{
|
MCPServers: []config.Field{
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -461,6 +461,10 @@ func (a *AgentPool) startAgentWithConfig(name string, config *AgentConfig) error
|
|||||||
opts = append(opts, EnableKnowledgeBaseWithResults(config.KnowledgeBaseResults))
|
opts = append(opts, EnableKnowledgeBaseWithResults(config.KnowledgeBaseResults))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if config.LoopDetectionSteps > 0 {
|
||||||
|
opts = append(opts, WithLoopDetectionSteps(config.LoopDetectionSteps))
|
||||||
|
}
|
||||||
|
|
||||||
xlog.Info("Starting agent", "name", name, "config", config)
|
xlog.Info("Starting agent", "name", name, "config", config)
|
||||||
|
|
||||||
agent, err := New(opts...)
|
agent, err := New(opts...)
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ type Job struct {
|
|||||||
UUID string
|
UUID string
|
||||||
Metadata map[string]interface{}
|
Metadata map[string]interface{}
|
||||||
|
|
||||||
|
pastActions []*ActionRequest
|
||||||
nextAction *Action
|
nextAction *Action
|
||||||
nextActionParams *ActionParams
|
nextActionParams *ActionParams
|
||||||
nextActionReasoning string
|
nextActionReasoning string
|
||||||
@@ -28,6 +29,11 @@ type Job struct {
|
|||||||
cancel context.CancelFunc
|
cancel context.CancelFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ActionRequest struct {
|
||||||
|
Action Action
|
||||||
|
Params *ActionParams
|
||||||
|
}
|
||||||
|
|
||||||
type JobOption func(*Job)
|
type JobOption func(*Job)
|
||||||
|
|
||||||
func WithConversationHistory(history []openai.ChatCompletionMessage) JobOption {
|
func WithConversationHistory(history []openai.ChatCompletionMessage) JobOption {
|
||||||
@@ -82,6 +88,17 @@ func (j *Job) SetNextAction(action *Action, params *ActionParams, reasoning stri
|
|||||||
j.nextActionReasoning = reasoning
|
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) {
|
func (j *Job) GetNextAction() (*Action, *ActionParams, string) {
|
||||||
return j.nextAction, j.nextActionParams, j.nextActionReasoning
|
return j.nextAction, j.nextActionParams, j.nextActionReasoning
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user