Codelab de la guía del taller práctico técnico de Duet AI para desarrolladores

1. Objetivos

El propósito de este taller es brindar educación práctica sobre Duet AI a usuarios y profesionales.

En este codelab, aprenderás lo siguiente:

  1. Activa Duet AI en tu proyecto de GCP y configúralo para usarlo en un IDE y la consola de Cloud.
  2. Usa Duet AI para generar, completar y explicar código.
  3. Usa Duet AI para explicar y solucionar un problema de una aplicación.
  4. Funciones de Duet AI como chat con IDE y chat de varios turnos, generación de código intercalado en comparación con el chat, acciones inteligentes como la explicación del código y el reconocimiento de solicitudes, y mucho más.

Narración

Con el objetivo de mostrar cómo se usa Duet AI para desarrolladores con autenticidad en el desarrollo diario, las actividades de este taller se llevan a cabo en un contexto narrativo.

Un nuevo desarrollador se une a una empresa de comercio electrónico. Su tarea consiste en agregar un servicio nuevo a la aplicación de comercio electrónico existente (compuesta por varios servicios). El nuevo servicio proporciona información adicional (dimensiones, peso, etc.) sobre los productos del catálogo. Este servicio permitirá costos de envío mejores y más económicos en función de las dimensiones y el peso de los productos.

Como el desarrollador es nuevo en la empresa, usará Duet AI para generar, explicar y documentar código.

Después de codificar el servicio, un administrador de la plataforma usará Duet AI (chat) para ayudar a crear el artefacto (contenedor de Docker) y los recursos necesarios para implementarlo en GCP (por ejemplo, Artifact Registry, permisos de IAM, un repositorio de código, la infraestructura de procesamiento, es decir, GKE o CloudRun, etcétera).

Una vez que la aplicación se implementa en GCP, un operador de aplicaciones/SRE usará Duet AI (y Cloud Ops) para ayudar a solucionar un error en el nuevo servicio.

Persona

El taller abarca la siguiente persona:

  1. Desarrollador de aplicaciones: Se requiere cierto nivel de conocimientos en programación y desarrollo de software.

Esta variación del taller de Duet AI es solo para desarrolladores. No es necesario tener conocimientos sobre los recursos en la nube de GCP. Aquí encontrarás las secuencias de comandos que explican cómo compilar los recursos de GCP necesarios para ejecutar esta aplicación. Puedes seguir las instrucciones de esta guía para implementar los recursos de GCP necesarios.

2. Prepara el entorno

Activando Duet AI

Puedes activar Duet AI en un proyecto de GCP mediante la API (gcloud o herramientas de IaC como Terraform) o la IU de la consola de Cloud.

Para activar Duet AI en un proyecto de Google Cloud, habilita la API de Cloud AI Companion y otorga los roles de Identity and Access Management (IAM) de Usuario de Cloud AI Companion y Visualizador de Service Usage a los usuarios.

A través de gcloud

Activa Cloud Shell:

Configura tu PROJECT_ID y USER, y habilita la API de Cloud AI Companion.

export PROJECT_ID=<YOUR PROJECT ID>
export USER=<YOUR USERNAME> # Use your full LDAP, e.g. name@example.com
gcloud config set project ${PROJECT_ID}
gcloud services enable cloudaicompanion.googleapis.com --project ${PROJECT_ID}

El resultado es como el siguiente:

Updated property [core/project].
Operation "operations/acat.p2-60565640195-f37dc7fe-b093-4451-9b12-934649e2a435" finished successfully.

Otorga los roles de Identity and Access Management (IAM) de Usuario de Cloud AI Companion y Visualizador de Service Usage a la cuenta de USER. La API de Cloud Companion se encuentra detrás de las funciones del IDE y de la consola que usaremos. El permiso de visualizador de Service Usage se usa como una verificación rápida antes de habilitar la IU en la consola (para que la IU de Duet solo aparezca en proyectos en los que la API esté habilitada).

gcloud projects add-iam-policy-binding  ${PROJECT_ID} \
--member=user:${USER} --role=roles/cloudaicompanion.user

gcloud projects add-iam-policy-binding  ${PROJECT_ID} \
--member=user:${USER} --role=roles/serviceusage.serviceUsageViewer

El resultado es como el siguiente:

...
- members:
  - user:<YOUR USER ACCOUNT>
  role: roles/cloudaicompanion.user

...
- members:
  - user:<YOUR USER ACCOUNT>
  role: roles/serviceusage.serviceUsageViewer

A través de la consola de Cloud

Para habilitar la API, ve a la página de la API de Cloud AI Companion en la consola de Google Cloud.

En el selector de proyectos, elige un proyecto.

Haz clic en Habilitar.

La página se actualizará y mostrará el estado Habilitada. Duet AI ahora está disponible en el proyecto de Google Cloud seleccionado para todos los usuarios que tengan los roles de IAM necesarios.

Si quieres otorgar los roles de IAM necesarios para usar Duet AI, ve a la página IAM.

En la columna Principal, busca el USUARIO para el que quieres habilitar el acceso a Duet AI y, luego, haz clic en el ícono de lápiz ✏️ Editar principal en esa fila.

En el panel de acceso Editar, haz clic en Agregar otro rol.

