Skip to content
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

Fix potential infinite tool call loop by resetting tool_choice after … #263

Merged
merged 9 commits into from
Mar 25, 2025

Conversation

mini-peanut
Copy link

Fix potential infinite tool call loop by resetting tool_choice after tool execution

Summary

This PR fixes an issue where setting tool_choice to "required" or a specific function name could cause models to get stuck in an infinite tool call loop.

When tool_choice is set to force tool usage, this setting persists across model invocations. This PR automatically resets tool_choice to "auto" after tool execution, allowing the model to decide whether to make additional tool calls in subsequent turns.

Unlike using tool_use_behavior="stop_on_first_tool", this approach lets the model continue processing tool results while preventing forced repeated tool calls.

Test plan

  • Added tests to verify tool_choice reset behavior for both agent and run_config settings
  • Added integration test to verify the solution prevents infinite loops
  • All tests pass

Checks

  • I've added new tests for the fix
  • I've updated the relevant documentation (added comment in code)
  • I've run make lint and make format
  • I've made sure tests pass

@rm-openai
Copy link
Collaborator

This is a good idea! What do you think about making it a configurable param, default to reset=True?

@mini-peanut
Copy link
Author

This is a good idea! What do you think about making it a configurable param, default to reset=True?

@rm-openai Thanks for the feedback! I considered adding a config parameter, but wonder if it might add complexity without clear use cases. Most users would want to prevent infinite loops by default, and those with specific needs could already implement custom behaviors through the existing API.

Unless you have specific scenarios in mind where maintaining forced tool calls is beneficial, perhaps the simpler approach is better?

@rm-openai
Copy link
Collaborator

rm-openai commented Mar 20, 2025

@mini-peanut, yeah one use case I had in mind was this:

Setup:

