- Notifications
You must be signed in to change notification settings - Fork1k
⚡️ Speed up methodAgentRunResult._set_output_tool_return
by 18798%#2196
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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
⚡️ Speed up methodAgentRunResult._set_output_tool_return
by 18798%#2196
Uh oh!
There was an error while loading.Please reload this page.
Conversation
Here's an optimized version of your code, focusing on the main bottleneck: `deepcopy(self._state.message_history)`.**Optimization explanation:**- `deepcopy` is extremely expensive (see profile: 99% of the time). Here, we only need to modify the last message's relevant tool part; the rest of `message_history` can be shared *unchanged*.- We replace `deepcopy` with a shallow copy for `messages`. Then we make a **copy of the last message** and only copy its `parts` list. This isolates mutation to the last message and its parts while *reusing* all previous messages unmodified.- This minimizes object allocations and recursive copying.**Functionality remains identical** (the changed code only avoids unnecessary copying).**Summary of improvements:** - No deep copy unless necessary (minimal required copying). - Lower memory churn and much faster. - Identical results and error handling. - Compatible with existing code and data class message structures. This will provide a **dramatic speedup**, especially when `message_history` is long.
Uh oh!
There was an error while loading.Please reload this page.
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.
Pull Request Overview
A significant performance optimization for theAgentRunResult._set_output_tool_return
method by replacing a full deep copy of the message history with a lazy copy that only deep copies the last message when needed.
- Removed full
deepcopy
ofmessage_history
- Introduced shallow copy of list and deep copy of only the last message
- Maintained original error conditions and semantics
Comments suppressed due to low confidence (3)
pydantic_ai_slim/pydantic_ai/agent.py:2105
- The new lazy-copy logic in this method introduces multiple code paths (no-op vs. deep-copy-and-modify). Consider adding unit tests that verify both branches—ensuring the original
message_history
isn’t mutated when there’s no matching tool part and that only the last message is copied when a match is found.
messages = self._state.message_history
pydantic_ai_slim/pydantic_ai/agent.py:2111
- [nitpick] The variable
copied_last
could be renamed tocopied_last_message
for better clarity and readability.
copied_last = deepcopy(last_message)
pydantic_ai_slim/pydantic_ai/agent.py:2105
- [nitpick] The docstring still describes a full deep copy of the history but the implementation now uses a lazy-copy approach. Update the method’s docstring to reflect that only the last message is deep-copied when modified.
messages = self._state.message_history
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.
LGTM
e6396cc
intopydantic:mainUh oh!
There was an error while loading.Please reload this page.
📄 18,798% (187.98x) speedup for
AgentRunResult._set_output_tool_return
inpydantic_ai_slim/pydantic_ai/agent.py
⏱️ Runtime :
8.31 milliseconds
→44.0 microseconds
(best of21
runs)📝 Explanation and details
Key optimization:
deepcopy(self._state.message_history)
) was by far the slowest operation (99.3% of time).Notes:
ModelMessage
is a dataclass or otherwise supports__class__(**__dict__)
for shallow copying. If this can't be guaranteed, substitute with a more explicit copy (e.g.,dataclasses.replace
if it is a dataclass).✅Correctness verification report:
⏪ Replay Tests and Runtime