-
Notifications
You must be signed in to change notification settings - Fork 548
feat: add ExitLoop API for tools to stop agent event loop #583
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -123,11 +123,13 @@ func (c *toolContext) RequestConfirmation(hint string, payload any) error { | |
| Confirmed: false, | ||
| Payload: payload, | ||
| } | ||
| // SkipSummarization stops the agent loop after this tool call. Without it, | ||
| // the function response event becomes lastEvent and IsFinalResponse() returns | ||
| // false (hasFunctionResponses == true), causing the loop to continue. | ||
| // This matches the behavior of the built-in RequireConfirmation path in | ||
| // functiontool (function.go). | ||
| c.eventActions.SkipSummarization = true | ||
| c.ExitLoop() | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| return nil | ||
| } | ||
|
|
||
| func (c *toolContext) ExitLoop() { | ||
| c.eventActions.ExitLoop = true | ||
| // Also set SkipSummarization for backward compatibility with older code | ||
| // that may not know about ExitLoop yet. | ||
| c.eventActions.SkipSummarization = true | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -34,7 +34,7 @@ func TestToolContext(t *testing.T) { | |||||||||
| } | ||||||||||
| } | ||||||||||
|
|
||||||||||
| func TestRequestConfirmation_SetsSkipSummarization(t *testing.T) { | ||||||||||
| func TestRequestConfirmation_SetsExitLoop(t *testing.T) { | ||||||||||
| inv := contextinternal.NewInvocationContext(t.Context(), contextinternal.InvocationContextParams{}) | ||||||||||
| actions := &session.EventActions{} | ||||||||||
| toolCtx := NewToolContext(inv, "fn1", actions, nil) | ||||||||||
|
|
@@ -44,8 +44,8 @@ func TestRequestConfirmation_SetsSkipSummarization(t *testing.T) { | |||||||||
| t.Fatalf("RequestConfirmation returned unexpected error: %v", err) | ||||||||||
| } | ||||||||||
|
|
||||||||||
| if !actions.SkipSummarization { | ||||||||||
| t.Error("RequestConfirmation did not set SkipSummarization to true") | ||||||||||
| if !actions.ExitLoop { | ||||||||||
| t.Error("RequestConfirmation did not set ExitLoop to true") | ||||||||||
|
Comment on lines
+47
to
+48
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The assertion should be updated to check
Suggested change
|
||||||||||
| } | ||||||||||
|
|
||||||||||
| if actions.RequestedToolConfirmations == nil { | ||||||||||
|
|
@@ -74,8 +74,8 @@ func TestRequestConfirmation_AutoGeneratesIDWhenEmpty(t *testing.T) { | |||||||||
| t.Fatalf("RequestConfirmation returned unexpected error: %v", err) | ||||||||||
| } | ||||||||||
|
|
||||||||||
| if !actions.SkipSummarization { | ||||||||||
| t.Error("SkipSummarization should be set even with auto-generated function call ID") | ||||||||||
| if !actions.ExitLoop { | ||||||||||
| t.Error("ExitLoop should be set even with auto-generated function call ID") | ||||||||||
|
Comment on lines
+77
to
+78
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The assertion should be updated to check
Suggested change
|
||||||||||
| } | ||||||||||
| if len(actions.RequestedToolConfirmations) != 1 { | ||||||||||
| t.Fatalf("expected 1 confirmation entry, got %d", len(actions.RequestedToolConfirmations)) | ||||||||||
|
|
@@ -89,3 +89,23 @@ func TestRequestConfirmation_AutoGeneratesIDWhenEmpty(t *testing.T) { | |||||||||
| } | ||||||||||
| } | ||||||||||
| } | ||||||||||
|
|
||||||||||
| func TestExitLoop(t *testing.T) { | ||||||||||
| inv := contextinternal.NewInvocationContext(t.Context(), contextinternal.InvocationContextParams{}) | ||||||||||
| actions := &session.EventActions{} | ||||||||||
| toolCtx := NewToolContext(inv, "fn1", actions, nil) | ||||||||||
|
|
||||||||||
| if actions.ExitLoop { | ||||||||||
| t.Error("ExitLoop should be false initially") | ||||||||||
| } | ||||||||||
|
|
||||||||||
| toolCtx.ExitLoop() | ||||||||||
|
|
||||||||||
| if !actions.ExitLoop { | ||||||||||
| t.Error("ExitLoop should be true after calling ExitLoop()") | ||||||||||
| } | ||||||||||
| // SkipSummarization should also be set for backward compatibility | ||||||||||
| if !actions.SkipSummarization { | ||||||||||
| t.Error("SkipSummarization should be true for backward compatibility") | ||||||||||
| } | ||||||||||
| } | ||||||||||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -122,7 +122,7 @@ type Event struct { | |||||
| // Note: when multiple agents participate in one invocation, there could be | ||||||
| // multiple events with IsFinalResponse() as True, for each participating agent. | ||||||
| func (e *Event) IsFinalResponse() bool { | ||||||
| if (e.Actions.SkipSummarization) || len(e.LongRunningToolIDs) > 0 { | ||||||
| if e.Actions.ExitLoop || e.Actions.SkipSummarization || len(e.LongRunningToolIDs) > 0 { | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Adding
Suggested change
|
||||||
| return true | ||||||
| } | ||||||
|
|
||||||
|
|
@@ -150,8 +150,12 @@ type EventActions struct { | |||||
|
|
||||||
| RequestedToolConfirmations map[string]toolconfirmation.ToolConfirmation | ||||||
|
|
||||||
| // If true, it won't call model to summarize function response. | ||||||
| // Only valid for function response event. | ||||||
| // ExitLoop signals the agent to stop its event loop after processing this event. | ||||||
| // Use this when a tool needs to halt the agent's execution, such as when | ||||||
| // requiring user confirmation or when the tool's result is the final output. | ||||||
| ExitLoop bool | ||||||
|
Comment on lines
+153
to
+156
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||||||
|
|
||||||
| // Deprecated: Use ExitLoop instead. SkipSummarization is kept for backward compatibility. | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Deprecating
Suggested change
|
||||||
| SkipSummarization bool | ||||||
| // If set, the event transfers to the specified agent. | ||||||
| TransferToAgent string | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -87,6 +87,11 @@ type Context interface { | |
| // - error: If there was a failure in initiating the confirmation process itself (e.g., invalid | ||
| // arguments, issue with the event system). The request to ask the user has not been sent. | ||
| RequestConfirmation(hint string, payload any) error | ||
|
|
||
| // ExitLoop signals the agent to stop its event loop after processing this tool's response. | ||
| // Call this when the tool's result should be the final output, or when the agent should | ||
| // halt execution (e.g., waiting for user input, confirmation, or external action). | ||
| ExitLoop() | ||
|
Comment on lines
+91
to
+94
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| } | ||
|
|
||
| // Toolset is an interface for a collection of tools. It allows grouping | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new
ExitLooplogic is correctly integrated here. It's good thatExitLooptakes precedence or is handled alongsideSkipSummarizationduring merging, ensuring the agent stops when intended.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks Gemini :)