Aspire — orchestrer notre pile GenAI en C
Dossier des grains #10838 et #10857 de l’Epic #10473 — The Unexpected AI Stack: C#/.NET, et des grains 1-3 de la digestion #11516 (Parts 3-5 de la même série). Ligne de parité #10838 : « Isolation de ports par worktree : à la main → aspire run --isolated ». Ligne de parité #10857 : « Modéliser la pile réelle multi-machines dans un AppHost unique : endpoints externes référencés (ComfyUI, vLLM) + conteneurs orchestrables (whisper-api) ». Grain 2 #11516 : « Tests d’intégration modernes : Testcontainers + TUnit + rollback — le paradigme de test absent du dépôt, démontré réel ».
Contenu
| Élément | Rôle |
|---|---|
GenAiStack.AppHost/apphost.cs |
AppHost Aspire (SDK file-based #:sdk Aspire.AppHost.Sdk@13.4.6) orchestrant notre service GenAI réel — whisper-api (image locale buildée depuis docker-configurations/services/whisper-api) |
GenAiStack.AppHost-wt2/apphost.cs |
Copie identique — joue le rôle du deuxième worktree pour la démonstration d’isolation de ports |
GenAiStackReel.AppHost/apphost.cs |
AppHost du grain #10857 : la pile réelle — comfyui (po-2023:8188) et vllm (ai-01:5002) déclarés comme ConnectionStrings (référencés, jamais recréés), plus le conteneur whisper-api orchestrable |
GenAiStackReel.AppHost-wt2/apphost.cs |
Copie pour la 2e instance isolée |
01-Aspire-Orchestration-GenAi.ipynb |
Notebook .NET Interactive : lancement --isolated de deux instances simultanées, aspire describe/logs, transcription réelle par le service orchestré, 3 exercices |
02-Aspire-GenAiStack-Reel.ipynb |
Notebook #10857 : deux instances isolées de la pile réelle, describe/logs, appels traversants authentifiés (complétion vLLM qwen3.6-35b-a3b, system_stats ComfyUI), 3 exercices |
04-Aspire-Streaming-Agent.ipynb |
Notebook #11516 (A1+A2) : le pattern agent streaming en .NET — System.Threading.Channels (canaux inbound/outbound, backpressure), BackgroundService (cycle de vie), minimal API typée TypedResults — 3 exercices |
StreamingAgent.App/ |
Projet .NET 10 du notebook 04 : un service d’agent réel (BackgroundService + Channels) exposé par des endpoints typés /health, /greet, /stream |
05-Aspire-Tests-Integration.ipynb |
Notebook #11516 Grain 2 (axes A5/A6/A7) : tests d’intégration modernes — TUnit + Microsoft Testing Platform, Testcontainers Postgres 18 jetable à port aléatoire, isolation par rollback transactionnel, filtres --treenode-filter, 3 exercices |
IntegrationTests/ |
Projet de tests auto-contenu (TUnit 1.65 + Testcontainers.PostgreSql 4.14 + EF Core 10 sur net10.0) : dotnet test démarre un vrai Postgres 18, 5/5 tests verts, conteneur auto-purgé |
06-Aspire-GardeFous-Roslyn.ipynb |
Notebook #10473 (axe Roslyn) : analyseurs Roslyn comme garde-fous du code d’agent — DiagnosticAnalyzer AGENTGUARD001 (blocage synchrone .Result/.Wait() d’une Task), verdict rendu par dotnet build lui-même + canal API (Verifier par compilation Roslyn en mémoire), contraste Python (ruff hors compilation), 3 exercices |
AgentGuard.Analyzers/ · AgentGuard.Demo/ · AgentGuard.Verifier/ |
Projets du notebook 06 : l’analyseur (netstandard2.0, chargé par OutputItemType="Analyzer"), le terrain fautif (copie de démo du motif notebook 04 avec .Result injecté — 2 avertissements au build), et le vérificateur de verdicts (compilation CSharpCompilation + WithAnalyzers sur les ref packs du SDK) |
09-Aspire-Harness-CopilotSdk.ipynb |
Notebook #10473 (axe Copilot SDK, veille #10475) : le harness des Copilot coding agents en C# — GitHub.Copilot.SDK 1.0.13 (runtime agent embarqué, aucune CLI externe), CopilotClient avec auth héritée de gh (aucune clé dans le code), catalogue des 15 modèles servis (ListModelsAsync), tour complet SendAndWaitAsync (réponse réelle), cycle de vie SessionEvent observé (histogramme du tour + contenu ré-assemblé), 3 exercices |
CopilotHarness.App/ |
Projet .NET 10 du notebook 09 : console à quatre modes (auth/models/ask/events) pilotant le harness réel — dépendance unique GitHub.Copilot.SDK 1.0.13 |
assets/echantillon-test-fr.wav |
Échantillon audio FR de test (synthèse SAPI Windows) envoyé au service orchestré |
Prérequis
- SDK .NET 10 (
dotnet --version≥ 10.0.110) et CLI Aspire 13.4.6 (dotnet tool install -g Aspire.Cli) - Docker démarré + image locale
whisper-api-whisper-api:latest(buildée depuis le Dockerfile dedocker-configurations/services/whisper-api) - GPU NVIDIA (le conteneur exige
--gpus all; la config est dans l’AppHost,CUDA_VISIBLE_DEVICES=1= RTX 3090 externe) - Cache HuggingFace hôte contenant le modèle
faster-whisper-large-v3-turbo(sinon, premier appel = téléchargement ~1.6 Go, une seule fois) - Pour le notebook 02 (#10857) : la pile réelle joignable — conteneur
comfyui-qwendémarré (8188) etVLLM_API_KEYrendu dansGenAI/.env(modèle :.env.example) viascripts/secrets/render_envs.pydepuismaster.env - Pour le notebook 04 (#11516) : aucun Docker requis —
StreamingAgent.Appcompile avec le SDK .NET 10 seul (dotnet build), puis le notebook le lance et l’interroge (port local 5128) - Pour le notebook 05 /
IntegrationTests/(#11516 Grain 2) : Docker suffit — l’imagepostgres:18est tirée au premierdotnet test(~30 s), les conteneurs de test s’auto-purgent - Pour le notebook 09 /
CopilotHarness.App/(#10473) : aucune installation — le package embarque son runtime ; l’authentification passe par la sessionghlocale (abonnement Copilot requis) ; compter ~2 requêtes premium par passage complet du notebook
Démarrage rapide
cd MyIA.AI.Notebooks/GenAI/Integrations-DotNet/Aspire/GenAiStack.AppHost
aspire run --detach --isolated # instance A (ports randomisés)
aspire describe whisper-api # état + URL du service
aspire logs whisper-api # journaux unifiés
aspire stop # arrêt + suppression des conteneursDeux instances simultanées : lancer la même commande depuis GenAiStack.AppHost-wt2/ (le « deuxième worktree »). Les deux coexistent sans collision — ports randomisés, noms de conteneurs suffixés.
Points d’attention
- Aucun secret dans l’AppHost :
AUTH_ENABLED=falsedésactive l’auth du service (contratauth_middleware.py), les variables d’environnement sont non-secrètes, le token d’API n’est jamais un littéral. - Bind mounts : le conteneur dépend de
docker-configurations/services/shared(modulelazy_model) et du cache HuggingFace hôte — chemins résolus relativement à l’AppHost pour fonctionner depuis n’importe quel worktree. --detachmême répertoire : deux instances--isolateddu même répertoire ne coexistent pas (le CLI remplace l’instance précédente) — la forme authentique est deux répertoires (deux worktrees), démontrée dans le notebook §4.
Voir aussi
- Issue #10838 · Issue #10857 · Epic #10473
- Digestion #11516 (Parts 3-5 : streaming agent, tests d’intégration, observabilité) — série source chrlschn.dev
- Grain #10474 : backend d’observabilité OTLP
aspire-otel/(même pattern SDK file-based) - Observabilité de la série SemanticKernel :
04-Filters-Observability.ipynb(même famille A9, application directe au service streaming) - Pile GenAI :
docker-configurations/