En Selecciona un rol, elige Usuario de Cloud AI Companion.

Haz clic en Agregar otro rol y selecciona Visualizador de Service Usage.

Haz clic en Guardar.

Configura el IDE

Los desarrolladores pueden elegir entre una variedad de IDEs que se adapten mejor a sus necesidades. La asistencia para código de Duet AI está disponible en varios IDE, como Visual Studio Code, los IDE de JetBrains (IntelliJ, PyCharm, GoLand, WebStorm, entre otros), Cloud Workstations y Editor de Cloud Shell.

En este lab, puedes usar Cloud Workstations o el Editor de Cloud Shell.

En este taller, se usa el editor de Cloud Shell.

Ten en cuenta que Cloud Workstations puede tardar entre 20 y 30 minutos en configurarse.

Para usarlo de inmediato, utiliza el Editor de Cloud Shell.

Para abrir el editor de Cloud Shell, haz clic en el ícono de lápiz ✏️ en la barra de menú superior de Cloud Shell.

El editor de Cloud Shell tiene una IU y una UX muy similares a VSCode.

d6a6565f83576063.png

Haz clic en CTRL (en Windows)/CMD (en Mac) + , (coma) para acceder al panel de configuración.

En la barra de búsqueda, escribe "duet ai".

Asegúrate o habilita Cloudcode › Duet AI: Habilitar y Cloudcode › Duet AI › Sugerencias intercaladas: Habilitar automático

111b8d587330ec74.png

En la barra de estado de la parte inferior, haz clic en Cloud Code - Acceder y sigue el flujo de trabajo para acceder.

Si ya accediste, en la barra de estado aparecerá el mensaje Cloud Code - No project.

Haz clic en Cloud Code - No proyecto. Aparecerá un panel desplegable de acciones en la parte superior. Haz clic en Seleccionar un proyecto de Google Cloud.

3241a59811e3c84a.png

Comienza a escribir el ID de tu proyecto. Tu proyecto debería aparecer en la lista.

c5358fc837588fe.png

Selecciona tu PROJECT_ID de la lista de proyectos.

La barra de estado inferior se actualiza para mostrar el ID del proyecto. Si no es así, deberás actualizar la pestaña del editor de Cloud Shell.

Haz clic en el ícono de Duet AI d97fc4e7b594c3af.pngen la barra de menú de la izquierda y aparecerá la ventana de chat de Duet AI. Si recibes un mensaje que dice Seleccionar proyecto de GCP. Haz clic y vuelve a seleccionar el proyecto.

Ahora puedes ver la ventana de chat de Duet AI

781f888360229ca6.png

3. Configurar la infraestructura

d3234d237f00fdbb.png

Para ejecutar el servicio de envío nuevo en GCP, necesitarás los siguientes recursos de GCP:

  1. Una instancia de Cloud SQL, con una base de datos.
  2. Un clúster de GKE para ejecutar el servicio alojado en contenedores
  3. Un Artifact Registry para almacenar la imagen de Docker
  4. Un Cloud Source Repository para el código.

En la terminal de Cloud Shell, clona el siguiente repositorio y ejecuta los siguientes comandos para configurar la infraestructura de tu proyecto de GCP.

# Set your project
export PROJECT_ID=<INSERT_YOUR_PROJECT_ID>
gcloud config set core/project ${PROJECT_ID}

# Enable Cloudbuild and grant Cloudbuild SA owner role 
export PROJECT_NUMBER=$(gcloud projects describe ${PROJECT_ID} --format 'value(projectNumber)')
gcloud services enable cloudbuild.googleapis.com
gcloud projects add-iam-policy-binding ${PROJECT_ID} --member serviceAccount:${PROJECT_NUMBER}@cloudbuild.gserviceaccount.com --role roles/owner

# Clone the repo
git clone https://github.com/duetailabs/dev.git ~/duetaidev
cd ~/duetaidev

# Run Cloudbuild to create the necessary resources
gcloud builds submit --substitutions=_PROJECT_ID=${PROJECT_ID}

# To destroy all GCP resources, run the following
# gcloud builds submit --substitutions=_PROJECT_ID=${PROJECT_ID} --config=cloudbuild_destroy.yaml

4. Desarrolla un servicio Flask en Python

9745ba5c70782e76.png

El servicio que crearemos constará, en última instancia, de los siguientes archivos. No es necesario que crees estos archivos ahora. Para crearlos de a uno, sigue las instrucciones que se indican a continuación:

  1. package-service.yaml: Es una especificación de Open API para el servicio de paquetes que tiene datos como la altura, el ancho, el peso y las instrucciones especiales de manejo.
  2. data_model.py: Es el modelo de datos para las especificaciones de la API de package-service. También crea la tabla packages en la base de datos product_details.
  3. connect_connector.py: Conexión de Cloud SQL (define el motor, la sesión y el ORM base)
  4. db_init.py: Genera datos de muestra en la tabla packages.
  5. main.py: Es un servicio de Flask en Python con un extremo GET para recuperar detalles del paquete a partir de los datos de packages basados en product_id.
  6. test.py: Prueba de unidad
  7. requirement.txt: Requisitos de Python
  8. Dockerfile: Para alojar esta aplicación en contenedores

