Python: [BREAKING] consolidate workflow run APIs (#1723)

* consolidate workflow run apis

* improve validation, add tests

* Proper code tags for docs

* Update sample output

* Remove cycle validation

* PR feedback

* Validation

* Cleanup
This commit is contained in:
Evan Mattson
2025-11-04 08:34:59 +09:00
committed by GitHub
Unverified
parent b0ee7028a6
commit 50d9b13bfc
26 changed files with 722 additions and 477 deletions
@@ -5,14 +5,8 @@ from collections.abc import Collection
from typing import Any
from agent_framework import ChatMessage, ChatMessageStoreProtocol
from agent_framework._threads import ChatMessageStoreState
from agent_framework.openai import OpenAIChatClient
from pydantic import BaseModel
class CustomStoreState(BaseModel):
"""Implementation of custom chat message store state."""
messages: list[ChatMessage]
class CustomChatMessageStore(ChatMessageStoreProtocol):
@@ -32,13 +26,13 @@ class CustomChatMessageStore(ChatMessageStoreProtocol):
async def deserialize_state(self, serialized_store_state: Any, **kwargs: Any) -> None:
if serialized_store_state:
state = CustomStoreState.model_validate(serialized_store_state, **kwargs)
state = ChatMessageStoreState.from_dict(serialized_store_state, **kwargs)
if state.messages:
self._messages.extend(state.messages)
async def serialize_state(self, **kwargs: Any) -> Any:
state = CustomStoreState(messages=self._messages)
return state.model_dump(**kwargs)
state = ChatMessageStoreState(messages=self._messages)
return state.to_dict(**kwargs)
async def main() -> None:
@@ -117,14 +117,14 @@ async def main():
# retrieves the outputs yielded by any terminal nodes.
events = await workflow.run("hello world")
print(events.get_outputs())
# Summarize the final run state (e.g., COMPLETED)
# Summarize the final run state (e.g., IDLE)
print("Final state:", events.get_final_state())
"""
Sample Output:
['DLROW OLLEH']
Final state: WorkflowRunState.COMPLETED
Final state: WorkflowRunState.IDLE
"""
@@ -261,17 +261,21 @@ async def main() -> None:
pending_responses: dict[str, str] | None = None
completed = False
initial_run = True
while not completed:
last_executor: str | None = None
stream = (
workflow.send_responses_streaming(pending_responses)
if pending_responses is not None
else workflow.run_stream(
if initial_run:
stream = workflow.run_stream(
"Create a short launch blurb for the LumenX desk lamp. Emphasize adjustability and warm lighting."
)
)
pending_responses = None
initial_run = False
elif pending_responses is not None:
stream = workflow.send_responses_streaming(pending_responses)
pending_responses = None
else:
break
requests: list[tuple[str, DraftFeedbackRequest]] = []
async for event in stream:
@@ -250,7 +250,7 @@ async def run_interactive_session(
event_stream = workflow.run_stream(initial_message)
elif checkpoint_id:
print("\nStarting workflow from checkpoint...\n")
event_stream = workflow.run_stream_from_checkpoint(checkpoint_id)
event_stream = workflow.run_stream(checkpoint_id)
else:
raise ValueError("Either initial_message or checkpoint_id must be provided")
@@ -47,7 +47,7 @@ What you learn:
- How to configure FileCheckpointStorage and call with_checkpointing on WorkflowBuilder.
- How to list and inspect checkpoints programmatically.
- How to interactively choose a checkpoint to resume from (instead of always resuming
from the most recent or a hard-coded one) using run_stream_from_checkpoint.
from the most recent or a hard-coded one) using run_stream.
- How workflows complete by yielding outputs when idle, not via explicit completion events.
Prerequisites:
@@ -281,7 +281,7 @@ async def main():
new_workflow = create_workflow(checkpoint_storage=checkpoint_storage)
print(f"\nResuming from checkpoint: {chosen_cp_id}")
async for event in new_workflow.run_stream_from_checkpoint(chosen_cp_id, checkpoint_storage=checkpoint_storage):
async for event in new_workflow.run_stream(checkpoint_id=chosen_cp_id, checkpoint_storage=checkpoint_storage):
print(f"Resumed Event: {event}")
"""
@@ -356,7 +356,7 @@ async def main() -> None:
workflow2 = build_parent_workflow(storage)
request_info_event: RequestInfoEvent | None = None
async for event in workflow2.run_stream_from_checkpoint(
async for event in workflow2.run_stream(
resume_checkpoint.checkpoint_id,
):
if isinstance(event, RequestInfoEvent):
@@ -37,7 +37,7 @@ Show how to integrate a human step in the middle of an LLM workflow by using
Demonstrate:
- Alternating turns between an AgentExecutor and a human, driven by events.
- Using Pydantic response_format to enforce structured JSON output from the agent instead of regex parsing.
- Driving the loop in application code with run_stream and send_responses_streaming.
- Driving the loop in application code with run_stream and responses parameter.
Prerequisites:
- Azure OpenAI configured for AzureOpenAIChatClient with required environment variables.
@@ -32,7 +32,7 @@ Concepts highlighted here:
must keep stable IDs so the checkpoint state aligns when we rebuild the graph.
2. **Executor snapshotting** - checkpoints capture the pending plan-review request
map, at superstep boundaries.
3. **Resume with responses** - `Workflow.run_stream_from_checkpoint` accepts a
3. **Resume with responses** - `Workflow.send_responses_streaming` accepts a
`responses` mapping so we can inject the stored human reply during restoration.
Prerequisites:
@@ -141,7 +141,7 @@ async def main() -> None:
# Resume execution and capture the re-emitted plan review request.
request_info_event: RequestInfoEvent | None = None
async for event in resumed_workflow.run_stream_from_checkpoint(resume_checkpoint.checkpoint_id):
async for event in resumed_workflow.run_stream(checkpoint_id=resume_checkpoint.checkpoint_id):
if isinstance(event, RequestInfoEvent) and isinstance(event.data, MagenticPlanReviewRequest):
request_info_event = event
@@ -212,7 +212,7 @@ async def main() -> None:
final_event_post: WorkflowOutputEvent | None = None
post_emitted_events = False
post_plan_workflow = build_workflow(checkpoint_storage)
async for event in post_plan_workflow.run_stream_from_checkpoint(post_plan_checkpoint.checkpoint_id):
async for event in post_plan_workflow.run_stream(checkpoint_id=post_plan_checkpoint.checkpoint_id):
post_emitted_events = True
if isinstance(event, WorkflowOutputEvent):
final_event_post = event