Repository: pydantic/pydantic-ai
File: pydantic_ai_slim/pydantic_ai/_agent_graph.py (Python)

Code before:
        parts: list[_messages.ModelRequestPart] = []

        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):
                # If tool call results were provided, that means the previous run ended on deferred tool calls.
                # That run would typically have ended on a `ModelResponse`, but if it had a mix of deferred tool calls and ones that could already be executed,
                # a `ModelRequest` would already have been added to the history with the preliminary results, even if it wouldn't have been sent to the model yet.
                # So now that we have all of the deferred results, we roll back to the last `ModelResponse` and store the contents of the `ModelRequest` on `deferred_tool_results` to be handled by `CallToolsNode`.
                ctx.deps.tool_call_results = self._update_tool_call_results_from_model_request(
                    tool_call_results, last_message
                )
                messages.pop()

            if not messages:
                raise exceptions.UserError('Tool call results were provided, but the message history is empty.')

        if messages and (last_message := messages[-1]):
            if isinstance(last_message, _messages.ModelRequest) and self.user_prompt is None:
                # 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)
                    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))

        instructions = await ctx.deps.get_instructions(run_context)
        next_message = _messages.ModelRequest(parts, instructions=instructions)

        return ModelRequestNode[DepsT, NodeRunEndT](request=next_message)

    async def _handle_message_history_model_response(
        self,
        ctx: GraphRunContext[GraphAgentState, GraphAgentDeps[DepsT, NodeRunEndT]],

Review comment (suggestion):
@claude lol no, the type of `UserPromptPart.content` is compatible with `deps.prompt`, so we don't need to stringify

Code after:
        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):
                # If tool call results were provided, that means the previous run ended on deferred tool calls.
                # That run would typically have ended on a `ModelResponse`, but if it had a mix of deferred tool calls and ones that could already be executed,
                # a `ModelRequest` would already have been added to the history with the preliminary results, even if it wouldn't have been sent to the model yet.
                # So now that we have all of the deferred results, we roll back to the last `ModelResponse` and store the contents of the `ModelRequest` on `deferred_tool_results` to be handled by `CallToolsNode`.
                ctx.deps.tool_call_results = self._update_tool_call_results_from_model_request(
                    tool_call_results, last_message
                )
                messages.pop()

            if not messages:
                raise exceptions.UserError('Tool call results were provided, but the message history is empty.')

        next_message: _messages.ModelRequest | None = None

        if messages and (last_message := messages[-1]):
            if isinstance(last_message, _messages.ModelRequest) and self.user_prompt is None:
                # Drop last message from history and reuse its parts
                messages.pop()
                next_message = _messages.ModelRequest(parts=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:
                    if len(user_prompt_parts) == 1:
                        ctx.deps.prompt = user_prompt_parts[0].content
                    else:
                        combined_content: list[_messages.UserContent] = []
                        for part in user_prompt_parts:
                            if isinstance(part.content, str):
                                combined_content.append(part.content)
                            else:
                                combined_content.extend(part.content)
                        ctx.deps.prompt = combined_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

        # Build the run context after `ctx.deps.prompt` has been updated
        run_context = build_run_context(ctx)

        parts: list[_messages.ModelRequestPart] = []
        if messages:
            await self._reevaluate_dynamic_prompts(messages, run_context)

        if next_message:
            await self._reevaluate_dynamic_prompts([next_message], run_context)
        else:
            parts: list[_messages.ModelRequestPart] = []