Si tienes algún problema persistente durante los ejercicios, encontrarás los archivos finales en el APÉNDICE de este codelab a modo de referencia.

En el paso anterior, creaste un Cloud Source Repository. Clona el repositorio. Compilarás los archivos de la aplicación en la carpeta del repositorio clonado.

En la terminal de Cloud Shell, ejecuta el siguiente comando para clonar el repositorio.

cd ~
gcloud source repos clone shipping shipping
cd ~/shipping 

Abre la barra lateral del chat de Duet AI desde el menú de la izquierda del Editor de Cloud Shell. El ícono es similar a 8b135a000b259175.png. Ahora puedes usar Duet AI para obtener asistencia con el código.

package-service.yaml

Sin ningún archivo abierto, pídele a Duet que genere una especificación de Open API para el servicio de envío.

Consigna 1: Genera una especificación YAML de OpenAPI para un servicio que proporcione información de envío y paquetes con un ID de producto numérico. El servicio debe incluir información sobre la altura, el ancho, la profundidad y el peso de los paquetes, así como cualquier instrucción especial de manipulación.

ba12626f491a1204.png

En la parte superior derecha de la ventana del código generado, aparecerán tres opciones.

Puedes COPY 71194556d8061dae.pngel código y PEGARlo en un archivo.

Puedes ADD df645de8c65607a.png el código en el archivo abierto actualmente en el editor.

También puedes OPEN a4c7ed6d845df343.png el código en un archivo nuevo.

Haz clic en OPEN a4c7ed6d845df343.png para agregar el código en un archivo nuevo.

Haz clic en CTRL/CMD + s para guardar el archivo y almacénalo en la carpeta de la aplicación con el nombre package-service.yaml. Haz clic en Aceptar.

f6ebd5b836949366.png

El archivo final se encuentra en la sección APÉNDICE de este codelab. De lo contrario, haz los cambios correspondientes de forma manual.

También puedes probar varias instrucciones para ver las respuestas de Duet AI.

Restablece el historial de chat de Duet AI haciendo clic en el ícono de papelera f574ca2c1e114856.png en la parte superior de la barra lateral de Duet AI.

data_model.py

A continuación, crearás el archivo de Python del modelo de datos para el servicio según la especificación de OpenAPI.

Con el archivo package-service.yaml abierto, ingresa el siguiente mensaje.

Consigna 1: Usa el ORM de Python sqlalchemy para generar un modelo de datos para este servicio de la API. Además, incluye una función independiente y un punto de entrada principal que cree las tablas de la base de datos.

b873a6a28bd28ca1.png

Observemos cada parte que se generó. Duet AI sigue siendo un asistente y, si bien puede ayudar a escribir código con rapidez, debes revisar el contenido generado y entenderlo a medida que avanzas.

Primero, hay una clase llamada Package de categoría Base que define el modelo de datos para la base de datos packages de la siguiente manera:

class Package(Base):
    __tablename__ = 'packages'

    id = Column(Integer, primary_key=True)
    product_id = Column(String(255))
    height = Column(Float)
    width = Column(Float)
    depth = Column(Float)
    weight = Column(Float)
    special_handling_instructions = Column(String(255))

A continuación, necesitas una función que cree la tabla en la base de datos como la siguiente:

def create_tables(engine):
    Base.metadata.create_all(engine)

Por último, necesitas una función principal que ejecute la función create_tables para compilar la tabla en la base de datos de Cloud SQL, como se muestra a continuación:

if __name__ == '__main__':
    from sqlalchemy import create_engine

    engine = create_engine('sqlite:///shipping.db')
    create_tables(engine)

    print('Tables created successfully.')

Ten en cuenta que la función main está creando un motor con una base de datos local sqlite. Para usar Cloud SQL, deberás cambiarlo. Lo harás un poco más tarde.

Usa OPEN a4c7ed6d845df343.png para el código en un flujo de trabajo de archivo nuevo, como antes. Guarda el código en un archivo llamado data_model.py (ten en cuenta el guion bajo en el nombre y no el guion).

Restablece el historial de chat de Duet AI haciendo clic en el ícono de papelera f574ca2c1e114856.png en la parte superior de la barra lateral de Duet AI.

connect-connector.py

Crea el conector de Cloud SQL.

Con el archivo data_model.py abierto, ingresa los siguientes mensajes.

Consigna 1: Con la biblioteca cloud-sql-python-connector, generar una función que inicialice un grupo de conexiones para una instancia de Cloud SQL de Postgres.

ed05cb6ff85d34c5.png

Ten en cuenta que la respuesta no usa la biblioteca cloud-sql-python-connector. Puedes definir mejor las instrucciones y darle a Duet un pequeño empujón agregando información específica a la misma conversación de chat.

Usemos otra instrucción.

Consigna 2: Debes usar la biblioteca cloud-sql-python-connector.

d09095b44dde35bf.png

Asegúrate de que use la biblioteca cloud-sql-python-connector.

Usa OPEN a4c7ed6d845df343.png para el código en un flujo de trabajo de archivo nuevo, como antes. Guarda el código en un archivo llamado connect_conector.py. Es posible que debas importar manualmente la biblioteca pg8000. Consulta el archivo que aparece a continuación.

Borra el historial de chat de Duet AI y, con el archivo connect_connector.py abierto, genera el ORM de DB engine, sessionmaker y base para usar en la aplicación.

Consigna 1: Crea un motor, una clase sessionmaker y un ORM base con el método connect_with_connector

6e4214b72ab13a63.png

La respuesta puede agregar engine, Session y Base al archivo connect_connector.py.

El archivo final se encuentra en la sección APÉNDICE de este codelab. De lo contrario, haz los cambios correspondientes de forma manual.

También puedes probar varias instrucciones para ver la posible variación de las respuestas de Duet AI.

Restablece el historial de chat de Duet AI haciendo clic en el ícono de papelera f574ca2c1e114856.png en la parte superior de la barra lateral de Duet AI.

Actualiza data_model.py

Debes usar el motor que creaste en el paso anterior (en el archivo connect_connector.py) para crear una tabla en la base de datos de Cloud SQL.

Borra el historial de chat de Duet AI. Abre el archivo data_model.py. Prueba la siguiente instrucción.

Consigna 1: En la función principal, importa y usa el motor de connect_connector.py.

2e768c9b6c523b9a.png

Deberías ver la respuesta que importa engine desde connect_connector (para Cloud SQL). create_table usa ese motor (en lugar de la base de datos local sqlite predeterminada).

Actualiza el archivo data_model.py.

El archivo final se encuentra en la sección APÉNDICE de este codelab. De lo contrario, haz los cambios correspondientes de forma manual.

También puedes probar varias instrucciones para ver las diferentes respuestas de Duet AI.

Restablece el historial de chat de Duet AI haciendo clic en el ícono de papelera f574ca2c1e114856.png en la parte superior de la barra lateral de Duet AI.

requirements.txt

Crea un archivo requirements.txt para la aplicación.

Abre connect_connector.py y el archivo data_model.py, e ingresa el siguiente mensaje.

Consigna 1: Genera un archivo de requisitos de pip para este modelo de datos y servicio

Consigna 2: Genera un archivo de requisitos de pip para este modelo de datos y servicio con las versiones más recientes

69fae373bc5c6a18.png

Verifica que los nombres y las versiones sean correctos. Por ejemplo, en la respuesta anterior, el nombre y la versión de google-cloud-sql-connecter son incorrectos. Corrige las versiones de forma manual y crea un archivo requirements.txt similar al siguiente:

cloud-sql-python-connector==1.2.4
sqlalchemy==1.4.36
pg8000==1.22.0

En la terminal de comandos, ejecuta lo siguiente:

pip3 install -r requirements.txt

Restablece el historial de chat de Duet AI haciendo clic en el ícono de papelera f574ca2c1e114856.png en la parte superior de la barra lateral de Duet AI.

Crea una tabla de paquetes en Cloud SQL

Configura las variables de entorno para el conector de bases de datos de Cloud SQL.

export INSTANCE_NAME=$(gcloud sql instances list --format='value(name)')
export INSTANCE_CONNECTION_NAME=$(gcloud sql instances describe ${INSTANCE_NAME} --format="value(connectionName)")
export DB_USER=evolution
export DB_PASS=evolution
export DB_NAME=product_details

Ahora ejecuta data_model.py.

python data_model.py

El resultado es similar al siguiente (verifica el código para ver qué es lo que realmente se espera):

Tables created successfully.

Conéctate a la instancia de Cloud SQL y verifica que la base de datos se haya creado.

gcloud sql connect ${INSTANCE_NAME} --user=evolution --database=product_details

Después de ingresar la contraseña (también llamada evolution), obtén las tablas.

product_details=> \dt

El resultado es similar a este:

           List of relations
 Schema |   Name   | Type  |   Owner   
--------+----------+-------+-----------
 public | packages | table | evolution
(1 row)

También puedes verificar el modelo de datos y los detalles de la tabla.

product_details=> \d+ packages

El resultado es similar a este:

                                                                        Table "public.packages"
            Column             |       Type        | Collation | Nullable |               Default                | Storage  | Compression | Stats target | Description 
-------------------------------+-------------------+-----------+----------+--------------------------------------+----------+-------------+--------------+-------------
 id                            | integer           |           | not null | nextval('packages_id_seq'::regclass) | plain    |             |              | 
 product_id                    | integer           |           | not null |                                      | plain    |             |              | 
 height                        | double precision  |           | not null |                                      | plain    |             |              | 
 width                         | double precision  |           | not null |                                      | plain    |             |              | 
 depth                         | double precision  |           | not null |                                      | plain    |             |              | 
 weight                        | double precision  |           | not null |                                      | plain    |             |              | 
 special_handling_instructions | character varying |           |          |                                      | extended |             |              | 
Indexes:
    "packages_pkey" PRIMARY KEY, btree (id)
Access method: heap

Escribe \q para salir de Cloud SQL.

db_init.py

A continuación, agreguemos algunos datos de muestra a la tabla packages.

Borra el historial de chat de Duet AI. Con el archivo data_model.py abierto, prueba los siguientes mensajes.

Consigna 1: Genera una función que cree 10 filas de paquetes de muestra y las confirme en la tabla de paquetes

Consigna 2: Con la sesión de connect_connector, genera una función que cree 10 filas de paquetes de muestra y las confirme en la tabla de paquetes

