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

Add ability to end agent runs as a result of a tool call #142

Open
wants to merge 10 commits into
base: main
Choose a base branch
from

Conversation

dmontagu
Copy link
Contributor

@dmontagu dmontagu commented Dec 4, 2024

With this change, you can have tool calls end the full run early by calling ctx.stop_run(result) inside the tool call — this immediately ends tool execution (and type-checkers can even tell that subsequent code is unreachable).

I've also added the ability to disable the standard result tools through a new keyword argument, so you can ensure that the only way for the agent to exit is to call a tool that exits by calling ctx.stop_run. I think we should add this in a separate PR that exposes more control over the auto-generated result tools.

I still need to add documentation and examples.

NOTE: I no longer intend for this to resolve #127, as this is a fairly convoluted way to implement a "result tool", and I think we should just add an API for that directly. But @samuelcolvin has noted that there are other possible use cases for this feature, such as providing ways to explicitly interrupt agent execution during tool calls, etc., so I think it still makes sense to merge this.

@dmontagu dmontagu changed the title Add ability to end agent runs early Add ability to end agent runs as a result of a tool call Dec 4, 2024
Copy link
Member

@samuelcolvin samuelcolvin left a comment

Choose a reason for hiding this comment

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

overall LGTM 👍

pydantic_ai_slim/pydantic_ai/tools.py Outdated Show resolved Hide resolved
@@ -45,6 +51,11 @@ class RunContext(Generic[AgentDeps]):
tool_name: str | None
"""Name of the tool being called."""

def end_run(self, result: ResultData) -> NoReturn:
"""End the call to `agent.run` as soon as possible, using the provided value as the result."""
# NOTE: this means we ignore any other tools called concurrently
Copy link
Member

Choose a reason for hiding this comment

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

mention in docs what happens about cancelling other tools. Do the return values of functions that finish get added to messages?

Copy link
Contributor Author

@dmontagu dmontagu Dec 9, 2024

Choose a reason for hiding this comment

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

mention in docs what happens about cancelling other tools.

I added this comment to match the comments near the result schema things:

        elif model_response.role == 'model-structured-response':
            if self._result_schema is not None:
                # if there's a result schema, and any of the calls match one of its tools, return the result
                # NOTE: this means we ignore any other tools called here
                if match := self._result_schema.find_tool(model_response):

Is there a discussion of this in the docs? I didn't see one in a quick search, but if so I can adapt that; if not, I guess we could add a note about that too?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I would propose that we wait to update the docs about this until after we merge Jeremiah's PR that adds the "eager" vs. "correct" mode, and support reflect those modes here too

pydantic_ai_slim/pydantic_ai/agent.py Outdated Show resolved Hide resolved
Copy link

cloudflare-workers-and-pages bot commented Dec 9, 2024

Deploying pydantic-ai with  Cloudflare Pages  Cloudflare Pages

Latest commit: d20034c
Status: ✅  Deploy successful!
Preview URL: https://af7fb016.pydantic-ai.pages.dev
Branch Preview URL: https://end-agent-run.pydantic-ai.pages.dev

View logs

messages.extend(task_results)
except _exceptions.StopAgentRun as e:
span.set_attribute('stop_agent_run', e.tool_name)
return _MarkFinalResult(data=e.result), []
Copy link
Contributor Author

@dmontagu dmontagu Dec 10, 2024

Choose a reason for hiding this comment

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

This is not correct, and basically should be a type error, but the problem is that e.result has type Any (it is the value passed to ctx.stop_run). However, I am not sure what's going on here so not sure how to tweak things to fix this. Maybe we can discuss tomorrow.

ToolReturn(tool_name='final_result', content=('abcdef', 777), timestamp=IsNow(tz=timezone.utc)),
]
)
assert await result.get_data() == snapshot(('abcdef', 777))
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm hitting:

E           pydantic_ai.exceptions.UnexpectedModelBehavior: Invalid message, unable to find tool. Called tool names: ['maybe_stop_run_tool']. Expected tool names: ['final_result']

And I can't for the life of me figure out what is happening or why. Could use some help understanding how streaming is supposed to work

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.

How can one simply return the response of the tool, instead of routing the response to a final result handler?
2 participants