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
@@ -0,0 +1,87 @@
# Copyright (c) Microsoft. All rights reserved.
import asyncio
import os
from typing import Any
from agent_framework_workflow import Executor, WorkflowBuilder, WorkflowContext, handler
"""Basic tracing workflow sample.
Sample: Workflow Tracing basics
A minimal two executor workflow demonstrates built in OpenTelemetry spans when diagnostics are enabled.
The sample raises an error if tracing is not configured.
Purpose:
- Require diagnostics by checking AGENT_FRAMEWORK_WORKFLOW_ENABLE_OTEL_DIAGNOSTICS and wiring a console exporter.
- Show the span categories produced by a simple graph:
- workflow.build (events: build.started, build.validation_completed, build.completed, edge_group.process)
- workflow.run (events: workflow.started, workflow.completed or workflow.error)
- executor.process (for each executor invocation)
- message.send (for each outbound message)
- Provide a tiny flow that is easy to run and reason about: uppercase then print.
Prerequisites:
- No external services required for the workflow itself.
- To print spans to the console, install the OpenTelemetry SDK: pip install opentelemetry-sdk
- Enable diagnostics:
configure your .env file with `AGENT_FRAMEWORK_WORKFLOW_ENABLE_OTEL_DIAGNOSTICS=true` or run:
export AGENT_FRAMEWORK_WORKFLOW_ENABLE_OTEL_DIAGNOSTICS=true
"""
def _ensure_tracing_configured() -> None:
"""Fail fast unless diagnostics are enabled and the SDK is present.
If the env var is set, attach a ConsoleSpanExporter so spans print to stdout.
"""
env = os.getenv("AGENT_FRAMEWORK_WORKFLOW_ENABLE_OTEL_DIAGNOSTICS", "").lower()
if env not in {"1", "true", "yes"}:
raise RuntimeError(
"Tracing diagnostics are disabled. Set AGENT_FRAMEWORK_WORKFLOW_ENABLE_OTEL_DIAGNOSTICS=true "
"and rerun the sample."
)
try:
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import ConsoleSpanExporter, SimpleSpanProcessor
except Exception as exc: # pragma: no cover
raise RuntimeError("OpenTelemetry SDK not found. Install it with: pip install opentelemetry-sdk") from exc
provider = TracerProvider()
provider.add_span_processor(SimpleSpanProcessor(ConsoleSpanExporter()))
trace.set_tracer_provider(provider)
class StartExecutor(Executor):
@handler # type: ignore[misc]
async def handle_input(self, message: str, ctx: WorkflowContext[str]) -> None:
# Transform and forward downstream. This produces executor.process and message.send spans.
await ctx.send_message(message.upper())
class EndExecutor(Executor):
@handler # type: ignore[misc]
async def handle_final(self, message: str, ctx: WorkflowContext[Any]) -> None:
# Sink executor. The framework emits WorkflowCompletedEvent automatically after this handler returns.
print(f"Final result: {message}")
async def main() -> None:
_ensure_tracing_configured() # Enforce tracing configuration before building or running the workflow.
# Build a two node graph: StartExecutor -> EndExecutor. The builder emits a workflow.build span.
workflow = (
WorkflowBuilder()
.add_edge(StartExecutor(id="start"), EndExecutor(id="end"))
.set_start_executor("start") # set_start_executor accepts an executor id string or the instance
.build()
) # workflow.build span emitted here
# Run once with a simple payload. You should see workflow.run plus executor and message spans.
await workflow.run("hello tracing") # workflow.run + executor.process and message.send spans
if __name__ == "__main__": # pragma: no cover
asyncio.run(main())