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
@@ -12,8 +12,10 @@ from agent_framework import (
|
||||
ConcurrentBuilder,
|
||||
Executor,
|
||||
Role,
|
||||
WorkflowCompletedEvent,
|
||||
WorkflowContext,
|
||||
WorkflowOutputEvent,
|
||||
WorkflowRunState,
|
||||
WorkflowStatusEvent,
|
||||
handler,
|
||||
)
|
||||
|
||||
@@ -57,15 +59,19 @@ async def test_concurrent_default_aggregator_emits_single_user_and_assistants()
|
||||
|
||||
wf = ConcurrentBuilder().participants([e1, e2, e3]).build()
|
||||
|
||||
completed: WorkflowCompletedEvent | None = None
|
||||
completed = False
|
||||
output: list[ChatMessage] | None = None
|
||||
async for ev in wf.run_stream("prompt: hello world"):
|
||||
if isinstance(ev, WorkflowCompletedEvent):
|
||||
completed = ev
|
||||
if isinstance(ev, WorkflowStatusEvent) and ev.state == WorkflowRunState.IDLE:
|
||||
completed = True
|
||||
elif isinstance(ev, WorkflowOutputEvent):
|
||||
output = cast(list[ChatMessage], ev.data)
|
||||
if completed and output is not None:
|
||||
break
|
||||
|
||||
assert completed is not None
|
||||
assert isinstance(completed.data, list)
|
||||
messages: list[ChatMessage] = cast(list[ChatMessage], completed.data) # type: ignore
|
||||
assert completed
|
||||
assert output is not None
|
||||
messages: list[ChatMessage] = output
|
||||
|
||||
# Expect one user message + one assistant message per participant
|
||||
assert len(messages) == 1 + 3
|
||||
@@ -91,16 +97,21 @@ async def test_concurrent_custom_aggregator_callback_is_used() -> None:
|
||||
|
||||
wf = ConcurrentBuilder().participants([e1, e2]).with_aggregator(summarize).build()
|
||||
|
||||
completed: WorkflowCompletedEvent | None = None
|
||||
completed = False
|
||||
output: str | None = None
|
||||
async for ev in wf.run_stream("prompt: custom"):
|
||||
if isinstance(ev, WorkflowCompletedEvent):
|
||||
completed = ev
|
||||
if isinstance(ev, WorkflowStatusEvent) and ev.state == WorkflowRunState.IDLE:
|
||||
completed = True
|
||||
elif isinstance(ev, WorkflowOutputEvent):
|
||||
output = cast(str, ev.data)
|
||||
if completed and output is not None:
|
||||
break
|
||||
|
||||
assert completed is not None
|
||||
assert completed
|
||||
assert output is not None
|
||||
# Custom aggregator returns a string payload
|
||||
assert isinstance(completed.data, str)
|
||||
assert completed.data == "One | Two"
|
||||
assert isinstance(output, str)
|
||||
assert output == "One | Two"
|
||||
|
||||
|
||||
async def test_concurrent_custom_aggregator_sync_callback_is_used() -> None:
|
||||
@@ -108,7 +119,7 @@ async def test_concurrent_custom_aggregator_sync_callback_is_used() -> None:
|
||||
e2 = _FakeAgentExec("agentB", "Two")
|
||||
|
||||
# Sync callback with ctx parameter (should run via asyncio.to_thread)
|
||||
def summarize_sync(results: list[AgentExecutorResponse], ctx: WorkflowContext[Any]) -> str: # type: ignore[unused-argument]
|
||||
def summarize_sync(results: list[AgentExecutorResponse], _ctx: WorkflowContext[Any]) -> str: # type: ignore[unused-argument]
|
||||
texts: list[str] = []
|
||||
for r in results:
|
||||
msgs: list[ChatMessage] = r.agent_run_response.messages
|
||||
@@ -117,15 +128,20 @@ async def test_concurrent_custom_aggregator_sync_callback_is_used() -> None:
|
||||
|
||||
wf = ConcurrentBuilder().participants([e1, e2]).with_aggregator(summarize_sync).build()
|
||||
|
||||
completed: WorkflowCompletedEvent | None = None
|
||||
completed = False
|
||||
output: str | None = None
|
||||
async for ev in wf.run_stream("prompt: custom sync"):
|
||||
if isinstance(ev, WorkflowCompletedEvent):
|
||||
completed = ev
|
||||
if isinstance(ev, WorkflowStatusEvent) and ev.state == WorkflowRunState.IDLE:
|
||||
completed = True
|
||||
elif isinstance(ev, WorkflowOutputEvent):
|
||||
output = cast(str, ev.data)
|
||||
if completed and output is not None:
|
||||
break
|
||||
|
||||
assert completed is not None
|
||||
assert isinstance(completed.data, str)
|
||||
assert completed.data == "One | Two"
|
||||
assert completed
|
||||
assert output is not None
|
||||
assert isinstance(output, str)
|
||||
assert output == "One | Two"
|
||||
|
||||
|
||||
def test_concurrent_custom_aggregator_uses_callback_name_for_id() -> None:
|
||||
|
||||
Reference in New Issue
Block a user