mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
Python: [Breaking] Remove WorkflowCompletedEvent, introduce workflow output and migrate to ctx.yield_output() + a huge refactoring (#845)
* Introduce input and output types for executor and workflow * WorkflowOutputContext handles two types * Remove can_handle_types from Executor * Update validation * Move workflow executor * Move workflow executor * Fix issues in WorkflowExecutor * refactor executor * update execute signature to create workflow context within Executor * fix simple sub workflow test; fix validation * fix output types in WorkflowExecutor * fix issue in Executor handling of SubWorkflowRequestInfo * update tests to use proper workflow output * update orchestration patterns to use output * Update sample -- not finished * Update python/packages/main/tests/workflow/test_workflow_states.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update python/packages/main/tests/workflow/test_concurrent.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * address comments * WorkflowOutputContext --> WorkflowContext * remove WorkflowCompletedEvent * update samples * Update doc string for important classes; update WorkflowExecutor to support concurrent execution * use Never instead of None for default type * Update usage of WorkflowContext[None to WorkflowContext[Never * address comments * remove filter for None * address comments, minor fixes * quality of life improvement on interceptor types --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
Unverified
parent
0f913bcdeb
commit
2133043f11
@@ -9,10 +9,11 @@ from agent_framework import (
|
||||
AgentExecutorResponse,
|
||||
AgentRunResponse,
|
||||
Executor,
|
||||
WorkflowCompletedEvent,
|
||||
WorkflowContext,
|
||||
WorkflowEvent,
|
||||
WorkflowEventSource,
|
||||
WorkflowOutputEvent,
|
||||
WorkflowRunState,
|
||||
WorkflowStatusEvent,
|
||||
handler,
|
||||
)
|
||||
from agent_framework._workflow._edge import SingleEdgeGroup
|
||||
@@ -32,11 +33,12 @@ class MockExecutor(Executor):
|
||||
"""A mock executor for testing purposes."""
|
||||
|
||||
@handler
|
||||
async def mock_handler(self, message: MockMessage, ctx: WorkflowContext[MockMessage]) -> None:
|
||||
async def mock_handler(self, message: MockMessage, ctx: WorkflowContext[MockMessage, int]) -> None:
|
||||
if message.data < 10:
|
||||
await ctx.send_message(MockMessage(data=message.data + 1))
|
||||
else:
|
||||
await ctx.add_event(WorkflowCompletedEvent(data=message.data))
|
||||
await ctx.yield_output(message.data)
|
||||
pass
|
||||
|
||||
|
||||
def test_create_runner():
|
||||
@@ -77,18 +79,14 @@ async def test_runner_run_until_convergence():
|
||||
result: int | None = None
|
||||
await executor_a.execute(
|
||||
MockMessage(data=0),
|
||||
WorkflowContext(
|
||||
executor_id=executor_a.id,
|
||||
source_executor_ids=["START"],
|
||||
shared_state=shared_state,
|
||||
runner_context=ctx,
|
||||
),
|
||||
["START"], # source_executor_ids
|
||||
shared_state, # shared_state
|
||||
ctx, # runner_context
|
||||
)
|
||||
async for event in runner.run_until_convergence():
|
||||
assert isinstance(event, WorkflowEvent)
|
||||
if isinstance(event, WorkflowCompletedEvent):
|
||||
if isinstance(event, WorkflowOutputEvent):
|
||||
result = event.data
|
||||
assert event.origin is WorkflowEventSource.EXECUTOR
|
||||
|
||||
assert result is not None and result == 10
|
||||
|
||||
@@ -112,16 +110,13 @@ async def test_runner_run_until_convergence_not_completed():
|
||||
|
||||
await executor_a.execute(
|
||||
MockMessage(data=0),
|
||||
WorkflowContext(
|
||||
executor_id=executor_a.id,
|
||||
source_executor_ids=["START"],
|
||||
shared_state=shared_state,
|
||||
runner_context=ctx,
|
||||
),
|
||||
["START"], # source_executor_ids
|
||||
shared_state, # shared_state
|
||||
ctx, # runner_context
|
||||
)
|
||||
with pytest.raises(RuntimeError, match="Runner did not converge after 5 iterations."):
|
||||
async for event in runner.run_until_convergence():
|
||||
assert not isinstance(event, WorkflowCompletedEvent)
|
||||
assert not isinstance(event, WorkflowStatusEvent) or event.state != WorkflowRunState.IDLE
|
||||
|
||||
|
||||
async def test_runner_already_running():
|
||||
@@ -143,12 +138,9 @@ async def test_runner_already_running():
|
||||
|
||||
await executor_a.execute(
|
||||
MockMessage(data=0),
|
||||
WorkflowContext(
|
||||
executor_id=executor_a.id,
|
||||
source_executor_ids=["START"],
|
||||
shared_state=shared_state,
|
||||
runner_context=ctx,
|
||||
),
|
||||
["START"], # source_executor_ids
|
||||
shared_state, # shared_state
|
||||
ctx, # runner_context
|
||||
)
|
||||
|
||||
with pytest.raises(RuntimeError, match="Runner is already running."):
|
||||
@@ -172,6 +164,6 @@ async def test_runner_emits_runner_completion_for_agent_response_without_targets
|
||||
)
|
||||
|
||||
events: list[WorkflowEvent] = [event async for event in runner.run_until_convergence()]
|
||||
completions = [e for e in events if isinstance(e, WorkflowCompletedEvent)]
|
||||
assert completions
|
||||
assert all(e.origin is WorkflowEventSource.FRAMEWORK for e in completions)
|
||||
# The runner should complete without errors when handling AgentExecutorResponse without targets
|
||||
# No specific events are expected since there are no executors to process the message
|
||||
assert isinstance(events, list) # Just verify the runner completed without errors
|
||||
|
||||
Reference in New Issue
Block a user