mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
Python: DevUI improvements. (#1091)
* enable deeplinking in ui, add agent details to entity info, add usage data, add middleware example in samples and foundry agent. * update ui build * Update python/packages/devui/frontend/src/components/workflow/workflow-input-form.tsx Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update python/packages/devui/pyproject.toml Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update python/packages/devui/pyproject.toml Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * imporove mapping for agent nodes and serialiation for agent run events * lint fixes * update pyproj toml and ui updates --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
Unverified
parent
61ac6d43b2
commit
01f438d710
@@ -2,12 +2,73 @@
|
||||
"""Sample weather agent for Agent Framework Debug UI."""
|
||||
|
||||
import os
|
||||
from collections.abc import Awaitable, Callable
|
||||
from typing import Annotated
|
||||
|
||||
from agent_framework import ChatAgent
|
||||
from agent_framework import (
|
||||
ChatAgent,
|
||||
ChatContext,
|
||||
ChatMessage,
|
||||
ChatResponse,
|
||||
FunctionInvocationContext,
|
||||
Role,
|
||||
chat_middleware,
|
||||
function_middleware,
|
||||
)
|
||||
from agent_framework.azure import AzureOpenAIChatClient
|
||||
|
||||
|
||||
@chat_middleware
|
||||
async def security_filter_middleware(
|
||||
context: ChatContext,
|
||||
next: Callable[[ChatContext], Awaitable[None]],
|
||||
) -> None:
|
||||
"""Chat middleware that blocks requests containing sensitive information."""
|
||||
# Block requests with sensitive information
|
||||
blocked_terms = ["password", "secret", "api_key", "token"]
|
||||
|
||||
for message in context.messages:
|
||||
if message.text:
|
||||
message_lower = message.text.lower()
|
||||
for term in blocked_terms:
|
||||
if term in message_lower:
|
||||
# Override the response without calling the LLM
|
||||
context.result = ChatResponse(
|
||||
messages=[
|
||||
ChatMessage(
|
||||
role=Role.ASSISTANT,
|
||||
text=(
|
||||
"I cannot process requests containing sensitive information. "
|
||||
"Please rephrase your question without including passwords, secrets, "
|
||||
"or other sensitive data."
|
||||
),
|
||||
)
|
||||
]
|
||||
)
|
||||
return
|
||||
|
||||
await next(context)
|
||||
|
||||
|
||||
@function_middleware
|
||||
async def atlantis_location_filter_middleware(
|
||||
context: FunctionInvocationContext,
|
||||
next: Callable[[FunctionInvocationContext], Awaitable[None]],
|
||||
) -> None:
|
||||
"""Function middleware that blocks weather requests for Atlantis."""
|
||||
# Check if location parameter is "atlantis"
|
||||
location = getattr(context.arguments, "location", None)
|
||||
if location and location.lower() == "atlantis":
|
||||
context.result = (
|
||||
"Blocked! Hold up right there!! Tell the user that "
|
||||
"'Atlantis is a special place, we must never ask about the weather there!!'"
|
||||
)
|
||||
context.terminate = True
|
||||
return
|
||||
|
||||
await next(context)
|
||||
|
||||
|
||||
def get_weather(
|
||||
location: Annotated[str, "The location to get the weather for."],
|
||||
) -> str:
|
||||
@@ -46,6 +107,7 @@ agent = ChatAgent(
|
||||
api_key=os.environ.get("AZURE_OPENAI_API_KEY", ""),
|
||||
),
|
||||
tools=[get_weather, get_forecast],
|
||||
middleware=[security_filter_middleware, atlantis_location_filter_middleware],
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
"""Sequential Agents Workflow - Writer → Reviewer."""
|
||||
|
||||
from .workflow import workflow
|
||||
|
||||
__all__ = ["workflow"]
|
||||
@@ -0,0 +1,167 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
"""Agent Workflow - Content Review with Quality Routing.
|
||||
|
||||
This sample demonstrates:
|
||||
- Using agents directly as executors
|
||||
- Conditional routing based on structured outputs
|
||||
- Quality-based workflow paths with convergence
|
||||
|
||||
Use case: Content creation with automated review.
|
||||
Writer creates content, Reviewer evaluates quality:
|
||||
- High quality (score >= 80): → Publisher → Summarizer
|
||||
- Low quality (score < 80): → Editor → Publisher → Summarizer
|
||||
Both paths converge at Summarizer for final report.
|
||||
"""
|
||||
|
||||
import os
|
||||
from typing import Any
|
||||
|
||||
from agent_framework import AgentExecutorResponse, WorkflowBuilder
|
||||
from agent_framework.azure import AzureOpenAIChatClient
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
# Define structured output for review results
|
||||
class ReviewResult(BaseModel):
|
||||
"""Review evaluation with scores and feedback."""
|
||||
|
||||
score: int # Overall quality score (0-100)
|
||||
feedback: str # Concise, actionable feedback
|
||||
clarity: int # Clarity score (0-100)
|
||||
completeness: int # Completeness score (0-100)
|
||||
accuracy: int # Accuracy score (0-100)
|
||||
structure: int # Structure score (0-100)
|
||||
|
||||
|
||||
# Condition function: route to editor if score < 80
|
||||
def needs_editing(message: Any) -> bool:
|
||||
"""Check if content needs editing based on review score."""
|
||||
if not isinstance(message, AgentExecutorResponse):
|
||||
return False
|
||||
try:
|
||||
review = ReviewResult.model_validate_json(message.agent_run_response.text)
|
||||
return review.score < 80
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
# Condition function: content is approved (score >= 80)
|
||||
def is_approved(message: Any) -> bool:
|
||||
"""Check if content is approved (high quality)."""
|
||||
if not isinstance(message, AgentExecutorResponse):
|
||||
return True
|
||||
try:
|
||||
review = ReviewResult.model_validate_json(message.agent_run_response.text)
|
||||
return review.score >= 80
|
||||
except Exception:
|
||||
return True
|
||||
|
||||
|
||||
# Create Azure OpenAI chat client
|
||||
chat_client = AzureOpenAIChatClient(api_key=os.environ.get("AZURE_OPENAI_API_KEY", ""))
|
||||
|
||||
# Create Writer agent - generates content
|
||||
writer = chat_client.create_agent(
|
||||
name="Writer",
|
||||
instructions=(
|
||||
"You are an excellent content writer. "
|
||||
"Create clear, engaging content based on the user's request. "
|
||||
"Focus on clarity, accuracy, and proper structure."
|
||||
),
|
||||
)
|
||||
|
||||
# Create Reviewer agent - evaluates and provides structured feedback
|
||||
reviewer = chat_client.create_agent(
|
||||
name="Reviewer",
|
||||
instructions=(
|
||||
"You are an expert content reviewer. "
|
||||
"Evaluate the writer's content based on:\n"
|
||||
"1. Clarity - Is it easy to understand?\n"
|
||||
"2. Completeness - Does it fully address the topic?\n"
|
||||
"3. Accuracy - Is the information correct?\n"
|
||||
"4. Structure - Is it well-organized?\n\n"
|
||||
"Return a JSON object with:\n"
|
||||
"- score: overall quality (0-100)\n"
|
||||
"- feedback: concise, actionable feedback\n"
|
||||
"- clarity, completeness, accuracy, structure: individual scores (0-100)"
|
||||
),
|
||||
response_format=ReviewResult,
|
||||
)
|
||||
|
||||
# Create Editor agent - improves content based on feedback
|
||||
editor = chat_client.create_agent(
|
||||
name="Editor",
|
||||
instructions=(
|
||||
"You are a skilled editor. "
|
||||
"You will receive content along with review feedback. "
|
||||
"Improve the content by addressing all the issues mentioned in the feedback. "
|
||||
"Maintain the original intent while enhancing clarity, completeness, accuracy, and structure."
|
||||
),
|
||||
)
|
||||
|
||||
# Create Publisher agent - formats content for publication
|
||||
publisher = chat_client.create_agent(
|
||||
name="Publisher",
|
||||
instructions=(
|
||||
"You are a publishing agent. "
|
||||
"You receive either approved content or edited content. "
|
||||
"Format it for publication with proper headings and structure."
|
||||
),
|
||||
)
|
||||
|
||||
# Create Summarizer agent - creates final publication report
|
||||
summarizer = chat_client.create_agent(
|
||||
name="Summarizer",
|
||||
instructions=(
|
||||
"You are a summarizer agent. "
|
||||
"Create a final publication report that includes:\n"
|
||||
"1. A brief summary of the published content\n"
|
||||
"2. The workflow path taken (direct approval or edited)\n"
|
||||
"3. Key highlights and takeaways\n"
|
||||
"Keep it concise and professional."
|
||||
),
|
||||
)
|
||||
|
||||
# Build workflow with branching and convergence:
|
||||
# Writer → Reviewer → [branches]:
|
||||
# - If score >= 80: → Publisher → Summarizer (direct approval path)
|
||||
# - If score < 80: → Editor → Publisher → Summarizer (improvement path)
|
||||
# Both paths converge at Summarizer for final report
|
||||
workflow = (
|
||||
WorkflowBuilder()
|
||||
.set_start_executor(writer)
|
||||
.add_edge(writer, reviewer)
|
||||
# Branch 1: High quality (>= 80) goes directly to publisher
|
||||
.add_edge(reviewer, publisher, condition=is_approved)
|
||||
# Branch 2: Low quality (< 80) goes to editor first, then publisher
|
||||
.add_edge(reviewer, editor, condition=needs_editing)
|
||||
.add_edge(editor, publisher)
|
||||
# Both paths converge: Publisher → Summarizer
|
||||
.add_edge(publisher, summarizer)
|
||||
.build()
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
"""Launch the branching workflow in DevUI."""
|
||||
import logging
|
||||
|
||||
from agent_framework.devui import serve
|
||||
|
||||
logging.basicConfig(level=logging.INFO, format="%(message)s")
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
logger.info("Starting Agent Workflow (Content Review with Quality Routing)")
|
||||
logger.info("Available at: http://localhost:8093")
|
||||
logger.info("\nThis workflow demonstrates:")
|
||||
logger.info("- Conditional routing based on structured outputs")
|
||||
logger.info("- Path 1 (score >= 80): Reviewer → Publisher → Summarizer")
|
||||
logger.info("- Path 2 (score < 80): Reviewer → Editor → Publisher → Summarizer")
|
||||
logger.info("- Both paths converge at Summarizer for final report")
|
||||
|
||||
serve(entities=[workflow], port=8093, auto_open=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user