Passez d’un agent C# local à un agent Microsoft Foundry hébergé, versionné et contrôlé par GitHub Actions, azd et une identité OIDC sans secret durable.

Déployer un agent Foundry .NET avec CI/CD sécurisé
Mettre un agent .NET en production ne se résume pas à publier une API qui appelle un modèle. Il faut aussi versionner le code qui expose l’agent, tester son protocole avant et après le déploiement, rattacher chaque version à un commit et donner à la CI uniquement les droits dont elle a besoin.
Les agents hébergés Microsoft Foundry répondent à ce besoin : votre code C# est exécuté dans une infrastructure gérée, tandis que Foundry prend en charge le cycle de vie de l’agent, sa session et son identité. Depuis l’été 2026, azd apporte une configuration unifiée dans azure.yaml, un mode de déploiement de code adapté à .NET et des commandes pour exécuter puis invoquer l’agent. Microsoft fournit aussi un modèle GitHub Actions qui déploie et teste l’agent avec OpenID Connect (OIDC), sans conserver de secret client Azure.
Ce tutoriel crée un agent C# minimal qui expose le protocole Responses, le teste localement, le décrit avec azd, puis ajoute une pipeline de déploiement vers un environnement staging. Le contrôle CI vérifie qu’une version est réellement invocable : ce n’est pas une évaluation de la qualité des réponses, à traiter séparément.
État des composants. Le service d’agents hébergés est disponible généralement, mais les packages .NET de l’intégration Foundry restent publiés en préversion. Épinglez leurs versions dans un projet réel, relisez les notes de version et validez toute montée de version dans
stagingavant la production.
Ce que l’hébergement géré change
Un agent hébergé est une application conteneurisée qui exécute votre code sur l’infrastructure Microsoft Foundry. Ce n’est donc pas simplement un agent créé par configuration dans un portail : le code, ses dépendances et ses outils C# font partie du livrable.
| Responsabilité | Application auto-hébergée | Agent Foundry hébergé |
|---|---|---|
| Démarrage HTTP et mise à l’échelle | Équipe plateforme | Service Foundry |
| Logique, prompts, outils et tests | Équipe applicative | Équipe applicative |
| Identité de l’agent déployé | À créer et gérer | Identité dédiée fournie par la plateforme |
| Historique de conversation | À persister et isoler | Géré par le protocole Responses |
| Livraison du code | Image ou runtime à opérer | azd deploy crée une version d’agent |
Cette séparation est particulièrement utile quand un agent doit appeler des outils C# ou des services internes tout en restant déployable comme n’importe quel autre composant. Elle ne dispense pas de concevoir les autorisations : l’identité de l’agent et celle de la CI ont des rôles différents.
commit Git
|
v
GitHub Actions -- OIDC --> Azure / Foundry
| |
| azd deploy v
+------------------> nouvelle version de l’agent
|
v
azd ai agent invoke
|
v
résultat du test de fumée
Pour un workflow long, avec reprise après panne et approbation humaine, complétez cette base avec les patterns de workflow d’agents .NET durables. Ici, l’objectif est plus étroit : rendre la livraison de l’agent elle-même répétable et vérifiable.
Prérequis et frontières de sécurité
Prévoyez :
- le SDK .NET 10 ou ultérieur ;
- Azure CLI connecté au bon tenant avec
az login; - Azure Developer CLI (
azd) 1.27.1 ou ultérieur ; - un projet Microsoft Foundry avec un déploiement de modèle, ou les droits nécessaires pour en provisionner un ;
- un dépôt GitHub si vous souhaitez exécuter la CI.
Installez les extensions azd requises et vérifiez-les avant de créer un projet :
azd ext install azure.ai.agents
azd ext install azure.ai.projects
azd ext list
Ne copiez ni clé de modèle ni mot de passe dans azure.yaml, le code C# ou les variables GitHub. L’exemple utilise DefaultAzureCredential : localement, il peut employer votre session Azure CLI ; dans Foundry, le runtime reçoit l’identité gérée de l’agent. La CI, elle, s’authentifie avec un jeton OIDC éphémère émis par GitHub.
Créez ensuite le projet :
dotnet new web --framework net10.0 --output src/SupportAgent
Set-Location src/SupportAgent
dotnet add package Microsoft.Agents.AI.Foundry.Hosting --prerelease
dotnet add package Azure.AI.Projects --prerelease
dotnet add package Azure.Identity
Dans une application livrée, remplacez --prerelease par les versions précises déjà validées et commitez le fichier packages.lock.json :
dotnet restore --use-lock-file
1. Exposer un agent C# par le protocole Responses
Le protocole Responses fournit un endpoint HTTP compatible OpenAI à l’adresse /responses. C’est le meilleur point de départ si votre client attend une conversation standard, du streaming et une gestion de session par la plateforme.
Remplacez Program.cs par ce code :
using Azure.AI.AgentServer.Core;
using Azure.AI.Projects;
using Azure.Identity;
using Microsoft.Agents.AI;
using Microsoft.Agents.AI.Foundry.Hosting;
var projectEndpoint = new Uri(
Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT")
?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT est requis."));
var deployment = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME")
?? throw new InvalidOperationException("AZURE_AI_MODEL_DEPLOYMENT_NAME est requis.");
AIAgent agent = new AIProjectClient(projectEndpoint, new DefaultAzureCredential())
.AsAIAgent(
model: deployment,
name: "support-agent",
instructions: """
Tu aides l’équipe de support. Réponds en français, brièvement.
N’invente ni procédure ni statut de service. Si le contexte ne suffit pas,
indique clairement ce qu’il faut vérifier.
""");
var builder = AgentHost.CreateBuilder(args);
builder.Services.AddFoundryResponses(agent);
builder.RegisterProtocol("responses", endpoints => endpoints.MapFoundryResponses());
var app = builder.Build();
app.Run();
AgentHost.CreateBuilder prépare l’hôte attendu par Foundry. AddFoundryResponses enregistre l’agent, puis MapFoundryResponses publie le contrat HTTP. Ne créez pas une seconde base d’historique de conversation dans ce code : pour le protocole Responses, la plateforme gère déjà le cycle de vie et l’historique de session.
Le contrat de l’agent est volontairement simple. Ajoutez ensuite vos fonctions C#, votre validation d’entrées et vos appels à des services internes, mais conservez une frontière nette : les outils ayant un effet de bord doivent vérifier l’autorisation côté code, et non obéir uniquement à une instruction du modèle.
2. Décrire le déploiement dans azure.yaml
Initialisez le projet pour azd. Si vous partez d’un projet Foundry existant, sélectionnez-le pendant l’assistant ; sinon, azd provision créera les ressources déclarées.
azd ai agent init --protocol responses --deploy-mode code
Le mode code est le choix naturel pour un agent .NET : azd envoie le code source et réalise la construction distante. Le fichier azure.yaml devient la source de vérité des ressources et du déploiement. Voici une base à adapter, notamment au nom et à la version d’un modèle réellement disponible dans votre région :
# yaml-language-server: $schema=https://raw.githubusercontent.com/Azure/azure-dev/main/schemas/v1.0/azure.yaml.json
name: support-agent
requiredVersions:
azd: ">=1.27.1"
extensions:
azure.ai.agents: ">=1.0.0-beta.8"
azure.ai.projects: ">=1.0.0-beta.4"
services:
ai-project:
host: azure.ai.project
deployments:
- name: ${AZURE_AI_MODEL_DEPLOYMENT_NAME}
model:
format: OpenAI
name: gpt-5.4-mini
version: "2026-03-17"
sku:
name: GlobalStandard
capacity: 10
support-agent:
host: azure.ai.agent
kind: hosted
name: support-agent
project: .
language: docker
uses:
- ai-project
protocols:
- protocol: responses
version: 2.0.0
env:
AZURE_AI_MODEL_DEPLOYMENT_NAME: ${AZURE_AI_MODEL_DEPLOYMENT_NAME}
container:
resources:
cpu: "0.5"
memory: 1Gi
Le bloc uses est important : il déclare la dépendance de l’agent envers le projet Foundry, ce qui permet à azd d’ordonner les opérations. Les substitutions ${NOM} sont résolues par azd depuis l’environnement actif ; elles ne doivent pas recevoir un secret committé.
Créez un environnement de développement et fournissez seulement la configuration non secrète :
azd env new dev
azd env set AZURE_AI_MODEL_DEPLOYMENT_NAME "<nom-du-deploiement>"
azd provision
azd downpeut supprimer le groupe de ressources créé par cet environnement. N’exécutez cette commande que si vous avez vérifié l’environnement actif et l’impact sur les ressources partagées.
3. Tester localement avant le cloud
Le test local raccourcit la boucle de développement : le code tourne sur votre poste et se connecte au projet Foundry avec votre identité Azure. azd injecte les variables de l’environnement actif lors de l’exécution.
Dans un premier terminal :
azd ai agent run
Dans un second terminal, lancez un test de fumée :
azd ai agent invoke --local "Bonjour. Réponds par une confirmation courte."
Pour vérifier le contrat HTTP sans dépendre de la CLI, vous pouvez aussi appeler l’endpoint local :
$body = @{ input = "Bonjour. Réponds par une confirmation courte." } | ConvertTo-Json
(Invoke-WebRequest `
-Uri "http://localhost:8088/responses" `
-Method POST `
-ContentType "application/json" `
-Body $body).Content
Un test de fumée répond à une seule question : l’agent déployable démarre-t-il et répond-il au protocole ? Il ne prouve pas que la réponse est utile, sûre ou correcte. Pour cela, versionnez des scénarios et des seuils d’évaluation comme expliqué dans Évaluer une application IA .NET dans une pipeline CI.
4. Configurer l’identité OIDC de GitHub
Créez une application Microsoft Entra et une identité fédérée limitée à votre dépôt et à l’environnement GitHub staging. Le sujet de cette fédération doit empêcher une branche arbitraire ou un fork d’obtenir un jeton de déploiement. Attribuez ensuite à cette identité les rôles exigés par votre mode de déploiement : pour un déploiement de code, Microsoft indique Foundry User et Contributor sur le projet Foundry cible.
Dans les variables GitHub Actions de l’environnement staging, ajoutez les valeurs de configuration suivantes :
| Variable | Rôle |
|---|---|
AZURE_CLIENT_ID | ID de l’application fédérée OIDC |
AZURE_TENANT_ID | Tenant Microsoft Entra |
AZURE_SUBSCRIPTION_ID | Abonnement cible |
AZURE_LOCATION | Région de l’environnement |
FOUNDRY_PROJECT_ENDPOINT | Endpoint du projet Foundry |
AZURE_AI_PROJECT_ID | Identifiant du projet Foundry |
FOUNDRY_MODEL_NAME | Déploiement de modèle attendu |
Ce ne sont pas des clés d’accès, mais évitez malgré tout de les afficher inutilement dans les journaux. Les mots de passe, clés d’API et chaînes de connexion restent dans un coffre de secrets ou dans une connexion Foundry résolue côté plateforme, jamais dans une variable de dépôt en clair.
5. Déployer et vérifier dans GitHub Actions
Ajoutez .github/workflows/hosted-agent-staging.yml. Le workflow s’exécute sur une branche protégée, obtient un jeton OIDC à durée courte, déploie puis invoque l’agent distant. L’étape show laisse une trace diagnostique de la version activée.
name: Deploy hosted agent to staging
on:
push:
branches: [main]
paths:
- "src/SupportAgent/**"
- "azure.yaml"
- ".github/workflows/hosted-agent-staging.yml"
workflow_dispatch:
permissions:
contents: read
id-token: write
concurrency:
group: foundry-agent-staging
cancel-in-progress: false
env:
AGENT_PROJECT_DIR: ./src/SupportAgent
AZD_ENV_NAME: staging
AGENT_TEST_PROMPT: "Bonjour depuis la CI. Confirme brièvement que tu réponds."
jobs:
deploy-and-smoke-test:
runs-on: ubuntu-latest
environment: staging
steps:
- uses: actions/checkout@v4
- name: Install Azure Developer CLI
uses: Azure/setup-azd@v2
- name: Install Foundry extensions
run: |
set -euo pipefail
azd ext install azure.ai.agents
azd ext install azure.ai.projects
azd ai agent --help > /dev/null
- name: Sign in to Azure with OIDC
uses: azure/login@v2
with:
client-id: ${{ vars.AZURE_CLIENT_ID }}
tenant-id: ${{ vars.AZURE_TENANT_ID }}
subscription-id: ${{ vars.AZURE_SUBSCRIPTION_ID }}
- name: Configure azd environment
working-directory: ${{ env.AGENT_PROJECT_DIR }}
run: |
set -euo pipefail
azd config set auth.useAzCliAuth true
azd config set defaults.subscription "${{ vars.AZURE_SUBSCRIPTION_ID }}"
azd env select "$AZD_ENV_NAME" || azd env new "$AZD_ENV_NAME" --no-prompt
azd env set AZURE_SUBSCRIPTION_ID "${{ vars.AZURE_SUBSCRIPTION_ID }}"
azd env set AZURE_TENANT_ID "${{ vars.AZURE_TENANT_ID }}"
azd env set AZURE_LOCATION "${{ vars.AZURE_LOCATION }}"
azd env set FOUNDRY_PROJECT_ENDPOINT "${{ vars.FOUNDRY_PROJECT_ENDPOINT }}"
azd env set AZURE_AI_PROJECT_ID "${{ vars.AZURE_AI_PROJECT_ID }}"
azd env set FOUNDRY_MODEL_NAME "${{ vars.FOUNDRY_MODEL_NAME }}"
- name: Deploy agent
working-directory: ${{ env.AGENT_PROJECT_DIR }}
run: azd deploy --no-prompt
- name: Show deployed agent status
working-directory: ${{ env.AGENT_PROJECT_DIR }}
run: azd ai agent show --no-prompt
- name: Smoke-test the deployed agent
working-directory: ${{ env.AGENT_PROJECT_DIR }}
run: |
set -euo pipefail
azd ai agent invoke "$AGENT_TEST_PROMPT" > /tmp/agent-response.txt 2>&1
test -s /tmp/agent-response.txt
sed -n '1,80p' /tmp/agent-response.txt
Adaptez AGENT_PROJECT_DIR et le filtre paths à votre dépôt. Ne lancez pas un déploiement ayant accès à Azure depuis une pull request issue d’un fork. L’environnement GitHub staging doit aussi porter des règles de branche et, pour la production, une approbation humaine distincte.
La réponse non vide constitue un seuil volontairement bas : elle détecte un hôte qui ne démarre pas, une mauvaise configuration du protocole ou une identité sans droit d’invocation. Ajoutez un test de contrat déterministe pour les propriétés réellement stables de votre agent, puis une campagne d’évaluation séparée et protégée pour les critères LLM. Mélanger ces trois niveaux dans un unique test rend les incidents difficiles à diagnostiquer.
Points de vigilance avant la production
- Ne provisionnez pas à chaque push. Dans une CI quotidienne, utilisez
azd deploysur un environnement déjà existant. Réservezazd provisionà une étape contrôlée d’infrastructure. - Séparez CI et agent. La CI peut créer une version ; l’identité de l’agent doit recevoir séparément le seul accès nécessaire à ses outils et données d’exécution.
- Rendez les effets de bord idempotents. Un retry de plateforme, un redéploiement ou une reprise ne doit pas créer deux tickets, deux commandes ou deux changements de configuration.
- Épinglez et mettez à jour consciemment. Les extensions
azdet packages de préversion évoluent vite. Déclarez les versions minimales, utilisez un lockfile NuGet et testez leur compatibilité dans un environnement isolé. - Surveillez après le déploiement. Le test de fumée ne remplace ni les traces, ni les métriques, ni les alertes. Corrélez la version déployée avec les appels, erreurs et latences ; le guide sur OpenTelemetry et Aspire détaille cette couche.