agent = Agent(
  instructions="Use the find_company tool to find the company info. Then use the search_directory tool to get the CEO's email.",
  tools=[find_company, search_directory],
  tool_choice="required",
  tool_use_behavior={"stop_at_tool_names": "search_directory"},

If we reset tool_choice, then we can't trust the Agent to reliably call the second tool.

Thoughts?

@mini-peanut
Copy link
Author

@mini-peanut, yeah one use case I had in mind was this:

Setup:

agent = Agent(
  instructions="Use the find_company tool to find the company info. Then use the search_directory tool to get the CEO's email.",
  tools=[find_company, search_directory],
  tool_choice="required",
  tool_use_behavior={"stop_at_tool_names": "search_directory"},

If we reset tool_choice, then we can't trust the Agent to reliably call the second tool.

Thoughts?

@rm-openai Thanks for sharing that use case. I'd like to refine my approach to focus on the specific problem we're solving.

The Problem: Setting tool_choice to "required" or a specific function name can inadvertently cause infinite loops.

Core Hypothesis: When a user forces a single specific function call, they rarely intend for that same function to be repeatedly called in an infinite loop. This differs from intentional sequential calling of different functions.

Problem Scenario: This issue typically manifests in two specific cases:

  1. When tool_choice is set to a specific function name, causing the same function to be called repeatedly
  2. When tool_choice="required" with only one available tool, which functionally behaves the same way

Concerns with Adding a Configuration Parameter:
Users with legitimate sequential tool usage would need to explicitly set reset_tool_choice_after_use to False.

Targeted Solution: We can address these specific scenarios without disrupting legitimate use cases:

# Only reset in the problematic scenarios where loops are likely unintentional
if (isinstance(tool_choice, str) and tool_choice not in ["auto", "required", "none"]) or 
   (tool_choice == "required" and len(tools) == 1):
    # Reset to "auto"

This approach precisely targets the infinite loop problem without affecting the multi-tool sequential calling pattern you described, and without requiring additional configuration.

rm-openai
rm-openai previously approved these changes Mar 21, 2025
@rm-openai
Copy link
Collaborator

lgtm - but would you mind fixing lint/typechecking please? can't merge without that

- Refactor tool_choice reset to target only problematic edge cases
- Replace manual ModelSettings recreation with dataclasses.replace
- Fix line length and error handling lint issues in tests
@mini-peanut
Copy link
Author

@rm-openai Fixed, and the code should pass the checks. Thanks for your patience

@rm-openai
Copy link
Collaborator

Unfortunately looks like typechecking is still not passing

tools = agent.tools
# Only reset in the problematic scenarios where loops are likely unintentional
if cls._should_reset_tool_choice(agent.model_settings, tools):
agent.model_settings = dataclasses.replace(

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As mentioned earlier, it looks like the original agent instance is being modified. This means the agent may not be guaranteed to use the tool in subsequent runs. This may not be the intended behavior.

Example:

agent = Agent(
    ...
    tools=[custom_tool],
    model_settings=ModelSettings(tool_choice="required"),
)

await Runner.run(agent, "first run")   # starts run with tool_choice="required"
await Runner.run(agent, "second run")  # starts run with tool_choice="auto"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rm-openai Thoughts?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm that's true, does feel like an issue with this solution. Maybe we should save it as internal state.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@baderalfahad @rm-openai I've fixed the issue by creating new copies of the agent and model settings objects instead of modifying the original instances. The linting errors have also been resolved. This should pass the type checking now.

tool_choice="auto"
)

if (

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If modifying the agent model settings is necessary, this should ideally only be done after checking the run config first. This is because the run config overrides agent-specific settings for a run.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. These two operations should actually be parallel rather than sequential, but putting the run_config check first does make more sense. I've made this change in the latest commit.

xianghuijin added 2 commits March 23, 2025 17:20
This fixes the issue where the original agent's model_settings was being directly modified during the tool choice reset process. The original implementation caused the agent's tool_choice to unintentionally reset to "auto" for subsequent runs, which could be unexpected behavior.

The fix creates new copies of the agent and model settings objects using dataclasses.replace() instead of modifying the original objects. This ensures that the tool choice reset is limited to the current run only, maintaining the expected behavior for sequential runs with the same agent.

Addresses feedback from @baderalfahad about the agent instance being modified when it should maintain its original state between runs.
This update moves the tool_choice reset logic to a more appropriate location within the RunImpl class, ensuring that the original agent's model_settings remains unmodified during the reset process. The logic now checks for problematic scenarios before creating a modified copy of the agent's settings, maintaining expected behavior across sequential runs. This change enhances clarity and efficiency in handling tool choices.

Addresses previous feedback regarding the modification of the agent instance and improves the overall structure of the reset logic.
@mini-peanut mini-peanut force-pushed the fix-tool-choice-loop branch from bc53072 to 0c747af Compare March 23, 2025 12:04
@rm-openai rm-openai merged commit 927a29c into openai:main Mar 25, 2025
5 checks passed
@rm-openai
Copy link
Collaborator

I'm merging this because it's mostly great. I think it will need a couple of followups:

  1. Instead of copying the agent, we should do internal bookkeping of the resets
  2. I still think this should be configurable
  3. I'm not sure it makes sense to reset the RunConfig ModelSettings.

I'll follow up with all of those!

rm-openai added a commit that referenced this pull request Mar 25, 2025
## Summary:

#263 added this behavior. The goal was to prevent infinite loops when tool choice was set. The key change I'm making is:
1. Making it configurable on the agent.
2. Doing bookkeeping in the Runner to track this, to prevent mutating agents.
3. Not resetting the global tool choice in RunConfig.

## Test Plan:
Unit tests.
.
rm-openai added a commit that referenced this pull request Mar 25, 2025
## Summary:

#263 added this behavior. The goal was to prevent infinite loops when tool choice was set. The key change I'm making is:
1. Making it configurable on the agent.
2. Doing bookkeeping in the Runner to track this, to prevent mutating agents.
3. Not resetting the global tool choice in RunConfig.

## Test Plan:
Unit tests.
.
rm-openai added a commit that referenced this pull request Mar 25, 2025
## Summary:

#263 added this behavior. The goal was to prevent infinite loops when tool choice was set. The key change I'm making is:
1. Making it configurable on the agent.
2. Doing bookkeeping in the Runner to track this, to prevent mutating agents.
3. Not resetting the global tool choice in RunConfig.

## Test Plan:
Unit tests.
.
rm-openai added a commit that referenced this pull request Mar 25, 2025
## Summary:

#263 added this behavior. The goal was to prevent infinite loops when
tool choice was set. The key change I'm making is:
1. Making it configurable on the agent.
2. Doing bookkeeping in the Runner to track this, to prevent mutating
agents.
3. Not resetting the global tool choice in RunConfig.

## Test Plan:
Unit tests.
.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants