Python: Improve the workflow getting started samples (#570)

* Wip: samples

* wip - samples

* Updates to workflow getting started samples

* Checkpointing enhancements

* Cleanup

* PR feedback

* Updates

* Sample updates

* Updates

* Revamp samples, improve doc strings and code comments

* Cleanup unused comment

* Formatting cleanup

* wip

* Further work on samples. Allow agent to be specified as edge.

* Cleanup

* Typing cleanup

* Sample updates

---------

Co-authored-by: Chris <66376200+crickman@users.noreply.github.com>
Co-authored-by: Eric Zhu <ekzhu@users.noreply.github.com>
This commit is contained in:
Evan Mattson
2025-09-06 04:16:25 +09:00
committed by GitHub
Unverified
parent cd0587c5f6
commit 518fd447fd
46 changed files with 4130 additions and 1683 deletions
@@ -318,7 +318,7 @@ def test_logging_for_missing_input_types(caplog: Any) -> None:
class NoInputTypesExecutor(Executor):
# Handler without type annotation for input parameter
async def handle_message(self, message: Any, ctx: WorkflowContext) -> None:
async def handle_message(self, message: Any, ctx: WorkflowContext[Any]) -> None:
await ctx.send_message("processed")
def _discover_handlers(self) -> None:
@@ -581,7 +581,7 @@ def test_handler_ctx_missing_annotation_raises() -> None:
def test_handler_ctx_unsubscripted_workflow_context_raises() -> None:
class BadExecutor(Executor):
@handler
async def handle(self, message: str, ctx: WorkflowContext) -> None: # missing T
async def handle(self, message: str, ctx: WorkflowContext) -> None: # type: ignore # missing T
pass
start = StringExecutor(id="s")
@@ -4,7 +4,43 @@ from dataclasses import dataclass
from typing import Any
import pytest
from agent_framework.workflow import Executor, WorkflowBuilder, WorkflowContext, handler
from agent_framework import AgentRunResponse, AgentRunResponseUpdate, AgentThread, BaseAgent, ChatMessage, Role
from agent_framework.workflow import AgentExecutor, Executor, WorkflowBuilder, WorkflowContext, handler
class DummyAgent(BaseAgent):
async def run(self, messages=None, *, thread: AgentThread | None = None, **kwargs): # type: ignore[override]
norm: list[ChatMessage] = []
if messages:
for m in messages: # type: ignore[iteration-over-optional]
if isinstance(m, ChatMessage):
norm.append(m)
elif isinstance(m, str):
norm.append(ChatMessage(role=Role.USER, text=m))
return AgentRunResponse(messages=norm)
async def run_stream(self, messages=None, *, thread: AgentThread | None = None, **kwargs): # type: ignore[override]
# Minimal async generator
yield AgentRunResponseUpdate()
def test_builder_accepts_agents_directly():
agent1 = DummyAgent(id="agent1", name="writer")
agent2 = DummyAgent(id="agent2", name="reviewer")
wf = WorkflowBuilder().set_start_executor(agent1).add_edge(agent1, agent2).build()
# Confirm auto-wrapped executors use agent names as IDs
assert wf.start_executor_id == "writer"
assert any(isinstance(e, AgentExecutor) and e.id in {"writer", "reviewer"} for e in wf.executors.values())
def test_builder_agents_always_stream():
agent = DummyAgent(id="agentX", name="streamer")
wf = WorkflowBuilder().set_start_executor(agent).build()
exec_obj = wf.get_start_executor()
assert isinstance(exec_obj, AgentExecutor)
assert getattr(exec_obj, "_streaming", False) is True
@dataclass