1. Introduction
In this codelab, you'll look at how Gemini Code Assist can support you across key stages of the Software Development Life Cycle (SDLC) like design, build & test and deploy. We will design and develop an entire application and deploy it on Google Cloud.
We will be building an API and application to search across sessions in a technical event. Each session will have a title, summary, duration, categories and one or more speakers.
What you'll do
- Design, Build, Test and Deploy a web application based on an OpenAPI spec from scratch
What you'll learn
- How to use Gemini Code Assist to generate an OpenAPI Specification
- How to use Gemini Code Assist code generation features to develop a Python Flask Application for the OpenAPI specification
- How to use Gemini Code Assist to generate a web front-end for the Python Flask Application
- How to use Gemini Code Assist to get assistance on how to deploy the application to Google Cloud Run
- Use Gemini Code Assist features like Code Explanation, Test Case generation, while building and testing the application
What you'll need
- Chrome web browser
- A Gmail account
- A Cloud Project with billing enabled
- Gemini Code Assist enabled for your Cloud Project
This lab is targeted to developers of all levels, including beginners. Although the sample application is in Python language, you don't need to be familiar with Python programming in order to understand what's going on. Our focus will be on getting familiar with the capabilities of Gemini Code Assist.
2. Setup Gemini Code Assist
This section covers everything you need to do to get started with this lab.
Enable Gemini Code Assist in Cloud Shell IDE
We will be using Cloud Shell IDE, a fully-managed Code OSS-based development environment, for the rest of the codelab. We need to enable and configure Code Assist in the Cloud Shell IDE and the steps are given below:
- Visit ide.cloud.google.com. It may take a while for the IDE to appear, so please be patient and accept any setup default choices. In case you see some instructions on setting up the IDE, please go ahead and complete those with default settings.
- Click on the Cloud Code - Sign in button in the bottom status bar as shown. Authorize the plugin as instructed. If you see "Cloud Code - no project" in the status bar, select that and then select the specific Google Cloud Project from the list of projects that you plan to work with.
- Click on the Code Assist button in the bottom right corner as shown and select one last time the correct Google Cloud project. If you are asked to enable the Cloud AI Companion API, please do so and move forward.
- Once you've selected your Google Cloud project, ensure that you are able to see that in the Cloud Code status message in the status bar and that you also have Code Assist enabled on the right, in the status bar as shown below:
Gemini Code Assist is ready to use!
3. Setup Firestore
Cloud Firestore is a fully-managed serverless document database that we will use as a backend for our application data. Data in Cloud Firestore is structured in collections of documents.
We need to create a collection named sessions
in our default Firestore database. This collection will hold sample data (documents) that we will then use in our application.
Open the Terminal from inside your Cloud Shell IDE via the main menu as shown below:
We need to create a collection named sessions
. This will hold a list of sample session documents. Each document will have the following attributes:
- title: string
- categories: array of strings
- speakers: array of strings
- duration: string
- summary: string
Let's populate this collection with sample data by copying a file that contains the sample data into a bucket in your own project, from where you can then import the collection via the gcloud firestore import
command.
Firestore Database initialization
Visit the Firestore page in the Cloud console.
If you have not initialized a Firestore database before in the project, do create the default
database. During creation of the database, go with the following values:
- Firestore mode:
Native
- Location: Choose the Location type as
Region
and select the region that is appropriate for your application. Note down this location since you will need it in the next step for the bucket location. - Create the Database.
We will now create the sessions
collection by following the steps given below:
- Create a bucket in your project with the
gsutil
command given below. Replace the<PROJECT_ID>
variable in the command below with your Google Cloud Project Id. Replace<BUCKET_LOCATION>
with a region name that corresponds to the Geographic Area of your default Firestore database (as noted in the previous step), this could be US-WEST1, EUROPE-WEST1, ASIA-EAST1 :
gsutil mb -l <BUCKET-LOCATION> gs://<PROJECT_ID>-my-bucket
- Now that the bucket is created, we need to copy the database export that we have prepared into this bucket, before we can import it into the Firebase database. Use the command given below:
gsutil cp -r gs://sessions-master-database-bucket/2024-03-26T09:28:15_95256 gs://<PROJECT_ID>-my-bucket
Now that we have the data to import, we can move to the final step of importing the data into the Firebase database (default
) that we've created.
- Use the gcloud command given below:
gcloud firestore import gs://<PROJECT_ID>-my-bucket/2024-03-26T09:28:15_95256
The import will take a few seconds and once it's ready, you can validate your Firestore database and the collection by visiting https://console.cloud.google.com/firestore/databases, select the default
database and the sessions
collection as shown below:
This completes the creation of the Firestore collection that we will be using in our application.
4. Create the Application Template
We will create a sample application (a Python Flask application) that we will use across the rest of the codelab. This application will search across sessions offered at a technical conference.
Follow these steps:
- Click on the Google Cloud project name in the status bar below.
- A list of options will appear. Click on New Application from the list below.
- Select Cloud Run application (this will be the runtime for our app).
- Select the Python (Flask): Cloud Run application template.
- Give the application a name and save it in your preferred location.
- A notification confirms that your application was created, and a new window opens with your application loaded as shown below. A
README.md
file is opened. You can close that view for now.
5. Interacting with Gemini Code Assist
For the purpose of this lab, we will be using the Gemini Code Assist Chat available inside of Cloud Shell IDE as part of Cloud Code extension in VS Code. You can bring it up by clicking on the Code Assist button in the left navigation bar. Look for the Code Assist icon in the left navigation toolbar and click on that.
This will bring up the Code Assist chat pane inside Cloud Shell IDE and you can chat with Code Assist.
Notice the trash can icon at the top - this is your way to reset the context for the Code Assist chat history. Note also that this chat interaction is contextual to the file(s) that you are working on in the IDE.
6. API Design
Our first step will be to take the assistance of Gemini Code Assist during the Design phase. In this step, we shall generate an OpenAPI specification for the entities (technical sessions in an event) that we want to search across.
Give the following prompt:
Generate an Open API spec that will allow me to retrieve all sessions, sessions by category, session by id. Each session has the following attributes: id, title, list of speakers, list of categories, summary and duration.
This should generate an OpenAPI Specification for searching across sessions via various query parameters. The specification sample is given below:
openapi: 3.0.0
info:
title: Sessions API
description: This API allows you to retrieve all sessions, sessions by category, and session by id.
version: 1.0.0
servers:
- url: https://sessions.example.com
paths:
/sessions:
get:
summary: Get all sessions
operationId: getSessions
responses:
'200':
description: OK
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Session'
/sessions/{id}:
get:
summary: Get session by id
operationId: getSessionById
parameters:
- name: id
in: path
required: true
description: The id of the session
schema:
type: string
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/Session'
/sessions/categories/{category}:
get:
summary: Get sessions by category
operationId: getSessionsByCategory
parameters:
- name: category
in: path
required: true
description: The category of the sessions
schema:
type: string
responses:
'200':
description: OK
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Session'
components:
schemas:
Session:
type: object
properties:
id:
type: string
description: The id of the session
title:
type: string
description: The title of the session
speakers:
type: array
items:
type: string
description: The list of speakers for the session
categories:
type: array
items:
type: string
description: The list of categories for the session
summary:
type: string
description: The summary of the session
duration:
type: string
description: The duration of the session
You can notice that the specification has the following:
- A schema defined for the Session type.
- Several API paths defined:
/sessions
/sessions/{id}
/sessions/categories/{category}
Create a file called sessionsapi.yaml
in the top folder and copy the content over from the Code Assist chat window using the "insert in current file option" (the +
button) and keep the file open in the Cloud Shell IDE.
At this point, you can note an interesting feature of Gemini Code Assist: citation. This information is surfaced to the developer when the generated code directly quotes at length from another source, such as existing open source code. It offers the source and the license for the developer to decide what to do with it.
Assuming we are fine with the generated content we can now use this specification document to generate a Python Flask Application for it.
7. Generate the Application
We will now ask Code Assist to generate the application. Give the following prompt with the sessionsapi.yaml
file open.
Generate a Python Application using the Flask framework, based on the sessionsapi.yaml file. This application uses a local in memory list of sessions. Do not use any Flask extensions.
This should provide you with a skeleton for the Python Flask Application that is based on the functionality and paths that have been specified in the OpenAPI specification file.
The Python Flask Application code that is provided should be similar to the following one:
from flask import Flask, jsonify, request
app = Flask(__name__)
sessions = [
{
"id": "1",
"title": "Session 1",
"speakers": ["Speaker 1", "Speaker 2"],
"categories": ["Category 1", "Category 2"],
"summary": "This is a summary of session 1.",
"duration": "1 hour",
},
{
"id": "2",
"title": "Session 2",
"speakers": ["Speaker 3", "Speaker 4"],
"categories": ["Category 3", "Category 4"],
"summary": "This is a summary of session 2.",
"duration": "1 hour 30 minutes",
},
]
@app.route('/sessions', methods=['GET'])
def get_sessions():
return jsonify(sessions)
@app.route('/sessions/<id>', methods=['GET'])
def get_session_by_id(id):
session = next((session for session in sessions if session['id'] == id), None)
if session is None:
return jsonify({}), 404
return jsonify(session)
@app.route('/sessions/categories/<category>', methods=['GET'])
def get_sessions_by_category(category):
sessions_by_category = [session for session in sessions if category in session['categories']]
return jsonify(sessions_by_category)
if __name__ == '__main__':
app.run()
There is an existing app.py
file generated as part of the previous step. Simply replace its contents with the code generated by Code Assist and save the file.
We would like to change the app.run()
line to use port 8080, host address 0.0.0.0, and also run in Debug mode during local execution.Here is a way to do that. First up, let's highlight/select the line:
app.run()
Then, in the Code Assist Chat interface, type the prompt: Explain this.
This should show a detailed explanation of that particular line, an example of which is shown below:
Now, use the following prompt:
update the code to run the application on port 8080, host address 0.0.0.0, and in debug mode
The generated suggested code should be as follows: :
app.run(host='0.0.0.0', port=8080, debug=True)
Remember to update the app.py
file with this snippet.
Run the application locally
Let's run the application locally now to validate the application requirements as per what we had started out with.
The first step will be to create a virtual Python environment with the Python package dependencies in requirements.txt to be installed in the virtual environment. To do that, go to the Command Palette (Ctrl+Shift+P) in Cloud Shell IDE and type in Create Python environment. Follow the next few steps to select a Virtual Environment (venv), Python 3.x interpreter and the requirements.txt file.
Once the environment is created, launch a new terminal window (Ctrl+Shift+`) and give the following command:
python app.py
A sample execution is shown below:
(.venv) romin@cloudshell: $ python app.py
* Serving Flask app 'app'
* Debug mode: on
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on all addresses (0.0.0.0)
* Running on http://127.0.0.1:8080
* Running on http://10.88.0.3:8080
Press CTRL+C to quit
* Restarting with stat
* Debugger is active!
* Debugger PIN: 132-247-368
You can now preview the API at the following URLs. We are assuming that your development server is running on port 8080. If not, please change it to the appropriate port number.
https://<host-name>:8080/sessions
https://<host-name>:8080/sessions/{id}
https://<host-name>:8080/sessions/categories/{category}
Follow the steps given below to ensure that you are able to retrieve using these URLs, the JSON data contained in the app.py
file:
Open a new terminal window and try any of the following commands:
curl -X GET http://127.0.0.1:8080/sessions
curl -X GET http://127.0.0.1:8080/sessions/<ID>
curl -X GET http://127.0.0.1:8080/sessions/categories/<CATEGORY_NAME>
8. Code Refactoring
Rather than have app.py
contain the hard-coded sample JSON data, we probably would like to separate/extract this into another module, so that we can maintain a clean separation between the code and the data. Let's do that!
Keep the app.py
file open and give the following prompt:
Can I improve this code and separate out the sessions data from this app.py file?
This should give you some suggestions on how to do that. A sample suggestion that we got and you should get something similar to it, is shown below:
Let's follow that and separate out our data into a sessions.py
file as suggested by Code Assist.
Create a new file named sessions.py
, the content of which is the JSON list, as per our data generated is given below:
sessions = [
{
"id": "1",
"title": "Session 1",
"speakers": ["Speaker 1", "Speaker 2"],
"categories": ["Category 1", "Category 2"],
"summary": "This is a summary of session 1.",
"duration": "1 hour",
},
{
"id": "2",
"title": "Session 2",
"speakers": ["Speaker 3", "Speaker 4"],
"categories": ["Category 3", "Category 4"],
"summary": "This is a summary of session 2.",
"duration": "1 hour 30 minutes",
},
]
The app.py
file is now much simplified and is shown below:
from flask import Flask, jsonify, request
from sessions import sessions
app = Flask(__name__)
@app.route('/sessions', methods=['GET'])
def get_sessions():
return jsonify(sessions.sessions)
@app.route('/sessions/<id>', methods=['GET'])
def get_session_by_id(id):
session = next((session for session in sessions.sessions if session['id'] == id), None)
if session is None:
return jsonify({}), 404
return jsonify(session)
@app.route('/sessions/categories/<category>', methods=['GET'])
def get_sessions_by_category(category):
sessions_by_category = [session for session in sessions.sessions if category in session['categories']]
return jsonify(sessions_by_category)
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8080, debug=True)
Ensure that you are still able to run the application locally and exercise the API with these refactored changes. The python development server is likely to be still running so you only need to recall the curl
commands from the previous step.
9. Integrate with Firestore Collection
The next step is to move away from the local in-memory JSON list that we have for our sessions and connect our application to the sessions collection in the Firestore database, that we created at the beginning of this codelab.
Keep the sessions.py
file open and give the following prompt:
Can you further refactor the sessions.py code to read from a Firestore database that has a collection named sessions. The collection has the same attributes as the session object that we have defined. Use the Python module google-cloud-firestore.
We received the following suggestion to read all the sessions from the Firestore collection:
import google.cloud.firestore
# Create a Firestore client
db = google.cloud.firestore.Client()
# Get the sessions collection
sessions_collection = db.collection("sessions")
# Create a list of sessions
sessions = []
# Iterate over the documents and add them to the list
for doc in sessions_collection.stream():
session = doc.to_dict()
session["id"] = doc.id
sessions.append(session)
Go ahead and incorporate the code in sessions.py
.
If you have the Flask Development server running locally, your application might have closed down complaining that the Python module was not found.
You can ask Code Assist for example, about which Python module needs to be added to the requirements.txt
file, as follows:
Which Python package needs to be installed to make the firestore code work?
This will provide you with the name of the Python module (e.g. google-cloud-firestore
). Add that to the requirements.txt
file.
You will need to recreate the Python environment with the newly added module (google-cloud-firestore
). To do that, give the following command in the existing Terminal window:
pip install -r requirements.txt
Run the application again (restart it with python app.py
) and visit the /sessions
URL. You should now get the sample documents that we added to the sessions
collection.
Feel free to query other URIs to retrieve specific sessions or all sessions for a given category as described in the earlier steps.
10. Code Explanation
Now is a good time to use the "Explain this"
feature of Gemini Code Assist to get some good understanding of the code. Feel free to go into any of the files or select specific snippets of code and ask Code Assist with the following prompt: Explain this
.
As an exercise, visit the sessions.py
file and highlight the Firestore specific code and get code explanation on that. Try also using this feature on other files in your project, not just python code.
11. Generate the Web Application
Now that we have generated the API and integrated it with a live Firestore collection, let us generate a Web-based front-end for the application. Our Web front-end will currently keep its functionality to a minimum, i.e. be able to search for sessions that belong to a specific category. Keep in mind that we have an API path for that i.e. /sessions/categories/{category}
so our web application should invoke that and retrieve the results.
Let's dive right into it. Give the following prompt to Code Assist:
Generate a web application that allows me to search for sessions by category and uses the Flask application that we created. Please use basic HTML, CSS and JS. Embed all the Javascript and CSS code into a single HTML file only.
This will generate the Web application HTML with the JavaScript and CSS embedded in it. It will also ask you to add a new route to the app.py
file, so that any user visiting the root or base URL will be served the home page. If it does not mention that information, do ask about it or use the snippet given below:
@app.route('/')
def index():
return render_template('index.html')
You can save this as index.html
but may have a question on where this file should be saved (i.e. which folder?). We can ask a follow up question to Code Assist.
Given that I am using the flask framework, where should I put the index.html file?
It should give you clear information that it uses the render_template
framework and hence the index.html file will need to be placed inside of the templates
folder. You will find this folder available since we had generated an Application based on Flask template at the start of this codelab. As a result, there is an existing index.html
file and you should simply replace its contents with the new one that has been generated here. Code Assist also mentions to import render_template
in your app.py
file.
Go ahead and save Web application code in the index.html
file and remember to place that file in the templates
folder.
Run the application locally
Let's run the application locally now to validate the application requirements as per what we had started out with.
Ensure that the local Flask server is still running and not showing any errors. In case of errors, address them and start up the server.
Once up and running, visit the home URL of the application. It should show you the index.html
file served as shown below:
Provide one of the categories from the data as input (e.g. AI
) and click on the Search
button. This should display the sessions tagged with the AI
category.
An optional exercise is to show additional metadata like duration, categories, speakers and summary for each of the sessions.
12. Test Case Generation
We have generated the sessions API and it's time to use Gemini Code Assist now to generate unit test cases for the different API Paths.
Keep the app.py
file open and give the following prompt:
Generate unit test cases for app routes. Use unittest module.
We got the following response:
import unittest
from app import app
class FlaskAppTestCase(unittest.TestCase):
def test_index(self):
tester = app.test_client()
response = tester.get('/')
self.assertEqual(response.status_code, 200)
def test_get_sessions(self):
tester = app.test_client()
response = tester.get('/sessions')
self.assertEqual(response.status_code, 200)
def test_get_session_by_id(self):
tester = app.test_client()
response = tester.get('/sessions/1')
self.assertEqual(response.status_code, 200)
def test_get_sessions_by_category(self):
tester = app.test_client()
response = tester.get('/sessions/categories/category1')
self.assertEqual(response.status_code, 200)
if __name__ == '__main__':
unittest.main()
Create a file named tests.py
with the above code.
A Note on Test Case Generation
You might see a different code listing than the one above and that might cause some problems in running the test cases. For e.g. we saw that in some of our runs, the following key pieces of code were missed out:
from app import app
The above code is required to import the existing Flask App against which we will be invoking the test cases.
if __name__ == '__main__':
`unittest.main()`
The above code is needed to run the test cases.
Our recommendation is to look at each of the test cases, check the assertEqual
and other conditions in the generated code to ensure that it will work. Since the data is external in Firestore collection, it might not be having access to it and might use some dummy data as a result of which the tests might fail. So modify your test cases accordingly or comment out some of the test cases that you may not immediately need.
As a demonstration, we ran the test cases using the following command (Be sure to run the local development server since calls will be made to the local API Endpoints):
python tests.py
We got the following summary result:
Ran 4 tests in 0.274s
FAILED (failures=2)
That is indeed correct since the session id was not correct in the 3rd test and there is no category named category1
.
So adjust the test cases accordingly and test it out.
13. Test Driven Development
Let us now look at adding a new search method in our sessions API following the Test Driven Development (TDD) methodology, which is about writing test cases first, making them fail due to a lack of implementation and using Gemini Code Assist to generate the missing implementation so that the test passes.
Go to tests.py file (assuming that you have fixed the tests.py
file to have all tests that pass). Ask Code Assist the following prompt:
Generate a new test case to search for sessions by speaker
This gave us the following test case implementation that we duly inserted into the tests.py
file.
def test_get_sessions_by_speaker(self):
tester = app.test_client()
response = tester.get('/sessions/speakers/speaker1')
self.assertEqual(response.status_code, 200)
self.assertEqual(response.json, [sessions.sessions[0], sessions.sessions[1]])
If you run the tests, you should see the following error:
$ python tests.py
.F.
======================================================================
FAIL: test_get_sessions_by_speaker (__main__.FlaskAppTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/romin/hello-world-5/tests.py", line 21, in test_get_sessions_by_speaker
self.assertEqual(response.status_code, 200)
AssertionError: 404 != 200
----------------------------------------------------------------------
Ran 3 tests in 0.010s
FAILED (failures=1)
This is because the test case has invoked the following path (
/sessions/speakers/
)
and there is no implementation of that in app.py
.
Let us ask Code Assist to provide us with an implementation. Go to the app.py
file and give the following prompt to Code Assist:
Add a new route to search for sessions by a specific speaker
We got the following implementation suggested by Code Assist, which we added to the app.py
file:
@app.route('/sessions/speakers/<speaker>', methods=['GET'])
def get_sessions_by_speaker(speaker):
sessions_by_speaker = [session for session in sessions.sessions if speaker in session['speakers']]
return jsonify(sessions_by_speaker)
Revisit the tests.py
file and we modified our test case as follows for a quick check:
def test_get_sessions_by_speaker(self):
tester = app.test_client()
response = tester.get('/sessions/speakers/Romin Irani')
self.assertEqual(response.status_code, 200)
self.assertEqual(len(response.json), 1)
The test ran fine. We leave it as an exercise for you to look at the test cases generated, tweak them a bit depending on the data that you might have in Firestore and have the appropriate assert*
methods in the Python Unit Test cases.
14. Deploying to Google Cloud Run
Now that we feel good about the quality of our development, our final step will be to deploy this application to Google Cloud Run. But maybe, for good measures, we should ask Code Assist if we've forgotten anything. With app.py
open, submit the following prompt :
Is there something here I should change before I deploy to production?
Good thing you asked since we actually forgot to set the debugging flag to off :
As indicated, turn debugging off and proceed to ask Gemini Code Assist for help with the gcloud
command that can be used to deploy the application to Cloud Run directly from source (without having to build a container first).
Give the following prompt:
I would like to deploy the application to Cloud Run directly from source. What is the gcloud command to do that?
Try a few variations of the above prompt. Another one that we tried was:
I would like to deploy this application to Cloud Run. I don't want to build a container image locally but deploy directly from source to Cloud Run. What is the gcloud command for that?
Ideally you should get the following gcloud
command:
gcloud run deploy sessions --source .
You may also get:
gcloud run deploy <service-name> --source . \
—-platform managed \
—-allow-unauthenticated
Execute the above command from the root folder of the application. When asked for region
, select us-central1
and when asked for allowing unauthenticated invocations
, choose Y
. You may also be asked to enable Google Cloud APIs like Artifact Registry, Cloud Build and Cloud Run and permission to create an Artifact Registry repository, please go ahead and give the permission.
The deployment process will take about 2 minutes to complete, so please be patient.
Once successfully deployed, you will see the Cloud Run service URL. Visit that public URL and you should see the same web application deployed and running successfully.
Congratulations, well done !
15. (Optional) Use Cloud Logging
We can introduce logging in our application such that the application logs are centralized in one of the Google Cloud services (Cloud Logging). We can then use the Observability Gemini feature to get an understanding of log entries too.
To do this, we will first have to use an existing Python Cloud Logging library from Google Cloud and use that for logging informational, warning or error messages (depending on the log / severity level).
Let's try and ask that first to Code Assist. Try the following prompt:
How do I use the google-cloud-logging package in Python?
You should get a response that provides some information on it, as given below:
Let's add logging statements to the function that searches for sessions by category.
First up, add google-cloud-logging
Python package to the requirements.txt
file.
Next up is a snippet of code that shows how we integrated the code to implement logging:
...
from google.cloud import logging
...
app = Flask(__name__)
# Create a logger
logger = logging.Client().logger('my-log')
@app.route('/sessions/categories/<category>', methods=['GET'])
def get_sessions_by_category(category):
logger.log_text(f"Fetching sessions with category {category}")
sessions_by_category = [session for session in sessions.sessions if category in session['categories']]
logger.log_text(f'Found {len(sessions_by_category)} sessions with category {category}')
return jsonify(sessions_by_category)
# # Other App Routes
Deploy the service to Cloud Run again using the same command as in the previous section, and once it's deployed, execute a few calls to the /sessions/categories/<category>
endpoint.
Go to the Cloud Console → Logs Explorer
...and you should be able to filter into these logging statements as shown below:
You can click on any of the log statements, expand it and then click on Explain this log entry
, which will use Gemini to explain the log entry. Note that if you have not enabled Gemini for Google Cloud, you will be asked to enable the Cloud AI Companion API. Please go ahead and do that as instructed.
A sample response is given below:
16. Congratulations
Congratulations, you've successfully built an application from scratch and used Gemini Code Assist across multiple aspects of the SDLC including design, build, testing and deployment.
What's next?
Check out some of these codelabs...
- A Tour of Duet AI for Developers
- Using Duet AI Throughout the Software Development Life Cycle
- Getting Stylish with Duet AI for Developers