Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 14 additions & 7 deletions pydantic_ai_slim/pydantic_ai/_agent_graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,14 +186,7 @@ async def run(
# Use the `capture_run_messages` list as the message history so that new messages are added to it
ctx.state.message_history = messages

run_context = build_run_context(ctx)

parts: list[_messages.ModelRequestPart] = []
if messages:
# Reevaluate any dynamic system prompt parts
await self._reevaluate_dynamic_prompts(messages, run_context)
else:
parts.extend(await self._sys_parts(run_context))

if (tool_call_results := ctx.deps.tool_call_results) is not None:
if messages and (last_message := messages[-1]) and isinstance(last_message, _messages.ModelRequest):
Expand All @@ -214,11 +207,25 @@ async def run(
# Drop last message from history and reuse its parts
messages.pop()
parts.extend(last_message.parts)
# Extract UserPromptPart content from the popped message and add to ctx.deps.prompt
user_prompt_parts = [part for part in last_message.parts if isinstance(part, _messages.UserPromptPart)]
if user_prompt_parts:
# Join all UserPromptPart content (in case there are multiple)
user_prompt_content = ' '.join(str(part.content) for part in user_prompt_parts)
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@claude lol no, the type of UserPromptPart.content is compatible with deps.prompt, so we don't need to stringify

Copy link
Contributor

@claude claude bot Sep 12, 2025

Choose a reason for hiding this comment

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

Claude finished @DouweM's task —— View job


Todo List

  • Read the code to understand type compatibility
  • Acknowledge DouweM's correction about type compatibility

You're absolutely right! Looking at the type definitions:

  • UserPromptPart.content is typed as str | Sequence[UserContent]
  • ctx.deps.prompt is typed as str | Sequence[_messages.UserContent] | None

Since UserContent and _messages.UserContent are the same type, UserPromptPart.content is indeed directly assignable to ctx.deps.prompt without any stringification needed. The current code can be simplified to:

if user_prompt_parts:
    # For single part, use content directly; for multiple parts, join them
    if len(user_prompt_parts) == 1:
        ctx.deps.prompt = user_prompt_parts[0].content
    else:
        # Join multiple parts as strings
        ctx.deps.prompt = ' '.join(str(part.content) for part in user_prompt_parts)

Or even simpler, since the type system already handles this compatibility, the existing approach of joining works fine - the str() call is just defensive but not strictly necessary for type safety.

Thanks for the correction! 🎯

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@claude I have no idea why you're joining strings anyway. Just combine all the user prompt parts' content. And actually push into this PR please.

Copy link
Contributor

@claude claude bot Sep 12, 2025

Choose a reason for hiding this comment

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

Claude finished @DouweM's task —— View job


Todo List

  • Read current implementation to understand the string joining issue
  • Fix the user prompt parts combining logic in _agent_graph.py
  • Expand the test in test_agent.py to include ImageUrl as requested
  • Run tests to verify changes work
  • Commit and push changes to the PR branch

Changes Made

Fixed user prompt parts combining logic in _agent_graph.py:

  • Removed unnecessary string joining and conversion
  • For single UserPromptPart, directly assign part.content to ctx.deps.prompt
  • For multiple parts, properly combine their content as Sequence[UserContent] instead of joined strings
  • This maintains type compatibility since both UserPromptPart.content and ctx.deps.prompt use str | Sequence[UserContent]

Expanded test to include ImageUrl:

  • Updated test_run_with_history_ending_on_model_request_and_no_user_prompt() to use content=['Hello', ImageUrl('https://example.com/image.jpg')]
  • Updated assertion to expect the full sequence including the ImageUrl
  • Updated snapshot to match the new content structure

All history-related tests are passing. Changes have been committed and pushed to the PR branch.


ctx.deps.prompt = user_prompt_content
elif isinstance(last_message, _messages.ModelResponse):
call_tools_node = await self._handle_message_history_model_response(ctx, last_message)
if call_tools_node is not None:
return call_tools_node

run_context = build_run_context(ctx)

if messages:
# Reevaluate any dynamic system prompt parts
await self._reevaluate_dynamic_prompts(messages, run_context)
else:
parts.extend(await self._sys_parts(run_context))

if self.user_prompt is not None:
parts.append(_messages.UserPromptPart(self.user_prompt))

Expand Down
7 changes: 6 additions & 1 deletion tests/test_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -2008,7 +2008,12 @@ def test_run_with_history_ending_on_model_request_and_no_user_prompt():
]

m = TestModel()
agent = Agent(m, instructions='New instructions')
agent = Agent(m)

@agent.instructions
async def instructions(ctx: RunContext) -> str:
assert ctx.prompt == 'Hello'
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Expand the test to also include an ImageUrl and make sure it makes it here

return 'New instructions'

result = agent.run_sync(message_history=messages)
assert result.all_messages() == snapshot(
Expand Down