34a9afc5f04ba5.png

Usa OPEN a4c7ed6d845df343.png para el código en un flujo de trabajo de archivo nuevo, como antes. Guarda el código en un archivo llamado db_init.py.

El archivo final se encuentra en la sección APÉNDICE de este codelab. De lo contrario, haz los cambios correspondientes de forma manual.

También puedes probar varias instrucciones para ver las diferentes respuestas de Duet AI.

Restablece el historial de chat de Duet AI haciendo clic en el ícono de papelera f574ca2c1e114856.png en la parte superior de la barra lateral de Duet AI.

Crea datos de paquetes de muestra

Ejecuta db_init.py desde la línea de comandos.

python db_init.py

El resultado es similar a este:

Packages created successfully.

Vuelve a conectarte a la instancia de Cloud SQL y verifica que los datos de muestra se hayan agregado a la tabla de paquetes.

Conéctate a la instancia de Cloud SQL y verifica que la base de datos se haya creado.

gcloud sql connect ${INSTANCE_NAME} --user=evolution --database=product_details

Después de ingresar la contraseña (también llamada evolution), obtén todos los datos de la tabla de paquetes.

product_details=> SELECT * FROM packages;

El resultado es similar a este:

 id | product_id | height | width | depth | weight |   special_handling_instructions   
----+------------+--------+-------+-------+--------+-----------------------------------
  1 |          0 |     10 |    10 |    10 |     10 | No special handling instructions.
  2 |          1 |     10 |    10 |    10 |     10 | No special handling instructions.
  3 |          2 |     10 |    10 |    10 |     10 | No special handling instructions.
  4 |          3 |     10 |    10 |    10 |     10 | No special handling instructions.
  5 |          4 |     10 |    10 |    10 |     10 | No special handling instructions.
  6 |          5 |     10 |    10 |    10 |     10 | No special handling instructions.
  7 |          6 |     10 |    10 |    10 |     10 | No special handling instructions.
  8 |          7 |     10 |    10 |    10 |     10 | No special handling instructions.
  9 |          8 |     10 |    10 |    10 |     10 | No special handling instructions.
 10 |          9 |     10 |    10 |    10 |     10 | No special handling instructions.
(10 rows)

Escribe \q para salir de Cloud SQL.

main.py

Con los archivos data_model.py, package-service.yaml y connect_connector.py abiertos, crea un main.py para la aplicación.

Consigna 1: Usa la biblioteca de flask de Python: crea una implementación que use extremos de REST HTTP para este servicio

Consigna 2: Usa la biblioteca de flask de Python: crea una implementación que use extremos de REST HTTP para este servicio. importar y usar SessionMaker de connect_conector.py para los datos de paquetes

Consigna 3: Usa la biblioteca de flask de Python: crea una implementación que use extremos de REST HTTP para este servicio. importar y usar el paquete de data_model.py y SessionMaker de connect_conector.py a los datos de los paquetes

Consigna 4: Usa la biblioteca de flask de Python: crea una implementación que use extremos de REST HTTP para este servicio. importar y usar un paquete de data_model.py y SessionMaker de connect_conector.py a datos de paquetes. Usa la IP del host 0.0.0.0 para app.run

6d794fc52a90e6ae.png

Actualiza los requisitos de main.py.

Mensaje: Crea un archivo de requisitos para main.py

1cc0b318d2d4ca2f.png

Agrega esto al archivo requirements.txt. Asegúrate de usar la versión 3.0.0 de Flask.

Usa OPEN a4c7ed6d845df343.png para el código en un flujo de trabajo de archivo nuevo, como antes. Guarda el código en un archivo llamado main.py.

El archivo final se encuentra en la sección APÉNDICE de este codelab. De lo contrario, haz los cambios correspondientes de forma manual.

Restablece el historial de chat de Duet AI haciendo clic en el ícono de papelera f574ca2c1e114856.png en la parte superior de la barra lateral de Duet AI.

5. Prueba y ejecuta la aplicación

Instala los requisitos.

pip3 install -r requirements.txt

Ejecuta main.py.

python main.py

El resultado es similar a este:

 * Serving Flask app 'main'
 * Debug mode: off
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:5000
 * Running on http://10.88.0.3:5000
Press CTRL+C to quit

Desde una segunda terminal, prueba el extremo /packages/<product_id>.

curl localhost:5000/packages/1

El resultado es similar a este:

{"depth":10.0,"height":10.0,"special_handling_instructions":"No special handling instructions.","weight":10.0,"width":10.0}

También puedes probar cualquier otro ID del producto en tus datos de muestra.

Ingresa CTRL_C para salir del contenedor de Docker que se está ejecutando en la terminal.

Cómo generar pruebas de unidades

Con el archivo main.py abierto, genera pruebas de unidades.

Consigna 1: Genera pruebas de unidades.

e861e5b63e1b2657.png

Usa OPEN a4c7ed6d845df343.png para el código en un flujo de trabajo de archivo nuevo, como antes. Guarda el código en un archivo llamado test.py.

En la función test_get_package, se debe definir un product_id. Puedes agregarla de forma manual.

El archivo final se encuentra en la sección APÉNDICE de este codelab. De lo contrario, haz los cambios correspondientes de forma manual.

