Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 37 additions & 2 deletions python/google-adk/sample-agent/.env.template
Original file line number Diff line number Diff line change
Expand Up @@ -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=<<YOUR_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=<<YOUR_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=<<YOUR_GOOGLE_CLOUD_PROJECT>>
GOOGLE_CLOUD_LOCATION=<<YOUR_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=<<PATH_TO_SERVICE_ACCOUNT_KEY_JSON>>
# -----------------------------------------------------------------------------
# Agent365 Service Connection (OAuth client credentials)
# -----------------------------------------------------------------------------
Expand Down Expand Up @@ -68,6 +81,10 @@ AGENTIC_USER_ID=<<YOUR_AGENTIC_USER_ID>>
AGENTIC_APP_ID=<<YOUR_AGENTIC_APP_ID>>
AGENTIC_TENANT_ID=<<YOUR_TENANT_ID>>

# A365 platform fallback vars (same values as AGENTIC_APP_ID and AGENTIC_USER_ID)
A365_AGENT_APP_INSTANCE_ID=<<YOUR_AGENTIC_APP_ID>>
A365_AGENTIC_USER_ID=<<YOUR_AGENTIC_USER_ID>>

# -----------------------------------------------------------------------------
# Local Development
# -----------------------------------------------------------------------------
Expand Down Expand Up @@ -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=<<YOUR_BLUEPRINT_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=<<YOUR_AGENT_INSTANCE_ID>>
AGENT365OBSERVABILITY__AGENTNAME=<<YOUR_AGENT_IDENTITY_NAME>>
AGENT365OBSERVABILITY__AGENTDESCRIPTION=<<YOUR_AGENT_DESCRIPTION>>
AGENT365OBSERVABILITY__TENANTID=<<YOUR_TENANT_ID>>
AGENT365OBSERVABILITY__AGENTBLUEPRINTID=<<YOUR_BLUEPRINT_ID>>
AGENT365OBSERVABILITY__CLIENTID=<<YOUR_BLUEPRINT_ID>>
AGENT365OBSERVABILITY__CLIENTSECRET=<<YOUR_PLAINTEXT_CLIENT_SECRET>>
51 changes: 28 additions & 23 deletions python/google-adk/sample-agent/AGENT-CODE-WALKTHROUGH.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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)

Expand All @@ -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`:

Expand Down Expand Up @@ -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!

---
Expand Down Expand Up @@ -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
Expand Down
96 changes: 66 additions & 30 deletions python/google-adk/sample-agent/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 "<your-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 "<your-agent-name>" --aiteammate
```

### Running on Azure App Service
Expand All @@ -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` | `<blueprintId>/.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` |
Comment thread
Yogeshp-MSFT marked this conversation as resolved.
| `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)

Expand Down Expand Up @@ -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=<AgenticUserId from a365.generated.config.json>
AGENTIC_UPN=<UPN you entered during instance creation>
AGENTIC_NAME=<display name you entered>
AGENTIC_APP_ID=<agent instance app ID from Entra>
AGENTIC_USER_ID=<Object ID of the AI Teammate user from Entra>
A365_AGENT_APP_INSTANCE_ID=<same as AGENTIC_APP_ID>
A365_AGENTIC_USER_ID=<same as AGENTIC_USER_ID>
AGENT365OBSERVABILITY__AGENTID=<same as AGENTIC_APP_ID>
```

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=<AgenticUserId>
```
> **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.

Expand All @@ -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 |

---

Expand Down
22 changes: 18 additions & 4 deletions python/google-adk/sample-agent/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)

Expand Down
Loading
Loading