mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
a2856d3b92
* restructure: Python samples into progressive 01-05 layout - 01-get-started/: 6 numbered steps (hello agent → hosting) - 02-agents/: all agent concept samples (tools, middleware, providers, etc.) - 03-workflows/: ALL existing workflow samples preserved as-is - 04-hosting/: azure-functions, durabletask, a2a - 05-end-to-end/: demos, evaluation, hosted agents - Old files moved to _to_delete/ for review - Added AGENTS.md with structure documentation - autogen-migration/ and semantic-kernel-migration/ preserved at root * fix: switch to AzureOpenAI Foundry, fix CI failures - Switch all 01-get-started samples to AzureOpenAIResponsesClient with Azure AI Foundry project endpoint (AZURE_AI_PROJECT_ENDPOINT + AZURE_OPENAI_RESPONSES_DEPLOYMENT_NAME + AzureCliCredential) - Add _to_delete/ and 05-end-to-end/ to pyrightconfig.samples.json excludes - Fix test paths in packages/ that referenced old getting_started/ dirs: durabletask conftest + streaming test, azurefunctions conftest, devui conftest + capture_messages + openai_sdk_integration - Fix workflow_as_agent_human_in_the_loop.py import (sibling import) - Update hosting READMEs and tool comment paths - Replace root README.md with new structure overview - Update AGENTS.md to document Azure OpenAI Foundry as default provider * cleanup: remove _to_delete folder, copy resource files to active dirs All files in _to_delete/ were either: - Exact duplicates of files in the new structure (240 files) - Same file with only comment path updates (100 files) - One import-fix diff (workflow_as_agent_human_in_the_loop.py) - One superseded minimal_sample.py Resource files (sample.pdf, countries.json, employees.pdf, weather.json) copied to 02-agents/sample_assets/ and 02-agents/resources/ since active samples reference them. * fix: address PR review comments, centralize resources, remove root duplicates - Fix type annotation in 04_memory.py (string union -> proper types) - Fix old sample paths in observability files - Fix grammar/spelling in observability samples - Move sample_assets/ and resources/ to shared/ folder - Remove 8 duplicate observability files from 02-agents root - Update resource path references in multimodal_input and provider samples * fix: update broken links from old getting_started paths to new structure - Update relative paths in READMEs: getting_started/ → 01-get-started/, 02-agents/, 03-workflows/, 04-hosting/, 05-end-to-end/ - Fix absolute GitHub URLs in package READMEs - Fix broken link in ollama package README * fix: convert absolute GitHub URLs to relative paths for link checker Absolute URLs to python/samples/ on main branch 404 until PR merges. Converted to relative paths that linkspector can verify locally. * fix: update link for handoff sample moved to orchestrations/ * fix: update chatkit-integration README path from demos/ to 05-end-to-end/ * fix: update broken links in orchestrations README to match flat directory structure
167 lines
5.6 KiB
Python
167 lines
5.6 KiB
Python
# Copyright (c) Microsoft. All rights reserved.
|
|
|
|
import asyncio
|
|
import os
|
|
from dataclasses import dataclass
|
|
|
|
from agent_framework import (
|
|
AgentExecutor,
|
|
AgentExecutorRequest,
|
|
AgentExecutorResponse,
|
|
Executor,
|
|
Message,
|
|
WorkflowBuilder,
|
|
WorkflowContext,
|
|
WorkflowViz,
|
|
handler,
|
|
)
|
|
from agent_framework.azure import AzureOpenAIResponsesClient
|
|
from azure.identity import AzureCliCredential
|
|
from typing_extensions import Never
|
|
|
|
"""
|
|
Sample: Concurrent (Fan-out/Fan-in) with Agents + Visualization
|
|
|
|
What it does:
|
|
- Fan-out: dispatch the same prompt to multiple domain agents (research, marketing, legal).
|
|
- Fan-in: aggregate their responses into one consolidated output.
|
|
- Visualization: generate Mermaid and GraphViz representations via `WorkflowViz` and optionally export SVG.
|
|
|
|
Prerequisites:
|
|
- AZURE_AI_PROJECT_ENDPOINT must be your Azure AI Foundry Agent Service (V2) project endpoint.
|
|
- Azure AI/ Azure OpenAI for `AzureOpenAIResponsesClient` agents.
|
|
- Authentication via `azure-identity` — uses `AzureCliCredential()` (run `az login`).
|
|
- For visualization export: `pip install graphviz>=0.20.0` and install GraphViz binaries.
|
|
"""
|
|
|
|
|
|
class DispatchToExperts(Executor):
|
|
"""Dispatches the incoming prompt to all expert agent executors (fan-out)."""
|
|
|
|
@handler
|
|
async def dispatch(self, prompt: str, ctx: WorkflowContext[AgentExecutorRequest]) -> None:
|
|
# Wrap the incoming prompt as a user message for each expert and request a response.
|
|
initial_message = Message("user", text=prompt)
|
|
await ctx.send_message(AgentExecutorRequest(messages=[initial_message], should_respond=True))
|
|
|
|
|
|
@dataclass
|
|
class AggregatedInsights:
|
|
"""Structured output from the aggregator."""
|
|
|
|
research: str
|
|
marketing: str
|
|
legal: str
|
|
|
|
|
|
class AggregateInsights(Executor):
|
|
"""Aggregates expert agent responses into a single consolidated result (fan-in)."""
|
|
|
|
@handler
|
|
async def aggregate(self, results: list[AgentExecutorResponse], ctx: WorkflowContext[Never, str]) -> None:
|
|
# Map responses to text by executor id for a simple, predictable demo.
|
|
by_id: dict[str, str] = {}
|
|
for r in results:
|
|
# AgentExecutorResponse.agent_response.text contains concatenated assistant text
|
|
by_id[r.executor_id] = r.agent_response.text
|
|
|
|
research_text = by_id.get("researcher", "")
|
|
marketing_text = by_id.get("marketer", "")
|
|
legal_text = by_id.get("legal", "")
|
|
|
|
aggregated = AggregatedInsights(
|
|
research=research_text,
|
|
marketing=marketing_text,
|
|
legal=legal_text,
|
|
)
|
|
|
|
# Provide a readable, consolidated string as the final workflow result.
|
|
consolidated = (
|
|
"Consolidated Insights\n"
|
|
"====================\n\n"
|
|
f"Research Findings:\n{aggregated.research}\n\n"
|
|
f"Marketing Angle:\n{aggregated.marketing}\n\n"
|
|
f"Legal/Compliance Notes:\n{aggregated.legal}\n"
|
|
)
|
|
|
|
await ctx.yield_output(consolidated)
|
|
|
|
|
|
async def main() -> None:
|
|
"""Build and run the concurrent workflow with visualization."""
|
|
|
|
# Create agent instances
|
|
researcher = AgentExecutor(
|
|
AzureOpenAIResponsesClient(
|
|
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
|
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
|
credential=AzureCliCredential(),
|
|
).as_agent(
|
|
instructions=(
|
|
"You're an expert market and product researcher. Given a prompt, provide concise, factual insights,"
|
|
" opportunities, and risks."
|
|
),
|
|
name="researcher",
|
|
)
|
|
)
|
|
|
|
marketer = AgentExecutor(
|
|
AzureOpenAIResponsesClient(
|
|
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
|
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
|
credential=AzureCliCredential(),
|
|
).as_agent(
|
|
instructions=(
|
|
"You're a creative marketing strategist. Craft compelling value propositions and target messaging"
|
|
" aligned to the prompt."
|
|
),
|
|
name="marketer",
|
|
)
|
|
)
|
|
|
|
legal = AgentExecutor(
|
|
AzureOpenAIResponsesClient(
|
|
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
|
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
|
credential=AzureCliCredential(),
|
|
).as_agent(
|
|
instructions=(
|
|
"You're a cautious legal/compliance reviewer. Highlight constraints, disclaimers, and policy concerns"
|
|
" based on the prompt."
|
|
),
|
|
name="legal",
|
|
)
|
|
)
|
|
|
|
# Create executor instances
|
|
dispatcher = DispatchToExperts(id="dispatcher")
|
|
aggregator = AggregateInsights(id="aggregator")
|
|
|
|
# Build a simple fan-out/fan-in workflow
|
|
workflow = (
|
|
WorkflowBuilder(start_executor=dispatcher)
|
|
.add_fan_out_edges(dispatcher, [researcher, marketer, legal])
|
|
.add_fan_in_edges([researcher, marketer, legal], aggregator)
|
|
.build()
|
|
)
|
|
|
|
# Generate workflow visualization
|
|
print("Generating workflow visualization...")
|
|
viz = WorkflowViz(workflow)
|
|
# Print out the mermaid string.
|
|
print("Mermaid string: \n=======")
|
|
print(viz.to_mermaid())
|
|
print("=======")
|
|
# Print out the DiGraph string with internal executors.
|
|
print("DiGraph string: \n=======")
|
|
print(viz.to_digraph(include_internal_executors=True))
|
|
print("=======")
|
|
|
|
# Export the DiGraph visualization as SVG.
|
|
svg_file = viz.export(format="svg")
|
|
print(f"SVG file saved to: {svg_file}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
asyncio.run(main())
|