mirror of
https://github.com/pydantic/pydantic-ai-harness.git
synced 2026-07-20 10:25:35 +00:00
Keep already-empty ModelRequests in LimitWarner._strip_old_warnings (#311)
LimitWarner rebuilds history each request, dropping any ModelRequest left with no parts after warning markers are stripped. An already-empty ModelRequest -- which pydantic-ai's empty-response retry appends when a model returns an empty response against a non-optional output type -- has no parts to begin with, so it was dropped too. That left history ending on a ModelResponse, and the next request failed the _agent_graph precondition with `UserError: Processed history must end with a ModelRequest`, killing the run. Keep a request that was already empty; only drop one that became empty because every part was a warning marker. Regression test covers the empty-response-retry tail. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
67f8d870a5
commit
e4839c654b
@@ -121,6 +121,13 @@ class LimitWarner(AbstractCapability[AgentDepsT]):
|
||||
if not isinstance(msg, ModelRequest):
|
||||
cleaned.append(msg)
|
||||
continue
|
||||
# An already-empty request has no warnings to strip; keep it. pydantic-ai's
|
||||
# empty-response retry appends a `ModelRequest` with no parts, and dropping it
|
||||
# would leave history ending on a `ModelResponse`, which fails the next
|
||||
# request's "must end with a ModelRequest" precondition.
|
||||
if not msg.parts:
|
||||
cleaned.append(msg)
|
||||
continue
|
||||
parts = [p for p in msg.parts if not self._is_marker_part(p)]
|
||||
if not parts:
|
||||
continue
|
||||
|
||||
@@ -807,6 +807,20 @@ class TestLimitWarnerEdgeCases:
|
||||
assert isinstance(first, ModelRequest)
|
||||
assert len(first.parts) == 1
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_keeps_already_empty_request(self):
|
||||
"""An already-empty ModelRequest must survive stripping so history still ends with a
|
||||
ModelRequest. pydantic-ai's empty-response retry appends an empty ModelRequest;
|
||||
dropping it left history ending on a ModelResponse and crashed the next request with
|
||||
`Processed history must end with a ModelRequest`."""
|
||||
lw = LimitWarner(max_iterations=100)
|
||||
messages: list[ModelMessage] = [_user('real'), ModelResponse(parts=[]), ModelRequest(parts=[])]
|
||||
rc = _make_request_context(messages)
|
||||
ctx = _make_ctx(requests=5)
|
||||
result = await lw.before_model_request(ctx, rc)
|
||||
assert len(result.messages) == 3
|
||||
assert isinstance(result.messages[-1], ModelRequest)
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_context_warning_below_threshold(self):
|
||||
"""Context window should not warn when below threshold."""
|
||||
|
||||
Reference in New Issue
Block a user