Welcome to the GitHub Copilot Workshop! In this workshop, you will learn how to use GitHub Copilot to explain and improve code. GitHub Copilot Chat enables interactive dialogue with AI through a chat experience. Let's learn how to use GitHub Copilot through this workshop!
This workshop uses the following GitHub repository:
Project URL: https://github.com/moulongzhang/2025-Github-Copilot-Workshop-Python
First, open the project URL above in your browser and fork the repository:
Once the fork is complete, a copy of the repository will be created in your GitHub account.
Using your forked repository, you can start the project using one of the following methods:
https://github.com/[your-username]/2025-Github-Copilot-Workshop-Python
)If you have VS Code installed locally:
git clone https://github.com/[your-username]/2025-Github-Copilot-Workshop-Python.git
cd 2025-Github-Copilot-Workshop-Python
code .
After opening the project, please install the following extensions:
Let's experience GitHub Copilot's basic code completion functionality.
Make sure you are signed in to VS Code.
Create a new Python file and enter the following comment:
# Function to calculate Fibonacci sequence
def fibonacci(n):
Verify that Copilot automatically suggests code.
⚙️ github.copilot.nextEditSuggestions.enabled
is a setting that enables GitHub Copilot's next-generation edit suggestion feature. This feature allows you to receive more advanced code editing suggestions.
Open the settings screen using one of the following methods:
File
→ Preferences
→ Settings
Code
→ Settings...
→ Settings
Ctrl + ,
Cmd + ,
Ctrl + Shift + P
(Windows/Linux) or Cmd + Shift + P
(macOS)Preferences: Open Settings (UI)
Enter the following in the settings search box:
github.copilot.nextEditSuggestions.enabled
false
to true
Verify the setting is correctly applied:
Ctrl + Shift + P
(Windows/Linux) or Cmd + Shift + P
(macOS)Preferences: Open User Settings (JSON)
{
"github.copilot.nextEditSuggestions.enabled": true
}
Ctrl + S
(Windows/Linux) or Cmd + S
(macOS)Open the point.py
file included in the project. This file contains a class representing points in two-dimensional space:
import math
class Point2D:
def __init__(self, x, y):
self.x = x
self.y = y
def distance_to(self, other):
dx = self.x - other.x
dy = self.y - other.y
return math.sqrt(dx * dx + dy * dy)
def __str__(self):
return f"Point2D({self.x}, {self.y})"
Now, we want to extend this class to represent points in three-dimensional space. First, let's manually change the class name to Point3D
. GitHub Copilot will then suggest the next edit candidates.
The suggestions should show changes like:
z
parameter to the __init__
methodself.z = z
distance_to
method for three-dimensional distance calculation__str__
methodIn this state, pressing the Tab
key will move the cursor to the location where GitHub Copilot is making suggestions. To accept the suggestion, press the Tab
key again.
GitHub Copilot should then suggest the next edit candidate. This suggestion can also be accepted by pressing the Tab
key. Using Next Edit Suggestion allows you to edit code efficiently.
Let's continue extending the Point2D class to Point3D. You should be able to adapt all methods for three-dimensional space.
Expected final code example:
import math
class Point3D:
def __init__(self, x, y, z):
self.x = x
self.y = y
self.z = z
def distance_to(self, other):
dx = self.x - other.x
dy = self.y - other.y
dz = self.z - other.z
return math.sqrt(dx * dx + dy * dy + dz * dz)
def __str__(self):
return f"Point3D({self.x}, {self.y}, {self.z})"
Replace 2D
(two-dimensional) with 3D
(three-dimensional) in the commented first line.
# Class representing points in three-dimensional space
class Point2D:
def __init__(self, x, y):
self.x = x
self.y = y
def distance_to(self, other):
# TODO: Add distance calculation code here
pass
def __str__(self):
# TODO: Return string representation
pass
Place the cursor after the TODO comments and check Copilot's suggestions.
Please save the following file as delivery_manager.py
.
import time
import random
from typing import List, Callable, Optional
from dataclasses import dataclass, field
from enum import Enum
class EventArgs:
"""Base class for event arguments"""
pass
class Event:
"""Class equivalent to C#'s event"""
def __init__(self):
self._handlers: List[Callable] = []
def add_handler(self, handler: Callable):
"""Add event handler"""
if handler not in self._handlers:
self._handlers.append(handler)
def remove_handler(self, handler: Callable):
"""Remove event handler"""
if handler in self._handlers:
self._handlers.remove(handler)
def invoke(self, sender, args: EventArgs = None):
"""Fire event"""
for handler in self._handlers:
handler(sender, args or EventArgs())
@dataclass
class KitchenObjectSO:
"""Kitchen object data class"""
name: str
object_id: int
@dataclass
class RecipeSO:
"""Recipe data class"""
name: str
kitchen_object_so_list: List[KitchenObjectSO] = field(default_factory=list)
@dataclass
class RecipeListSO:
"""Recipe list data class"""
recipe_so_list: List[RecipeSO] = field(default_factory=list)
class PlateKitchenObject:
"""Plate kitchen object"""
def __init__(self):
self._kitchen_object_so_list: List[KitchenObjectSO] = []
def add_kitchen_object(self, kitchen_object: KitchenObjectSO):
"""Add kitchen object"""
self._kitchen_object_so_list.append(kitchen_object)
def get_kitchen_object_so_list(self) -> List[KitchenObjectSO]:
"""Get kitchen object list"""
return self._kitchen_object_so_list.copy()
class KitchenGameManager:
"""Kitchen game manager (Singleton)"""
_instance: Optional['KitchenGameManager'] = None
def __init__(self):
self._is_game_playing = False
@classmethod
def get_instance(cls) -> 'KitchenGameManager':
"""Get Singleton instance"""
if cls._instance is None:
cls._instance = cls()
return cls._instance
def is_game_playing(self) -> bool:
"""Check if game is in progress"""
return self._is_game_playing
def start_game(self):
"""Start game"""
self._is_game_playing = True
def stop_game(self):
"""Stop game"""
self._is_game_playing = False
class DeliveryManager:
"""Delivery management class (Python version)"""
_instance: Optional['DeliveryManager'] = None
def __init__(self, recipe_list_so: RecipeListSO):
# Event definitions
self.on_recipe_spawned = Event()
self.on_recipe_completed = Event()
self.on_recipe_success = Event()
self.on_recipe_failed = Event()
# Private variables
self._recipe_list_so = recipe_list_so
self._waiting_recipe_so_list: List[RecipeSO] = []
self._spawn_recipe_timer = 0.0
self._spawn_recipe_timer_max = 4.0
self._waiting_recipes_max = 4
self._successful_recipes_amount = 0
self._last_update_time = time.time()
@classmethod
def get_instance(cls, recipe_list_so: RecipeListSO = None) -> 'DeliveryManager':
"""Get Singleton instance"""
if cls._instance is None:
if recipe_list_so is None:
raise ValueError("recipe_list_so is required for initial creation")
cls._instance = cls(recipe_list_so)
return cls._instance
def update(self):
"""Frame update processing (equivalent to Unity's Update)"""
current_time = time.time()
delta_time = current_time - self._last_update_time
self._last_update_time = current_time
self._spawn_recipe_timer -= delta_time
if self._spawn_recipe_timer <= 0.0:
self._spawn_recipe_timer = self._spawn_recipe_timer_max
kitchen_game_manager = KitchenGameManager.get_instance()
if (kitchen_game_manager.is_game_playing() and
len(self._waiting_recipe_so_list) < self._waiting_recipes_max):
# Randomly select recipe
waiting_recipe_so = random.choice(self._recipe_list_so.recipe_so_list)
self._waiting_recipe_so_list.append(waiting_recipe_so)
# Fire event
self.on_recipe_spawned.invoke(self)
def deliver_recipe(self, plate_kitchen_object: PlateKitchenObject):
"""Check if recipe ingredients match plate ingredients"""
for i, waiting_recipe_so in enumerate(self._waiting_recipe_so_list):
plate_ingredients = plate_kitchen_object.get_kitchen_object_so_list()
# Check if ingredient counts match
if len(waiting_recipe_so.kitchen_object_so_list) == len(plate_ingredients):
plate_contents_matches_recipe = True
# Check each recipe ingredient
for recipe_kitchen_object_so in waiting_recipe_so.kitchen_object_so_list:
ingredient_found = False
# Match with plate ingredients
for plate_kitchen_object_so in plate_ingredients:
if plate_kitchen_object_so == recipe_kitchen_object_so:
ingredient_found = True
break
if not ingredient_found:
plate_contents_matches_recipe = False
break
# If ingredients match completely
if plate_contents_matches_recipe:
self._successful_recipes_amount += 1
self._waiting_recipe_so_list.pop(i)
# Fire success events
self.on_recipe_completed.invoke(self)
self.on_recipe_success.invoke(self)
return
# If no matching recipe found
self.on_recipe_failed.invoke(self)
def get_waiting_recipe_so_list(self) -> List[RecipeSO]:
"""Get waiting recipe list"""
return self._waiting_recipe_so_list.copy()
def get_successful_recipes_amount(self) -> int:
"""Get number of successful recipes"""
return self._successful_recipes_amount
# Usage example
if __name__ == "__main__":
# Create sample data
tomato = KitchenObjectSO("Tomato", 1)
lettuce = KitchenObjectSO("Lettuce", 2)
bread = KitchenObjectSO("Bread", 3)
# Sample recipes
sandwich_recipe = RecipeSO("Sandwich", [bread, lettuce, tomato])
salad_recipe = RecipeSO("Salad", [lettuce, tomato])
recipe_list = RecipeListSO([sandwich_recipe, salad_recipe])
# Initialize game manager and delivery manager
game_manager = KitchenGameManager.get_instance()
game_manager.start_game()
delivery_manager = DeliveryManager.get_instance(recipe_list)
# Set up event handlers
def on_recipe_spawned(sender, args):
print("New recipe has been generated!")
def on_recipe_success(sender, args):
print("Recipe delivery successful!")
def on_recipe_failed(sender, args):
print("Recipe delivery failed...")
delivery_manager.on_recipe_spawned.add_handler(on_recipe_spawned)
delivery_manager.on_recipe_success.add_handler(on_recipe_success)
delivery_manager.on_recipe_failed.add_handler(on_recipe_failed)
# Sample execution
print("Game starting...")
# Run update process for 5 seconds
start_time = time.time()
while time.time() - start_time < 5:
delivery_manager.update()
time.sleep(0.1) # Update every 100ms
print(f"Number of waiting recipes: {len(delivery_manager.get_waiting_recipe_so_list())}")
# Sample delivery test
plate = PlateKitchenObject()
plate.add_kitchen_object(bread)
plate.add_kitchen_object(lettuce)
plate.add_kitchen_object(tomato)
print("Delivering sandwich...")
delivery_manager.deliver_recipe(plate)
print(f"Number of successful recipes: {delivery_manager.get_successful_recipes_amount()}")
Let's have Copilot Chat explain this code.
Ctrl+Alt+I
(on macOS Ctrl+Cmd+I
)Confirm the chat mode is set to "Question" (we'll introduce "Agent" mode later).
#delivery_manager.py
in the chat fielddelivery_manager.py
fileLet's ask Copilot Chat about the problematic parts of this code.
First, let's ask what problems this code has as a whole class.
Ask Copilot Chat:
Looking at this entire DeliveryManager class, what problems and improvement points are there? Please tell me from the perspectives of design patterns, code quality, and maintainability.
Next, let's focus on the deliver_recipe()
method and ask what methods exist to improve this method.
#deliver_recipe
in the chat fielddeliver_recipe
methodWhat methods are there to improve this deliver_recipe method? Please make suggestions from the perspectives of readability, performance, and error handling.
To improve the current code, ask Copilot Chat:
Please improve this Python code. I'd like suggestions from the perspectives of performance, readability, and error handling.
Are there any security issues with this code?
Please check if this follows Python best practices.
So far we've been using Copilot Chat in "Question" mode, but now let's try "Agent" mode. Agents can understand user intent and execute tasks more autonomously. Through practical examples, we'll learn how agents function.
First, with the delivery_manager.py
file open, select "Agent" from the mode selection in Copilot Chat.
Then, enter the following prompt:
Please list the issues that exist in the DeliveryManager class. Then, present improvement plans to solve each issue.
It should suggest multiple improvement points.
By trying the same question with different models, you can compare the characteristics of each model.
Now let's have them implement the improvement suggestions they provided:
Please implement all the improvement suggestions you presented.
Copilot will then make direct code changes to the code open in the editor. However, this is still at the suggestion stage, and the user decides whether to accept these changes. You can accept or reject by clicking the "Keep" or "Undo" button above the chat field.
Here, let's check the comments returned by the agent. The agent didn't just follow instructions and change code, but may also confirm that errors occurred after code changes and try to fix those errors too. In an appropriate environment, the agent automatically detects errors that occur after code changes and attempts to fix them. In this way, agents can understand user intent and execute tasks more autonomously.
When using agent mode, Copilot may ask whether it's okay to execute commands. This is because Copilot always asks for user confirmation before executing any command. Check the command content, and if there's no problem executing it, click "Allow this time". This allows Copilot to execute that command and make necessary changes.
This section is optional. If you want to try more advanced features after learning the basic functions of GitHub Copilot, please proceed.
Return all changes currently in the staging area to the working directory:
git restore .
Create and switch to the feature/pomodoro branch:
git checkout -b feature/pomodoro
By enabling GitHub Advanced Security's Code Scanning feature, you can automatically detect code vulnerabilities.
This will execute automatic code scanning when pushing or creating pull requests.
Let's verify the Copilot features available on GitHub.
Confirm the following features are enabled:
By using Model Context Protocol (MCP) servers, you can extend Copilot's functionality.
Ctrl+Shift+P
(Windows/Linux) / Cmd+Shift+P
(Mac)mcp: add server
https://api.githubcopilot.com/mcp/
github-mcp-server
in the Server ID field (or press Enter to skip)MCP server configuration is saved in .vscode/mcp.json
.
Now you can reference GitHub information directly in Copilot Chat.
So far, we've learned the basic usage of GitHub Copilot available in VS Code. Next, let's actually develop an application.
In this hands-on, we'll develop a Pomodoro timer application. This application has functionality to set work time and break time and manage timers.
We aim to create an application with the following UI:
Let's first create a new Python file in VS Code. Since we want to create this as a web application, we'll use Flask. Let's name the main file "app.py".
Create a web timer application for the Pomodoro Technique.
First, rather than starting implementation immediately, let's consult with Copilot about what approach and design to proceed with. From here on, we'll proceed entirely in agent mode.
What's helpful when creating a web application with UI like this is Copilot Chat's image upload functionality. Using this, you can make Copilot understand your application's UI image.
First, save the UI image from the previous page as pomodoro.png
in the project root. Then, click Add Context
in the chat field and select "Image from Clipboard" or "Files & Folders...". Then select the UI image.
Once the image is uploaded, it will be displayed in Copilot Chat.
Then, enter the following prompt:
We plan to create a Pomodoro timer web app in this project. The attached image is a UI mock for that app. What design should we proceed with to create this app using Flask and HTML/CSS/JavaScript? Please suggest an architecture.
It will then suggest a recommended web application architecture.
If there are points that should be improved or considerations that are lacking in this architecture, try pointing them out. For example, the following suggestion:
Considering the ease of unit testing, please also list any improvements or additions needed to the current architecture.
After this exchange, once the architectural design is settled, let's save that content to a file once. By doing so, you can reference the same architectural content even if you open a different chat session.
Since the architecture has been settled through our conversation so far, please compile a web application architecture proposal in a file called architecture.md in the project root, based on the content of our conversation.
Now that the UI mock and architectural design are established, let's consider what specific functionality needs to be implemented. Let's consult with Copilot Chat about this too. At that time, let's attach pomodoro.png and architecture.md.
For creating this Pomodoro timer application, please list the necessary functions that need to be implemented.
Let's improve this content through chat with Copilot. Once the content is finalized, let's save this content in a file called features.md, just like we did with the architecture.
Thank you. That content looks good, so please write the list of functions that need to be implemented in a file called features.md.
Now we're about to start implementation, but a tip for mastering Copilot is not to try to implement large functions all at once, but to start implementing small functions first. This improves the accuracy of the code Copilot suggests and allows for smoother development progress.
Let's also consult with Copilot about what granularity to break down and implement this application development. Here, let's attach pomodoro.png, architecture.md, and features.md.
I want to implement this Pomodoro timer application step by step. Based on the attached image, architecture, and feature list, please suggest what granularity should be used to implement functions and propose a step-by-step implementation plan.
When I tried it, it suggested a plan consisting of 6 steps. If there are points you'd like to see improved, try pointing them out to Copilot. And let's save this content in a file called plan.md so it can be referenced later. Please think for yourself what prompt should be used to give instructions.
Now that all the preparation is complete, let's finally start implementation. Following the implementation plan suggested in the previous step, we'll implement functionality step by step.
First, let's create a project directory structure according to our architecture.
First, please modify the current project folder structure to realize an architecture like architecture.md
. Move files and change configuration files as necessary.
Then, after attaching pomodoro.png
, architecture.md
, and plan.md
, give instructions to Copilot like this:
Please implement step 1 of plan.md. If you need to move files that already exist in this project to different directories, please perform that work as well. If there are additional considerations needed, please ask me questions.
In my case, it asked questions that needed consideration as shown below. In such cases, provide the necessary information.
After that, Copilot implements step 1. Once implementation is complete, Copilot builds the project on its own initiative and checks for errors. If errors occur, it makes additional corrections to resolve those errors. This kind of autonomous behavior is characteristic of agent mode.
Once implementation is complete, check the following points:
Below is the result of step 1 implementation in my case. What kind of application this becomes at this stage will differ from person to person.
Before continuing with implementation, let's write unit tests for the implemented functionality. By writing unit tests, we can confirm that changes in later steps don't affect existing functionality.
If unit tests are already implemented at the stage of the previous page, please skip this page.
Try executing a prompt like this:
There are no unit tests at all for the current implementation, so please implement unit tests.
Copilot agent will then ask whether it's okay to use commands to install dependencies for unit testing. Like this, agents always ask for user confirmation before executing any command. Here, click "Continue" to allow execution of necessary commands.
Copilot then executes the previous command in the terminal within VS Code and installs necessary dependencies. Similarly afterwards, Copilot always asks for user confirmation before executing any command. If executing that command causes an error, the agent makes additional corrections to resolve that error.
This section is optional. Please proceed if you have already learned basic Copilot functions and want to challenge more advanced implementation.
From here, let's implement the remaining features step by step as a free exercise.
Here are some points that should be helpful.
When you want to give instructions for specific elements on the UI, you can make Copilot recognize those elements by uploading a UI screenshot to Copilot. At that time, it's good to circle or draw arrows on the screenshot to clearly indicate which elements you want to give instructions for.
Alternatively, you can upload two screenshots - the current one and the expected one - to have Copilot check the differences and give instructions to get as close as possible to the expected UI.
When writing prompts or specifying context, if you're frequently giving similar instructions, you can have Copilot remember those instructions. Specifically, create a file called .github/copilot-instructions.md
in your project and write instructions in it. When this file exists, Copilot automatically loads those instructions and can reference them in subsequent chats.
Below is a sample of custom instructions.
This project implements a Pomodoro timer with Flask.
The following are important files in the project. Please reference these files as needed for user instructions.
- `pomodoro.png`: This is the UI mock of the application.
- `architecture.md`: This is the application architecture document.
- `features.md`: This is the list of functions to implement.
- `plan.md`: This is the step-by-step implementation plan.
Additionally, by recording project-specific commands such as commands to build the project or execute tests, Copilot will automatically use those commands.
In such cases, try the following approaches:
Let's commit the created code to the Git repository and push it to a remote branch. Here we'll introduce three methods.
The traditional method of directly executing Git commands in the terminal:
git add .
git commit -m "Add Pomodoro timer functionality"
git push origin feature/pomodoro-timer
Method using VS Code's integrated Git functionality:
If you have already set up MCP server, you can give instructions directly to Copilot in agent mode:
Function creation is complete, so please add the code differences to git staging.
Then, please commit with an appropriate commit message and push the changes to the remote branch.
Next, we'll manage the implementation plan as GitHub Issues:
Please create issues on GitHub for each step in plan.md
This instruction will have Copilot execute the following:
plan.md
This enables planned project management and agile development.
After pushing, let's create a Pull Request on GitHub.com and utilize Copilot's code review functionality.
Copilot will automatically generate a summary of the Pull Request.
In the Reviewers section, you can assign Copilot as a reviewer to request code review from Copilot.
After the Pull Request is opened, you can view Copilot Code Review results:
Pull Requests also display results from static vulnerability scanning by GitHub Advanced Security (GHAS):
Let's use the website version of GitHub Copilot to automatically generate project improvement suggestions as Issues and utilize Coding Agent.
Please create 3 issues to customize the Pomodoro timer.
Pattern A: Enhanced Visual Feedback
Circular progress bar animation: Smooth decreasing animation based on remaining time
Color changes: Gradient change from blue→yellow→red as time passes
Background effects: Particle effects or ripple animations during focus time
Test purpose: Measure the impact of visual immersion on user concentration
Pattern B: Improved Customizability
Flexible time settings: Selectable from 15/25/35/45 minutes instead of fixed 25 minutes
Theme switching: Dark/Light/Focus mode (minimal)
Sound settings: On/off toggle for start/end/tick sounds
Custom break time: Selectable from 5/10/15 minutes
Test purpose: Measure the impact of personalized settings on user retention rate
Pattern C: Adding Gamification Elements
Experience point system: XP and level up based on completed Pomodoros
Achievement badges: Achievement system like "3 consecutive days", "10 completions this week"
Weekly/monthly statistics: More detailed graph display (completion rate, average focus time, etc.)
Streak display: Consecutive day count display
Test purpose: Measure the impact of gamification elements on motivation maintenance and continued use
When Coding Agent is assigned, the following results can be expected:
In this workshop, we learned the following:
Great work!