Address comments

This commit is contained in:
Tao Chen
2026-06-11 15:27:36 -07:00
Unverified
parent ed27241543
commit b0d0224ed4
5 changed files with 110 additions and 6 deletions
@@ -616,10 +616,15 @@ class ResponsesHostServer(ResponsesAgentServerHost):
latest_checkpoint_id: str = self._initial_checkpoint_id
restore_storage: FileCheckpointStorage = self._initial_checkpoint_storage
if context_id is not None:
restore_storage = _checkpoint_storage_for_context(self._checkpoint_storage_path, context_id)
latest_checkpoint = await restore_storage.get_latest(workflow_name=self._agent.workflow.name)
context_storage = _checkpoint_storage_for_context(self._checkpoint_storage_path, context_id)
latest_checkpoint = await context_storage.get_latest(workflow_name=self._agent.workflow.name)
if latest_checkpoint is not None:
# Only switch the restore storage when a checkpoint was actually
# found under the per-context directory. Otherwise the initial
# checkpoint id would not resolve in `context_storage` and the
# restore call below would fail.
latest_checkpoint_id = latest_checkpoint.checkpoint_id
restore_storage = context_storage
# Restore the workflow to the latest checkpoint and run it with the
# new input. Events (including request info events) will not be emitted
@@ -3178,6 +3178,66 @@ class TestCheckpointContextPathValidation:
is server._initial_checkpoint_storage # pyright: ignore[reportPrivateUsage]
)
async def test_handle_inner_workflow_falls_back_to_initial_storage_when_context_dir_is_empty(
self, tmp_path: Any
) -> None:
"""When ``previous_response_id`` is supplied but its checkpoint directory has no
checkpoints, the restoration must fall back to BOTH the initial checkpoint id
and the initial checkpoint storage. Otherwise the initial id would be looked up
inside the per-context storage where it does not exist, and the restore would
fail.
"""
from agent_framework import WorkflowAgent
from azure.ai.agentserver.responses import ResponseContext
from azure.ai.agentserver.responses.models import CreateResponse, ItemMessage
previous_response_id = "resp_previous"
response_id = "resp_current"
root = tmp_path / "root"
root.mkdir()
# The per-context storage exists but contains no checkpoints.
(root / previous_response_id).mkdir()
agent = MagicMock(spec=WorkflowAgent)
agent.id = "wf-agent"
agent.name = "wf"
agent.description = ""
agent.context_providers = []
agent.workflow = MagicMock()
agent.workflow.name = "wf"
agent.workflow._runner_context.has_checkpointing = MagicMock(return_value=False)
agent.workflow.create_checkpoint = AsyncMock(return_value="cp_initial")
agent.run = AsyncMock(
side_effect=[
AgentResponse(messages=[]),
AgentResponse(messages=[Message(role="assistant", contents=[Content.from_text("ok")])]),
]
)
server = ResponsesHostServer(agent, store=InMemoryResponseProvider())
server._checkpoint_storage_path = str(root) # pyright: ignore[reportPrivateUsage]
request = CreateResponse(model="m", input="hi", previous_response_id=previous_response_id)
context = ResponseContext(
response_id=response_id, previous_response_id=previous_response_id, mode_flags=MagicMock()
)
input_item = ItemMessage({"type": "message", "role": "user", "content": "next turn"})
with patch.object(ResponseContext, "get_input_items", new=AsyncMock(return_value=[input_item])):
async for _ in server._handle_inner_workflow(request, context): # pyright: ignore[reportPrivateUsage]
pass
# The restoration call must use the initial id AND the initial storage,
# not the empty per-context storage. Mismatching the two would attempt
# to load ``cp_initial`` from a directory that doesn't contain it.
assert agent.run.call_count == 2
restore_call = agent.run.call_args_list[0]
assert restore_call.kwargs["checkpoint_id"] == "cp_initial"
assert restore_call.kwargs["checkpoint_storage"] is server._initial_checkpoint_storage # pyright: ignore[reportPrivateUsage]
# The new turn still writes checkpoints under the current response id.
new_turn_call = agent.run.call_args_list[1]
assert new_turn_call.kwargs["checkpoint_storage"].storage_path == (root / response_id).resolve()
@pytest.mark.parametrize(
"bad_id",
[