diff --git a/python/google-adk/sample-agent/.env.template b/python/google-adk/sample-agent/.env.template index fe39cd21..efefc592 100644 --- a/python/google-adk/sample-agent/.env.template +++ b/python/google-adk/sample-agent/.env.template @@ -11,12 +11,25 @@ # ----------------------------------------------------------------------------- # Google Gemini Configuration # ----------------------------------------------------------------------------- +# Set GOOGLE_GENAI_USE_VERTEXAI=TRUE to use Vertex AI (recommended for production). +# Set to FALSE to use the public Gemini API with GOOGLE_API_KEY. GOOGLE_GENAI_USE_VERTEXAI=FALSE -GOOGLE_API_KEY=<> GEMINI_MODEL=gemini-2.5-flash + +# --- When GOOGLE_GENAI_USE_VERTEXAI=FALSE (public Gemini API) --- +# Get your API key from: https://aistudio.google.com/app/apikey +GOOGLE_API_KEY=<> + +# --- When GOOGLE_GENAI_USE_VERTEXAI=TRUE (Vertex AI) --- +# Project ID and region of your GCP project where Vertex AI is enabled. +# Region list: https://cloud.google.com/vertex-ai/docs/general/locations GOOGLE_CLOUD_PROJECT=<> GOOGLE_CLOUD_LOCATION=<> -GOOGLE_GENAI_USE_VERTEXAI=TRUE + +# Path to the GCP service account JSON key file (Application Default Credentials). +# Local dev: absolute path to your downloaded key file +# Deployment: leave UNSET — use the platform's managed identity instead (see notes below) +GOOGLE_APPLICATION_CREDENTIALS=<> # ----------------------------------------------------------------------------- # Agent365 Service Connection (OAuth client credentials) # ----------------------------------------------------------------------------- @@ -68,6 +81,10 @@ AGENTIC_USER_ID=<> AGENTIC_APP_ID=<> AGENTIC_TENANT_ID=<> +# A365 platform fallback vars (same values as AGENTIC_APP_ID and AGENTIC_USER_ID) +A365_AGENT_APP_INSTANCE_ID=<> +A365_AGENTIC_USER_ID=<> + # ----------------------------------------------------------------------------- # Local Development # ----------------------------------------------------------------------------- @@ -98,3 +115,21 @@ ENABLE_A365_OBSERVABILITY_EXPORTER=false PYTHON_ENVIRONMENT=development OBSERVABILITY_SERVICE_NAME=GoogleADKSampleAgent OBSERVABILITY_SERVICE_NAMESPACE=GoogleADKTesting + +# Agent ID for observability routing (use blueprint ID for local, instance ID for production) +AGENT_ID=<> + +# ALT_BLUEPRINT_NAME allows the SDK to use SERVICE_CONNECTION for token exchange +AGENTAPPLICATION__USERAUTHORIZATION__HANDLERS__AGENTIC__SETTINGS__ALT_BLUEPRINT_NAME=SERVICE_CONNECTION + +# --- Agent 365 Observability Exporter Configuration --- +# Required when ENABLE_A365_OBSERVABILITY_EXPORTER=true (production) +# AGENTID should be the agent instance ID (from activity.recipient.agentic_app_id at runtime) +# For local dev, use the blueprint ID as a fallback +AGENT365OBSERVABILITY__AGENTID=<> +AGENT365OBSERVABILITY__AGENTNAME=<> +AGENT365OBSERVABILITY__AGENTDESCRIPTION=<> +AGENT365OBSERVABILITY__TENANTID=<> +AGENT365OBSERVABILITY__AGENTBLUEPRINTID=<> +AGENT365OBSERVABILITY__CLIENTID=<> +AGENT365OBSERVABILITY__CLIENTSECRET=<> diff --git a/python/google-adk/sample-agent/AGENT-CODE-WALKTHROUGH.md b/python/google-adk/sample-agent/AGENT-CODE-WALKTHROUGH.md index b62719c6..a2a302e5 100644 --- a/python/google-adk/sample-agent/AGENT-CODE-WALKTHROUGH.md +++ b/python/google-adk/sample-agent/AGENT-CODE-WALKTHROUGH.md @@ -43,7 +43,7 @@ from microsoft_agents.hosting.core import Authorization, TurnContext from mcp_tool_registration_service import McpToolRegistrationService # Observability Components -from microsoft_agents_a365.observability.core.middleware.baggage_builder import BaggageBuilder +from microsoft.opentelemetry.a365.core import BaggageBuilder ``` **What it does**: Brings in all the external libraries and tools the agent needs to work. @@ -98,7 +98,7 @@ def __init__( 3. **Sets Instructions**: Defines how the agent should behave and respond **Settings**: -- Uses "gemini-2.0-flash" model by default (Google's fast Gemini model) +- Uses "gemini-2.5-flash" model by default (Google's fast Gemini model) - Configurable agent name and instructions - No explicit temperature/creativity settings (uses Google ADK defaults) @@ -110,21 +110,22 @@ Observability for Google ADK is configured in the hosting layer (`main.py`) rath ```python # In main.py -from microsoft_agents_a365.observability.core.config import configure +from microsoft.opentelemetry import use_microsoft_opentelemetry -if __name__ == "__main__": - configure( - service_name="GoogleADKSampleAgent", - service_namespace="GoogleADKTesting", - ) +def main(): + if os.getenv("ENABLE_OBSERVABILITY", "true").lower() == "true": + use_microsoft_opentelemetry( + enable_a365=True, + enable_azure_monitor=False, + ) ``` -**What it does**: Configures Microsoft Agent 365 observability for tracking and monitoring. +**What it does**: Configures Microsoft Agent 365 observability via the Microsoft OpenTelemetry Distro. **What happens**: 1. Sets up distributed tracing across your agent's operations -2. Enables telemetry export to Azure Monitor or other backends -3. Automatically tracks agent invocations, tool calls, and errors +2. The distro reads `CONNECTIONS__SERVICE_CONNECTION__SETTINGS__*` and `A365_AGENT_APP_INSTANCE_ID` / `A365_AGENTIC_USER_ID` for FIC auth automatically +3. Exports traces to the A365 backend when `ENABLE_A365_OBSERVABILITY_EXPORTER=true` **In the agent code**, observability context is passed via `BaggageBuilder`: @@ -227,12 +228,15 @@ async def invoke_agent( **With Observability Scope**: ```python async def invoke_agent_with_scope(self, message: str, auth, auth_handler_name, context): - tenant_id = context.activity.recipient.tenant_id - agent_id = context.activity.recipient.agentic_user_id + recipient = context.activity.recipient + tenant_id = getattr(recipient, "tenant_id", None) or os.getenv("AGENTIC_TENANT_ID", "") + agent_id = getattr(recipient, "agentic_app_id", None) or os.getenv("AGENTIC_APP_ID", "") with BaggageBuilder().tenant_id(tenant_id).agent_id(agent_id).build(): return await self.invoke_agent(message, auth, auth_handler_name, context) ``` +**Note**: Uses `agentic_app_id` (the agent instance app ID), not `agentic_user_id`. The observability backend requires `agent_id` on the route, token `azp`, and span `gen_ai.agent.id` to all match the app instance ID. + **Why it's important**: This is the core conversation handler - it orchestrates the entire agent invocation flow! --- @@ -304,25 +308,26 @@ class MyAgent(AgentApplication): ```python # main.py -if __name__ == "__main__": - configure( - service_name="GoogleADKSampleAgent", - service_namespace="GoogleADKTesting", - ) +from microsoft.opentelemetry import use_microsoft_opentelemetry - google_adk_agent = GoogleADKAgent() - app = MyAgent(agent=google_adk_agent) +def main(): + if os.getenv("ENABLE_OBSERVABILITY", "true").lower() == "true": + use_microsoft_opentelemetry( + enable_a365=True, + enable_azure_monitor=False, + ) - run_http_app(app=app, host="0.0.0.0", port=3978) + agent_application = MyAgent(GoogleADKAgent()) + start_server(agent_application) ``` **What it does**: Hosts the Google ADK agent as an HTTP service with Microsoft 365 Agents SDK. **What happens**: -1. **Configure Observability**: Sets up distributed tracing and monitoring +1. **Configure Observability**: Sets up the Microsoft OpenTelemetry Distro for distributed tracing 2. **Create Agent**: Instantiates the GoogleADKAgent wrapper 3. **Create Host**: Wraps the agent in the Agent365 hosting framework -4. **Start Server**: Runs an HTTP server that handles incoming messages +4. **Start Server**: Runs an aiohttp server that handles incoming messages **Key Features**: - Enterprise authentication and authorization diff --git a/python/google-adk/sample-agent/README.md b/python/google-adk/sample-agent/README.md index 71c73af3..2be6d142 100644 --- a/python/google-adk/sample-agent/README.md +++ b/python/google-adk/sample-agent/README.md @@ -207,14 +207,14 @@ Run `agentsplayground --help` for all options. # 1. Initialize config (first time only) a365 config init -# 2. Provision all cloud resources and set up the blueprint -a365 setup all +# 2. Provision blueprint and permissions (--aiteammate for AI Teammate agents) +a365 setup all --agent-name "" --aiteammate # 3. Deploy agent code to Azure a365 deploy -# 4. Publish agent to Microsoft 365 admin center -a365 publish +# 4. Publish manifest to Microsoft 365 admin center +a365 publish --agent-name "" --aiteammate ``` ### Running on Azure App Service @@ -239,23 +239,46 @@ All values below come from `a365.config.json` and `a365.generated.config.json` ( | Key | Source | Value | |-----|--------|-------| -| `GOOGLE_API_KEY` | Google AI Studio | Your Google API key | -| `GOOGLE_GENAI_USE_VERTEXAI` | — | `FALSE` | +| `GOOGLE_GENAI_USE_VERTEXAI` | — | `TRUE` (Vertex AI) or `FALSE` (public Gemini API) | | `GEMINI_MODEL` | — | `gemini-2.5-flash` | +| `GOOGLE_API_KEY` | Google AI Studio | Your Google API key (when `VERTEXAI=FALSE`) | +| `GOOGLE_CLOUD_PROJECT` | GCP Console | GCP project ID (when `VERTEXAI=TRUE`) | +| `GOOGLE_CLOUD_LOCATION` | GCP Console | e.g. `us-central1` (when `VERTEXAI=TRUE`) | +| `GOOGLE_APPLICATION_CREDENTIALS` | GCP Console | Path to service account JSON key (when `VERTEXAI=TRUE`) | | `CONNECTIONS__SERVICE_CONNECTION__SETTINGS__CLIENTID` | `a365.generated.config.json` → `agentBlueprintId` | Blueprint App ID | | `CONNECTIONS__SERVICE_CONNECTION__SETTINGS__CLIENTSECRET` | `a365.generated.config.json` → `agentBlueprintClientSecret` | Blueprint client secret | | `CONNECTIONS__SERVICE_CONNECTION__SETTINGS__TENANTID` | `a365.config.json` → `tenantId` | Azure tenant ID | -| `CONNECTIONS__SERVICE_CONNECTION__SETTINGS__SCOPES` | `a365.generated.config.json` → `agentBlueprintId` + `/.default` | `/.default` | +| `CONNECTIONS__SERVICE_CONNECTION__SETTINGS__SCOPES` | — | `5a807f24-c9de-44ee-a3a7-329e88a00ffc/.default` | +| `CONNECTIONSMAP__0__SERVICEURL` | — | `*` | +| `CONNECTIONSMAP__0__CONNECTION` | — | `SERVICE_CONNECTION` | | `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` | +| `AGENTAPPLICATION__USERAUTHORIZATION__HANDLERS__AGENTIC__SETTINGS__ALT_BLUEPRINT_NAME` | — | `SERVICE_CONNECTION` | | `AUTH_HANDLER_NAME` | — | `AGENTIC` | -| `AGENTIC_UPN` | `a365.config.json` → `agentUserPrincipalName` | Agent user principal name | -| `AGENTIC_NAME` | `a365.config.json` → `agentUserDisplayName` | Agent display name | -| `AGENTIC_APP_ID` | `a365.generated.config.json` → `agentBlueprintId` | Blueprint App ID | +| `AGENTIC_UPN` | Instance creation in Copilot | Agent user principal name | +| `AGENTIC_NAME` | Instance creation in Copilot | Agent identity display name | +| `AGENTIC_APP_ID` | Instance creation in Copilot | Agent instance app ID (not the blueprint ID) | | `AGENTIC_TENANT_ID` | `a365.config.json` → `tenantId` | Azure tenant ID | -| `AGENTIC_USER_ID` | `a365.generated.config.json` → `AgenticUserId` | Populated after Teams admin approves the agent instance | +| `AGENTIC_USER_ID` | Instance creation in Copilot | Agent user Object ID (from Entra ID → Users) | +| `A365_AGENT_APP_INSTANCE_ID` | Same as `AGENTIC_APP_ID` | Used by SDK/distro for FIC auth | +| `A365_AGENTIC_USER_ID` | Same as `AGENTIC_USER_ID` | Used by SDK/distro for FIC auth | | `ENABLE_OBSERVABILITY` | — | `true` | +| `ENABLE_A365_OBSERVABILITY_EXPORTER` | — | `true` | +| `ENABLE_KAIRO_EXPORTER` | — | `true` | +| `PYTHON_ENVIRONMENT` | — | `production` | | `OBSERVABILITY_SERVICE_NAME` | — | `GoogleADKSampleAgent` | +| `OBSERVABILITY_SERVICE_NAMESPACE` | — | `GoogleADKTesting` | +| `AGENT_ID` | `a365.generated.config.json` → `agentBlueprintId` | Blueprint ID | +| `AGENT365OBSERVABILITY__AGENTID` | Same as `AGENTIC_APP_ID` | Agent instance app ID for observability routing | +| `AGENT365OBSERVABILITY__AGENTNAME` | — | Agent identity display name | +| `AGENT365OBSERVABILITY__AGENTDESCRIPTION` | — | Agent description | +| `AGENT365OBSERVABILITY__TENANTID` | `a365.config.json` → `tenantId` | Azure tenant ID | +| `AGENT365OBSERVABILITY__AGENTBLUEPRINTID` | `a365.generated.config.json` → `agentBlueprintId` | Blueprint App ID | +| `AGENT365OBSERVABILITY__CLIENTID` | `a365.generated.config.json` → `agentBlueprintId` | Blueprint App ID | +| `AGENT365OBSERVABILITY__CLIENTSECRET` | `a365.generated.config.json` → `agentBlueprintClientSecret` | Blueprint client secret | +| `LOG_LEVEL` | — | `DEBUG` | +| `SCM_DO_BUILD_DURING_DEPLOYMENT` | — | `true` (Azure App Service only) | ### Running on GCP (Cloud Run) @@ -326,31 +349,31 @@ After `a365 deploy` and `a365 publish` complete, the following steps require bro ### Step 3: Create agent instance -1. In Microsoft Teams, go to **Apps** and search for your agent name -2. Select your agent and click **Request Instance** +1. In Microsoft Copilot, search for your blueprint name +2. Click **Create Instance** and enter a name and email address for the AI Teammate 3. A tenant admin must approve the request at: ``` https://admin.cloud.microsoft/#/agents/all/requested ``` -### Step 4: Update AGENTIC_USER_ID after approval +### Step 4: Update agent identity env vars after instance creation -Once the admin approves the agent instance, the agent user is created. Update `AGENTIC_USER_ID` in two places: +Once the instance is created and approved, the AI Teammate user is provisioned in Entra ID. Update the identity variables: -1. Find the value in `a365.generated.config.json` → `AgenticUserId` +1. Find the AI Teammate user in **Azure Portal → Entra ID → Users** → search by the UPN/email you entered during instance creation. -2. Update `.env`: +2. Update `.env` (or Azure App Service Application Settings): ```env - AGENTIC_USER_ID= + AGENTIC_UPN= + AGENTIC_NAME= + AGENTIC_APP_ID= + AGENTIC_USER_ID= + A365_AGENT_APP_INSTANCE_ID= + A365_AGENTIC_USER_ID= + AGENT365OBSERVABILITY__AGENTID= ``` -3. Update the Azure App Service Application Setting: - ```bash - az webapp config appsettings set \ - --name gemini-buddy-agent-webapp \ - --resource-group AgentSDKTestRG \ - --settings AGENTIC_USER_ID= - ``` +> **Note:** These values are fallbacks — in production, the A365 platform delivers the agent identity per-message via `activity.recipient`. The env vars are used for observability configuration and as fallbacks when the activity doesn't carry the identity (e.g., local dev). > **Note:** The agent user creation is asynchronous — it can take a few minutes to a few hours to become searchable in Teams after the instance is approved. @@ -362,18 +385,31 @@ All configuration is via environment variables (`.env` for local, App Settings f | Variable | Default | Description | |----------|---------|-------------| -| `GOOGLE_API_KEY` | — | **Required**. Google Gemini API key | +| `GOOGLE_API_KEY` | — | Google Gemini API key (required when `VERTEXAI=FALSE`) | | `GEMINI_MODEL` | `gemini-2.5-flash` | Gemini model to use | -| `GOOGLE_GENAI_USE_VERTEXAI` | `FALSE` | Set `TRUE` to use Vertex AI instead of Gemini API | +| `GOOGLE_GENAI_USE_VERTEXAI` | `FALSE` | `TRUE` = Vertex AI, `FALSE` = public Gemini API | +| `GOOGLE_CLOUD_PROJECT` | — | GCP project ID (required when `VERTEXAI=TRUE`) | +| `GOOGLE_CLOUD_LOCATION` | — | GCP region, e.g. `us-central1` (required when `VERTEXAI=TRUE`) | +| `GOOGLE_APPLICATION_CREDENTIALS` | — | Path to service account JSON key (required when `VERTEXAI=TRUE`) | | `AUTH_HANDLER_NAME` | _(empty)_ | Empty = anonymous (Playground/local), `AGENTIC` = production | | `BEARER_TOKEN` | _(empty)_ | Token for MCP tool access. Get with `a365 develop get-token -o raw` | -| `AGENTIC_APP_ID` | — | Agent App ID from A365 portal | +| `AGENTIC_APP_ID` | — | Agent instance app ID (fallback; delivered per-message in production) | | `AGENTIC_TENANT_ID` | — | Azure tenant ID | -| `AGENTIC_USER_ID` | — | Agent User ID from A365 portal | -| `PORT` | `3978` | Server port (Azure sets this to `8000` automatically) | +| `AGENTIC_USER_ID` | — | Agent user Object ID (fallback; delivered per-message in production) | +| `A365_AGENT_APP_INSTANCE_ID` | — | Same as `AGENTIC_APP_ID`; used by SDK/distro for FIC auth | +| `A365_AGENTIC_USER_ID` | — | Same as `AGENTIC_USER_ID`; used by SDK/distro for FIC auth | +| `PORT` | `3978` | Server port (Azure sets `8000` automatically) | | `ENABLE_OBSERVABILITY` | `true` | Enable OpenTelemetry tracing | | `ENABLE_A365_OBSERVABILITY_EXPORTER` | `false` | Send traces to A365 backend (`true` for production) | +| `ENABLE_KAIRO_EXPORTER` | `false` | Enable Kairo exporter (`true` for production) | +| `PYTHON_ENVIRONMENT` | `development` | `development` or `production` | | `LOG_LEVEL` | `INFO` | Logging level (`DEBUG`, `INFO`, `WARNING`, `ERROR`) | +| `OBSERVABILITY_SERVICE_NAME` | `GoogleADKSampleAgent` | Service name for OpenTelemetry traces | +| `OBSERVABILITY_SERVICE_NAMESPACE` | `GoogleADKTesting` | Service namespace for OpenTelemetry traces | +| `AGENT_ID` | — | Blueprint ID for observability routing | +| `AGENT365OBSERVABILITY__AGENTID` | — | Agent instance app ID for observability exporter | +| `AGENT365OBSERVABILITY__CLIENTID` | — | Blueprint App ID for exporter auth | +| `AGENT365OBSERVABILITY__CLIENTSECRET` | — | Blueprint client secret for exporter auth | --- diff --git a/python/google-adk/sample-agent/agent.py b/python/google-adk/sample-agent/agent.py index 23c6d61e..242f252c 100644 --- a/python/google-adk/sample-agent/agent.py +++ b/python/google-adk/sample-agent/agent.py @@ -9,9 +9,7 @@ from mcp_tool_registration_service import McpToolRegistrationService -from microsoft_agents_a365.observability.core.middleware.baggage_builder import ( - BaggageBuilder, -) +from microsoft.opentelemetry.a365.core import BaggageBuilder from google.adk.runners import Runner from google.adk.sessions.in_memory_session_service import InMemorySessionService @@ -172,9 +170,25 @@ async def invoke_agent_with_scope( """ # Playground sends a minimal recipient (id + name only). # Fall back to env vars so observability baggage is still populated. + # NOTE: Use agentic_app_id (app instance ID), NOT agentic_user_id. + # The observability backend requires agent_id on the route, token azp, + # and span gen_ai.agent.id to all match the app instance ID. recipient = context.activity.recipient tenant_id = getattr(recipient, "tenant_id", None) or os.getenv("AGENTIC_TENANT_ID", "") - agent_id = getattr(recipient, "agentic_user_id", None) or os.getenv("AGENTIC_USER_ID", "") + agent_id = getattr(recipient, "agentic_app_id", None) or os.getenv("AGENTIC_APP_ID", "") + # Log identity source for diagnostics, but mask the IDs themselves + # (last 4 chars only) and emit at DEBUG to avoid leaking sensitive + # identifiers into INFO-level production logs. + if logger.isEnabledFor(logging.DEBUG): + def _mask(value: str) -> str: + return f"***{value[-4:]}" if value and len(value) > 4 else "***" + + logger.debug( + "Observability identity — agent_id (route/span): '%s', " + "tenant_id: '%s', source: %s", + _mask(agent_id), _mask(tenant_id), + "activity.recipient.agentic_app_id" if getattr(recipient, "agentic_app_id", None) else "env:AGENTIC_APP_ID", + ) with BaggageBuilder().tenant_id(tenant_id).agent_id(agent_id).build(): return await self.invoke_agent(message=message, auth=auth, auth_handler_name=auth_handler_name, context=context) diff --git a/python/google-adk/sample-agent/docs/design.md b/python/google-adk/sample-agent/docs/design.md index a7a66f2d..02481816 100644 --- a/python/google-adk/sample-agent/docs/design.md +++ b/python/google-adk/sample-agent/docs/design.md @@ -2,117 +2,156 @@ ## Overview -This sample demonstrates an agent built using Google's Agent Development Kit (ADK). It showcases integration with Google's AI models and tools within the Microsoft Agent 365 ecosystem. +This sample demonstrates an agent built using Google's Agent Development Kit (ADK). It showcases integration with Google's Gemini models and tools within the Microsoft Agent 365 ecosystem. ## What This Sample Demonstrates - Google ADK integration with Agent 365 -- Google AI model configuration +- Google Gemini model configuration (public API and Vertex AI) - MCP tool integration with Google ADK -- Microsoft Agent 365 observability -- Async message processing +- Microsoft Agent 365 observability via Microsoft OpenTelemetry Distro +- Async message processing with typing indicators ## Architecture ``` ┌─────────────────────────────────────────────────────────────────┐ -│ start_with_generic_host.py │ +│ main.py │ +│ (Observability config + server startup) │ └─────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────┐ -│ GenericAgentHost │ +│ MyAgent (hosting.py) │ +│ (AgentApplication — message routing, auth, notifications) │ └─────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────┐ -│ GoogleADKAgent │ +│ GoogleADKAgent (agent.py) │ │ ┌─────────────────────────────────────────────────────────────┐│ -│ │ Google AI Client ││ -│ │ Google ADK → Gemini Model → Response ││ +│ │ Google ADK Agent ││ +│ │ Agent → Runner → Gemini Model → Response ││ │ └─────────────────────────────────────────────────────────────┘│ │ ┌─────────────────────────────────────────────────────────────┐│ │ │ MCP Tools ││ -│ │ Tool Registration → Function Calling → Tool Execution ││ +│ │ McpToolRegistrationService → McpToolset → Tool Execution ││ │ └─────────────────────────────────────────────────────────────┘│ └─────────────────────────────────────────────────────────────────┘ ``` ## Key Components +### main.py +Entry point and server startup: +- Configures observability via `use_microsoft_opentelemetry()` +- Builds JWT auth middleware for production +- Starts aiohttp server on configured port + +### hosting.py +Agent hosting framework: +- `MyAgent(AgentApplication)` — handles message routing, auth, notifications +- Message handler with typing indicators and immediate acknowledgments +- Email and Word comment notification handlers +- Agent lifecycle event handling + ### agent.py -Main agent implementation: -- Google ADK client configuration -- Gemini model setup -- MCP tool integration -- Message processing +Core agent implementation: +- Google ADK Agent with Gemini model +- MCP tool initialization with bearer token validation and timeout +- Observability baggage injection (`agentic_app_id`, not `agentic_user_id`) +- Per-turn instruction personalization with user display name + +### mcp_tool_registration_service.py +MCP tool integration: +- Discovers MCP servers via `McpToolServerConfigurationService` +- Creates `McpToolset` with `StreamableHTTPConnectionParams` for each server +- Returns new Agent instance with tools attached ## Google ADK-Specific Patterns ### Agent Setup ```python -import google.generativeai as genai +from google.adk.agents import Agent +from google.adk.runners import Runner +from google.adk.sessions.in_memory_session_service import InMemorySessionService -class GoogleADKAgent(AgentInterface): +class GoogleADKAgent: def __init__(self): - # Configure Google AI - genai.configure(api_key=os.getenv("GOOGLE_API_KEY")) - - # Create model with tools - self.model = genai.GenerativeModel( - model_name="gemini-1.5-pro", - system_instruction=self.system_prompt, + self.agent = Agent( + name="my_agent", + model=os.getenv("GEMINI_MODEL", "gemini-2.5-flash"), + description="Agent to test Mcp tools.", + instruction="You are a helpful AI assistant...", ) - async def process_user_message(self, message, auth, auth_handler_name, context): - # Setup MCP tools for Google ADK - tools = await self.setup_mcp_tools(auth, auth_handler_name, context) + async def invoke_agent(self, message, auth, auth_handler_name, context): + # Initialize agent with MCP tools (with 10s timeout) + agent = await self._initialize_agent(auth, auth_handler_name, context) - # Generate response with function calling - response = await self.model.generate_content_async( - message, - tools=tools, + # Create runner and process message + runner = Runner( + app_name="agents", + agent=agent, + session_service=InMemorySessionService(), ) + result = await runner.run_debug(user_messages=[message]) + + # Extract text responses from event stream + responses = [] + for event in result: + for part in event.content.parts: + if hasattr(part, 'text') and part.text: + responses.append(part.text) - # Handle function calls if present - while response.candidates[0].content.parts[-1].function_call: - function_response = await self.execute_function( - response.candidates[0].content.parts[-1].function_call - ) - response = await self.model.generate_content_async( - [message, response.candidates[0].content, function_response], - tools=tools, - ) - - return response.text + return responses[-1] if responses else "No response." ``` ## Configuration ### .env file ```bash -# Google AI Configuration -GOOGLE_API_KEY=... -GOOGLE_MODEL=gemini-1.5-pro +# Google Gemini Configuration +GOOGLE_GENAI_USE_VERTEXAI=FALSE # TRUE for Vertex AI, FALSE for public API +GEMINI_MODEL=gemini-2.5-flash +GOOGLE_API_KEY=... # When VERTEXAI=FALSE +GOOGLE_CLOUD_PROJECT=... # When VERTEXAI=TRUE +GOOGLE_CLOUD_LOCATION=us-central1 # When VERTEXAI=TRUE +GOOGLE_APPLICATION_CREDENTIALS=... # When VERTEXAI=TRUE # Authentication -BEARER_TOKEN=... -AUTH_HANDLER_NAME=AGENTIC -CLIENT_ID=... -TENANT_ID=... +AUTH_HANDLER_NAME=AGENTIC # Empty for local dev / Playground +BEARER_TOKEN=... # For local MCP tool access + +# Agent Identity (fallbacks — delivered per-message in production) +AGENTIC_APP_ID=... +AGENTIC_USER_ID=... +AGENTIC_TENANT_ID=... +A365_AGENT_APP_INSTANCE_ID=... # Same as AGENTIC_APP_ID +A365_AGENTIC_USER_ID=... # Same as AGENTIC_USER_ID + +# Service Connection +CONNECTIONS__SERVICE_CONNECTION__SETTINGS__CLIENTID=... +CONNECTIONS__SERVICE_CONNECTION__SETTINGS__CLIENTSECRET=... +CONNECTIONS__SERVICE_CONNECTION__SETTINGS__TENANTID=... # Observability -OBSERVABILITY_SERVICE_NAME=google-adk-sample-agent +ENABLE_OBSERVABILITY=true +ENABLE_A365_OBSERVABILITY_EXPORTER=true # false for local dev +OBSERVABILITY_SERVICE_NAME=GoogleADKSampleAgent ``` ## Message Flow ``` -1. HTTP POST /api/messages -2. GenericAgentHost routes to Google ADK agent -3. Gemini model processes message -4. Function calling loop if tools requested -5. Final response returned +1. HTTP POST /api/messages → aiohttp server (main.py) +2. JWT validation (production) or anonymous claims (local dev) +3. MyAgent routes to message_handler (hosting.py) +4. Immediate ack: "Got it — working on it…" + typing indicator loop +5. GoogleADKAgent.invoke_agent_with_scope() — sets observability baggage +6. _initialize_agent() — MCP tool registration with 10s timeout +7. Runner.run_debug() — Gemini processes message with tools +8. Response sent back to user; typing loop cancelled; MCP connections cleaned up ``` ## Dependencies @@ -120,24 +159,34 @@ OBSERVABILITY_SERVICE_NAME=google-adk-sample-agent ```toml [project] dependencies = [ - "google-generativeai>=0.5.0", - "microsoft-agents-hosting-aiohttp>=0.0.1", - "microsoft-agents-hosting-core>=0.0.1", - "microsoft_agents_a365_observability_core>=0.0.1", - "microsoft_agents_a365_tooling_core>=0.0.1", + "google-adk>=1.32.0,<2", + "microsoft-agents-hosting-aiohttp", + "microsoft-agents-hosting-core", + "microsoft-agents-authentication-msal", + "microsoft-agents-activity", + "microsoft_agents_a365_tooling >= 1.0.0", + "microsoft_agents_a365_observability_core >= 1.0.0", + "microsoft_agents_a365_notifications >= 1.0.0", + "microsoft-opentelemetry >= 1.2.0", "python-dotenv>=1.0.0", + "aiohttp>=3.9.0", ] ``` ## Running the Agent ```bash -uv run python start_with_generic_host.py +# Local dev +python main.py + +# With Agents Playground +agentsplayground -e "http://localhost:3978/api/messages" -c "emulator" ``` ## Extension Points -1. **Model Selection**: Choose different Gemini models -2. **MCP Tools**: Configure in tool manifest -3. **System Instructions**: Customize agent behavior -4. **Safety Settings**: Configure content filters +1. **Model Selection**: Change `GEMINI_MODEL` env var (e.g., `gemini-2.5-pro`) +2. **MCP Tools**: Configure in `ToolingManifest.json` +3. **System Instructions**: Customize `_INSTRUCTION_TEMPLATE` in `agent.py` +4. **Notifications**: Add handlers in `hosting.py` for new notification types +5. **Vertex AI**: Set `GOOGLE_GENAI_USE_VERTEXAI=TRUE` for production GCP deployment diff --git a/python/google-adk/sample-agent/hosting.py b/python/google-adk/sample-agent/hosting.py index 6d8c217d..42aa3528 100644 --- a/python/google-adk/sample-agent/hosting.py +++ b/python/google-adk/sample-agent/hosting.py @@ -171,6 +171,11 @@ async def agent_notification_handler( notification_type = notification_activity.notification_type logger.info(f"Received agent notification of type: {notification_type}") + # Lifecycle notifications (onboarding, etc.) don't support replies — just log and return. + if notification_type == NotificationTypes.AGENT_LIFECYCLE: + logger.info("Agent lifecycle event received — no reply needed.") + return + # Handle Email Notifications if notification_type == NotificationTypes.EMAIL_NOTIFICATION: await self.email_notification_handler(context, notification_activity) diff --git a/python/google-adk/sample-agent/main.py b/python/google-adk/sample-agent/main.py index d3378700..024f81b6 100644 --- a/python/google-adk/sample-agent/main.py +++ b/python/google-adk/sample-agent/main.py @@ -15,8 +15,8 @@ from microsoft_agents.hosting.core.authorization import AgentAuthConfiguration from microsoft_agents.hosting.aiohttp import start_agent_process, jwt_authorization_middleware -# Microsoft Agent 365 Observability Imports -from microsoft_agents_a365.observability.core.config import configure +# Microsoft OpenTelemetry Distro — replaces standalone configure() +from microsoft.opentelemetry import use_microsoft_opentelemetry # Load environment variables from .env file from dotenv import load_dotenv @@ -140,13 +140,16 @@ def main(): # ENABLE_A365_OBSERVABILITY_EXPORTER=true sends traces to the A365 backend; # false falls back to the console exporter (expected in local/dev). if os.getenv("ENABLE_OBSERVABILITY", "true").lower() == "true": - configure( - service_name=os.getenv("OBSERVABILITY_SERVICE_NAME", "GoogleADKSampleAgent"), - service_namespace=os.getenv("OBSERVABILITY_SERVICE_NAMESPACE", "GoogleADKTesting"), + # Use the Microsoft OpenTelemetry Distro with built-in FIC token resolver. + # The distro reads CONNECTIONS__SERVICE_CONNECTION__SETTINGS__* env vars + # and A365_AGENT_APP_INSTANCE_ID / A365_AGENTIC_USER_ID for FIC auth. + use_microsoft_opentelemetry( + enable_a365=True, + enable_azure_monitor=False, ) logger.info( - "Observability configured (service=%s, a365_exporter=%s)", - os.getenv("OBSERVABILITY_SERVICE_NAME", "GoogleADKSampleAgent"), + "Observability configured via Microsoft OpenTelemetry Distro " + "(enable_a365=True, a365_exporter=%s)", os.getenv("ENABLE_A365_OBSERVABILITY_EXPORTER", "false"), ) else: diff --git a/python/google-adk/sample-agent/pyproject.toml b/python/google-adk/sample-agent/pyproject.toml index a95436f2..bb4157b4 100644 --- a/python/google-adk/sample-agent/pyproject.toml +++ b/python/google-adk/sample-agent/pyproject.toml @@ -6,18 +6,18 @@ authors = [ { name = "Microsoft", email = "support@microsoft.com" } ] dependencies = [ - # Google ADK -- official package - "google-adk>=1.18.0", + # Google ADK -- official package (1.32+ lifted the otel<1.39 constraint) + "google-adk>=1.32.0,<2", # Microsoft Agents SDK - Official packages for hosting and integration - "microsoft-agents-hosting-aiohttp", - "microsoft-agents-hosting-core", - "microsoft-agents-authentication-msal", - "microsoft-agents-activity", + "microsoft-agents-hosting-aiohttp>=0.10.0.dev4", + "microsoft-agents-hosting-core>=0.10.0.dev4", + "microsoft-agents-authentication-msal>=0.10.0.dev4", + "microsoft-agents-activity>=0.10.0.dev4", # Core dependencies - "python-dotenv", - "aiohttp", + "python-dotenv>=1.0.0", + "aiohttp>=3.9.0", # HTTP server support for MCP servers "uvicorn[standard]>=0.20.0", @@ -32,10 +32,16 @@ dependencies = [ # Additional utilities "typing-extensions>=4.0.0", - # Microsoft Agent 365 SDK packages - "microsoft_agents_a365_tooling >= 0.1.0", - "microsoft_agents_a365_observability_core >= 0.1.0", - "microsoft_agents_a365_notifications >= 0.1.0", + # Microsoft Agent 365 SDK packages (GA 1.0.0 — released May 1 2026) + "microsoft-agents-a365-tooling>=1.0.0", + "microsoft-agents-a365-observability-core>=1.0.0", + "microsoft-agents-a365-observability-hosting>=1.0.0", + "microsoft-agents-a365-notifications>=1.0.0", + "microsoft-agents-a365-runtime>=1.0.0", + + # Microsoft OpenTelemetry Distro — required by main.py and agent.py + # (use_microsoft_opentelemetry, microsoft.opentelemetry.a365.core.BaggageBuilder) + "microsoft-opentelemetry>=1.2.0", ] requires-python = ">=3.11" @@ -50,26 +56,6 @@ default = true # This ensures we always get the latest features and fixes [tool.uv] prerelease = "allow" -# Overrides to resolve conflicts between google-adk and agent-framework-core: -# - google-adk requires opentelemetry-sdk<1.39.0 -# - agent-framework-core requires opentelemetry-sdk>=1.39.0 -# google-adk is the binding constraint; the otel API is stable across these versions. -# Also overrides gradio's outdated pydantic upper bound and old ruff pin. -override-dependencies = [ - # Pin entire otel stack to 1.38.x — google-adk requires sdk<1.39.0 - # and all otel packages must be on the same minor version. - "opentelemetry-api>=1.38.0,<1.39.0", - "opentelemetry-sdk>=1.38.0,<1.39.0", - "opentelemetry-exporter-otlp>=1.38.0,<1.39.0", - "opentelemetry-exporter-otlp-proto-http>=1.38.0,<1.39.0", - "opentelemetry-exporter-otlp-proto-grpc>=1.38.0,<1.39.0", - "opentelemetry-exporter-otlp-proto-common>=1.38.0,<1.39.0", - "opentelemetry-proto>=1.38.0,<1.39.0", - "opentelemetry-semantic-conventions>=0.59b0,<0.60b0", - "opentelemetry-instrumentation>=0.59b0,<0.60b0", - "pydantic>=2.0.0", - "ruff>=0.9.3", -] [project.optional-dependencies] dev = [ diff --git a/python/google-adk/sample-agent/requirements.txt b/python/google-adk/sample-agent/requirements.txt new file mode 100644 index 00000000..d163a965 --- /dev/null +++ b/python/google-adk/sample-agent/requirements.txt @@ -0,0 +1,22 @@ +# Google ADK +google-adk>=1.32.0,<2 + +# Microsoft Agents SDK +microsoft-agents-hosting-aiohttp==0.10.0.dev4 +microsoft-agents-hosting-core==0.10.0.dev4 +microsoft-agents-authentication-msal==0.10.0.dev4 +microsoft-agents-activity==0.10.0.dev4 + +# Microsoft Agent 365 +microsoft-agents-a365-notifications==1.0.0 +microsoft-agents-a365-tooling==1.0.0 +microsoft-agents-a365-observability-core==1.0.0 +microsoft-agents-a365-observability-hosting==1.0.0 +microsoft-agents-a365-runtime==1.0.0 + +# Microsoft OpenTelemetry Distro +microsoft-opentelemetry==1.2.0 + +# Utilities +python-dotenv>=1.0.0 +aiohttp>=3.9.0