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
1 change: 1 addition & 0 deletions python/pyrightconfig.samples.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"**/05-end-to-end/**",
"**/harness/**",
"**/local_telegram/**",
"**/foundry-hosted-agents/invocations/telegram/**",
"**/agent_with_foundry_tracing.py",
"**/azure_responses_client_with_foundry.py"
],
Expand Down
2 changes: 1 addition & 1 deletion python/samples/04-hosting/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ This directory contains Python samples that demonstrate different ways to host A
| A2A | Agent-to-Agent protocol interoperability or remote agent invocation. | [`a2a/README.md`](./a2a/README.md) |
| Azure Functions | HTTP or serverless hosting on Azure Functions. | [Durable extension Azure Functions samples](https://github.com/microsoft/agent-framework-durable-extension/tree/main/python/samples/azure_functions) |
| Durable Task | Durable execution, long-running flows, or orchestration patterns. | [Durable extension samples](https://github.com/microsoft/agent-framework-durable-extension/tree/main/python/samples) |
| Foundry Hosted Agents | Microsoft Foundry hosted agent deployment. | [`foundry-hosted-agents/README.md`](./foundry-hosted-agents/README.md) |
| Foundry Hosted Agents | Microsoft Foundry hosted agent deployment, including a Telegram + APIM Invocations sample. | [`foundry-hosted-agents/README.md`](./foundry-hosted-agents/README.md) |
| Self-Hosted Protocol Helpers | Application-owned OpenAI Responses endpoints or Telegram bots. | [`af-hosting/README.md`](./af-hosting/README.md) |

## How to Choose
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,5 +143,6 @@ Before deploying this pattern:
- add bounded retries, delivery telemetry, and dead-letter handling;
- decide how partial streaming edits should recover when a final edit fails.

> This sample is **self-hosted**. The multi-protocol Telegram + Invocations
> Foundry-hosted sample remains part of the separate Invocations work.
> This sample is **self-hosted**. For a Foundry direct-code deployment fronted
> by API Management with durable Cosmos DB history, see the
> [Telegram Invocations sample](../../foundry-hosted-agents/invocations/telegram/).
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ See [Using deployed agent](responses/using_deployed_agent.py) for service-create
|---|--------|-------------|
| 1 | [Basic](invocations/basic/) | A minimal agent demonstrating basic request/response using the invocations protocol. |
| 2 | [Break Glass](invocations/break_glass/) | An agent demonstrating a "break glass" scenario where customizations of the API behaviors are needed, allowing for more direct control over how requests and responses are handled by the hosting layer. |
| 3 | [Telegram](invocations/telegram/) | A Telegram bot routed through API Management to a direct-code hosted agent, with streaming responses and durable Cosmos DB history. |

## Running the Agent Host Locally

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
.azure/
.agent_configs/
.env
.git/
.github/
.pytest_cache/
.ruff_cache/
.venv/
README.md
azure.yaml
deploy.sh
remove.sh
eval.yaml
infra/
tests/
**/__pycache__/
**/*.pyc
!instructions.md
!pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
FOUNDRY_PROJECT_ENDPOINT=https://<foundry-account>.services.ai.azure.com/api/projects/<project>
AZURE_AI_MODEL_DEPLOYMENT_NAME=gpt-5.6-luna
ENABLE_SENSITIVE_DATA=true
AZURE_COSMOS_ENDPOINT=https://<cosmos-account>.documents.azure.com:443/
AZURE_COSMOS_DATABASE_NAME=telegram-agent
AZURE_COSMOS_CONTAINER_NAME=chat-history
KEY_VAULT_URL=https://<key-vault>.vault.azure.net/
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.azure/
.deploy-parameters.json
.env
.pytest_cache/
.venv/
**/__pycache__/
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
# Telegram bot with a Foundry Hosted Agent

This sample deploys a Telegram bot whose webhook is handled by an Agent Framework agent running as a Microsoft
Foundry Hosted Agent:

```text
Telegram -> API Management -> Foundry Hosted Agent (Invocations 2.0)
-> Agent Framework -> Telegram Bot API
|
+-> Cosmos DB conversation history
```

It is a complete learning deployment rather than a production reference architecture. The sibling
[`af-hosting/local_telegram`](../../../af-hosting/local_telegram/) sample is the better starting point for local
polling, an app-owned FastAPI webhook, and in-memory state. This sample focuses on Foundry direct-code deployment,
APIM ingress, managed identities, and durable history.

## What is deployed

The self-contained Bicep templates create:

- a resource group, Foundry account and project, and model deployment;
- a Log Analytics workspace and Application Insights connected to the Foundry project;
- Consumption-tier API Management with a system-assigned managed identity;
- a Key Vault containing the Telegram bot token and webhook secret;
- a serverless Cosmos DB account, database, and `/session_id`-partitioned container.

These resources can incur Azure and model-usage charges. Consumption APIM, serverless Cosmos DB, telemetry
ingestion, and model tokens are usage-billed; availability and pricing vary by region.

## Prerequisites

1. Bash, `curl`, `jq`, and `openssl`.
2. Azure CLI with Bicep and Azure Developer CLI (`azd`) with the `azure.ai.agents` extension.
3. Authenticated Azure CLI and azd sessions with permission to create subscription deployments, role assignments,
and the resources above.
4. A Telegram bot created with BotFather and its token in the current shell:

```bash
export TELEGRAM_BOT_TOKEN="<bot-token>"
```

Do not place the token in `.env`, `azure.yaml`, an azd environment, or source control. The deployment writes it
directly to Key Vault as a secure Bicep parameter.

## Deploy

From this directory, run:

```bash
./deploy.sh
```

The script:

1. validates prerequisites and configuration;
2. builds and previews Bicep before provisioning;
3. creates or selects an isolated azd environment and sets non-secret deployment outputs;
4. validates metadata and performs a Python 3.13 direct-code deployment whose remote build installs
`pyproject.toml` dependencies (the PEP 621 metadata used by uv includes an equivalent Poetry dependency table
for Foundry's current Oryx builder);
5. grants the hosted-agent identity Key Vault secret-read and Cosmos DB data-contributor roles;
6. checks the hosted endpoint and APIM secret rejection; and
7. registers and verifies the Telegram webhook for `message`, `edited_message`, and `callback_query`.

The script does not print tokens or webhook secrets. It creates a mode-`0600` parameter file only for the duration
of Bicep deployment and removes it on exit.

### Configuration

All settings are optional except `TELEGRAM_BOT_TOKEN`.

| Variable | Default | Purpose |
|---|---|---|
| `NAME_PREFIX` | `telegramagent` | 3-16 lowercase alphanumeric resource-name prefix |
| `AZURE_SUBSCRIPTION_ID` | current Azure CLI subscription | Target subscription |
| `AZURE_LOCATION` | `eastus2` | Foundry, APIM, Key Vault, and monitoring region |
| `COSMOS_LOCATION` | `AZURE_LOCATION` | Cosmos DB region; change if serverless capacity is unavailable |
| `RESOURCE_GROUP_NAME` | `rg-$NAME_PREFIX` | Resource group name |
| `AZD_ENV_NAME` | `$NAME_PREFIX-telegram` | Isolated azd environment |
| `APIM_PUBLISHER_EMAIL` | Azure account name | Required APIM publisher email |
| `APIM_PUBLISHER_NAME` | `Agent Framework sample` | APIM publisher name |
| `MODEL_NAME` | `gpt-5.6-luna` | Model and deployment name |
| `MODEL_VERSION` | `2026-07-09` | Model version |
| `MODEL_FORMAT` | `OpenAI` | Model format |
| `MODEL_SKU_NAME` | `DataZoneStandard` | Model deployment SKU |
| `MODEL_CAPACITY` | `10` | Model deployment capacity |
| `ENABLE_SENSITIVE_DATA` | `true` | Include prompts, responses, and tool data in exported telemetry |
| `FOUNDRY_ACCESS_TIMEOUT_SECONDS` | `180` | Maximum wait for a new deployer role, with immediate access checks |
| `APIM_SECRET_REFRESH_TIMEOUT_SECONDS` | `180` | Maximum wait for APIM to load the current Key Vault webhook secret |
| `INFRA_DEPLOYMENT_ATTEMPTS` | `6` | Bounded retries for eventual-consistency failures during provisioning |
| `INFRA_RETRY_DELAY_SECONDS` | `30` | Delay between infrastructure deployment attempts |
| `RBAC_PROPAGATION_WAIT_SECONDS` | `30` | Wait only after creating data-plane assignments |

Choose a model/version/SKU available in the selected region and subscription. To use a service principal, set
`DEPLOYER_OBJECT_ID` and `DEPLOYER_PRINCIPAL_TYPE=ServicePrincipal` when automatic discovery is unsuitable.

### Rotate the webhook secret

Normal redeployments reuse the existing Key Vault secret. Rotate it explicitly with:

```bash
ROTATE_TELEGRAM_WEBHOOK_SECRET=1 ./deploy.sh
```

The Bicep deployment creates a new secret version, APIM's versionless Key Vault reference follows it, and the final
step registers the same new value with Telegram.

## How requests flow

1. Telegram sends an authenticated HTTPS webhook to APIM.
2. APIM compares `X-Telegram-Bot-Api-Secret-Token` with a Key Vault-backed named value, then removes the header.
3. The policy reads the original JSON object, adds only the top-level `channel: "telegram"` discriminator, and
preserves the Telegram update fields.
4. It extracts the chat id from `message`, `edited_message`, or `callback_query.message`, sets it as
`agent_session_id`, and authenticates to Foundry with APIM's managed identity.
5. The hosted handler validates and dispatches `channel`, then uses the APIM-provided session id for the
`AgentSession` and Cosmos history partition.

One bot is deployed per sample environment, so the chat-derived session key is scoped by that environment.
`/new` clears that Cosmos history without invoking the model. `/start` and `/help` are also handled in application
code. Callback queries are acknowledged before their data is processed.

For photos, PDF documents, and MP3 or WAV audio, the agent calls Telegram `getFile`, rejects files over 1 MiB,
downloads the bytes, and creates an inline data URI. The conservative limit leaves room for base64 and Cosmos DB
item serialization overhead. Voice notes, video, and unsupported document/audio formats are rejected before model
invocation. A token-bearing Telegram file URL is never sent to the model. Captions remain text input when supported
media cannot be resolved.

Agent execution is streaming-only. The bot sends a placeholder, consumes a `ResponseStream`, throttles cumulative
`editMessageText` calls, and ignores only Telegram's idempotent “message is not modified” error. Final image
operations are preserved; an image-only response deletes the placeholder before sending the image. The Invocations
request stays open until streaming and Telegram delivery finish.

The agent configures the Azure Monitor OpenTelemetry exporter from the Foundry project's Application Insights
connection. Sensitive GenAI telemetry is enabled by default, so model spans can include prompts, responses, and
tool arguments/results. Set `ENABLE_SENSITIVE_DATA=false` before deployment when that content must not be collected.
The deployment grants the hosted-agent identity account-scoped `Foundry User` access so it can resolve that
connection. Content-bearing Agent Framework and HTTP client INFO logs remain suppressed.

## Validate

After deployment:

1. Send `/start`, `/help`, and a normal text message.
2. Ask a follow-up to verify durable context.
3. Send `/new`, then verify the previous topic is no longer remembered.
4. Send a captioned image and an inline-button callback.
5. Review traces in the deployed Application Insights resource.

Focused mocked tests require no Azure resources or Telegram bot:

```bash
uv sync --group dev
uv run --group dev pytest -q
uv run --group dev ruff check main.py tests
uv run --group dev pyright
```

## Production limitations

- Telegram waits synchronously while the model streams and messages are edited. Foundry/APIM/backend timeouts can
cause Telegram retries even after partial side effects.
- Updates are not deduplicated by `update_id`; there is no queue, dead-letter path, or durable delivery workflow.
- Retry and rate-limit handling is intentionally basic.
- Public endpoints are enabled; the sample does not configure private networking or an allowlist.
- There is no distributed per-chat lock, so concurrent updates can race across hosted instances.
- APIM maps all users in a group to the shared chat id. Add authorization before exposing sensitive tools or data.
- Sensitive telemetry is enabled for demonstration. Disable it for workloads whose prompts, responses, tool data,
or attachments must not be stored in Application Insights.
- Foundry hosted sessions are pinned to the agent version that created them. After deploying a new version, delete
an existing hosted session before testing that chat against the new version; Cosmos conversation history is
stored separately.

For a production system, acknowledge into a durable queue, deduplicate and serialize per chat, process
asynchronously, implement bounded retries and `429` handling, and apply the required network controls.

## Cleanup

Set the same configuration used for deployment, then run:

```bash
export TELEGRAM_BOT_TOKEN="<bot-token>"
./remove.sh
```

The script verifies that Telegram removed the webhook before deleting the resource group. It uses the same
`NAME_PREFIX`, `RESOURCE_GROUP_NAME`, and `AZURE_SUBSCRIPTION_ID` defaults and overrides as `deploy.sh`. The bot token
is read only from the current shell, and the script does not print the token or Telegram response.

To also remove the local azd environment after the resource group is gone:

```bash
azd env delete "${AZD_ENV_NAME:-${NAME_PREFIX:-telegramagent}-telegram}" --force
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/Azure/azure-dev/main/schemas/v1.0/azure.yaml.json

name: telegram-foundry-hosted-agent
services:
telegram-agent:
project: .
host: azure.ai.agent
language: python
uses:
- ai-project
codeConfiguration:
dependencyResolution: remote_build
entryPoint: main.py
runtime: python_3_13
container:
resources:
cpu: "0.5"
memory: 1Gi
description: >
Telegram bot routed through API Management to an Agent Framework agent
using the Foundry Invocations 2.0 protocol.
environmentVariables:
- name: AZURE_AI_MODEL_DEPLOYMENT_NAME
value: ${AZURE_AI_MODEL_DEPLOYMENT_NAME}
- name: AZURE_COSMOS_ENDPOINT
value: ${AZURE_COSMOS_ENDPOINT}
- name: AZURE_COSMOS_DATABASE_NAME
value: ${AZURE_COSMOS_DATABASE_NAME}
- name: AZURE_COSMOS_CONTAINER_NAME
value: ${AZURE_COSMOS_CONTAINER_NAME}
- name: KEY_VAULT_URL
value: ${KEY_VAULT_URL}
- name: ENABLE_SENSITIVE_DATA
value: ${ENABLE_SENSITIVE_DATA}
kind: hosted
metadata:
tags:
- Agent Framework
- Foundry Hosted Agent
- Invocations
- Telegram
name: telegram-agent
protocols:
- protocol: invocations
version: 2.0.0
ai-project:
host: azure.ai.project
endpoint: ${AZURE_AI_PROJECT_ENDPOINT}
infra:
provider: microsoft.foundry
Loading
Loading