-
Notifications
You must be signed in to change notification settings - Fork 722
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
Introduce tool_use_behavior on agents #203
+594
−26
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
from __future__ import annotations | ||
|
||
import asyncio | ||
from typing import Any, Literal | ||
|
||
from pydantic import BaseModel | ||
|
||
from agents import ( | ||
Agent, | ||
FunctionToolResult, | ||
ModelSettings, | ||
RunContextWrapper, | ||
Runner, | ||
ToolsToFinalOutputFunction, | ||
ToolsToFinalOutputResult, | ||
function_tool, | ||
) | ||
|
||
""" | ||
This example shows how to force the agent to use a tool. It uses `ModelSettings(tool_choice="required")` | ||
to force the agent to use any tool. | ||
|
||
You can run it with 3 options: | ||
1. `default`: The default behavior, which is to send the tool output to the LLM. In this case, | ||
`tool_choice` is not set, because otherwise it would result in an infinite loop - the LLM would | ||
call the tool, the tool would run and send the results to the LLM, and that would repeat | ||
(because the model is forced to use a tool every time.) | ||
2. `first_tool_result`: The first tool result is used as the final output. | ||
3. `custom`: A custom tool use behavior function is used. The custom function receives all the tool | ||
results, and chooses to use the first tool result to generate the final output. | ||
|
||
Usage: | ||
python examples/agent_patterns/forcing_tool_use.py -t default | ||
python examples/agent_patterns/forcing_tool_use.py -t first_tool | ||
python examples/agent_patterns/forcing_tool_use.py -t custom | ||
""" | ||
|
||
|
||
class Weather(BaseModel): | ||
city: str | ||
temperature_range: str | ||
conditions: str | ||
|
||
|
||
@function_tool | ||
def get_weather(city: str) -> Weather: | ||
print("[debug] get_weather called") | ||
return Weather(city=city, temperature_range="14-20C", conditions="Sunny with wind") | ||
|
||
|
||
async def custom_tool_use_behavior( | ||
context: RunContextWrapper[Any], results: list[FunctionToolResult] | ||
) -> ToolsToFinalOutputResult: | ||
weather: Weather = results[0].output | ||
return ToolsToFinalOutputResult( | ||
is_final_output=True, final_output=f"{weather.city} is {weather.conditions}." | ||
) | ||
|
||
|
||
async def main(tool_use_behavior: Literal["default", "first_tool", "custom"] = "default"): | ||
if tool_use_behavior == "default": | ||
behavior: Literal["run_llm_again", "stop_on_first_tool"] | ToolsToFinalOutputFunction = ( | ||
"run_llm_again" | ||
) | ||
elif tool_use_behavior == "first_tool": | ||
behavior = "stop_on_first_tool" | ||
elif tool_use_behavior == "custom": | ||
behavior = custom_tool_use_behavior | ||
|
||
agent = Agent( | ||
name="Weather agent", | ||
instructions="You are a helpful agent.", | ||
tools=[get_weather], | ||
tool_use_behavior=behavior, | ||
model_settings=ModelSettings( | ||
tool_choice="required" if tool_use_behavior != "default" else None | ||
), | ||
) | ||
|
||
result = await Runner.run(agent, input="What's the weather in Tokyo?") | ||
print(result.final_output) | ||
|
||
|
||
if __name__ == "__main__": | ||
import argparse | ||
|
||
parser = argparse.ArgumentParser() | ||
parser.add_argument( | ||
"-t", | ||
"--tool-use-behavior", | ||
type=str, | ||
required=True, | ||
choices=["default", "first_tool", "custom"], | ||
help="The behavior to use for tool use. Default will cause tool outputs to be sent to the model. " | ||
"first_tool_result will cause the first tool result to be used as the final output. " | ||
"custom will use a custom tool use behavior function.", | ||
) | ||
args = parser.parse_args() | ||
asyncio.run(main(args.tool_use_behavior)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
import asyncio | ||
|
||
from pydantic import BaseModel | ||
|
||
from agents import Agent, Runner, function_tool | ||
|
||
|
||
class Weather(BaseModel): | ||
city: str | ||
temperature_range: str | ||
conditions: str | ||
|
||
|
||
@function_tool | ||
def get_weather(city: str) -> Weather: | ||
print("[debug] get_weather called") | ||
return Weather(city=city, temperature_range="14-20C", conditions="Sunny with wind.") | ||
|
||
|
||
agent = Agent( | ||
name="Hello world", | ||
instructions="You are a helpful agent.", | ||
tools=[get_weather], | ||
) | ||
|
||
|
||
async def main(): | ||
result = await Runner.run(agent, input="What's the weather in Tokyo?") | ||
print(result.final_output) | ||
# The weather in Tokyo is sunny. | ||
|
||
|
||
if __name__ == "__main__": | ||
asyncio.run(main()) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
Perhaps a try/except/rethrow around the call to the user-provided function with a useful error message?
Was just playing with this branch and even with this function it was stopping after 1 turn.
... turns out it was a silly mistake on my part where I had forgotten to import
ToolsToFinalOutputResults
, but there were no obvious user-visible errors that I could find -- even with theopenai.agents
logger set to DEBUG. It just looked as-if the agent had decided it was done even thoughis_final_result
wasFalse
.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.
oh weird! Yeah I'll add that. Thanks