Restablece el historial de chat de Duet AI haciendo clic en el ícono de papelera f574ca2c1e114856.png en la parte superior de la barra lateral de Duet AI.

Ejecutar pruebas de unidades

Ejecuta la prueba de unidades.

python test.py

El resultado es similar a este:

.
----------------------------------------------------------------------
Ran 1 test in 1.061s

OK

Cierra todos los archivos en el Editor de Cloud Shell y borra el historial de chat haciendo clic en el ícono de papelera 1ecccfe10d6c540.png ubicado en la barra de estado superior.

Dockerfile

Crea un Dockerfile para esta aplicación.

Abre main.py y prueba los siguientes mensajes.

Consigna 1: Genera un Dockerfile para esta aplicación.

Consigna 2: Genera un Dockerfile para esta aplicación. Copia todos los archivos en el contenedor.

8c473caea437a5c3.png

También debes configurar ENVARS para INSTANCE_CONNECTION_NAME, DB_USER, DB_PASS y DB_NAME. Puedes hacerlo de forma manual. Tu Dockerfile debería verse de la siguiente manera:

FROM python:3.10-slim

WORKDIR /app

COPY . ./

RUN pip install -r requirements.txt

# Add these manually for your project
ENV INSTANCE_CONNECTION_NAME=YOUR_INSTANCE_CONNECTION_NAME
ENV DB_USER=evolution
ENV DB_PASS=evolution
ENV DB_NAME=product_details

CMD ["python", "main.py"]

Usa OPEN a4c7ed6d845df343.png para el código en un flujo de trabajo de archivo nuevo, como antes. Guarda el código en un archivo llamado Dockerfile.

El archivo final se encuentra en la sección APÉNDICE de este codelab. De lo contrario, haz los cambios correspondientes de forma manual.

Ejecución local de la aplicación

Con el Dockerfile abierto, prueba el siguiente mensaje.

Consigna 1: ¿Cómo ejecuto de forma local un contenedor con este Dockerfile?

570fd5c296ca8c83.png

Sigue las instrucciones.

# Build
docker build -t shipping .
# And run
docker run -p 5000:5000 -it shipping

El resultado es similar a este:

 * Serving Flask app 'main'
 * Debug mode: off
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:5000
 * Running on http://172.17.0.2:5000
Press CTRL+C to quit

Desde una segunda ventana de la terminal, accede al contenedor.

curl localhost:5000/packages/1

El resultado es similar a este:

{"depth":10.0,"height":10.0,"special_handling_instructions":"No special handling instructions.","weight":10.0,"width":10.0}

La aplicación alojada en contenedores funciona.

Ingresa CTRL_C para salir del contenedor de Docker que se está ejecutando en la terminal.

Compila una imagen de contenedor en Artifact Registry

Compila la imagen de contenedor y envíala a Artifact Registry.

cd ~/shipping
gcloud auth configure-docker us-central1-docker.pkg.dev
docker build -t us-central1-docker.pkg.dev/${PROJECT_ID}/shipping/shipping .
docker push us-central1-docker.pkg.dev/${PROJECT_ID}/shipping/shipping

El contenedor de la aplicación ahora se encuentra en us-central1-docker.pkg.dev/${PROJECT_ID}/shipping/shipping, que se puede implementar en GKE.

6. Implementa la aplicación en el clúster de GKE

Se creó un clúster de GKE Autopilot cuando compilaste los recursos de GCP para este taller. Conéctate al clúster de GKE.

gcloud container clusters get-credentials gke1 \
    --region=us-central1

Anotar la cuenta de servicio predeterminada de Kubernetes con la cuenta de servicio de Google

kubectl annotate serviceaccount default iam.gke.io/gcp-service-account=cloudsqlsa@${PROJECT_ID}.iam.gserviceaccount.com

El resultado es similar a este:

serviceaccount/default annotated

Prepara y aplica el archivo k8s.yaml.

cp ~/duetaidev/k8s.yaml_tmpl ~/shipping/.
export INSTANCE_NAME=$(gcloud sql instances list --format='value(name)')
export INSTANCE_CONNECTION_NAME=$(gcloud sql instances describe ${INSTANCE_NAME} --format="value(connectionName)")
export IMAGE_REPO=us-central1-docker.pkg.dev/${PROJECT_ID}/shipping/shipping
envsubst < ~/shipping/k8s.yaml_tmpl > k8s.yaml
kubectl apply -f k8s.yaml

El resultado es similar a este:

deployment.apps/shipping created
service/shipping created

Espera hasta que los Pods se estén ejecutando y el Service tenga asignada una dirección IP del balanceador de cargas externo.

kubectl get pods
kubectl get service shipping

El resultado es similar a este:

# kubectl get pods
NAME                      READY   STATUS    RESTARTS   AGE
shipping-f5d6f8d5-56cvk   1/1     Running   0          4m47s
shipping-f5d6f8d5-cj4vv   1/1     Running   0          4m48s
shipping-f5d6f8d5-rrdj2   1/1     Running   0          4m47s

# kubectl get service shipping
NAME       TYPE           CLUSTER-IP       EXTERNAL-IP    PORT(S)        AGE
shipping   LoadBalancer   34.118.225.125   34.16.39.182   80:30076/TCP   5m41s

