Lab 016 : GitHub Copilot Agent Mode¶
Ce que vous allez apprendre¶
- Ce qui distingue le mode agent du Copilot Chat classique
- Comment activer et utiliser le mode agent dans VS Code
- Comment l'agent lit votre code source, planifie et exécute des tâches en plusieurs étapes
- Comment connecter des serveurs MCP pour étendre les capacités de l'agent
- Bonnes pratiques et limitations
Introduction¶
GitHub Copilot dans VS Code dispose de trois modes :
| Mode | Ce qu'il fait |
|---|---|
| Ask | Répond aux questions sur le code ; lecture seule |
| Edit | Effectue des modifications sur les fichiers que vous spécifiez |
| Agent ⭐ | Explore de manière autonome votre code source, exécute des commandes, utilise des outils et accomplit des tâches en plusieurs étapes |
Le mode agent est le plus récent et le plus puissant. Vous décrivez un objectif, et Copilot agit comme un développeur junior : il lit les fichiers, écrit du code, exécute les tests et itère jusqu'à ce que ce soit terminé — en vous demandant votre approbation aux points de décision clés.
Disponible dans VS Code 1.99+
Le mode agent nécessite VS Code 1.99 ou une version ultérieure et l'extension GitHub Copilot. Vérifiez les mises à jour si vous ne voyez pas le sélecteur de mode.
Configuration des prérequis¶
- VS Code 1.99+ avec l'extension GitHub Copilot installée
- Compte GitHub gratuit avec Copilot activé (github.com/features/copilot)
- Un projet sur lequel travailler (nous utiliserons un projet Python simple)
Démarrage rapide avec GitHub Codespaces
Toutes les dépendances sont pré-installées dans le devcontainer.
📦 Fichiers de support¶
Téléchargez ces fichiers avant de commencer le lab
Enregistrez tous les fichiers dans un dossier lab-016/ dans votre répertoire de travail.
| Fichier | Description | Téléchargement |
|---|---|---|
outdoorgear_api.py |
Script Python | 📥 Télécharger |
Exercice du lab¶
Étape 1 : Activer le mode agent¶
- Ouvrez le panneau Copilot Chat (
Ctrl+Shift+I) - Cherchez le sélecteur de mode en haut de la zone de saisie du chat
- Sélectionnez « Agent »
Vous remarquerez que la zone de saisie change — vous pouvez maintenant décrire des objectifs, pas seulement poser des questions.
Étape 2 : Le projet cassé — Corrigez-le avec le mode agent 🐛¶
Cet exercice vous donne un vrai projet Python cassé à corriger en utilisant le mode agent. L'objectif est de voir comment l'agent lit les fichiers, identifie les problèmes et les corrige — étape par étape.
Téléchargez le projet :
Ou copiez le fichier 📥 outdoorgear_api.py ci-dessous :
# outdoorgear_api.py
# 🚧 BROKEN PROJECT — Use Copilot Agent Mode to fix and extend this
#
# SCENARIO:
# This is supposed to be the OutdoorGear Inc. product management API.
# It was written in a hurry and has several problems:
# - 5 bugs that prevent it from running
# - Missing features that are referenced but not implemented
# - No tests
# - Poor error handling
#
# YOUR MISSION (using Copilot Agent Mode):
# Phase 1: "Fix all the bugs in this file so the tests pass"
# Phase 2: "Add the missing search_by_price_range function"
# Phase 3: "Create a tests/ folder with pytest tests for all functions"
# Phase 4: "Add type hints and docstrings to all public functions"
#
# HOW TO USE:
# 1. Open this FOLDER in VS Code (not just this file)
# 2. Open Copilot Chat → switch to AGENT MODE
# 3. Try Phase 1 first: "Fix all the bugs in outdoorgear_api.py so the
# basic tests at the bottom pass when I run python outdoorgear_api.py"
# 4. Watch the agent: it will read the file, identify bugs, and fix them
# 5. Continue with Phase 2, 3, 4
from datetime import datetime
import json
CATALOG = [
{"id": "BOOT-001", "name": "TrailBlazer X200", "category": "footwear", "price": 189.99, "stock": 42, "active": True},
{"id": "TENT-001", "name": "Summit Pro Tent", "category": "camping", "price": 349.00, "stock": 15, "active": True},
{"id": "HRNS-001", "name": "ClimbTech Pro Harness", "category": "climbing", "price": 129.99, "stock": 28, "active": True},
{"id": "PACK-001", "name": "OmniPack 45L", "category": "packs", "price": 279.99, "stock": 31, "active": True},
{"id": "JACK-001", "name": "StormShell Jacket", "category": "clothing", "price": 349.00, "stock": 8, "active": True},
{"id": "BOOT-002", "name": "Summit Trail Low", "category": "footwear", "price": 99.99, "stock": 55, "active": False},
]
ORDERS = []
def get_all_products(include_inactive: bool = False) -> list:
"""Return all products, optionally including inactive ones."""
if include_inactive:
return CATALOG
return [p for p in CATALOG if p["active"] == True]
def get_product_by_id(product_id: str) -> dict | None:
"""Return a product by its ID, or None if not found."""
for product in CATALOG:
if product["id"] = product_id: # BUG 1: assignment instead of comparison
return product
return None
def add_to_cart(cart: dict, product_id: str, quantity: int) -> dict:
"""Add a product to the cart. Returns updated cart."""
product = get_product_by_id(product_id)
if product is None:
raise ValueError(f"Product {product_id} not found")
if product["stock"] < quantity:
raise ValueError(f"Only {product['stock']} units available")
if product_id in cart:
cart[product_id]["quantity"] =+ quantity # BUG 2: =+ instead of +=
else:
cart[product_id] = {
"product": product,
"quantity": quantity,
}
return cart
def calculate_cart_total(cart: dict) -> float:
"""Calculate total price for all items in cart."""
total = 0
for item in cart: # BUG 3: iterating over keys, not values
total += item["product"]["price"] * item["quantity"]
return round(total, 2)
def apply_promo_code(total: float, code: str) -> float:
"""Apply a promo code discount to the total."""
VALID_CODES = {
"SUMMER10": 0.10,
"OUTDOOR20": 0.20,
"WELCOME5": 0.05,
}
if code in VALID_CODES:
discount = total * VALID_CODES[code]
return round(total - discount, 2)
else:
raise ValueError(f"Invalid promo code: {code}") # BUG 4: should return total unchanged, not raise
def place_order(cart: dict, customer_name: str, promo_code: str = None) -> dict:
"""Convert cart to an order. Returns order confirmation."""
if len(cart) == 0: # BUG 5: should use "not cart" or "len(cart) == 0"
raise ValueError("Cannot place empty order") # actually this is fine, but...
total = calculate_cart_total(cart)
if promo_code:
total = apply_promo_code(total, promo_code)
order = {
"order_id": f"ORD-{len(ORDERS) + 1:04d}",
"customer": customer_name,
"items": cart,
"total": total,
"placed_at": datetime.now().isoformat(),
"status": "confirmed",
}
# Deduct stock
for product_id, item in cart.item(): # BUG 5: .item() should be .items()
for product in CATALOG:
if product["id"] == product_id:
product["stock"] -= item["quantity"]
ORDERS.append(order)
return order
# --- MISSING FEATURE ---
# The search_by_price_range function is referenced in the tests below
# but hasn't been implemented yet.
# Parameters: min_price: float, max_price: float
# Returns: list of products in that price range (inclusive), sorted by price ascending
# ============================================================
# Basic tests — these should pass after Phase 1
# ============================================================
if __name__ == "__main__":
print("Running basic tests...\n")
# Test 1: get all active products
active = get_all_products()
assert len(active) == 5, f"Expected 5 active products, got {len(active)}"
print("✅ Test 1: get_all_products() — 5 active products")
# Test 2: get product by ID
boot = get_product_by_id("BOOT-001")
assert boot is not None, "BOOT-001 should exist"
assert boot["name"] == "TrailBlazer X200"
print("✅ Test 2: get_product_by_id() — found BOOT-001")
# Test 3: add to cart and calculate total
cart = {}
cart = add_to_cart(cart, "BOOT-001", 2)
cart = add_to_cart(cart, "TENT-001", 1)
total = calculate_cart_total(cart)
assert abs(total - 728.98) < 0.01, f"Expected $728.98, got ${total}"
print(f"✅ Test 3: cart total = ${total}")
# Test 4: promo code
discounted = apply_promo_code(728.98, "SUMMER10")
assert abs(discounted - 656.08) < 0.01, f"Expected $656.08, got ${discounted}"
print(f"✅ Test 4: promo SUMMER10 applied = ${discounted}")
# Test 5: invalid promo code should NOT raise — should return original total
unchanged = apply_promo_code(728.98, "INVALID_CODE")
assert unchanged == 728.98, f"Invalid code should return original total, got ${unchanged}"
print(f"✅ Test 5: invalid promo code returns original total = ${unchanged}")
# Test 6: place order
order = place_order(cart, "Alex Chen", "SUMMER10")
assert order["status"] == "confirmed"
assert order["customer"] == "Alex Chen"
print(f"✅ Test 6: order placed — {order['order_id']}")
# Test 7: search by price range (requires Phase 2)
budget_items = search_by_price_range(50, 200)
assert len(budget_items) == 2 # TrailBlazer X200 ($189.99) and ClimbTech ($129.99)
assert budget_items[0]["price"] <= budget_items[1]["price"] # sorted ascending
print(f"✅ Test 7: search_by_price_range(50, 200) — found {len(budget_items)} products")
print("\n🎉 All tests passed! Ready for Phase 3 (write tests) and Phase 4 (type hints).")
Ouvrez le dossier dans VS Code (important — l'agent a besoin de voir l'ensemble du projet) :
Phase 1 : Laissez l'agent trouver et corriger les bugs¶
Passez en mode agent et tapez exactement ceci :
Fix all the bugs in outdoorgear_api.py so that the basic tests
at the bottom of the file pass when I run: python outdoorgear_api.py
Don't fix the "Test 7" failure yet — that requires a missing function.
Observez ce que fait l'agent :
- 🔍 Il lit le fichier sans que vous ayez à coller quoi que ce soit
- 🐛 Il identifie chaque bug et explique pourquoi c'est incorrect
- ✏️ Il propose des correctifs et vous demande votre approbation
- ▶️ Il exécute le fichier pour vérifier que la correction fonctionne
Après avoir accepté, lancez la vérification :
Les tests 1 à 6 devraient passer. Le test 7 échouera (c'est attendu — la fonction est manquante).Si l'agent se bloque
Essayez d'être plus précis : « Exécute python outdoorgear_api.py et montre-moi la sortie d'erreur, puis corrige le bug restant »
Phase 2 : Ajouter la fonctionnalité manquante¶
Demandez maintenant à l'agent d'implémenter la fonction search_by_price_range manquante :
Implement the search_by_price_range(min_price, max_price) function
that is referenced in Test 7.
It should return active products in that price range, sorted by price ascending.
Then run python outdoorgear_api.py to verify all 7 tests pass.
L'agent devrait : 1. Lire le code existant pour comprendre les structures de données 2. Implémenter la fonction 3. Exécuter les tests pour vérifier
Phase 3 : Écrire une suite de tests¶
Demandez maintenant à l'agent de créer des tests appropriés :
Create a tests/ folder with a file test_outdoorgear_api.py.
Write pytest tests that cover:
- get_all_products() with include_inactive=True and False
- get_product_by_id() for valid and invalid IDs
- add_to_cart() including the stock check
- calculate_cart_total() with multiple items
- apply_promo_code() with valid and invalid codes
- place_order() end-to-end
Run pytest to make sure all tests pass.
Observez l'agent :
- Il crée le dossier tests/
- Il écrit des tests complets en utilisant des fixtures pytest
- Il exécute pytest dans le terminal
- Il corrige toutes les erreurs de test qu'il trouve
Phase 4 : Améliorer la qualité du code¶
Add type hints to all public functions in outdoorgear_api.py.
Add Google-style docstrings to each function.
Don't change any logic.
Étape 3 : Exploration du code source¶
Essayez de demander à l'agent d'analyser ce qu'il vient de créer :
Give me a summary of the outdoorgear_api.py module:
1. What it does
2. All public functions and their signatures
3. Any edge cases not currently handled
L'agent lit l'ensemble du code source et synthétise une réponse cohérente — sans que vous ayez à coller du code.
Étape 4 : Connecter un serveur MCP (bonus)¶
Le mode agent prend en charge les serveurs MCP. Configurez VS Code pour utiliser le serveur MCP du Lab 020 :
.vscode/mcp.json :
{
"servers": {
"outdoorgear-products": {
"type": "stdio",
"command": "python",
"args": ["server.py"],
"cwd": "${workspaceFolder}"
}
}
}
Puis demandez en mode agent :
Étape 5 : Instructions personnalisées¶
Créez .github/copilot-instructions.md pour que l'agent suive toujours les conventions de votre projet :
# Copilot Instructions
## Project
Python API project for OutdoorGear Inc.
## Code Style
- Use type hints on all functions
- Docstrings follow Google style
- Tests use pytest with fixtures, no unittest
- All prices rounded to 2 decimal places
## Never
- Use print() for logging
- Hardcode product data outside CATALOG
- Skip ValueError validation on public functions
Mode agent vs. mode édition : quand utiliser lequel¶
| Utilisez le mode édition quand | Utilisez le mode agent quand |
|---|---|
| Vous savez exactement quoi modifier | Vous avez un objectif mais pas de plan |
| Modifications simples et ciblées | Tâches multi-fichiers et multi-étapes |
| Vous voulez un contrôle total sur chaque modification | Vous voulez que l'agent trouve la solution |
| Corrections rapides, refactorisations | Débogage, ajout de fonctionnalités, écriture de tests |
Ce que l'agent a fait (en coulisses)¶
Your request: "Fix all bugs"
│
▼
[read_file] outdoorgear_api.py ← agent reads without you pasting
│
▼
[analysis] Found 5 bugs:
Bug 1: line 45 — = instead of ==
Bug 2: line 57 — =+ instead of +=
...
│
▼
[replace_in_file] × 5 targeted fixes ← surgical edits, not rewriting whole file
│
▼
[run_terminal] python outdoorgear_api.py
│
▼
✅ All 6 tests pass
Résumé¶
- ✅ Lit votre code source — pas besoin de copier/coller du code dans le chat
- ✅ Exécution en plusieurs étapes — planifie et accomplit des tâches complexes
- ✅ Accès au terminal — exécute les tests, vérifie les corrections
- ✅ Intégration MCP — connecte des outils personnalisés
- ✅ Approbation à chaque étape — vous gardez le contrôle
Prochaines étapes¶
- Créer un serveur MCP pour étendre le mode agent : → Lab 020 — Serveur MCP en Python
- Créer un Chat Participant VS Code (@agent personnalisé) : → Lab 025 — VS Code Chat Participant