mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
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:
committed by
GitHub
Unverified
parent
b0ee7028a6
commit
50d9b13bfc
@@ -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
|
||||
"""
|
||||
|
||||
|
||||
|
||||
+10
-6
@@ -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:
|
||||
|
||||
+1
-1
@@ -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):
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user