Para los clústeres de GKE Autopilot, espera unos minutos hasta que los recursos estén listos.

Accede al servicio a través de la dirección EXTERNAL-IP.

export EXTERNAL_IP=$(kubectl get svc shipping --output jsonpath='{.status.loadBalancer.ingress[0].ip}')
curl http://${EXTERNAL_IP}/packages/1

El resultado es similar a este:

{"depth":10.0,"height":10.0,"special_handling_instructions":"No special handling instructions.","weight":10.0,"width":10.0}

7. Crédito adicional: Solucionar problemas de la solicitud

Quita el rol de IAM del cliente de Cloud SQL de la cuenta de servicio cloudsqlsa. Esto provoca un error de conexión a la base de datos de Cloud SQL.

gcloud projects remove-iam-policy-binding ${PROJECT_ID} \
    --member="serviceAccount:cloudsqlsa@${PROJECT_ID}.iam.gserviceaccount.com" \
    --role="roles/cloudsql.client"

Reinicia el Pod de envío.

kubectl rollout restart deployment shipping

Después de que se reinicie el Pod, intenta acceder de nuevo al servicio shipping.

export EXTERNAL_IP=$(kubectl get svc shipping --output jsonpath='{.status.loadBalancer.ingress[0].ip}')
curl http://${EXTERNAL_IP}/packages/1 

El resultado es similar a este:

...
<title>500 Internal Server Error</title>
<h1>Internal Server Error</h1>
<p>The server encountered an internal error and was unable to complete your request. Either the server is overloaded or there is an error in the application.</p>

Para inspeccionar los registros, dirígete a Kubernetes Engine > Cargas de trabajo

d225b1916c829167.png

Haz clic en la implementación de shipping y, luego, en la pestaña Registros.

1d0459141483d6a7.png

Haz clic en el ícono Ver en el Explorador de registros df8b9d19a9fe4c73.png a la derecha de la barra de estado. Se abrirá una nueva ventana del Explorador de registros.

e86d1c265e176bc4.png

Haz clic en una de las entradas de error Traceback y, luego, en Explain this Log Entry.

d6af045cf03008bc.png

Puedes leer la explicación del error.

A continuación, hagamos que Duet AI te ayude a solucionar el error.

Prueba la siguiente instrucción.

Consigna 1: Ayúdame a solucionar este error

9288dd6045369167.png

Ingresa el mensaje de error en el prompt.

Consigna 2: Prohibido: La principal de IAM autenticada no parece tener autorización para realizar solicitudes a la API. Verifica “API de Cloud SQL Admin” esté habilitada en tu proyecto de GCP y el “cliente de Cloud SQL” se le otorgó el rol a la principal de IAM

f1e64fbdc435d31c.png

y, luego,

Consigna 3: ¿Cómo asigno el rol de cliente de Cloud SQL a una cuenta de servicio de Google con gcloud?

bb8926b995a8875c.png

Asigna la función de cliente de Cloud SQL a cloudsqlsa.

gcloud projects add-iam-policy-binding ${PROJECT_ID} \
    --member="serviceAccount:cloudsqlsa@${PROJECT_ID}.iam.gserviceaccount.com" \
    --role="roles/cloudsql.client"

Espera unos minutos y vuelve a intentar acceder a la aplicación.

export EXTERNAL_IP=$(kubectl get svc shipping --output jsonpath='{.status.loadBalancer.ingress[0].ip}')
curl http://${EXTERNAL_IP}/packages/1

El resultado es similar a este:

{"depth":10.0,"height":10.0,"special_handling_instructions":"No special handling instructions.","weight":10.0,"width":10.0}

Usaste correctamente Duet AI en Cloud Logging, el Explorador de registros y la función Explicación de registros para solucionar el problema.

8. Conclusión

¡Felicitaciones! Completaste correctamente este codelab.

En este codelab, aprendiste lo siguiente:

  1. Activa Duet AI en tu proyecto de GCP y configúralo para usarlo en un IDE y la consola de Cloud.
  2. Usa Duet AI para generar, completar y explicar código.
  3. Usa Duet AI para explicar y solucionar un problema de una aplicación.
  4. Funciones de Duet AI como chat con IDE y chat de varios turnos, generación de código intercalado en comparación con el chat, acciones inteligentes como la explicación del código y el reconocimiento de solicitudes, y mucho más.

9. Apéndice

package-service.yaml

swagger: "2.0"
info:
 title: Shipping and Package Information API
 description: This API provides information about shipping and packages.
 version: 1.0.0
host: shipping.googleapis.com
schemes:
 - https
produces:
 - application/json
paths:
 /packages/{product_id}:
   get:
     summary: Get information about a package
     description: This method returns information about a package, including its height, width, depth, weight, and any special handling instructions.
     parameters:
       - name: product_id
         in: path
         required: true
         type: integer
         format: int64
     responses:
       "200":
         description: A successful response
         schema:
           type: object
           properties:
             height:
               type: integer
               format: int64
             width:
               type: integer
               format: int64
             depth:
               type: integer
               format: int64
             weight:
               type: integer
               format: int64
             special_handling_instructions:
               type: string
       "404":
         description: The product_id was not found

data_model.py

