Python: [BREAKING] Move workflow to main package (#767)

* Move workflow to main package

* Remove workflow specific unit test config

* Remove workflow-specific version info

* Revert unintended telemetry changes

* Removed the obsolete packages/workflow/tests target

* Rename dir workflow to _workflow

* Fix test imports
This commit is contained in:
Evan Mattson
2025-09-16 21:04:07 +09:00
committed by GitHub
Unverified
parent 65dd48aa1d
commit 732d9f6cd7
87 changed files with 524 additions and 595 deletions
@@ -16,3 +16,4 @@ from ._memory import * # noqa: F403
from ._threads import * # noqa: F403
from ._tools import * # noqa: F403
from ._types import * # noqa: F403
from ._workflow import * # noqa: F403
@@ -0,0 +1,15 @@
# Get Started with Microsoft Agent Framework Workflow
Workflow capabilities now ship with the core `agent-framework` package.
```bash
pip install agent-framework
```
Optional visualization support is still available via the `viz` extra:
```bash
pip install agent-framework[viz]
```
See the [project README](https://github.com/microsoft/agent-framework/tree/main/python/README.md) for more information.
@@ -1,6 +1,6 @@
# Copyright (c) Microsoft. All rights reserved.
import importlib.metadata
import contextlib
from ._agent import WorkflowAgent
from ._checkpoint import (
@@ -13,7 +13,18 @@ from ._concurrent import ConcurrentBuilder
from ._const import (
DEFAULT_MAX_ITERATIONS,
)
from ._edge import Case, Default
from ._edge import (
Case,
Default,
Edge,
FanInEdgeGroup,
FanOutEdgeGroup,
SingleEdgeGroup,
SwitchCaseEdgeGroup,
SwitchCaseEdgeGroupCase,
SwitchCaseEdgeGroupDefault,
)
from ._edge_runner import create_edge_runner
from ._events import (
AgentRunEvent,
AgentRunUpdateEvent,
@@ -67,15 +78,19 @@ from ._magentic import (
MagenticStartMessage,
StandardMagenticManager,
)
from ._runner import Runner
from ._runner_context import (
InProcRunnerContext,
Message,
RunnerContext,
)
from ._sequential import SequentialBuilder
from ._shared_state import SharedState
from ._telemetry import EdgeGroupDeliveryStatus, WorkflowTracer, workflow_tracer
from ._validation import (
EdgeDuplicationError,
GraphConnectivityError,
HandlerOutputAnnotationError,
TypeCompatibilityError,
ValidationTypeEnum,
WorkflowValidationError,
@@ -85,12 +100,6 @@ from ._viz import WorkflowViz
from ._workflow import Workflow, WorkflowBuilder, WorkflowRunResult
from ._workflow_context import WorkflowContext
try:
__version__ = importlib.metadata.version(__name__)
except importlib.metadata.PackageNotFoundError:
__version__ = "0.0.0" # Fallback for development mode
__all__ = [
"DEFAULT_MAX_ITERATIONS",
"AgentExecutor",
@@ -102,15 +111,20 @@ __all__ = [
"CheckpointStorage",
"ConcurrentBuilder",
"Default",
"Edge",
"EdgeDuplicationError",
"EdgeGroupDeliveryStatus",
"Executor",
"ExecutorCompletedEvent",
"ExecutorEvent",
"ExecutorFailedEvent",
"ExecutorInvokeEvent",
"FanInEdgeGroup",
"FanOutEdgeGroup",
"FileCheckpointStorage",
"FunctionExecutor",
"GraphConnectivityError",
"HandlerOutputAnnotationError",
"InMemoryCheckpointStorage",
"InProcRunnerContext",
"MagenticAgentDeltaEvent",
@@ -137,11 +151,17 @@ __all__ = [
"RequestInfoExecutor",
"RequestInfoMessage",
"RequestResponse",
"Runner",
"RunnerContext",
"SequentialBuilder",
"SharedState",
"SingleEdgeGroup",
"StandardMagenticManager",
"SubWorkflowRequestInfo",
"SubWorkflowResponse",
"SwitchCaseEdgeGroup",
"SwitchCaseEdgeGroupCase",
"SwitchCaseEdgeGroupDefault",
"TypeCompatibilityError",
"ValidationTypeEnum",
"Workflow",
@@ -158,19 +178,18 @@ __all__ = [
"WorkflowRunState",
"WorkflowStartedEvent",
"WorkflowStatusEvent",
"WorkflowTracer",
"WorkflowValidationError",
"WorkflowViz",
"__version__",
"create_edge_runner",
"executor",
"handler",
"intercepts_request",
"validate_workflow_graph",
"workflow_tracer",
]
# Rebuild models to resolve forward references after all imports are complete
import contextlib
with contextlib.suppress(AttributeError, TypeError, ValueError):
# Rebuild WorkflowExecutor to resolve Workflow forward reference
WorkflowExecutor.model_rebuild()
@@ -0,0 +1,186 @@
# Copyright (c) Microsoft. All rights reserved.
from ._agent import WorkflowAgent
from ._checkpoint import (
CheckpointStorage,
FileCheckpointStorage,
InMemoryCheckpointStorage,
WorkflowCheckpoint,
)
from ._concurrent import ConcurrentBuilder
from ._const import DEFAULT_MAX_ITERATIONS
from ._edge import (
Case,
Default,
Edge,
FanInEdgeGroup,
FanOutEdgeGroup,
SingleEdgeGroup,
SwitchCaseEdgeGroup,
SwitchCaseEdgeGroupCase,
SwitchCaseEdgeGroupDefault,
)
from ._edge_runner import create_edge_runner
from ._events import (
AgentRunEvent,
AgentRunUpdateEvent,
ExecutorCompletedEvent,
ExecutorEvent,
ExecutorFailedEvent,
ExecutorInvokeEvent,
RequestInfoEvent,
WorkflowCompletedEvent,
WorkflowErrorDetails,
WorkflowEvent,
WorkflowFailedEvent,
WorkflowRunState,
WorkflowStartedEvent,
WorkflowStatusEvent,
)
from ._executor import (
AgentExecutor,
AgentExecutorRequest,
AgentExecutorResponse,
Executor,
RequestInfoExecutor,
RequestInfoMessage,
RequestResponse,
SubWorkflowRequestInfo,
SubWorkflowResponse,
WorkflowExecutor,
handler,
intercepts_request,
)
from ._function_executor import FunctionExecutor, executor
from ._magentic import (
MagenticAgentDeltaEvent,
MagenticAgentExecutor,
MagenticAgentMessageEvent,
MagenticBuilder,
MagenticCallbackEvent,
MagenticCallbackMode,
MagenticContext,
MagenticFinalResultEvent,
MagenticManagerBase,
MagenticOrchestratorExecutor,
MagenticOrchestratorMessageEvent,
MagenticPlanReviewDecision,
MagenticPlanReviewReply,
MagenticPlanReviewRequest,
MagenticProgressLedger,
MagenticProgressLedgerItem,
MagenticRequestMessage,
MagenticResponseMessage,
MagenticStartMessage,
StandardMagenticManager,
)
from ._runner import Runner
from ._runner_context import (
InProcRunnerContext,
Message,
RunnerContext,
)
from ._sequential import SequentialBuilder
from ._shared_state import SharedState
from ._telemetry import EdgeGroupDeliveryStatus, WorkflowTracer, workflow_tracer
from ._validation import (
EdgeDuplicationError,
GraphConnectivityError,
HandlerOutputAnnotationError,
TypeCompatibilityError,
ValidationTypeEnum,
WorkflowValidationError,
validate_workflow_graph,
)
from ._viz import WorkflowViz
from ._workflow import Workflow, WorkflowBuilder, WorkflowRunResult
from ._workflow_context import WorkflowContext
__all__ = [
"DEFAULT_MAX_ITERATIONS",
"AgentExecutor",
"AgentExecutorRequest",
"AgentExecutorResponse",
"AgentRunEvent",
"AgentRunUpdateEvent",
"Case",
"CheckpointStorage",
"ConcurrentBuilder",
"Default",
"Edge",
"EdgeDuplicationError",
"EdgeGroupDeliveryStatus",
"Executor",
"ExecutorCompletedEvent",
"ExecutorEvent",
"ExecutorFailedEvent",
"ExecutorInvokeEvent",
"FanInEdgeGroup",
"FanOutEdgeGroup",
"FileCheckpointStorage",
"FunctionExecutor",
"GraphConnectivityError",
"HandlerOutputAnnotationError",
"InMemoryCheckpointStorage",
"InProcRunnerContext",
"MagenticAgentDeltaEvent",
"MagenticAgentExecutor",
"MagenticAgentMessageEvent",
"MagenticBuilder",
"MagenticCallbackEvent",
"MagenticCallbackMode",
"MagenticContext",
"MagenticFinalResultEvent",
"MagenticManagerBase",
"MagenticOrchestratorExecutor",
"MagenticOrchestratorMessageEvent",
"MagenticPlanReviewDecision",
"MagenticPlanReviewReply",
"MagenticPlanReviewRequest",
"MagenticProgressLedger",
"MagenticProgressLedgerItem",
"MagenticRequestMessage",
"MagenticResponseMessage",
"MagenticStartMessage",
"Message",
"RequestInfoEvent",
"RequestInfoExecutor",
"RequestInfoMessage",
"RequestResponse",
"Runner",
"RunnerContext",
"SequentialBuilder",
"SharedState",
"SingleEdgeGroup",
"StandardMagenticManager",
"SubWorkflowRequestInfo",
"SubWorkflowResponse",
"SwitchCaseEdgeGroup",
"SwitchCaseEdgeGroupCase",
"SwitchCaseEdgeGroupDefault",
"TypeCompatibilityError",
"ValidationTypeEnum",
"Workflow",
"WorkflowAgent",
"WorkflowBuilder",
"WorkflowCheckpoint",
"WorkflowCompletedEvent",
"WorkflowContext",
"WorkflowErrorDetails",
"WorkflowEvent",
"WorkflowExecutor",
"WorkflowFailedEvent",
"WorkflowRunResult",
"WorkflowRunState",
"WorkflowStartedEvent",
"WorkflowStatusEvent",
"WorkflowTracer",
"WorkflowValidationError",
"WorkflowViz",
"create_edge_runner",
"executor",
"handler",
"intercepts_request",
"validate_workflow_graph",
"workflow_tracer",
]
@@ -6,6 +6,8 @@ from collections.abc import AsyncIterable, Sequence
from datetime import datetime
from typing import TYPE_CHECKING, Any, ClassVar, TypedDict, cast
from pydantic import Field
from agent_framework import (
AgentRunResponse,
AgentRunResponseUpdate,
@@ -20,7 +22,6 @@ from agent_framework import (
)
from agent_framework._pydantic import AFBaseModel
from agent_framework.exceptions import AgentExecutionException
from pydantic import Field
from ._events import (
AgentRunUpdateEvent,
@@ -73,9 +74,10 @@ class WorkflowAgent(BaseAgent):
kwargs["workflow"] = workflow
# Validate the workflow's start executor can handle agent-facing message inputs
start_executor = workflow.get_start_executor()
if start_executor is None:
raise ValueError("Workflow's start executor is not defined.")
try:
start_executor = workflow.get_start_executor()
except KeyError as exc: # Defensive: workflow lacks a configured entry point
raise ValueError("Workflow's start executor is not defined.") from exc
if not start_executor.can_handle_type(list[ChatMessage]):
raise ValueError("Workflow's start executor cannot handle list[ChatMessage]")
@@ -256,6 +258,9 @@ class WorkflowAgent(BaseAgent):
message_id=str(uuid.uuid4()),
created_at=datetime.now().strftime("%Y-%m-%dT%H:%M:%S.%fZ"),
)
case _:
# Ignore non-agent workflow events
pass
# We only care about the above two events and discard the rest.
return None
@@ -349,16 +354,17 @@ class WorkflowAgent(BaseAgent):
if current is None:
return incoming
raw_list: list[object] = []
def _add_raw(value: object) -> None:
if isinstance(value, list):
raw_list.extend(cast(list[object], value))
else:
raw_list.append(value)
if current.raw_representation is not None:
if isinstance(current.raw_representation, list):
raw_list.extend(current.raw_representation)
else:
raw_list.append(current.raw_representation)
_add_raw(current.raw_representation)
if incoming.raw_representation is not None:
if isinstance(incoming.raw_representation, list):
raw_list.extend(incoming.raw_representation)
else:
raw_list.append(incoming.raw_representation)
_add_raw(incoming.raw_representation)
return AgentRunResponse(
messages=(current.messages or []) + (incoming.messages or []),
response_id=current.response_id or incoming.response_id,
@@ -407,11 +413,13 @@ class WorkflowAgent(BaseAgent):
if merged_additional_properties is None:
merged_additional_properties = {}
merged_additional_properties.update(aggregated.additional_properties)
if aggregated.raw_representation:
if isinstance(aggregated.raw_representation, list):
raw_representations.extend(aggregated.raw_representation)
raw_value = aggregated.raw_representation
if raw_value:
cast_value = cast(object | list[object], raw_value)
if isinstance(cast_value, list):
raw_representations.extend(cast(list[object], cast_value))
else:
raw_representations.append(aggregated.raw_representation)
raw_representations.append(cast_value)
# PHASE 3: HANDLE GLOBAL DANGLING UPDATES (NO RESPONSE_ID)
if global_dangling:
@@ -427,11 +435,13 @@ class WorkflowAgent(BaseAgent):
if merged_additional_properties is None:
merged_additional_properties = {}
merged_additional_properties.update(flattened.additional_properties)
if flattened.raw_representation:
if isinstance(flattened.raw_representation, list):
raw_representations.extend(flattened.raw_representation)
flat_raw = flattened.raw_representation
if flat_raw:
cast_flat = cast(object | list[object], flat_raw)
if isinstance(cast_flat, list):
raw_representations.extend(cast(list[object], cast_flat))
else:
raw_representations.append(flattened.raw_representation)
raw_representations.append(cast_flat)
# PHASE 4: CONSTRUCT FINAL RESPONSE WITH INPUT RESPONSE_ID
return AgentRunResponse(
@@ -177,7 +177,7 @@ class ConcurrentBuilder:
Usage:
```python
from agent_framework.workflow import ConcurrentBuilder
from agent_framework import ConcurrentBuilder
# Minimal: use default aggregator (returns list[ChatMessage])
workflow = ConcurrentBuilder().participants([agent1, agent2, agent3]).build()
@@ -6,10 +6,11 @@ from collections.abc import Callable, Sequence
from dataclasses import dataclass
from typing import Any, ClassVar
from agent_framework._pydantic import AFBaseModel
from pydantic import Field
from agent_framework_workflow._executor import Executor
from agent_framework._pydantic import AFBaseModel
from ._executor import Executor
logger = logging.getLogger(__name__)
@@ -12,9 +12,10 @@ from typing import TYPE_CHECKING, Any, Generic, TypeVar, Union, get_args, get_or
if TYPE_CHECKING:
from ._workflow import Workflow
from pydantic import Field
from agent_framework import AgentProtocol, AgentRunResponse, AgentRunResponseUpdate, AgentThread, ChatMessage
from agent_framework._pydantic import AFBaseModel
from pydantic import Field
from ._events import (
AgentRunEvent,
@@ -13,6 +13,8 @@ from enum import Enum
from typing import Annotated, Any, Literal, Protocol, TypeVar, Union, cast
from uuid import uuid4
from pydantic import BaseModel, ConfigDict, Field
from agent_framework import (
AgentProtocol,
AgentRunResponse,
@@ -25,7 +27,6 @@ from agent_framework import (
)
from agent_framework._agents import BaseAgent
from agent_framework._pydantic import AFBaseModel
from pydantic import BaseModel, ConfigDict, Field
from ._events import WorkflowCompletedEvent, WorkflowEvent
from ._executor import Executor, RequestInfoMessage, RequestResponse, handler
@@ -103,7 +103,7 @@ class SequentialBuilder:
Usage:
```python
from agent_framework.workflow import SequentialBuilder
from agent_framework import SequentialBuilder
workflow = SequentialBuilder().participants([agent1, agent2, summarizer_exec]).build()
```
@@ -3,11 +3,12 @@
from enum import Enum
from typing import TYPE_CHECKING, Any, ClassVar
from agent_framework._pydantic import AFBaseSettings
from opentelemetry.trace import Link, NoOpTracer, SpanKind, StatusCode, get_current_span, get_tracer
from opentelemetry.trace.span import SpanContext
from opentelemetry.util.types import Attributes
from agent_framework._pydantic import AFBaseSettings
if TYPE_CHECKING:
from ._workflow import Workflow
@@ -80,7 +80,7 @@ class WorkflowViz:
import graphviz # type: ignore
except ImportError as e:
raise ImportError(
"viz extra is required for export. Install it with: pip install agent-framework-workflow[viz]. "
"viz extra is required for export. Install it with: pip install agent-framework[viz]. "
"You also need to install graphviz separately. E.g., sudo apt-get install graphviz on Debian/Ubuntu "
"or brew install graphviz on macOS. See https://graphviz.org/download/ for details."
) from e
@@ -5,11 +5,12 @@ import logging
import sys
import uuid
from collections.abc import AsyncIterable, Awaitable, Callable, Sequence
from typing import Any
from typing import Any, cast
from pydantic import Field
from agent_framework import AgentProtocol
from agent_framework._pydantic import AFBaseModel
from pydantic import Field
from ._agent import WorkflowAgent
from ._checkpoint import CheckpointStorage
@@ -51,6 +52,14 @@ else:
logger = logging.getLogger(__name__)
def _default_edge_groups() -> list[EdgeGroup]:
return []
def _default_executors() -> dict[str, Executor]:
return {}
class WorkflowRunResult(list[WorkflowEvent]):
"""A list of events generated during the workflow execution in non-streaming mode.
@@ -116,10 +125,10 @@ class Workflow(AFBaseModel):
"""
edge_groups: list[EdgeGroup] = Field(
default_factory=list, description="List of edge groups that define the workflow edges"
default_factory=_default_edge_groups, description="List of edge groups that define the workflow edges"
)
executors: dict[str, Executor] = Field(
default_factory=dict, description="Dictionary mapping executor IDs to Executor instances"
default_factory=_default_executors, description="Dictionary mapping executor IDs to Executor instances"
)
start_executor_id: str = Field(min_length=1, description="The ID of the starting executor for the workflow")
max_iterations: int = Field(
@@ -181,21 +190,20 @@ class Workflow(AFBaseModel):
# Ensure WorkflowExecutor instances have their workflow field serialized
if "executors" in data:
executors_data = data["executors"]
executors_data = cast(dict[str, Any], data["executors"])
executor_map: dict[str, Executor] = self.executors
for executor_id, executor_data in executors_data.items():
# Check if this is a WorkflowExecutor that might be missing its workflow field
if (
isinstance(executor_data, dict)
and executor_data.get("type") == "WorkflowExecutor"
and "workflow" not in executor_data
):
# Get the original executor object and serialize its workflow
original_executor = self.executors.get(executor_id)
if original_executor and hasattr(original_executor, "workflow"):
from ._executor import WorkflowExecutor
if isinstance(executor_data, dict):
executor_dict = cast(dict[str, Any], executor_data)
if executor_dict.get("type") == "WorkflowExecutor" and "workflow" not in executor_dict:
# Get the original executor object and serialize its workflow
original_executor = executor_map.get(executor_id)
if original_executor is not None and hasattr(original_executor, "workflow"):
from ._executor import WorkflowExecutor
if isinstance(original_executor, WorkflowExecutor):
executor_data["workflow"] = original_executor.workflow.model_dump(**kwargs)
if isinstance(original_executor, WorkflowExecutor):
executor_dict["workflow"] = original_executor.workflow.model_dump(**kwargs)
return data
@@ -1,82 +0,0 @@
# Copyright (c) Microsoft. All rights reserved.
import importlib
from typing import Any
PACKAGE_NAME = "agent_framework_workflow"
PACKAGE_EXTRA = "workflow"
_IMPORTS = [
"Executor",
"FunctionExecutor",
"executor",
"WorkflowContext",
"__version__",
"events",
"WorkflowBuilder",
"ExecutorCompletedEvent",
"ExecutorEvent",
"ExecutorInvokeEvent",
"RequestInfoEvent",
"WorkflowCompletedEvent",
"WorkflowEvent",
"WorkflowStartedEvent",
"AgentRunEvent",
"AgentRunUpdateEvent",
"handler",
"AgentExecutor",
"MagenticAgentDeltaEvent",
"MagenticCallbackEvent",
"MagenticCallbackMode",
"MagenticAgentMessageEvent",
"MagenticFinalResultEvent",
"MagenticManagerBase",
"MagenticOrchestratorMessageEvent",
"AgentExecutorRequest",
"AgentExecutorResponse",
"RequestInfoExecutor",
"RequestInfoMessage",
"WorkflowRunResult",
"Workflow",
"WorkflowAgent",
"WorkflowViz",
"FileCheckpointStorage",
"ExecutorFailedEvent",
"InMemoryCheckpointStorage",
"CheckpointStorage",
"WorkflowCheckpoint",
"Case",
"Default",
"RequestResponse",
"SubWorkflowRequestInfo",
"SubWorkflowResponse",
"WorkflowExecutor",
"intercepts_request",
"MagenticBuilder",
"PlanReviewDecision",
"PlanReviewReply",
"PlanReviewRequest",
"RequestInfoEvent",
"StandardMagenticManager",
"WorkflowStatusEvent",
"WorkflowRunState",
"WorkflowErrorDetails",
"WorkflowFailedEvent",
"ConcurrentBuilder",
"SequentialBuilder",
]
def __getattr__(name: str) -> Any:
if name in _IMPORTS:
try:
return getattr(importlib.import_module(PACKAGE_NAME), name)
except ModuleNotFoundError as exc:
raise ModuleNotFoundError(
f"The '{PACKAGE_EXTRA}' extra is not installed, "
f"please do `pip install agent-framework[{PACKAGE_EXTRA}]`"
) from exc
raise AttributeError(f"Module {PACKAGE_NAME} has no attribute {name}.")
def __dir__() -> list[str]:
return _IMPORTS
@@ -1,111 +0,0 @@
# Copyright (c) Microsoft. All rights reserved.
from agent_framework_workflow import (
AgentExecutor,
AgentExecutorRequest,
AgentExecutorResponse,
AgentRunEvent,
AgentRunUpdateEvent,
Case,
CheckpointStorage,
ConcurrentBuilder,
Default,
Executor,
ExecutorCompletedEvent,
ExecutorEvent,
ExecutorFailedEvent,
ExecutorInvokeEvent,
FileCheckpointStorage,
FunctionExecutor,
InMemoryCheckpointStorage,
MagenticBuilder,
MagenticCallbackEvent,
MagenticCallbackMode,
MagenticPlanReviewDecision,
MagenticPlanReviewReply,
MagenticPlanReviewRequest,
MagenticProgressLedger,
MagenticProgressLedgerItem,
RequestInfoEvent,
RequestInfoExecutor,
RequestInfoMessage,
RequestResponse,
SequentialBuilder,
StandardMagenticManager,
SubWorkflowRequestInfo,
SubWorkflowResponse,
Workflow,
WorkflowAgent,
WorkflowBuilder,
WorkflowCheckpoint,
WorkflowCompletedEvent,
WorkflowContext,
WorkflowErrorDetails,
WorkflowEvent,
WorkflowExecutor,
WorkflowFailedEvent,
WorkflowRunResult,
WorkflowRunState,
WorkflowStartedEvent,
WorkflowStatusEvent,
WorkflowViz,
__version__,
executor,
handler,
intercepts_request,
)
__all__ = [
"AgentExecutor",
"AgentExecutorRequest",
"AgentExecutorResponse",
"AgentRunEvent",
"AgentRunUpdateEvent",
"Case",
"CheckpointStorage",
"ConcurrentBuilder",
"Default",
"Executor",
"ExecutorCompletedEvent",
"ExecutorEvent",
"ExecutorFailedEvent",
"ExecutorInvokeEvent",
"FileCheckpointStorage",
"FunctionExecutor",
"InMemoryCheckpointStorage",
"MagenticBuilder",
"MagenticCallbackEvent",
"MagenticCallbackMode",
"MagenticPlanReviewDecision",
"MagenticPlanReviewReply",
"MagenticPlanReviewRequest",
"MagenticProgressLedger",
"MagenticProgressLedgerItem",
"RequestInfoEvent",
"RequestInfoExecutor",
"RequestInfoMessage",
"RequestResponse",
"SequentialBuilder",
"StandardMagenticManager",
"SubWorkflowRequestInfo",
"SubWorkflowResponse",
"Workflow",
"WorkflowAgent",
"WorkflowBuilder",
"WorkflowCheckpoint",
"WorkflowCompletedEvent",
"WorkflowContext",
"WorkflowErrorDetails",
"WorkflowEvent",
"WorkflowExecutor",
"WorkflowFailedEvent",
"WorkflowRunResult",
"WorkflowRunState",
"WorkflowStartedEvent",
"WorkflowStatusEvent",
"WorkflowViz",
"__version__",
"executor",
"handler",
"intercepts_request",
]
+4 -4
View File
@@ -33,7 +33,8 @@ dependencies = [
"azure-monitor-opentelemetry>=1.7.0",
"azure-monitor-opentelemetry-exporter>=1.0.0b41",
"opentelemetry-exporter-otlp-proto-grpc>=1.36.0",
"opentelemetry-semantic-conventions-ai>=0.4.13"
"opentelemetry-semantic-conventions-ai>=0.4.13",
"aiofiles>=24.1.0"
]
[project.optional-dependencies]
@@ -43,8 +44,8 @@ azure = [
foundry = [
"agent-framework-foundry"
]
workflow = [
"agent-framework-workflow"
viz = [
"graphviz>=0.20.0",
]
runtime = [
"agent-framework-runtime"
@@ -55,7 +56,6 @@ mem0 = [
all = [
"agent-framework-azure",
"agent-framework-foundry",
"agent-framework-workflow",
"agent-framework-runtime",
"agent-framework-mem0"
]
@@ -5,7 +5,7 @@ import tempfile
from datetime import datetime, timezone
from pathlib import Path
from agent_framework_workflow._checkpoint import (
from agent_framework import (
FileCheckpointStorage,
InMemoryCheckpointStorage,
WorkflowCheckpoint,
@@ -3,13 +3,15 @@
from typing import Any, cast
import pytest
from agent_framework import AgentRunResponse, ChatMessage, Role
from agent_framework_workflow import (
from agent_framework import (
AgentExecutorRequest,
AgentExecutorResponse,
AgentRunResponse,
ChatMessage,
ConcurrentBuilder,
Executor,
Role,
WorkflowCompletedEvent,
WorkflowContext,
handler,
@@ -5,12 +5,19 @@ from typing import Any
from unittest.mock import patch
import pytest
from agent_framework.workflow import Executor, WorkflowContext, handler
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import SimpleSpanProcessor
from opentelemetry.sdk.trace.export.in_memory_span_exporter import InMemorySpanExporter
from agent_framework_workflow._edge import (
from agent_framework import (
Executor,
InProcRunnerContext,
Message,
SharedState,
WorkflowContext,
handler,
)
from agent_framework._workflow._edge import (
Edge,
FanInEdgeGroup,
FanOutEdgeGroup,
@@ -19,10 +26,8 @@ from agent_framework_workflow._edge import (
SwitchCaseEdgeGroupCase,
SwitchCaseEdgeGroupDefault,
)
from agent_framework_workflow._edge_runner import create_edge_runner
from agent_framework_workflow._runner_context import InProcRunnerContext, Message
from agent_framework_workflow._shared_state import SharedState
from agent_framework_workflow._telemetry import EdgeGroupDeliveryStatus, workflow_tracer
from agent_framework._workflow._edge_runner import create_edge_runner
from agent_framework._workflow._telemetry import EdgeGroupDeliveryStatus, workflow_tracer
@pytest.fixture
@@ -34,7 +39,7 @@ def tracing_enabled():
os.environ["AGENT_FRAMEWORK_WORKFLOW_ENABLE_OTEL_DIAGNOSTICS"] = "true"
# Force reload the settings to pick up the environment variable
from agent_framework_workflow._telemetry import WorkflowDiagnosticSettings
from agent_framework._workflow._telemetry import WorkflowDiagnosticSettings
workflow_tracer.settings = WorkflowDiagnosticSettings()
@@ -635,7 +640,7 @@ async def test_source_edge_group_with_selection_func_send_message() -> None:
data = MockMessage(data="test")
message = Message(data=data, source_id=source.id)
with patch("agent_framework_workflow._edge_runner.EdgeRunner._execute_on_target") as mock_send:
with patch("agent_framework._workflow._edge_runner.EdgeRunner._execute_on_target") as mock_send:
success = await edge_runner.send_message(message, shared_state, ctx)
assert success is True
@@ -688,7 +693,7 @@ async def test_source_edge_group_with_selection_func_send_message_with_target()
data = MockMessage(data="test")
message = Message(data=data, source_id=source.id, target_id=target1.id)
with patch("agent_framework_workflow._edge_runner.EdgeRunner._execute_on_target") as mock_send:
with patch("agent_framework._workflow._edge_runner.EdgeRunner._execute_on_target") as mock_send:
success = await edge_runner.send_message(message, shared_state, ctx)
assert success is True
@@ -921,7 +926,7 @@ async def test_target_edge_group_send_message_buffer() -> None:
data = MockMessage(data="test")
with patch("agent_framework_workflow._edge_runner.EdgeRunner._execute_on_target") as mock_send:
with patch("agent_framework._workflow._edge_runner.EdgeRunner._execute_on_target") as mock_send:
success = await edge_runner.send_message(
Message(data=data, source_id=source1.id),
shared_state,
@@ -1203,7 +1208,7 @@ async def test_switch_case_edge_group_send_message() -> None:
data = MockMessage(data=-1)
message = Message(data=data, source_id=source.id)
with patch("agent_framework_workflow._edge_runner.EdgeRunner._execute_on_target") as mock_send:
with patch("agent_framework._workflow._edge_runner.EdgeRunner._execute_on_target") as mock_send:
success = await edge_runner.send_message(message, shared_state, ctx)
assert success is True
@@ -1212,7 +1217,7 @@ async def test_switch_case_edge_group_send_message() -> None:
# Default condition should
data = MockMessage(data=1)
message = Message(data=data, source_id=source.id)
with patch("agent_framework_workflow._edge_runner.EdgeRunner._execute_on_target") as mock_send:
with patch("agent_framework._workflow._edge_runner.EdgeRunner._execute_on_target") as mock_send:
success = await edge_runner.send_message(message, shared_state, ctx)
assert success is True
@@ -1,7 +1,8 @@
# Copyright (c) Microsoft. All rights reserved.
import pytest
from agent_framework.workflow import Executor, WorkflowContext, handler
from agent_framework import Executor, WorkflowContext, handler
def test_executor_without_handlers():
@@ -3,26 +3,24 @@
from collections.abc import AsyncIterable
from typing import Any
from pydantic import PrivateAttr
from agent_framework import (
AgentExecutor,
AgentRunResponse,
AgentRunResponseUpdate,
AgentThread,
BaseAgent,
ChatMessage,
Role,
TextContent,
)
from pydantic import PrivateAttr
from agent_framework_workflow import (
AgentExecutor,
SequentialBuilder,
TextContent,
WorkflowBuilder,
WorkflowCompletedEvent,
WorkflowContext,
handler,
)
from agent_framework_workflow._executor import AgentExecutorResponse, Executor
from agent_framework._workflow._executor import AgentExecutorResponse, Executor
class _SimpleAgent(BaseAgent):
@@ -3,7 +3,8 @@
from typing import Any
import pytest
from agent_framework.workflow import (
from agent_framework import (
FunctionExecutor,
WorkflowBuilder,
WorkflowCompletedEvent,
@@ -5,19 +5,13 @@ from dataclasses import dataclass
from typing import Any
import pytest
from agent_framework import (
AgentRunResponse,
AgentRunResponseUpdate,
ChatMessage,
ChatResponse,
ChatResponseUpdate,
Role,
TextContent,
)
from agent_framework._agents import BaseAgent
from agent_framework._clients import ChatClientProtocol as AFChatClient
from agent_framework_workflow import (
Executor,
MagenticBuilder,
MagenticManagerBase,
@@ -27,12 +21,16 @@ from agent_framework_workflow import (
MagenticProgressLedger,
MagenticProgressLedgerItem,
RequestInfoEvent,
Role,
TextContent,
WorkflowCompletedEvent,
WorkflowContext,
WorkflowEvent, # type: ignore # noqa: E402
handler,
)
from agent_framework_workflow._magentic import (
from agent_framework._agents import BaseAgent
from agent_framework._clients import ChatClientProtocol as AFChatClient
from agent_framework._workflow._magentic import (
MagenticContext,
MagenticStartMessage,
)
@@ -239,7 +237,7 @@ async def test_magentic_orchestrator_round_limit_produces_partial_result():
manager.satisfied_after_signoff = False
wf = MagenticBuilder().participants(agentA=_DummyExec("agentA")).with_standard_manager(manager).build()
from agent_framework_workflow import WorkflowEvent # type: ignore
from agent_framework import WorkflowEvent # type: ignore
events: list[WorkflowEvent] = []
async for ev in wf.run_stream("round limit test"):
@@ -263,7 +261,7 @@ class _DummyExec(Executor):
pass
from agent_framework_workflow import StandardMagenticManager # noqa: E402
from agent_framework import StandardMagenticManager # noqa: E402
class _StubChatClient(AFChatClient):
@@ -415,7 +413,7 @@ async def _collect_agent_responses_setup(participant_obj: object):
captured: list[ChatMessage] = []
async def sink(event) -> None: # type: ignore[no-untyped-def]
from agent_framework_workflow._magentic import MagenticAgentMessageEvent
from agent_framework._workflow._magentic import MagenticAgentMessageEvent
if isinstance(event, MagenticAgentMessageEvent) and event.message is not None:
captured.append(event.message)
@@ -4,12 +4,12 @@ import asyncio
from dataclasses import dataclass
import pytest
from agent_framework.workflow import Executor, WorkflowCompletedEvent, WorkflowContext, WorkflowEvent, handler
from agent_framework_workflow._edge import SingleEdgeGroup
from agent_framework_workflow._runner import Runner
from agent_framework_workflow._runner_context import InProcRunnerContext, RunnerContext
from agent_framework_workflow._shared_state import SharedState
from agent_framework import Executor, WorkflowCompletedEvent, WorkflowContext, WorkflowEvent, handler
from agent_framework._workflow._edge import SingleEdgeGroup
from agent_framework._workflow._runner import Runner
from agent_framework._workflow._runner_context import InProcRunnerContext, RunnerContext
from agent_framework._workflow._shared_state import SharedState
@dataclass
@@ -4,19 +4,17 @@ from collections.abc import AsyncIterable
from typing import Any
import pytest
from agent_framework import (
AgentRunResponse,
AgentRunResponseUpdate,
AgentThread,
BaseAgent,
ChatMessage,
Role,
TextContent,
)
from agent_framework_workflow import (
Executor,
Role,
SequentialBuilder,
TextContent,
WorkflowCompletedEvent,
WorkflowContext,
handler,
@@ -4,9 +4,11 @@ import json
from typing import Any
import pytest
from agent_framework.workflow import Executor, WorkflowBuilder, WorkflowContext, handler
from agent_framework_workflow._edge import (
from agent_framework import Executor, WorkflowBuilder, WorkflowContext, handler
from agent_framework._workflow._edge import (
Case,
Default,
Edge,
FanInEdgeGroup,
FanOutEdgeGroup,
@@ -15,7 +17,7 @@ from agent_framework_workflow._edge import (
SwitchCaseEdgeGroupCase,
SwitchCaseEdgeGroupDefault,
)
from agent_framework_workflow._executor import (
from agent_framework._workflow._executor import (
WorkflowExecutor,
)
@@ -629,8 +631,6 @@ class TestSerializationWorkflowClasses:
def test_comprehensive_edge_groups_workflow_serialization() -> None:
"""Test serialization of a workflow that uses all edge group types: SwitchCase, FanOut, and FanIn."""
from agent_framework_workflow._edge import Case, Default
# Create executors for a comprehensive workflow
router = SampleExecutor(id="router")
processor_a = SampleExecutor(id="proc_a")
@@ -5,7 +5,7 @@ from dataclasses import dataclass
import pytest
from agent_framework_workflow import (
from agent_framework import (
Executor,
WorkflowBuilder,
WorkflowContext,
@@ -37,7 +37,7 @@ class SimpleSubExecutor(Executor):
@handler
async def process(self, request: SimpleRequest, ctx: WorkflowContext[None]) -> None:
"""Process a simple request."""
from agent_framework_workflow import WorkflowCompletedEvent
from agent_framework import WorkflowCompletedEvent
# Just echo back with prefix and complete
response = SimpleResponse(result=f"processed: {request.text}")
@@ -7,7 +7,7 @@ from typing import Any
import pytest
from pydantic import Field
from agent_framework_workflow import (
from agent_framework import (
Executor,
RequestInfoExecutor,
RequestInfoMessage,
@@ -10,13 +10,13 @@ from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import SimpleSpanProcessor
from opentelemetry.sdk.trace.export.in_memory_span_exporter import InMemorySpanExporter
from agent_framework_workflow import WorkflowBuilder
from agent_framework_workflow._executor import Executor, handler
from agent_framework_workflow._runner_context import InProcRunnerContext, Message
from agent_framework_workflow._shared_state import SharedState
from agent_framework_workflow._telemetry import WorkflowTracer, workflow_tracer
from agent_framework_workflow._workflow import Workflow
from agent_framework_workflow._workflow_context import WorkflowContext
from agent_framework import WorkflowBuilder
from agent_framework._workflow._executor import Executor, handler
from agent_framework._workflow._runner_context import InProcRunnerContext, Message
from agent_framework._workflow._shared_state import SharedState
from agent_framework._workflow._telemetry import WorkflowTracer, workflow_tracer
from agent_framework._workflow._workflow import Workflow
from agent_framework._workflow._workflow_context import WorkflowContext
@pytest.fixture
@@ -26,7 +26,7 @@ def tracing_enabled() -> Generator[None, None, None]:
os.environ["AGENT_FRAMEWORK_WORKFLOW_ENABLE_OTEL"] = "true"
# Force reload the settings to pick up the environment variable
from agent_framework_workflow._telemetry import WorkflowDiagnosticSettings
from agent_framework._workflow._telemetry import WorkflowDiagnosticSettings
workflow_tracer.settings = WorkflowDiagnosticSettings()
@@ -153,7 +153,7 @@ async def test_workflow_tracer_configuration() -> None:
os.environ["AGENT_FRAMEWORK_WORKFLOW_ENABLE_OTEL"] = "true"
# Force reload the settings to pick up the environment variable
from agent_framework_workflow._telemetry import WorkflowDiagnosticSettings
from agent_framework._workflow._telemetry import WorkflowDiagnosticSettings
tracer.settings = WorkflowDiagnosticSettings()
@@ -5,7 +5,7 @@ from typing import Any
import pytest
from agent_framework_workflow import (
from agent_framework import (
EdgeDuplicationError,
Executor,
GraphConnectivityError,
@@ -17,8 +17,8 @@ from agent_framework_workflow import (
handler,
validate_workflow_graph,
)
from agent_framework_workflow._edge import SingleEdgeGroup
from agent_framework_workflow._validation import HandlerOutputAnnotationError
from agent_framework._workflow._edge import SingleEdgeGroup
from agent_framework._workflow._validation import HandlerOutputAnnotationError
class StringExecutor(Executor):
@@ -3,9 +3,8 @@
"""Tests for the workflow visualization module."""
import pytest
from agent_framework.workflow import Executor, WorkflowBuilder, WorkflowContext, WorkflowViz, handler
from agent_framework_workflow import WorkflowExecutor
from agent_framework import Executor, WorkflowBuilder, WorkflowContext, WorkflowExecutor, WorkflowViz, handler
class MockExecutor(Executor):
@@ -5,9 +5,11 @@ from dataclasses import dataclass
from typing import Any
import pytest
from agent_framework.workflow import (
from agent_framework import (
Executor,
FileCheckpointStorage,
Message,
RequestInfoEvent,
RequestInfoExecutor,
RequestInfoMessage,
@@ -19,8 +21,6 @@ from agent_framework.workflow import (
handler,
)
from agent_framework_workflow import Message
@dataclass
class NumberMessage:
@@ -381,7 +381,7 @@ async def test_workflow_run_stream_from_checkpoint_with_external_storage(simple_
storage = FileCheckpointStorage(temp_dir)
# Create a test checkpoint manually in storage
from agent_framework_workflow._checkpoint import WorkflowCheckpoint
from agent_framework import WorkflowCheckpoint
test_checkpoint = WorkflowCheckpoint(
workflow_id="test-workflow",
@@ -418,7 +418,7 @@ async def test_workflow_run_from_checkpoint_non_streaming(simple_executor: Execu
storage = FileCheckpointStorage(temp_dir)
# Create a test checkpoint manually in storage
from agent_framework_workflow._checkpoint import WorkflowCheckpoint
from agent_framework import WorkflowCheckpoint
test_checkpoint = WorkflowCheckpoint(
workflow_id="test-workflow",
@@ -451,7 +451,7 @@ async def test_workflow_run_stream_from_checkpoint_with_responses(simple_executo
storage = FileCheckpointStorage(temp_dir)
# Create a test checkpoint manually in storage
from agent_framework_workflow._checkpoint import WorkflowCheckpoint
from agent_framework import WorkflowCheckpoint
test_checkpoint = WorkflowCheckpoint(
workflow_id="test-workflow",
@@ -561,7 +561,7 @@ async def test_workflow_multiple_runs_no_state_collision():
async def test_comprehensive_edge_groups_workflow():
"""Test a workflow that uses SwitchCaseEdgeGroup, FanOutEdgeGroup, and FanInEdgeGroup."""
from agent_framework_workflow._edge import Case, Default
from agent_framework import Case, Default
# Create 6 executors for different roles with different increment values
router = IncrementExecutor(id="router", limit=1000, increment=1) # Increment by 1
@@ -668,7 +668,7 @@ async def test_workflow_with_simple_cycle_and_exit_condition():
# Verify cycling occurred (should have events from both executors)
# Check for ExecutorInvokeEvent and ExecutorCompletedEvent types that have executor_id
from agent_framework_workflow._events import ExecutorCompletedEvent, ExecutorInvokeEvent
from agent_framework import ExecutorCompletedEvent, ExecutorInvokeEvent
executor_events = [e for e in events if isinstance(e, (ExecutorInvokeEvent, ExecutorCompletedEvent))]
executor_ids = {e.executor_id for e in executor_events}
@@ -4,21 +4,20 @@ import uuid
from typing import Any
import pytest
from agent_framework import (
AgentRunResponse,
AgentRunResponseUpdate,
AgentRunUpdateEvent,
ChatMessage,
Executor,
FunctionResultContent,
RequestInfoExecutor,
RequestInfoMessage,
Role,
TextContent,
UsageContent,
UsageDetails,
)
from agent_framework.workflow import (
AgentRunUpdateEvent,
Executor,
RequestInfoExecutor,
RequestInfoMessage,
WorkflowAgent,
WorkflowBuilder,
WorkflowContext,
@@ -4,8 +4,20 @@ from dataclasses import dataclass
from typing import Any
import pytest
from agent_framework import AgentRunResponse, AgentRunResponseUpdate, AgentThread, BaseAgent, ChatMessage, Role
from agent_framework.workflow import AgentExecutor, Executor, WorkflowBuilder, WorkflowContext, handler
from agent_framework import (
AgentExecutor,
AgentRunResponse,
AgentRunResponseUpdate,
AgentThread,
BaseAgent,
ChatMessage,
Executor,
Role,
WorkflowBuilder,
WorkflowContext,
handler,
)
class DummyAgent(BaseAgent):
@@ -1,12 +1,15 @@
# Copyright (c) Microsoft. All rights reserved.
import pytest
from agent_framework.workflow import (
from agent_framework import (
Executor,
ExecutorFailedEvent,
InProcRunnerContext,
RequestInfoEvent,
RequestInfoExecutor,
RequestInfoMessage,
SharedState,
Workflow,
WorkflowBuilder,
WorkflowCompletedEvent,
@@ -17,10 +20,7 @@ from agent_framework.workflow import (
WorkflowStatusEvent,
handler,
)
from agent_framework_workflow import InProcRunnerContext
from agent_framework_workflow._shared_state import SharedState
from agent_framework_workflow._workflow_context import WorkflowContext as WFContext
from agent_framework import WorkflowContext as WFContext
class FailingExecutor(Executor):
@@ -0,0 +1,6 @@
# Copyright (c) Microsoft. All rights reserved.
def test_sample():
"""Sample test so test task doesn't stop due to no tests."""
assert True
-21
View File
@@ -1,21 +0,0 @@
MIT License
Copyright (c) Microsoft Corporation.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE
-9
View File
@@ -1,9 +0,0 @@
# Get Started with Microsoft Agent Framework Workflow
Please install this package as the extra for `agent-framework`:
```bash
pip install agent-framework[workflow]
```
and see the [README](https://github.com/microsoft/agent-framework/tree/main/python/README.md) for more information.
-91
View File
@@ -1,91 +0,0 @@
[project]
name = "agent-framework-workflow"
description = "Workflow integration for Microsoft Agent Framework."
authors = [{ name = "Microsoft", email = "SK-Support@microsoft.com"}]
readme = "README.md"
requires-python = ">=3.10"
version = "0.1.0b1"
license-files = ["LICENSE"]
urls.homepage = "https://learn.microsoft.com/en-us/semantic-kernel/overview/"
urls.source = "https://github.com/microsoft/agent-framework/tree/main/python"
urls.release_notes = "https://github.com/microsoft/agent-framework/releases?q=tag%3Apython-1&expanded=true"
urls.issues = "https://github.com/microsoft/agent-framework/issues"
classifiers = [
"License :: OSI Approved :: MIT License",
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Framework :: Pydantic :: 2",
"Typing :: Typed",
]
dependencies = [
"agent-framework",
]
[project.optional-dependencies]
viz = [
"graphviz>=0.20.0",
]
[tool.uv]
prerelease = "if-necessary-or-explicit"
environments = [
"sys_platform == 'darwin'",
"sys_platform == 'linux'",
"sys_platform == 'win32'"
]
[tool.uv-dynamic-versioning]
fallback-version = "0.0.0"
[tool.pytest.ini_options]
testpaths = 'tests'
addopts = "-ra -q -r fEX"
asyncio_mode = "auto"
asyncio_default_fixture_loop_scope = "function"
filterwarnings = []
timeout = 120
[tool.ruff]
extend = "../../pyproject.toml"
[tool.coverage.run]
omit = [
"**/__init__.py"
]
[tool.pyright]
extend = "../../pyproject.toml"
exclude = ['tests']
[tool.mypy]
plugins = ['pydantic.mypy']
strict = true
python_version = "3.10"
ignore_missing_imports = true
disallow_untyped_defs = true
no_implicit_optional = true
check_untyped_defs = true
warn_return_any = true
show_error_codes = true
warn_unused_ignores = false
disallow_incomplete_defs = true
disallow_untyped_decorators = true
[tool.bandit]
targets = ["agent_framework_workflow"]
exclude_dirs = ["tests"]
[tool.poe]
executor.type = "uv"
include = "../../shared_tasks.toml"
[tool.poe.tasks]
mypy = "mypy --config-file $POE_ROOT/pyproject.toml agent_framework_workflow"
test = "pytest --cov=agent_framework_workflow --cov-report=term-missing:skip-covered tests"
[build-system]
requires = ["flit-core >= 3.9,<4.0"]
build-backend = "flit_core.buildapi"