From 86a2a8f850e65f33e6a2fe5752ed30d146cab319 Mon Sep 17 00:00:00 2001 From: Mrunal Hirve Date: Fri, 7 Nov 2025 21:35:06 -0800 Subject: [PATCH 1/8] Adding Notifications on AF --- python/agent-framework/sample-agent/agent.py | 248 ++++++------ .../sample-agent/agent_interface.py | 3 +- .../sample-agent/host_agent_server.py | 359 ++++++++---------- .../sample-agent/pyproject.toml | 2 +- 4 files changed, 265 insertions(+), 347 deletions(-) diff --git a/python/agent-framework/sample-agent/agent.py b/python/agent-framework/sample-agent/agent.py index 1bdb91f2..0195d9b6 100644 --- a/python/agent-framework/sample-agent/agent.py +++ b/python/agent-framework/sample-agent/agent.py @@ -48,6 +48,9 @@ from local_authentication_options import LocalAuthenticationOptions from microsoft_agents.hosting.core import Authorization, TurnContext +# Notifications +from microsoft_agents_a365.notifications.agent_notification import NotificationTypes + # Observability Components from microsoft_agents_a365.observability.extensions.agentframework.trace_instrumentor import ( AgentFrameworkInstrumentor, @@ -88,6 +91,9 @@ def __init__(self): # Initialize MCP services self._initialize_services() + + # Track if MCP servers have been set up + self.mcp_servers_initialized = False # @@ -111,32 +117,23 @@ def _create_chat_client(self): "AZURE_OPENAI_API_VERSION environment variable is required" ) - logger.info(f"Creating AzureOpenAIChatClient with endpoint: {endpoint}") - logger.info(f"Deployment: {deployment}") - logger.info(f"API Version: {api_version}") - self.chat_client = AzureOpenAIChatClient( endpoint=endpoint, credential=AzureCliCredential(), deployment_name=deployment, api_version=api_version, ) - - logger.info("✅ AzureOpenAIChatClient created successfully") + logger.info("✅ AzureOpenAIChatClient created") def _create_agent(self): """Create the AgentFramework agent with initial configuration""" try: - logger.info("Creating AgentFramework agent...") - self.agent = ChatAgent( chat_client=self.chat_client, instructions="You are a helpful assistant with access to tools.", - tools=[], # Tools will be added dynamically by MCP setup + tools=[], ) - - logger.info("✅ AgentFramework agent created successfully") - + logger.info("✅ AgentFramework agent created") except Exception as e: logger.error(f"Failed to create agent: {e}") raise @@ -149,53 +146,31 @@ def _create_agent(self): # def token_resolver(self, agent_id: str, tenant_id: str) -> str | None: - """ - Token resolver function for Agent 365 Observability exporter. - - Uses the cached agentic token obtained from AGENT_APP.auth.get_token(context, "AGENTIC"). - This is the only valid authentication method for this context. - """ - + """Token resolver for Agent 365 Observability""" try: - logger.info( - f"Token resolver called for agent_id: {agent_id}, tenant_id: {tenant_id}" - ) - - # Use cached agentic token from agent authentication cached_token = get_cached_agentic_token(tenant_id, agent_id) - if cached_token: - logger.info("Using cached agentic token from agent authentication") - return cached_token - else: - logger.warning( - f"No cached agentic token found for agent_id: {agent_id}, tenant_id: {tenant_id}" - ) - return None - + if not cached_token: + logger.warning(f"No cached token for agent {agent_id}") + return cached_token except Exception as e: - logger.error( - f"Error resolving token for agent {agent_id}, tenant {tenant_id}: {e}" - ) + logger.error(f"Error resolving token: {e}") return None def _setup_observability(self): - """ - Configure observability using agent_framework.observability.setup_observability() - """ + """Configure observability""" try: setup_observability() - logger.info("✅ AgentFramework observability configured successfully") + logger.info("✅ Observability configured") except Exception as e: - logger.error(f"❌ Error setting up observability: {e}") + logger.error(f"❌ Observability error: {e}") def _enable_agentframework_instrumentation(self): - """Enable AgentFramework instrumentation for automatic tracing""" + """Enable AgentFramework instrumentation""" try: - # Initialize Agent 365 Observability Wrapper for AgentFramework SDK AgentFrameworkInstrumentor().instrument() - logger.info("✅ AgentFramework instrumentation enabled") + logger.info("✅ Instrumentation enabled") except Exception as e: - logger.warning(f"⚠️ Could not enable AgentFramework instrumentation: {e}") + logger.warning(f"⚠️ Instrumentation failed: {e}") # @@ -205,39 +180,32 @@ def _enable_agentframework_instrumentation(self): # def _initialize_services(self): - """Initialize MCP services and authentication options""" + """Initialize MCP services""" try: - # Create MCP tool registration service self.tool_service = McpToolRegistrationService() - logger.info("✅ AgentFramework MCP tool registration service initialized") + logger.info("✅ MCP tool service initialized") except Exception as e: - logger.warning(f"⚠️ Could not initialize MCP tool service: {e}") + logger.warning(f"⚠️ MCP tool service failed: {e}") self.tool_service = None async def setup_mcp_servers(self, auth: Authorization, context: TurnContext): """Set up MCP server connections""" + if self.mcp_servers_initialized: + return + try: if not self.tool_service: - logger.warning( - "⚠️ MCP tool service not available - skipping MCP server setup" - ) + logger.warning("⚠️ MCP tool service unavailable") return - logger.info("🔍 Starting MCP server setup...") - agent_user_id = os.getenv("AGENT_ID", "user123") use_agentic_auth = os.getenv("USE_AGENTIC_AUTH", "false").lower() == "true" - logger.info(f"🆔 Agent User ID: {agent_user_id}") - logger.info(f"🔐 Using agentic auth: {use_agentic_auth}") - if use_agentic_auth: - logger.info("🔄 Adding tool servers with agentic authentication...") scope = os.getenv("AGENTIC_AUTH_SCOPE") if not scope: - logger.warning( - "⚠️ AGENTIC_AUTH_SCOPE environment variable is not set when USE_AGENTIC_AUTH=true" - ) + logger.warning("⚠️ AGENTIC_AUTH_SCOPE not set") + return return scopes = [scope] authToken = await auth.exchange_token(context, scopes, "AGENTIC") @@ -253,9 +221,6 @@ async def setup_mcp_servers(self, auth: Authorization, context: TurnContext): auth_token=auth_token, ) else: - logger.info( - "🔄 Adding tool servers with bearer token authentication..." - ) self.agent = await self.tool_service.add_tool_servers_to_agent( chat_client=self.chat_client, agent_instructions="You are a helpful assistant with access to tools.", @@ -268,110 +233,125 @@ async def setup_mcp_servers(self, auth: Authorization, context: TurnContext): ) if self.agent: - logger.info("✅ Agent MCP setup completed successfully") + logger.info("✅ MCP setup completed") + self.mcp_servers_initialized = True else: - logger.warning("⚠️ Agent MCP setup returned None") + logger.warning("⚠️ MCP setup failed") except Exception as e: - logger.error(f"Error setting up MCP servers: {e}") - logger.exception("Full error details:") + logger.error(f"MCP setup error: {e}") # # ========================================================================= - # INITIALIZATION AND MESSAGE PROCESSING + # MESSAGE PROCESSING # ========================================================================= # async def initialize(self): - """Initialize the agent and MCP server connections""" - logger.info("Initializing AgentFramework Agent with MCP servers...") - try: - logger.info("Agent initialized successfully") - except Exception as e: - logger.error(f"Failed to initialize agent: {e}") - raise + """Initialize the agent""" + logger.info("Agent initialized") async def process_user_message( self, message: str, auth: Authorization, context: TurnContext ) -> str: """Process user message using the AgentFramework SDK""" try: - # Setup MCP servers await self.setup_mcp_servers(auth, context) - - # Run the agent with the user message result = await self.agent.run(message) - - # Extract the response from the result - if result: - if hasattr(result, "contents"): - return str(result.contents) - elif hasattr(result, "text"): - return str(result.text) - elif hasattr(result, "content"): - return str(result.content) - else: - return str(result) - else: - return "I couldn't process your request at this time." - + return self._extract_result(result) or "I couldn't process your request at this time." except Exception as e: logger.error(f"Error processing message: {e}") return f"Sorry, I encountered an error: {str(e)}" # + # ========================================================================= + # NOTIFICATION HANDLING + # ========================================================================= + # + + async def handle_agent_notification_activity( + self, notification_activity, auth: Authorization, context: TurnContext + ) -> str: + """Handle agent notification activities (email, Word mentions, etc.)""" + try: + notification_type = notification_activity.notification_type + logger.info(f"📬 Processing notification: {notification_type}") + + # Setup MCP servers on first call + await self.setup_mcp_servers(auth, context) + + # Handle Email Notifications + if notification_type == NotificationTypes.EMAIL_NOTIFICATION: + if not hasattr(notification_activity, "email") or not notification_activity.email: + return "I could not find the email notification details." + + email = notification_activity.email + email_body = getattr(email, "html_body", "") or getattr(email, "body", "") + message = f"You have received the following email. Please follow any instructions in it. {email_body}" + + result = await self.agent.run(message) + return self._extract_result(result) or "Email notification processed." + + # Handle Word Comment Notifications + elif notification_type == NotificationTypes.WPX_COMMENT: + if not hasattr(notification_activity, "wpx_comment") or not notification_activity.wpx_comment: + return "I could not find the Word notification details." + + wpx = notification_activity.wpx_comment + doc_id = getattr(wpx, "document_id", "") + comment_id = getattr(wpx, "initiating_comment_id", "") + drive_id = "default" + + # Get Word document content + doc_message = f"You have a new comment on the Word document with id '{doc_id}', comment id '{comment_id}', drive id '{drive_id}'. Please retrieve the Word document as well as the comments and return it in text format." + doc_result = await self.agent.run(doc_message) + word_content = self._extract_result(doc_result) + + # Process the comment with document context + comment_text = notification_activity.text or "" + response_message = f"You have received the following Word document content and comments. Please refer to these when responding to comment '{comment_text}'. {word_content}" + result = await self.agent.run(response_message) + return self._extract_result(result) or "Word notification processed." + + # Generic notification handling + else: + notification_message = notification_activity.text or f"Notification received: {notification_type}" + result = await self.agent.run(notification_message) + return self._extract_result(result) or "Notification processed successfully." + + except Exception as e: + logger.error(f"Error processing notification: {e}") + return f"Sorry, I encountered an error processing the notification: {str(e)}" + + def _extract_result(self, result) -> str: + """Extract text content from agent result""" + if not result: + return "" + if hasattr(result, "contents"): + return str(result.contents) + elif hasattr(result, "text"): + return str(result.text) + elif hasattr(result, "content"): + return str(result.content) + else: + return str(result) + + # + # ========================================================================= # CLEANUP # ========================================================================= # async def cleanup(self) -> None: - """Clean up agent resources and MCP server connections""" + """Clean up agent resources""" try: - logger.info("Cleaning up agent resources...") - - # Cleanup MCP tool service if it exists if hasattr(self, "tool_service") and self.tool_service: - try: - await self.tool_service.cleanup() - logger.info("MCP tool service cleanup completed") - except Exception as cleanup_ex: - logger.warning(f"Error cleaning up MCP tool service: {cleanup_ex}") - + await self.tool_service.cleanup() logger.info("Agent cleanup completed") - except Exception as e: - logger.error(f"Error during cleanup: {e}") + logger.error(f"Cleanup error: {e}") # - - -# ============================================================================= -# MAIN ENTRY POINT -# ============================================================================= -# - - -async def main(): - """Main function to run the AgentFramework Agent with MCP servers""" - try: - # Create and initialize the agent - agent = AgentFrameworkAgent() - await agent.initialize() - - except Exception as e: - logger.error(f"Failed to start agent: {e}") - print(f"Error: {e}") - - finally: - # Cleanup - if "agent" in locals(): - await agent.cleanup() - - -if __name__ == "__main__": - asyncio.run(main()) - -# diff --git a/python/agent-framework/sample-agent/agent_interface.py b/python/agent-framework/sample-agent/agent_interface.py index 6533ef50..f1578702 100644 --- a/python/agent-framework/sample-agent/agent_interface.py +++ b/python/agent-framework/sample-agent/agent_interface.py @@ -48,6 +48,5 @@ def check_agent_inheritance(agent_class) -> bool: if not issubclass(agent_class, AgentInterface): print(f"❌ Agent {agent_class.__name__} does not inherit from AgentInterface") return False - - print(f"✅ Agent {agent_class.__name__} properly inherits from AgentInterface") return True + diff --git a/python/agent-framework/sample-agent/host_agent_server.py b/python/agent-framework/sample-agent/host_agent_server.py index 383f36b4..c1d08ba0 100644 --- a/python/agent-framework/sample-agent/host_agent_server.py +++ b/python/agent-framework/sample-agent/host_agent_server.py @@ -1,20 +1,17 @@ # Copyright (c) Microsoft. All rights reserved. -""" -Generic Agent Host Server -A generic hosting server that can host any agent class that implements the required interface. -""" +"""Generic Agent Host Server - Hosts agents implementing AgentInterface""" +# --- Imports --- import logging import os import socket from os import environ -# Import our agent base class -from agent_interface import AgentInterface, check_agent_inheritance from aiohttp.web import Application, Request, Response, json_response, run_app from aiohttp.web_middlewares import middleware as web_middleware from dotenv import load_dotenv +from agent_interface import AgentInterface, check_agent_inheritance from microsoft_agents.activity import load_configuration_from_env from microsoft_agents.authentication.msal import MsalConnectionManager from microsoft_agents.hosting.aiohttp import ( @@ -22,8 +19,6 @@ jwt_authorization_middleware, start_agent_process, ) - -# Microsoft Agents SDK imports from microsoft_agents.hosting.core import ( AgentApplication, AgentAuthConfiguration, @@ -34,6 +29,11 @@ TurnContext, TurnState, ) +from microsoft_agents_a365.notifications.agent_notification import ( + AgentNotification, + AgentNotificationActivity, + ChannelId, +) from microsoft_agents_a365.observability.core.config import configure from microsoft_agents_a365.observability.core.middleware.baggage_builder import ( BaggageBuilder, @@ -43,31 +43,46 @@ ) from token_cache import cache_agentic_token -# Configure logging +# --- Configuration --- ms_agents_logger = logging.getLogger("microsoft_agents") ms_agents_logger.addHandler(logging.StreamHandler()) ms_agents_logger.setLevel(logging.INFO) +observability_logger = logging.getLogger("microsoft_agents_a365.observability") +observability_logger.setLevel(logging.ERROR) + logger = logging.getLogger(__name__) -# Load configuration load_dotenv() agents_sdk_config = load_configuration_from_env(environ) +# --- Public API --- +def create_and_run_host( + agent_class: type[AgentInterface], *agent_args, **agent_kwargs +): + """Create and run a generic agent host""" + if not check_agent_inheritance(agent_class): + raise TypeError( + f"Agent class {agent_class.__name__} must inherit from AgentInterface" + ) + + configure( + service_name="AgentFrameworkTracingWithAzureOpenAI", + service_namespace="AgentFrameworkTesting", + ) + + host = GenericAgentHost(agent_class, *agent_args, **agent_kwargs) + auth_config = host.create_auth_configuration() + host.start_server(auth_config) + + +# --- Generic Agent Host --- class GenericAgentHost: - """Generic host that can host any agent implementing the AgentInterface""" + """Generic host for agents implementing AgentInterface""" + # --- Initialization --- def __init__(self, agent_class: type[AgentInterface], *agent_args, **agent_kwargs): - """ - Initialize the generic host with an agent class and its initialization parameters. - - Args: - agent_class: The agent class to instantiate (must implement AgentInterface) - *agent_args: Positional arguments to pass to the agent constructor - **agent_kwargs: Keyword arguments to pass to the agent constructor - """ - # Check that the agent inherits from AgentInterface if not check_agent_inheritance(agent_class): raise TypeError( f"Agent class {agent_class.__name__} must inherit from AgentInterface" @@ -78,7 +93,6 @@ def __init__(self, agent_class: type[AgentInterface], *agent_args, **agent_kwarg self.agent_kwargs = agent_kwargs self.agent_instance = None - # Microsoft Agents SDK components self.storage = MemoryStorage() self.connection_manager = MsalConnectionManager(**agents_sdk_config) self.adapter = CloudAdapter(connection_manager=self.connection_manager) @@ -91,164 +105,162 @@ def __init__(self, agent_class: type[AgentInterface], *agent_args, **agent_kwarg authorization=self.authorization, **agents_sdk_config, ) - - # Setup message handlers + self.agent_notification = AgentNotification(self.agent_app) self._setup_handlers() + logger.info("✅ Notification handlers registered successfully") + + # --- Observability --- + async def _setup_observability_token( + self, context: TurnContext, tenant_id: str, agent_id: str + ): + try: + exaau_token = await self.agent_app.auth.exchange_token( + context, + scopes=get_observability_authentication_scope(), + auth_handler_id="AGENTIC", + ) + cache_agentic_token(tenant_id, agent_id, exaau_token.token) + except Exception as e: + logger.warning(f"⚠️ Failed to cache observability token: {e}") + async def _validate_agent_and_setup_context(self, context: TurnContext): + tenant_id = context.activity.recipient.tenant_id + agent_id = context.activity.recipient.agentic_app_id + + if not self.agent_instance: + logger.error("Agent not available") + await context.send_activity("❌ Sorry, the agent is not available.") + return None + + await self._setup_observability_token(context, tenant_id, agent_id) + return tenant_id, agent_id + + # --- Handlers (Messages & Notifications) --- def _setup_handlers(self): - """Setup the Microsoft Agents SDK message handlers""" + """Setup message and notification handlers""" + use_agentic_auth = os.getenv("USE_AGENTIC_AUTH", "false").lower() == "true" + handler = ["AGENTIC"] if use_agentic_auth else None async def help_handler(context: TurnContext, _: TurnState): - """Handle help requests and member additions""" - welcome_message = ( - "👋 **Welcome to Generic Agent Host!**\n\n" - f"I'm powered by: **{self.agent_class.__name__}**\n\n" - "Ask me anything and I'll do my best to help!\n" - "Type '/help' for this message." + await context.send_activity( + f"👋 **Hi there!** I'm **{self.agent_class.__name__}**, your AI assistant.\n\n" + "How can I help you today?" ) - await context.send_activity(welcome_message) - logger.info("📨 Sent help/welcome message") - # Register handlers self.agent_app.conversation_update("membersAdded")(help_handler) self.agent_app.message("/help")(help_handler) - use_agentic_auth = os.getenv("USE_AGENTIC_AUTH", "false").lower() == "true" - handler = ["AGENTIC"] if use_agentic_auth else None - @self.agent_app.activity("message", auth_handlers=handler) async def on_message(context: TurnContext, _: TurnState): - """Handle all messages with the hosted agent""" try: - tenant_id = context.activity.recipient.tenant_id - agent_id = context.activity.recipient.agentic_app_id - with BaggageBuilder().tenant_id(tenant_id).agent_id(agent_id).build(): - # Ensure the agent is available - if not self.agent_instance: - error_msg = "❌ Sorry, the agent is not available." - logger.error(error_msg) - await context.send_activity(error_msg) - return - - exaau_token = await self.agent_app.auth.exchange_token( - context, - scopes=get_observability_authentication_scope(), - auth_handler_id="AGENTIC", - ) - - # Cache the agentic token for observability export - cache_agentic_token(tenant_id, agent_id, exaau_token.token) + result = await self._validate_agent_and_setup_context(context) + if result is None: + return + tenant_id, agent_id = result + with BaggageBuilder().tenant_id(tenant_id).agent_id(agent_id).build(): user_message = context.activity.text or "" - logger.info(f"📨 Processing message: '{user_message}'") - - # Skip empty messages - if not user_message.strip(): + if not user_message.strip() or user_message.strip() == "/help": return - # Skip messages that are handled by other decorators (like /help) - if user_message.strip() == "/help": - return - - # Process with the hosted agent - logger.info(f"🤖 Processing with {self.agent_class.__name__}...") + logger.info(f"📨 {user_message}") response = await self.agent_instance.process_user_message( user_message, self.agent_app.auth, context ) - - # Send response back - logger.info( - f"📤 Sending response: '{response[:100] if len(response) > 100 else response}'" - ) await context.send_activity(response) - logger.info("✅ Response sent successfully to client") - except Exception as e: - error_msg = f"Sorry, I encountered an error: {str(e)}" - logger.error(f"❌ Error processing message: {e}") - await context.send_activity(error_msg) + logger.error(f"❌ Error: {e}") + await context.send_activity(f"Sorry, I encountered an error: {str(e)}") - async def initialize_agent(self): - """Initialize the hosted agent instance""" - if self.agent_instance is None: + @self.agent_notification.on_agent_notification( + channel_id=ChannelId(channel="agents", sub_channel="*"), + auth_handlers=handler, + ) + async def on_notification( + context: TurnContext, + state: TurnState, + notification_activity: AgentNotificationActivity, + ): try: - logger.info(f"🤖 Initializing {self.agent_class.__name__}...") + result = await self._validate_agent_and_setup_context(context) + if result is None: + return + tenant_id, agent_id = result - # Create the agent instance - self.agent_instance = self.agent_class( - *self.agent_args, **self.agent_kwargs - ) + with BaggageBuilder().tenant_id(tenant_id).agent_id(agent_id).build(): + logger.info(f"📬 {notification_activity.notification_type}") + + if not hasattr( + self.agent_instance, "handle_agent_notification_activity" + ): + logger.warning("⚠️ Agent doesn't support notifications") + await context.send_activity( + "This agent doesn't support notification handling yet." + ) + return - # Initialize the agent - await self.agent_instance.initialize() + response = ( + await self.agent_instance.handle_agent_notification_activity( + notification_activity, self.agent_app.auth, context + ) + ) + await context.send_activity(response) - logger.info(f"✅ {self.agent_class.__name__} initialized successfully") except Exception as e: - logger.error( - f"❌ Failed to initialize {self.agent_class.__name__}: {e}" + logger.error(f"❌ Notification error: {e}") + await context.send_activity( + f"Sorry, I encountered an error processing the notification: {str(e)}" ) - raise + # --- Agent Initialization --- + async def initialize_agent(self): + if self.agent_instance is None: + logger.info(f"🤖 Initializing {self.agent_class.__name__}...") + self.agent_instance = self.agent_class(*self.agent_args, **self.agent_kwargs) + await self.agent_instance.initialize() + + # --- Authentication --- def create_auth_configuration(self) -> AgentAuthConfiguration | None: - """Create authentication configuration based on available environment variables.""" client_id = environ.get("CLIENT_ID") tenant_id = environ.get("TENANT_ID") client_secret = environ.get("CLIENT_SECRET") if client_id and tenant_id and client_secret: - logger.info( - "🔒 Using Client Credentials authentication (CLIENT_ID/TENANT_ID provided)" + logger.info("🔒 Using Client Credentials authentication") + return AgentAuthConfiguration( + client_id=client_id, + tenant_id=tenant_id, + client_secret=client_secret, + scopes=["https://api.botframework.com/.default"], ) - try: - return AgentAuthConfiguration( - client_id=client_id, - tenant_id=tenant_id, - client_secret=client_secret, - scopes=["https://api.botframework.com/.default"], - ) - except Exception as e: - logger.error( - f"Failed to create AgentAuthConfiguration, falling back to anonymous: {e}" - ) - return None if environ.get("BEARER_TOKEN"): - logger.info( - "🔑 BEARER_TOKEN present but incomplete app registration; continuing in anonymous dev mode" - ) + logger.info("🔑 Anonymous dev mode") else: - logger.warning("⚠️ No authentication env vars found; running anonymous") - + logger.warning("⚠️ No auth env vars; running anonymous") return None + # --- Server --- def start_server(self, auth_configuration: AgentAuthConfiguration | None = None): - """Start the server using Microsoft Agents SDK""" - async def entry_point(req: Request) -> Response: - agent: AgentApplication = req.app["agent_app"] - adapter: CloudAdapter = req.app["adapter"] - return await start_agent_process(req, agent, adapter) - - async def init_app(app): - await self.initialize_agent() + return await start_agent_process( + req, req.app["agent_app"], req.app["adapter"] + ) - # Health endpoint async def health(_req: Request) -> Response: - status = { - "status": "ok", - "agent_type": self.agent_class.__name__, - "agent_initialized": self.agent_instance is not None, - "auth_mode": "authenticated" if auth_configuration else "anonymous", - } - return json_response(status) - - # Build middleware list + return json_response( + { + "status": "ok", + "agent_type": self.agent_class.__name__, + "agent_initialized": self.agent_instance is not None, + } + ) + middlewares = [] if auth_configuration: middlewares.append(jwt_authorization_middleware) - # Anonymous claims middleware @web_middleware async def anonymous_claims(request, handler): if not auth_configuration: @@ -265,118 +277,45 @@ async def anonymous_claims(request, handler): middlewares.append(anonymous_claims) app = Application(middlewares=middlewares) - logger.info( - "🔒 Auth middleware enabled" - if auth_configuration - else "🔧 Anonymous mode (no auth middleware)" - ) - - # Routes app.router.add_post("/api/messages", entry_point) app.router.add_get("/api/messages", lambda _: Response(status=200)) app.router.add_get("/api/health", health) - # Context app["agent_configuration"] = auth_configuration app["agent_app"] = self.agent_app app["adapter"] = self.agent_app.adapter - app.on_startup.append(init_app) + app.on_startup.append(lambda app: self.initialize_agent()) + app.on_shutdown.append(lambda app: self.cleanup()) - # Port configuration desired_port = int(environ.get("PORT", 3978)) port = desired_port - # Simple port availability check with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: s.settimeout(0.5) if s.connect_ex(("127.0.0.1", desired_port)) == 0: - logger.warning( - f"⚠️ Port {desired_port} already in use. Attempting {desired_port + 1}." - ) port = desired_port + 1 print("=" * 80) - print(f"🏢 Generic Agent Host - {self.agent_class.__name__}") + print(f"🏢 {self.agent_class.__name__}") print("=" * 80) - print( - f"\n🔒 Authentication: {'Enabled' if auth_configuration else 'Anonymous'}" - ) - print("🤖 Using Microsoft Agents SDK patterns") - print("🎯 Compatible with Agents Playground") - if port != desired_port: - print(f"⚠️ Requested port {desired_port} busy; using fallback {port}") - print(f"\n🚀 Starting server on localhost:{port}") - print(f"📚 Bot Framework endpoint: http://localhost:{port}/api/messages") - print(f"❤️ Health: http://localhost:{port}/api/health") - print("🎯 Ready for testing!\n") - - # Register cleanup on app shutdown - async def cleanup_on_shutdown(app): - """Cleanup handler for graceful shutdown""" - logger.info("Shutting down gracefully...") - await self.cleanup() - - app.on_shutdown.append(cleanup_on_shutdown) + print(f"🔒 Auth: {'Enabled' if auth_configuration else 'Anonymous'}") + print(f"🚀 Server: localhost:{port}") + print(f"📚 Endpoint: http://localhost:{port}/api/messages") + print(f"❤️ Health: http://localhost:{port}/api/health\n") try: run_app(app, host="localhost", port=port, handle_signals=True) except KeyboardInterrupt: print("\n👋 Server stopped") - except Exception as error: - logger.error(f"Server error: {error}") - raise error + # --- Cleanup --- async def cleanup(self): - """Clean up resources""" if self.agent_instance: try: await self.agent_instance.cleanup() - logger.info("Agent cleanup completed") except Exception as e: - logger.error(f"Error during agent cleanup: {e}") - - -def create_and_run_host(agent_class: type[AgentInterface], *agent_args, **agent_kwargs): - """ - Convenience function to create and run a generic agent host. - - Args: - agent_class: The agent class to host (must implement AgentInterface) - *agent_args: Positional arguments to pass to the agent constructor - **agent_kwargs: Keyword arguments to pass to the agent constructor - """ - try: - # Check that the agent inherits from AgentInterface - if not check_agent_inheritance(agent_class): - raise TypeError( - f"Agent class {agent_class.__name__} must inherit from AgentInterface" - ) - - configure( - service_name="AgentFrameworkTracingWithAzureOpenAI", - service_namespace="AgentFrameworkTesting", - ) + logger.error(f"Cleanup error: {e}") - # Create the host - host = GenericAgentHost(agent_class, *agent_args, **agent_kwargs) - # Create authentication configuration - auth_config = host.create_auth_configuration() - # Start the server - host.start_server(auth_config) - - except Exception as error: - logger.error(f"Failed to start generic agent host: {error}") - raise error - - -if __name__ == "__main__": - print( - "Generic Agent Host - Use create_and_run_host() function to start with your agent class" - ) - print("Example:") - print(" from common.host_agent_server import create_and_run_host") - print(" from my_agent import MyAgent") - print(" create_and_run_host(MyAgent, api_key='your_key')") diff --git a/python/agent-framework/sample-agent/pyproject.toml b/python/agent-framework/sample-agent/pyproject.toml index 159e43fb..3b73e44c 100644 --- a/python/agent-framework/sample-agent/pyproject.toml +++ b/python/agent-framework/sample-agent/pyproject.toml @@ -55,7 +55,7 @@ default = true [[tool.uv.index]] name = "microsoft_kairo" -url = "../../../../python/dist" +url = "../../dist" format = "flat" [build-system] From 975b729ed5440a7437be4f06ead3c2e881108a05 Mon Sep 17 00:00:00 2001 From: Mrunal Hirve Date: Fri, 7 Nov 2025 21:44:10 -0800 Subject: [PATCH 2/8] Update readme --- .../sample-agent/AGENT-CODE-WALKTHROUGH.md | 106 ++++++++++++++++++ .../sample-agent/AGENT-TESTING.md | 2 + 2 files changed, 108 insertions(+) diff --git a/python/agent-framework/sample-agent/AGENT-CODE-WALKTHROUGH.md b/python/agent-framework/sample-agent/AGENT-CODE-WALKTHROUGH.md index 849cd430..57ea7d1b 100644 --- a/python/agent-framework/sample-agent/AGENT-CODE-WALKTHROUGH.md +++ b/python/agent-framework/sample-agent/AGENT-CODE-WALKTHROUGH.md @@ -8,6 +8,7 @@ Step-by-step walkthrough of the complete agent implementation in `sample_agent\a |-----------|---------| | **AgentFramework SDK** | Core AI orchestration and conversation management | | **Microsoft 365 Agents SDK** | Enterprise hosting and authentication integration | +| **Agent Notifications** | Handle @mentions from Outlook, Word, and Excel | | **MCP Servers** | External tool access and integration | | **Microsoft Agent 365 Observability** | Comprehensive tracing and monitoring | @@ -43,6 +44,9 @@ from agent_interface import AgentInterface from local_authentication_options import LocalAuthenticationOptions from microsoft_agents.hosting.core import Authorization, TurnContext +# Notifications +from microsoft_agents_a365.notifications.agent_notification import NotificationTypes + # Observability Components from microsoft_agents_a365.observability.core.config import configure @@ -57,6 +61,7 @@ from microsoft_agents_a365.tooling.extensions.agentframework.services.mcp_tool_r **Key Imports**: - **AgentFramework**: Tools to talk to AI models and manage conversations - **Microsoft 365 Agents**: Enterprise security and hosting features +- **Notifications**: Handle @mentions from Outlook, Word, and Excel - **MCP Tooling**: Connects the agent to external tools and services - **Observability**: Tracks what the agent is doing for monitoring and debugging @@ -298,6 +303,107 @@ The agent supports multiple authentication modes and extensive configuration opt ## Step 7: Message Processing +```python +async def process_user_message( + self, message: str, auth: Authorization, context: TurnContext +) -> str: + """Process user message using the AgentFramework SDK""" + try: + await self.setup_mcp_servers(auth, context) + result = await self.agent.run(message) + return self._extract_result(result) or "I couldn't process your request at this time." + except Exception as e: + logger.error(f"Error processing message: {e}") + return f"Sorry, I encountered an error: {str(e)}" +``` + +**What it does**: Handles regular chat messages from users. + +**What happens**: +1. **Setup Tools**: Makes sure MCP tools are connected (only runs once on first message) +2. **Run Agent**: Sends the message to the AI agent for processing +3. **Extract Response**: Pulls out the text response from the agent's result +4. **Error Handling**: Catches problems and returns friendly error messages + +--- + +## Step 8: Notification Handling + +```python +async def handle_agent_notification_activity( + self, notification_activity, auth: Authorization, context: TurnContext +) -> str: + """Handle agent notification activities (email, Word mentions, etc.)""" + try: + notification_type = notification_activity.notification_type + logger.info(f"📬 Processing notification: {notification_type}") + + await self.setup_mcp_servers(auth, context) + + # Handle Email Notifications + if notification_type == NotificationTypes.EMAIL_NOTIFICATION: + if not hasattr(notification_activity, "email") or not notification_activity.email: + return "I could not find the email notification details." + + email = notification_activity.email + email_body = getattr(email, "html_body", "") or getattr(email, "body", "") + message = f"You have received the following email. Please follow any instructions in it. {email_body}" + + result = await self.agent.run(message) + return self._extract_result(result) or "Email notification processed." + + # Handle Word Comment Notifications + elif notification_type == NotificationTypes.WPX_COMMENT: + if not hasattr(notification_activity, "wpx_comment") or not notification_activity.wpx_comment: + return "I could not find the Word notification details." + + wpx = notification_activity.wpx_comment + doc_id = getattr(wpx, "document_id", "") + comment_id = getattr(wpx, "initiating_comment_id", "") + drive_id = "default" + + # Get Word document content + doc_message = f"You have a new comment on the Word document with id '{doc_id}', comment id '{comment_id}', drive id '{drive_id}'. Please retrieve the Word document as well as the comments and return it in text format." + doc_result = await self.agent.run(doc_message) + word_content = self._extract_result(doc_result) + + # Process the comment with document context + comment_text = notification_activity.text or "" + response_message = f"You have received the following Word document content and comments. Please refer to these when responding to comment '{comment_text}'. {word_content}" + result = await self.agent.run(response_message) + return self._extract_result(result) or "Word notification processed." + + # Generic notification handling + else: + notification_message = notification_activity.text or f"Notification received: {notification_type}" + result = await self.agent.run(notification_message) + return self._extract_result(result) or "Notification processed successfully." + + except Exception as e: + logger.error(f"Error processing notification: {e}") + return f"Sorry, I encountered an error processing the notification: {str(e)}" +``` + +**What it does**: Handles notifications from Microsoft 365 apps like Outlook and Word. + +**What happens**: +1. **Setup Tools**: Makes sure MCP tools are connected (notifications might arrive before any regular messages) +2. **Identify Type**: Checks what kind of notification it is (email, Word comment, etc.) +3. **Email Notifications**: Extracts email body and processes with the agent +4. **Word Comments**: Retrieves document content, then processes the comment with context +5. **Generic Handling**: Falls back to simple text processing for other notification types + +**Supported Notification Types**: +- `NotificationTypes.EMAIL_NOTIFICATION`: @mentions in Outlook emails +- `NotificationTypes.WPX_COMMENT`: @mentions in Word/Excel comments +- Other notification types handled generically + +**Why MCP Setup is Needed**: Notifications need access to tools (like Microsoft Graph to read documents) just like regular messages. The `mcp_servers_initialized` flag ensures setup only runs once regardless of whether a message or notification arrives first. + +--- + +## Step 9: Cleanup + ```python async def initialize(self): """Initialize the agent and MCP server connections""" diff --git a/python/agent-framework/sample-agent/AGENT-TESTING.md b/python/agent-framework/sample-agent/AGENT-TESTING.md index 05a48547..7f3736ee 100644 --- a/python/agent-framework/sample-agent/AGENT-TESTING.md +++ b/python/agent-framework/sample-agent/AGENT-TESTING.md @@ -121,6 +121,8 @@ Using proper Microsoft Agents SDK patterns After starting the server, you can test it using the Microsoft 365 Agents Playground. In a separate terminal, start the playground: ```powershell +agentsplayground +# or teamsapptester ``` From 3ff0a0bf3278d00955aa3cdf4f0852a76aa8d78d Mon Sep 17 00:00:00 2001 From: Mrunal Hirve Date: Sat, 8 Nov 2025 12:01:17 -0800 Subject: [PATCH 3/8] PR comments --- python/agent-framework/sample-agent/AGENT-TESTING.md | 4 ++-- python/agent-framework/sample-agent/agent.py | 3 +-- python/agent-framework/sample-agent/pyproject.toml | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/python/agent-framework/sample-agent/AGENT-TESTING.md b/python/agent-framework/sample-agent/AGENT-TESTING.md index 7f3736ee..f06d77a7 100644 --- a/python/agent-framework/sample-agent/AGENT-TESTING.md +++ b/python/agent-framework/sample-agent/AGENT-TESTING.md @@ -121,9 +121,9 @@ Using proper Microsoft Agents SDK patterns After starting the server, you can test it using the Microsoft 365 Agents Playground. In a separate terminal, start the playground: ```powershell + agentsplayground -# or -teamsapptester + ``` You should see the Microsoft 365 Agents Playground running locally diff --git a/python/agent-framework/sample-agent/agent.py b/python/agent-framework/sample-agent/agent.py index 0195d9b6..b14a3d96 100644 --- a/python/agent-framework/sample-agent/agent.py +++ b/python/agent-framework/sample-agent/agent.py @@ -204,8 +204,7 @@ async def setup_mcp_servers(self, auth: Authorization, context: TurnContext): if use_agentic_auth: scope = os.getenv("AGENTIC_AUTH_SCOPE") if not scope: - logger.warning("⚠️ AGENTIC_AUTH_SCOPE not set") - return + logger.error("❌ AGENTIC_AUTH_SCOPE is required when USE_AGENTIC_AUTH is enabled") return scopes = [scope] authToken = await auth.exchange_token(context, scopes, "AGENTIC") diff --git a/python/agent-framework/sample-agent/pyproject.toml b/python/agent-framework/sample-agent/pyproject.toml index 3b73e44c..c13aa1a3 100644 --- a/python/agent-framework/sample-agent/pyproject.toml +++ b/python/agent-framework/sample-agent/pyproject.toml @@ -54,7 +54,7 @@ url = "https://pypi.org/simple" default = true [[tool.uv.index]] -name = "microsoft_kairo" +name = "microsoft_agents_a365" url = "../../dist" format = "flat" From 0b61f8eb394822bf22daa2162532e2858b5caac8 Mon Sep 17 00:00:00 2001 From: Mrunal Hirve Date: Sat, 8 Nov 2025 20:07:17 -0800 Subject: [PATCH 4/8] small fix --- python/agent-framework/sample-agent/agent.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/python/agent-framework/sample-agent/agent.py b/python/agent-framework/sample-agent/agent.py index b14a3d96..f8354b1e 100644 --- a/python/agent-framework/sample-agent/agent.py +++ b/python/agent-framework/sample-agent/agent.py @@ -67,6 +67,8 @@ class AgentFrameworkAgent(AgentInterface): """AgentFramework Agent integrated with MCP servers and Observability""" + + AGENT_PROMPT = "You are a helpful assistant with access to tools." # ========================================================================= # INITIALIZATION @@ -130,7 +132,7 @@ def _create_agent(self): try: self.agent = ChatAgent( chat_client=self.chat_client, - instructions="You are a helpful assistant with access to tools.", + instructions=self.AGENT_PROMPT, tools=[], ) logger.info("✅ AgentFramework agent created") @@ -211,7 +213,7 @@ async def setup_mcp_servers(self, auth: Authorization, context: TurnContext): auth_token = authToken.token self.agent = await self.tool_service.add_tool_servers_to_agent( chat_client=self.chat_client, - agent_instructions="You are a helpful assistant with access to tools.", + agent_instructions=self.AGENT_PROMPT, initial_tools=[], agentic_app_id=agent_user_id, environment_id=self.auth_options.env_id, @@ -222,7 +224,7 @@ async def setup_mcp_servers(self, auth: Authorization, context: TurnContext): else: self.agent = await self.tool_service.add_tool_servers_to_agent( chat_client=self.chat_client, - agent_instructions="You are a helpful assistant with access to tools.", + agent_instructions=self.AGENT_PROMPT, initial_tools=[], agentic_app_id=agent_user_id, environment_id=self.auth_options.env_id, From 2070451edc235a91b3e93c56d348850c7f045628 Mon Sep 17 00:00:00 2001 From: Mrunal Hirve Date: Sun, 9 Nov 2025 18:21:21 -0800 Subject: [PATCH 5/8] removed observability --- python/agent-framework/sample-agent/agent.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/python/agent-framework/sample-agent/agent.py b/python/agent-framework/sample-agent/agent.py index f8354b1e..71f9ae28 100644 --- a/python/agent-framework/sample-agent/agent.py +++ b/python/agent-framework/sample-agent/agent.py @@ -158,14 +158,6 @@ def token_resolver(self, agent_id: str, tenant_id: str) -> str | None: logger.error(f"Error resolving token: {e}") return None - def _setup_observability(self): - """Configure observability""" - try: - setup_observability() - logger.info("✅ Observability configured") - except Exception as e: - logger.error(f"❌ Observability error: {e}") - def _enable_agentframework_instrumentation(self): """Enable AgentFramework instrumentation""" try: From 53da21d39d7c56c6451dcf1d22880b18ab09f745 Mon Sep 17 00:00:00 2001 From: Mrunal Hirve Date: Sun, 9 Nov 2025 18:32:45 -0800 Subject: [PATCH 6/8] update manifest --- python/agent-framework/sample-agent/ToolingManifest.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/python/agent-framework/sample-agent/ToolingManifest.json b/python/agent-framework/sample-agent/ToolingManifest.json index 9d5cacf2..c7bc00b7 100644 --- a/python/agent-framework/sample-agent/ToolingManifest.json +++ b/python/agent-framework/sample-agent/ToolingManifest.json @@ -2,7 +2,10 @@ "mcpServers": [ { "mcpServerName": "mcp_MailTools", - "mcpServerUniqueName": "mcp_MailTools" + "mcpServerUniqueName": "mcp_MailTools", + "url": "https://preprod.agent365.svc.cloud.dev.microsoft/agents/servers/mcp_MailTools", + "scope": "McpServers.Mail.All", + "audience": "05879165-0320-489e-b644-f72b33f3edf0" } ] } \ No newline at end of file From 2670f1138dbb6fa944648fb90380b643730eff1a Mon Sep 17 00:00:00 2001 From: Mrunal Hirve Date: Mon, 10 Nov 2025 11:00:24 -0800 Subject: [PATCH 7/8] changes --- .../sample-agent/.env.template | 39 +- .../sample-agent/AGENT-TESTING.md | 458 ------------------ .../sample-agent/SETUP-GUIDE.md | 182 +++++++ .../sample-agent/pyproject.toml | 14 +- 4 files changed, 205 insertions(+), 488 deletions(-) delete mode 100644 python/agent-framework/sample-agent/AGENT-TESTING.md create mode 100644 python/agent-framework/sample-agent/SETUP-GUIDE.md diff --git a/python/agent-framework/sample-agent/.env.template b/python/agent-framework/sample-agent/.env.template index 0fa2ede6..4ea70131 100644 --- a/python/agent-framework/sample-agent/.env.template +++ b/python/agent-framework/sample-agent/.env.template @@ -1,56 +1,49 @@ # This is a demo .env file - -# OpenAI Configuration +# Replace with your actual OpenAI API key OPENAI_API_KEY= -OPENAI_MODEL=gpt-4o - -# MCP Server Configuration -MCP_SERVER_PORT=8000 -MCP_SERVER_HOST=localhost -MCP_PLATFORM_ENDPOINT=https://test.agent365.svc.cloud.dev.microsoft - +MCP_SERVER_HOST= +MCP_PLATFORM_ENDPOINT= # Logging + LOG_LEVEL=INFO - + # Observability Configuration OBSERVABILITY_SERVICE_NAME=agent-framework-sample OBSERVABILITY_SERVICE_NAMESPACE=agent-framework.samples - -# Environment Configuration -# OBO Default-6e8b84fa-ae41-4a00-9ad1-934b73e5d73c -# Agentic auth - Default-5369a35c-46a5-4677-8ff9-2e65587654e7 + ENV_ID= BEARER_TOKEN= +OPENAI_MODEL= -# Authentication Mode +#USE_ENVIRONMENT_ID=false USE_AGENTIC_AUTH=true # Agentic Authentication Scope -AGENTIC_AUTH_SCOPE=05879165-0320-489e-b644-f72b33f3edf0/.default - +AGENTIC_AUTH_SCOPE= + AGENT_ID= # Agent365 Agentic Authentication Configuration CONNECTIONS__SERVICE_CONNECTION__SETTINGS__CLIENTID= CONNECTIONS__SERVICE_CONNECTION__SETTINGS__CLIENTSECRET= CONNECTIONS__SERVICE_CONNECTION__SETTINGS__TENANTID= -CONNECTIONS__SERVICE_CONNECTION__SETTINGS__SCOPES=https://api.botframework.com/.default +CONNECTIONS__SERVICE_CONNECTION__SETTINGS__SCOPES= AGENTAPPLICATION__USERAUTHORIZATION__HANDLERS__AGENTIC__SETTINGS__TYPE=AgenticUserAuthorization AGENTAPPLICATION__USERAUTHORIZATION__HANDLERS__AGENTIC__SETTINGS__SCOPES=https://graph.microsoft.com/.default AGENTAPPLICATION__USERAUTHORIZATION__HANDLERS__AGENTIC__SETTINGS__ALTERNATEBLUEPRINTCONNECTIONNAME=https://graph.microsoft.com/.default - + CONNECTIONSMAP_0_SERVICEURL=* CONNECTIONSMAP_0_CONNECTION=SERVICE_CONNECTION - + # Optional: Server Configuration PORT=3978 - + # Azure OpenAI Configuration AZURE_OPENAI_API_KEY= AZURE_OPENAI_ENDPOINT= -AZURE_OPENAI_DEPLOYMENT="gpt-4o" -AZURE_OPENAI_API_VERSION="2024-02-01" +AZURE_OPENAI_DEPLOYMENT= +AZURE_OPENAI_API_VERSION= # Required for observability SDK ENABLE_OBSERVABILITY=true diff --git a/python/agent-framework/sample-agent/AGENT-TESTING.md b/python/agent-framework/sample-agent/AGENT-TESTING.md deleted file mode 100644 index f06d77a7..00000000 --- a/python/agent-framework/sample-agent/AGENT-TESTING.md +++ /dev/null @@ -1,458 +0,0 @@ -# Agent Testing Guide - -This document provides comprehensive testing instructions for the AgentFramework Agent sample, including setup, testing scenarios, troubleshooting, and validation steps. - -## Overview - -The AgentFramework Agent sample supports multiple testing modes and scenarios: -- **Local Development Testing**: Using console output and direct interaction -- **Microsoft 365 Agents SDK Testing**: Through the generic host server -- **MCP Tool Testing**: Validating external tool integrations -- **Observability Testing**: Verifying tracing and monitoring capabilities -- **Authentication Testing**: Both anonymous and agentic authentication modes - -## Prerequisites - -### Required Software -- Python 3.11 or higher -- Azure OpenAI access -- Azure CLI (for authentication) -- Access to Microsoft Agent365 MCP servers (for tool testing) - -### Environment Setup -1. Install uv (Python package manager): - ```powershell - # On Windows - powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex" - - # Or using pip if you prefer - pip install uv - ``` - -2. Create and activate a virtual environment: - ```powershell - uv venv venv - .\venv\Scripts\Activate.ps1 - ``` - -3. Create your environment configuration file: - ```powershell - Copy-Item .env.example .env - ``` - Or create a new `.env` file with the required variables. - -4. Configure your environment variables in `.env`: - - Copy the `.env.example` file as a starting point - - At minimum, set your Azure OpenAI credentials - - Review other variables in `.env.example` and configure as needed for your testing scenario - - **Azure OpenAI Configuration**: You need to specify your Azure OpenAI endpoint: - ```env - AZURE_OPENAI_ENDPOINT=https://your-resource.openai.azure.com/ - AZURE_OPENAI_DEPLOYMENT=gpt-4o # Your deployment name - AZURE_OPENAI_API_VERSION=2024-02-01 # API version - ``` - -5. Authenticate with Azure CLI: - ```bash - az login - ``` - -6. Install all dependencies (ensure your virtual environment is activated): - - **Using pyproject.toml with uv** - ```powershell - # Install dependencies using pyproject.toml - uv pip install -e . or uv pip install -e . --preview - ``` - - **Note**: The pyproject.toml includes all required packages and a local index configuration pointing to `../../dist` for package resolution. - ```toml - # Local packages from local index - # - Update package versions to match your built wheels - "microsoft_agents_a365_tooling >= 2025.10.20", - "microsoft_agents_a365_tooling_extensions_agentframework >= 2025.10.20", - "microsoft_agents_a365_observability_core >= 2025.10.20", - "microsoft_agents_a365_notifications >= 2025.10.20", - ``` - - **Important**: Verify these package versions match your locally built wheels in the `../../dist` directory and ensure the directory path is correct before installation. - -## Testing Scenarios - -### 1. Basic Agent Functionality Testing - -#### Basic Conversation Testing -- **Purpose**: Test AI model integration and response generation through proper endpoints -- **Setup**: Use the hosted server mode with `/api/messages` endpoint -- **Test Cases**: - - Simple greeting: "Hello, how are you?" - - Information request: "What can you help me with?" - - Complex query: "Explain quantum computing in simple terms" - -**Expected Results**: -- Coherent, helpful responses -- Response times under 10 seconds -- No authentication or API key errors - -### 2. Server Hosting Testing - -#### Start the Generic Host Server -```powershell -uv run python start_with_generic_host.py -``` - -**Expected Console Output for the Python server:** -``` -================================================================================ -Microsoft Agents SDK Integration - OFFICIAL IMPLEMENTATION -================================================================================ - -🔒 Authentication: Anonymous (or Agentic if configured) -Using proper Microsoft Agents SDK patterns -🎯 Compatible with Agents Playground - -🚀 Starting server on localhost:3978 -📚 Microsoft 365 Agents SDK endpoint: http://localhost:3978/api/messages -❤️ Health: http://localhost:3978/api/health -🎯 Ready for testing! -``` - -#### Testing with Microsoft 365 Agents Playground -After starting the server, you can test it using the Microsoft 365 Agents Playground. -In a separate terminal, start the playground: -```powershell - -agentsplayground - -``` - -You should see the Microsoft 365 Agents Playground running locally - -#### Health Check Testing -- **Test**: `Invoke-RestMethod -Uri http://localhost:3978/api/health` (PowerShell) or `curl http://localhost:3978/api/health` -- **Expected Response**: - ```json - { - "status": "ok", - "agentframework_agent_initialized": true, - "auth_mode": "anonymous" - } - ``` - -#### Port Conflict Testing -- **Test**: Start multiple instances simultaneously -- **Expected Behavior**: Server automatically tries next available port (3979, 3980, etc.) -- **Validation**: Check console output for actual port used - -### 3. Microsoft 365 Agents SDK Integration Testing - -#### Message Endpoint Testing -- **Endpoint**: `POST http://localhost:3978/api/messages` -- **Test Payload**: - ```json - { - "type": "message", - "text": "Hello, can you help me?", - "from": { - "id": "test-user", - "name": "Test User" - }, - "conversation": { - "id": "test-conversation" - } - } - ``` - - -#### Expected Response Flow -1. Server receives message -2. Agent processes request with observability tracing -3. Response returned with appropriate structure -4. Trace output visible in console (if observability enabled) - -### 4. MCP Tool Integration Testing - -#### Testing from Microsoft 365 Agents Playground -Once you have the agent running and the playground started with `teamsapptester`, you can test MCP tool functionality directly through the playground interface: - -- **Interactive Testing**: Use the playground's chat interface to request tool actions -- **Real-time Feedback**: See tool execution results immediately in the conversation -- **Visual Validation**: Confirm tools are working through the user-friendly interface - -#### Tool Discovery Testing -- **Validation Points**: - - Tools loaded from MCP servers during agent initialization - - Console output shows tool count: "✅ Loaded X tools from MCP servers" - - No connection errors to MCP servers - -#### Tool Functionality Testing -- **Email Tools** (if available): - - "Send an email to test@example.com with subject 'Test' and body 'Hello'" - - "Check my recent emails" - - "Help me organize my inbox" - -- **Calendar Tools** (if available): - - "Create a meeting for tomorrow at 2 PM" - - "Check my availability this week" - - "Show my upcoming appointments" - -#### Tool Error Handling Testing -- **Scenarios**: - - Request tools when MCP servers are unavailable - - Invalid tool parameters - - Authentication failures for tool access - -- **Expected Behavior**: - - Graceful error messages to users - - Agent continues functioning without tools - - Clear error logging for debugging - -### 5. Authentication Testing - -#### Anonymous Authentication Testing -- **Configuration**: Default setup without agentic auth -- **Expected Behavior**: - - Agent starts successfully - - Basic functionality works - - Console shows "🔒 Authentication: Anonymous" - -#### Agentic Authentication Testing -- **Configuration**: Set `USE_AGENTIC_AUTH=true` in `.env` -- **Required Environment Variables**: - ```env - USE_AGENTIC_AUTH=true - AGENT_ID=your_agent_id - CONNECTIONS__SERVICE_CONNECTION__SETTINGS__CLIENTID=client_id - CONNECTIONS__SERVICE_CONNECTION__SETTINGS__CLIENTSECRET=client_secret - CONNECTIONS__SERVICE_CONNECTION__SETTINGS__TENANTID=tenant_id - ``` - -- **Testing through Agents Playground**: - 1. Ensure that Agentic Auth is set up as in the previous step - 2. Start the AgentsPlayground with `teamsapptester` - 3. Click on **'Mock An Activity'** → **'Trigger Custom Activity'** → **'Custom activity'** - 4. Add the following JSON payload: - ```json - { - "type": "message", - "id": "c4970243-ca33-46eb-9818-74d69f553f63", - "timestamp": "2025-09-24T17:40:19+00:00", - "serviceUrl": "http://localhost:56150/_connector", - "channelId": "agents", - "from": { - "id": "manager@contoso.com", - "name": "Agent Manager", - "role": "user" - }, - "recipient": { - "id": "a365testingagent@testcsaaa.onmicrosoft.com", - "name": "A365 Testing Agent", - "agenticUserId": "ea1a172b-f443-4ee0-b8a1-27c7ab7ea9e5", - "agenticAppId": "933f6053-d249-4479-8c0b-78ab25424002", - "tenantId": "5369a35c-46a5-4677-8ff9-2e65587654e7", - "role": "agenticUser" - }, - "conversation": { - "conversationType": "personal", - "tenantId": "00000000-0000-0000-0000-0000000000001", - "id": "personal-chat-id" - }, - "membersAdded": [], - "membersRemoved": [], - "reactionsAdded": [], - "reactionsRemoved": [], - "locale": "en-US", - "attachments": [], - "entities": [ - { - "id": "email", - "type": "productInfo" - }, - { - "type": "clientInfo", - "locale": "en-US", - "timezone": null - }, - { - "type": "emailNotification", - "id": "c4970243-ca33-46eb-9818-74d69f553f63", - "conversationId": "personal-chat-id", - "htmlBody": "\n
\n Send Email to with subject 'Hello World' and message 'This is a test'.
\n\n\n" - } - ], - "channelData": { - "tenant": { - "id": "00000000-0000-0000-0000-0000000000001" - } - }, - "listenFor": [], - "textHighlights": [] - } - ``` - -- **Expected Behavior**: - - Agent starts with Azure AD authentication - - Console shows "🔒 Authentication: Agentic" - - Tool access uses authenticated context - - Custom activity is processed successfully through the playground - -### 6. Observability Testing - -**Prerequisites**: Ensure your `.env` file includes the observability configuration: -```env -# Observability Configuration -OBSERVABILITY_SERVICE_NAME=agentframework-agent-sample -OBSERVABILITY_SERVICE_NAMESPACE=agents.samples -ENABLE_A365_OBSERVABILITY_EXPORTER=false # For console output during development -``` - -#### Trace Output Validation -- **Expected Console Output**: - ``` - ✅ Observability configured successfully - ``` - -#### Span Creation Testing -- **Test**: Send a message to the agent -- **Expected Trace Elements**: - - Custom span: "process_user_message" - - Span attributes: message length, content preview - - Azure OpenAI API call spans (automatic instrumentation when available) - - Tool execution spans (if tools are used) - -**Sample Console Output**: -```json -{ - "name": "process_user_message", - "context": { - "trace_id": "0x46eaa206d93e21d1c49395848172f60b", - "span_id": "0x6cd9b00954a506aa" - }, - "kind": "SpanKind.INTERNAL", - "start_time": "2025-10-16T00:01:54.794475Z", - "end_time": "2025-10-16T00:02:00.824454Z", - "status": { - "status_code": "UNSET" - }, - "attributes": { - "user.message.length": 59, - "user.message.preview": "Send Email to YourEmail@microsoft.com saying Hel...", - "response.length": 133, - "response.preview": "The email saying \"Hello World!\" has been successfu..." - }, - "resource": { - "attributes": { - "service.namespace": "agent365-samples", - "service.name": "agentframework-agent-sample" - } - } -} -``` - -#### Error Tracing Testing -- **Test**: Force an error (invalid API credentials, network issues) -- **Expected Behavior**: - - Exceptions recorded in spans - - Error status set on spans - - Detailed error information in traces - -## Troubleshooting Common Issues - -### Agent Startup Issues - -#### Azure OpenAI Configuration Problems -- **Error**: "AZURE_OPENAI_ENDPOINT environment variable is required" -- **Solution**: Verify Azure OpenAI configuration in `.env` file -- **Validation**: Check Azure CLI authentication with `az login` - -#### Import Errors -- **Error**: "Required packages not installed" -- **Solution**: Run `uv pip install -e .` -- **Note**: Ensure using Python 3.11+ and correct virtual environment - -#### Port Binding Errors -- **Error**: "error while attempting to bind on address" -- **Solution**: Server automatically tries next port, or set custom `PORT` in `.env` - -### Runtime Issues - -#### MCP Server Connection Failures -- **Symptoms**: "Error setting up MCP servers" in logs -- **Causes**: Network issues, authentication problems, server unavailability -- **Solutions**: - - Check network connectivity - - Verify bearer token or agentic auth configuration - - Confirm MCP server URLs are correct - -#### Observability Configuration Failures -- **Symptoms**: "WARNING: Failed to configure observability" -- **Impact**: Agent continues working, but without tracing -- **Solutions**: - - Check Microsoft Agent 365 observability packages installation - - Verify environment variables are set correctly - - Review console output for specific error details - -#### Model API Errors -- **Symptoms**: API call failures, authentication errors -- **Solutions**: - - Check Azure CLI authentication status - - Verify Azure OpenAI endpoint and deployment name - - Ensure proper Azure RBAC permissions - -### Testing Environment Issues - -#### Authentication Context Problems -- **Symptoms**: Tools fail to execute, authorization errors -- **Solutions**: - - Verify agentic authentication setup - - Check bearer token validity - - Ensure proper Azure AD configuration - -#### Network Connectivity Issues -- **Symptoms**: Timeouts, connection refused errors -- **Solutions**: - - Check internet connectivity - - Verify firewall settings - - Test MCP server URLs directly - -## Validation Checklist - -### ✅ Basic Functionality -- [ ] Agent initializes without errors -- [ ] Observability configuration succeeds -- [ ] Health endpoint returns 200 OK -- [ ] Basic conversation works -- [ ] Graceful error handling - -### ✅ Server Integration -- [ ] Microsoft 365 Agents SDK endpoint responds -- [ ] Message processing works end-to-end -- [ ] Concurrent requests handled properly -- [ ] Server shutdown is clean - -### ✅ MCP Tool Integration -- [ ] Tools discovered and loaded -- [ ] Tool execution works correctly -- [ ] Tool errors handled gracefully -- [ ] Authentication context passed properly - -### ✅ Observability -- [ ] Traces appear in console output -- [ ] Custom spans created correctly -- [ ] Exception tracking works -- [ ] Performance metrics captured - -### ✅ Authentication -- [ ] Anonymous mode works for development -- [ ] Agentic authentication works for enterprise -- [ ] Proper authentication context propagation -- [ ] Secure credential handling - -### ✅ Configuration -- [ ] Environment variables loaded correctly -- [ ] Default values work appropriately -- [ ] Error messages are clear and actionable -- [ ] Different model configurations work - -This comprehensive testing guide ensures the AgentFramework Agent sample is thoroughly validated across all its capabilities and integration points. - diff --git a/python/agent-framework/sample-agent/SETUP-GUIDE.md b/python/agent-framework/sample-agent/SETUP-GUIDE.md new file mode 100644 index 00000000..8b9db0e7 --- /dev/null +++ b/python/agent-framework/sample-agent/SETUP-GUIDE.md @@ -0,0 +1,182 @@ +# Quick Setup Guide + +Get the A365 Python SDK sample running in 7 simple steps. + +## Setup Steps + +### 1. Verify Python installation + +First, ensure you have Python 3.11 or higher installed: + +```powershell +# Check if Python is installed +python --version +``` + +If Python is not found: +- Download and install Python 3.11+ from +- Make sure to check "Add Python to PATH" during installation +- Restart VS Code and try again + +**✅ Success Check**: You should see Python 3.11.x or higher + +### 2. Verify prerequisites + +Ensure you have the required Microsoft Agent365 packages: + +```powershell +# Navigate to the agent-framework directory (parent of sample-agent) +cd .. + +# Check if dist folder exists with required wheel files +ls dist +``` + +If the dist folder doesn't exist or is empty, you have two options: + +#### Option A: Download from GitHub Actions (Recommended) +1. Create the dist folder: `mkdir dist` +2. Download the required .whl files: + - Visit: https://github.com/microsoft/Agent365-python/actions/runs/19200334217 + - Click on **Artifacts** → **python-3.11** + - Download the zip file and extract the wheel files into the dist folder: + - `microsoft_agents_a365_tooling-*.whl` + - `microsoft_agents_a365_tooling_extensions_agentframework-*.whl` + - `microsoft_agents_a365_observability_core-*.whl` + - `microsoft_agents_a365_observability_extensions_agent_framework-*.whl` + - `microsoft_agents_a365_runtime-*.whl` + - `microsoft_agents_a365_notifications-*.whl` + +#### Option B: Install from PyPI +If packages are available on PyPI, you can install them directly (skip to step 6 and modify the installation command). + +**✅ Success Check**: The dist folder should contain the Microsoft Agent365 wheel files. + +```powershell +# Return to sample-agent directory for next steps +cd sample-agent +``` + +### 3. Set up environment configuration + +Open PowerShell in VS Code (Terminal → New Terminal) and navigate to the sample-agent directory: + +```powershell +# Navigate to the sample-agent directory (where this README is located) +# Make sure you're in the sample-agent folder +cd sample-agent + +# Copy the environment template +copy .env.template .env +``` + +### 4. Update environment variables + +Open the newly created `.env` file and update the following values: + +```env +AZURE_OPENAI_API_KEY= +AZURE_OPENAI_ENDPOINT= +AZURE_OPENAI_DEPLOYMENT= +AZURE_OPENAI_API_VERSION="2024-02-01" +``` + +### 5. Install uv + +uv is a fast Python package manager. Open PowerShell in VS Code (Terminal → New Terminal) and run: + +```powershell +# Install uv +powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex" + +# Add uv to PATH for this session (only if not already there) +if ($env:PATH -notlike "*$env:USERPROFILE\.local\bin*") { + $env:PATH += ";$env:USERPROFILE\.local\bin" +} + +# Test that uv works +uv --version +``` + +### 6. Set up the project + +Continue in the same terminal (make sure you're still in the sample-agent directory): + +```powershell +# Verify you're in the right directory - you should see pyproject.toml +ls pyproject.toml + +# Create virtual environment with pip included +uv venv .venv --seed + +# Activate the virtual environment +.\.venv\Scripts\Activate.ps1 + +# Verify setup - you should see (.venv) in your prompt +python --version +``` + +**✅ Success Check**: Your terminal shows `(.venv)` at the beginning and you can see `pyproject.toml` in the directory + +### 7. Install dependencies + +Install all dependencies from the local wheel files and PyPI: + +```powershell +uv pip install -e . --find-links ../dist --pre +``` + +**Important**: You may see some warning messages about dependencies. **This is normal and expected** - the agent will work correctly. + +**✅ Success Check**: "Installed X packages" message appears + +### 8. Start the agent + +```powershell +python start_with_generic_host.py +``` + +**✅ Success Check**: You should see: +``` +🚀 Starting server on localhost:3978 +🎯 Ready for testing! +======== Running on http://localhost:3978 ======== +``` + +## Testing with Microsoft 365 Agents Playground + +After starting the server, you can test it using the Microsoft 365 Agents Playground. + +In a separate terminal, start the playground: + +```powershell +agentsplayground +``` + +You should see the Microsoft 365 Agents Playground running locally and ready to interact with your agent. + +## Troubleshooting + +### Common Issues + +- **"python is not recognized"** → Install Python 3.11+ from python.org and check "Add Python to PATH" + +- **"uv not found"** → Restart your terminal and try step 4 again + +- **"No module named 'dotenv'"** → Try: `uv pip install python-dotenv` + +- **"No module named..."** → Make sure you see `(.venv)` in your prompt and that the installation command in step 6 completed successfully. Most missing dependencies should already be included in requirements.txt, but if you still get errors, you can install them individually: + ```powershell + # For any additional missing modules: + uv pip install + ``` + +- **Dependency conflict warnings** → These are expected! Continue with the next step - the agent will work fine + +- **"No solution found when resolving dependencies"** → Make sure you're using the installation process in step 6 and that the dist folder exists with wheel files + +- **Agent won't start** → Check you're in the sample-agent directory and that all installation steps completed successfully + +## Done! + +Your agent is now running and ready for testing. Configuration values will be provided during the bug bash session. diff --git a/python/agent-framework/sample-agent/pyproject.toml b/python/agent-framework/sample-agent/pyproject.toml index c13aa1a3..cc2bf569 100644 --- a/python/agent-framework/sample-agent/pyproject.toml +++ b/python/agent-framework/sample-agent/pyproject.toml @@ -37,12 +37,12 @@ dependencies = [ # Local packages from local index # - Update package versions to match your built wheels - "microsoft_agents_a365_tooling >= 0.1.0", - "microsoft_agents_a365_tooling_extensions_agentframework >= 0.1.0", - "microsoft_agents_a365_observability_core >= 0.1.0", - "microsoft_agents_a365_observability_extensions_agent_framework >= 0.1.0", - "microsoft_agents_a365_runtime >= 0.1.0", - "microsoft_agents_a365_notifications >= 0.1.0", + "microsoft_agents_a365_tooling >= 0.0.0", + "microsoft_agents_a365_tooling_extensions_agentframework >= 0.0.0", + "microsoft_agents_a365_observability_core >= 0.0.0", + "microsoft_agents_a365_observability_extensions_agent_framework >= 0.0.0", + "microsoft_agents_a365_runtime >= 0.0.0", + "microsoft_agents_a365_notifications >= 0.0.0", ] requires-python = ">=3.11" @@ -55,7 +55,7 @@ default = true [[tool.uv.index]] name = "microsoft_agents_a365" -url = "../../dist" +url = "../dist" format = "flat" [build-system] From b0aefd99900bdf301a29a4242492c9e21d6756d8 Mon Sep 17 00:00:00 2001 From: Mrunal Hirve Date: Mon, 10 Nov 2025 13:39:23 -0800 Subject: [PATCH 8/8] setup fix --- .../{SETUP-GUIDE.md => SETUP-GUIDE-Unofficial.md} | 6 ++++-- python/agent-framework/sample-agent/pyproject.toml | 12 ++++++------ 2 files changed, 10 insertions(+), 8 deletions(-) rename python/agent-framework/sample-agent/{SETUP-GUIDE.md => SETUP-GUIDE-Unofficial.md} (97%) diff --git a/python/agent-framework/sample-agent/SETUP-GUIDE.md b/python/agent-framework/sample-agent/SETUP-GUIDE-Unofficial.md similarity index 97% rename from python/agent-framework/sample-agent/SETUP-GUIDE.md rename to python/agent-framework/sample-agent/SETUP-GUIDE-Unofficial.md index 8b9db0e7..a978c561 100644 --- a/python/agent-framework/sample-agent/SETUP-GUIDE.md +++ b/python/agent-framework/sample-agent/SETUP-GUIDE-Unofficial.md @@ -1,5 +1,7 @@ # Quick Setup Guide +> **NOTE: This file should be removed before Ignite.** + Get the A365 Python SDK sample running in 7 simple steps. ## Setup Steps @@ -37,7 +39,7 @@ If the dist folder doesn't exist or is empty, you have two options: #### Option A: Download from GitHub Actions (Recommended) 1. Create the dist folder: `mkdir dist` 2. Download the required .whl files: - - Visit: https://github.com/microsoft/Agent365-python/actions/runs/19200334217 + - Visit: https://github.com/microsoft/Agent365-python (get the packages from main) - Click on **Artifacts** → **python-3.11** - Download the zip file and extract the wheel files into the dist folder: - `microsoft_agents_a365_tooling-*.whl` @@ -107,7 +109,7 @@ Continue in the same terminal (make sure you're still in the sample-agent direct ls pyproject.toml # Create virtual environment with pip included -uv venv .venv --seed +uv venv .venv # Activate the virtual environment .\.venv\Scripts\Activate.ps1 diff --git a/python/agent-framework/sample-agent/pyproject.toml b/python/agent-framework/sample-agent/pyproject.toml index cc2bf569..d3d2698a 100644 --- a/python/agent-framework/sample-agent/pyproject.toml +++ b/python/agent-framework/sample-agent/pyproject.toml @@ -37,12 +37,12 @@ dependencies = [ # Local packages from local index # - Update package versions to match your built wheels - "microsoft_agents_a365_tooling >= 0.0.0", - "microsoft_agents_a365_tooling_extensions_agentframework >= 0.0.0", - "microsoft_agents_a365_observability_core >= 0.0.0", - "microsoft_agents_a365_observability_extensions_agent_framework >= 0.0.0", - "microsoft_agents_a365_runtime >= 0.0.0", - "microsoft_agents_a365_notifications >= 0.0.0", + "microsoft_agents_a365_tooling >= 0.1.0.dev12", + "microsoft_agents_a365_tooling_extensions_agentframework >= 0.1.0.dev12", + "microsoft_agents_a365_observability_core >= 0.1.0.dev12", + "microsoft_agents_a365_observability_extensions_agent_framework >= 0.1.0.dev12", + "microsoft_agents_a365_runtime >= 0.1.0.dev12", + "microsoft_agents_a365_notifications 0.1.0.dev12", ] requires-python = ">=3.11"