Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
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
6 changes: 6 additions & 0 deletions pydantic_ai_slim/pydantic_ai/_agent_graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,12 @@ async def run( # noqa: C901
ctx.state.message_history = messages
ctx.deps.new_message_index = len(messages)

# Validate that message history starts with a user message
if messages and isinstance(messages[0], _messages.ModelResponse):
raise exceptions.UserError(
'Message history cannot start with a `ModelResponse`. Conversations must begin with a user message.'
)

if self.deferred_tool_results is not None:
return await self._handle_deferred_tool_results(self.deferred_tool_results, messages, ctx)

Expand Down
4 changes: 3 additions & 1 deletion tests/models/test_outlines.py
Original file line number Diff line number Diff line change
Expand Up @@ -573,6 +573,7 @@ def test_input_format(transformers_multimodal_model: OutlinesModel, binary_image

# unsupported: tool calls
tool_call_message_history: list[ModelMessage] = [
ModelRequest(parts=[UserPromptPart(content='some user prompt')]),
ModelResponse(parts=[ToolCallPart(tool_call_id='1', tool_name='get_location')]),
ModelRequest(parts=[ToolReturnPart(tool_name='get_location', content='London', tool_call_id='1')]),
]
Expand All @@ -588,7 +589,8 @@ def test_input_format(transformers_multimodal_model: OutlinesModel, binary_image

# unsupported: non-image file parts
file_part_message_history: list[ModelMessage] = [
ModelResponse(parts=[FilePart(content=BinaryContent(data=b'test', media_type='text/plain'))])
ModelRequest(parts=[UserPromptPart(content='some user prompt')]),
ModelResponse(parts=[FilePart(content=BinaryContent(data=b'test', media_type='text/plain'))]),
]
with pytest.raises(
UserError, match='File parts other than `BinaryImage` are not supported for Outlines models yet.'
Expand Down
19 changes: 19 additions & 0 deletions tests/test_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -6132,3 +6132,22 @@ def llm(messages: list[ModelMessage], _info: AgentInfo) -> ModelResponse:
]
)
assert run.all_messages_json().startswith(b'[{"parts":[{"content":"Hello",')


def test_message_history_cannot_start_with_model_response():
"""Test that message history starting with ModelResponse raises UserError."""

def simple_response(_messages: list[ModelMessage], _info: AgentInfo) -> ModelResponse:
return ModelResponse(parts=[TextPart(content='Final response')]) # pragma: no cover

agent = Agent(FunctionModel(simple_response))

invalid_history = [
ModelResponse(parts=[TextPart(content='ai response')]),
]

with pytest.raises(
UserError,
match='Message history cannot start with a `ModelResponse`.',
):
agent.run_sync('hello', message_history=invalid_history)
Loading