from sqlalchemy import Column, Integer, String, Float
from sqlalchemy.ext.declarative import declarative_base

from connect_connector import engine

Base = declarative_base()

class Package(Base):
    __tablename__ = 'packages'

    id = Column(Integer, primary_key=True)
    product_id = Column(Integer, nullable=False)
    height = Column(Float, nullable=False)
    width = Column(Float, nullable=False)
    depth = Column(Float, nullable=False)
    weight = Column(Float, nullable=False)
    special_handling_instructions = Column(String, nullable=True)

def create_tables():
    Base.metadata.create_all(engine)

if __name__ == '__main__':
    create_tables()

    print('Tables created successfully.')

connect_connector.py

import os

from google.cloud.sql.connector import Connector, IPTypes
import sqlalchemy

# You may need to manually import pg8000 and Base as follows
import pg8000
from sqlalchemy.ext.declarative import declarative_base


def connect_with_connector() -> sqlalchemy.engine.base.Engine:
   """Initializes a connection pool for a Cloud SQL instance of Postgres."""
   # Note: Saving credentials in environment variables is convenient, but not
   # secure - consider a more secure solution such as
   # Cloud Secret Manager (https://cloud.google.com/secret-manager) to help
   # keep secrets safe.
   instance_connection_name = os.environ[
       "INSTANCE_CONNECTION_NAME"
   ]  # e.g. 'project:region:instance'
   db_user = os.environ["DB_USER"]  # e.g. 'my-database-user'
   db_pass = os.environ["DB_PASS"]  # e.g. 'my-database-password'
   db_name = os.environ["DB_NAME"]  # e.g. 'my-database'

   ip_type = IPTypes.PRIVATE if os.environ.get("PRIVATE_IP") else IPTypes.PUBLIC

   connector = Connector()

   def getconn() -> sqlalchemy.engine.base.Engine:
       conn: sqlalchemy.engine.base.Engine = connector.connect(
           instance_connection_name,
           "pg8000",
           user=db_user,
           password=db_pass,
           db=db_name,
           ip_type=ip_type,
       )
       return conn

   pool = sqlalchemy.create_engine(
       "postgresql+pg8000://",
       creator=getconn,
       # ...
   )
   return pool

# Create a connection pool
engine = connect_with_connector()

# Create a sessionmaker class to create new sessions
SessionMaker = sqlalchemy.orm.sessionmaker(bind=engine)

# Create a Base class for ORM
# You may need to manually fix the following
Base = declarative_base()

db_init.py

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from connect_connector import engine

from data_model import Package

def create_packages():
    # Create a session
    session = sessionmaker(bind=engine)()

    # Create 10 sample packages
    for i in range(10):
        package = Package(
            product_id=i,
            height=10.0,
            width=10.0,
            depth=10.0,
            weight=10.0,
            special_handling_instructions="No special handling instructions."
        )

        # Add the package to the session
        session.add(package)

    # Commit the changes
    session.commit()

if __name__ == '__main__':
    create_packages()

    print('Packages created successfully.')

main.py

from flask import Flask, request, jsonify

from data_model import Package
from connect_connector import SessionMaker

app = Flask(__name__)

session_maker = SessionMaker()

@app.route("/packages/<int:product_id>", methods=["GET"])
def get_package(product_id):
  """Get information about a package."""

  session = session_maker

  package = session.query(Package).filter(Package.product_id == product_id).first()

  if package is None:
    return jsonify({"message": "Package not found."}), 404

  return jsonify(
      {
          "height": package.height,
          "width": package.width,
          "depth": package.depth,
          "weight": package.weight,
          "special_handling_instructions": package.special_handling_instructions,
      }
  ), 200

if __name__ == "__main__":
  app.run(host="0.0.0.0")

test.py

import unittest

from data_model import Package
from connect_connector import SessionMaker

from main import app

class TestPackage(unittest.TestCase):

    def setUp(self):
        self.session_maker = SessionMaker()

    def tearDown(self):
        self.session_maker.close()

    def test_get_package(self):
        """Test the `get_package()` function."""

        package = Package(
        product_id=11, # Ensure that the product_id different from the sample data
        height=10,
        width=10,
        depth=10,
        weight=10,
        special_handling_instructions="Fragile",
        )

        session = self.session_maker

        session.add(package)
        session.commit()

        response = app.test_client().get("/packages/11")

        self.assertEqual(response.status_code, 200)

        self.assertEqual(
            response.json,
            {
                "height": 10,
                "width": 10,
                "depth": 10,
                "weight": 10,
                "special_handling_instructions": "Fragile",
            },
        )

if __name__ == "__main__":
    unittest.main()

requirements.txt

cloud-sql-python-connector==1.2.4
sqlalchemy==1.4.36
pg8000==1.22.0
Flask==3.0.0
gunicorn==20.1.0
psycopg2-binary==2.9.3

Dockerfile

FROM python:3.10-slim

WORKDIR /app

COPY . ./

RUN pip install -r requirements.txt

# Add these manually for your project
ENV INSTANCE_CONNECTION_NAME=YOUR_INSTANCE_CONNECTION_NAME
ENV DB_USER=evolution
ENV DB_PASS=evolution
ENV DB_NAME=product_details

CMD ["python", "main.py"]