Compila e implementa una app de LangChain en Cloud Run

1. Descripción general

En este codelab, aprenderás a implementar una app de LangChain que use Gemini para permitirte hacer preguntas sobre las notas de lanzamiento de Cloud Run.

A continuación, se muestra un ejemplo de cómo funciona la app: si haces la pregunta "¿Puedo activar un bucket de Cloud Storage como volumen en Cloud Run?", la app responde con "Sí, desde el 19 de enero de 2024" o algo similar.

Para devolver respuestas fundamentadas, la app primero recupera notas de la versión de Cloud Run que son similares a la pregunta y, luego, le indica a Gemini tanto la pregunta como las notas de la versión. (este es un patrón comúnmente conocido como RAV). Este es un diagrama que muestra la arquitectura de la app:

2. Configuración y requisitos

Primero, asegúrate de que tu entorno de desarrollo esté configurado correctamente.

  • Necesitarás un proyecto de Google Cloud para implementar los recursos que necesitarás para la app.
  • Para implementar la app, debes tener gcloud instalado en tu máquina local, autenticado y configurado para usar el proyecto.
    • gcloud auth login
    • gcloud config set project
  • Si deseas ejecutar la aplicación en tu máquina local, lo que recomiendo, debes asegurarte de que tus credenciales predeterminadas de la aplicación estén configuradas correctamente, incluida la configuración del proyecto de cuota.
    • gcloud auth application-default login
    • gcloud auth application-default set-quota-project
  • También debes tener instalado el siguiente software:
    • Python (se requiere la versión 3.11 o una posterior)
    • La CLI de LangChain
    • poetry para la administración de dependencias
    • pipx para instalar y ejecutar la CLI de LangChain y poetry en entornos virtuales aislados

En este blog encontrarás ayuda para comenzar a instalar las herramientas necesarias para esta explicación.

Cloud Workstations

En lugar de tu máquina local, también puedes usar estaciones de trabajo de Cloud en Google Cloud. Ten en cuenta que, a partir de abril de 2024, se ejecuta una versión de Python anterior a la 3.11, por lo que es posible que debas actualizar Python antes de comenzar.

Habilita las APIs de Cloud

Primero, ejecuta el siguiente comando para asegurarte de que configuraste el proyecto de Google Cloud correcto:

gcloud config list project

Si no se muestra el proyecto correcto, puedes configurarlo con este comando:

gcloud config set project <PROJECT_ID>

Ahora, habilita las siguientes APIs:

gcloud services enable \
  bigquery.googleapis.com \
  sqladmin.googleapis.com \
  aiplatform.googleapis.com \
  cloudresourcemanager.googleapis.com \
  artifactregistry.googleapis.com \
  cloudbuild.googleapis.com \
  run.googleapis.com \
  secretmanager.googleapis.com

Choose a region

Google Cloud se encuentra disponible en muchas ubicaciones del mundo, y debes elegir una para implementar los recursos que usarás en este lab. Establece la región como una variable de entorno en tu shell (los comandos posteriores usan esta variable):

export REGION=us-central1

3. Crea la instancia de la base de datos de vectores

Una parte clave de esta app es recuperar las notas de la versión que son relevantes para la pregunta del usuario. Para que sea más concreto, si haces una pregunta sobre Cloud Storage, debes agregar la siguiente nota de la versión a la instrucción:

Puedes usar incorporaciones de texto y una base de datos de vectores para encontrar notas de lanzamiento semánticamente similares.

Te mostraré cómo usar PostgreSQL en Cloud SQL como una base de datos vectorial. La creación de una instancia nueva de Cloud SQL lleva tiempo, así que hagámoslo ahora.

gcloud sql instances create sql-instance \
  --database-version POSTGRES_14 \
  --tier db-f1-micro \
  --region $REGION

Puedes permitir que se ejecute este comando y continuar con los siguientes pasos. En algún momento, deberás crear una base de datos y agregar un usuario, pero no perdamos tiempo mirando el ícono giratorio.

PostgreSQL es un servidor de base de datos relacional y cada instancia nueva de Cloud SQL tiene la extensión pgvector instalada de forma predeterminada, lo que significa que también puedes usarla como base de datos de vectores.

4. Crea una estructura para la app de LangChain

Para continuar, deberás tener instalada la CLI de LangChain y poetry para administrar las dependencias. A continuación, te indicamos cómo instalarlos con pipx:

pipx install langchain-cli poetry

Crea un andamiaje para la app de LangChain con el siguiente comando. Cuando se te solicite, asigna el nombre run-rag a la carpeta y presiona Intro para omitir la instalación de paquetes:

langchain app new

Cambia al directorio run-rag y, luego, instala las dependencias.

poetry install

Acabas de crear una app de LangServe. LangServe une FastAPI a una cadena de LangChain. Viene con un campo de pruebas integrado que facilita el envío de instrucciones y la inspección de los resultados, incluidos todos los pasos intermedios. Te sugiero que abras la carpeta run-rag en tu editor y explores su contenido.

5. Crea el trabajo de indexación

Antes de comenzar a armar la app web, asegurémonos de que las notas de la versión de Cloud Run estén indexadas en la base de datos de Cloud SQL. En esta sección, crearás un trabajo de indexación que haga lo siguiente:

La tarea de indexación toma las notas de la versión, las convierte en vectores con un modelo de incorporación de texto y las almacena en una base de datos de vectores. Esto permite buscar notas de lanzamiento similares de manera eficiente en función de su significado semántico.

En la carpeta run-rag/app, crea un archivo indexer.py con el siguiente contenido:

import os
from google.cloud.sql.connector import Connector
import pg8000
from langchain_community.vectorstores.pgvector import PGVector
from langchain_google_vertexai import VertexAIEmbeddings
from google.cloud import bigquery


# Retrieve all Cloud Run release notes from BigQuery 
client = bigquery.Client()
query = """
SELECT
  CONCAT(FORMAT_DATE("%B %d, %Y", published_at), ": ", description) AS release_note
FROM `bigquery-public-data.google_cloud_release_notes.release_notes`
WHERE product_name= "Cloud Run"
ORDER BY published_at DESC
"""
rows = client.query(query)

print(f"Number of release notes retrieved: {rows.result().total_rows}")

# Set up a PGVector instance 
connector = Connector()

def getconn() -> pg8000.dbapi.Connection:
    conn: pg8000.dbapi.Connection = connector.connect(
        os.getenv("DB_INSTANCE_NAME", ""),
        "pg8000",
        user=os.getenv("DB_USER", ""),
        password=os.getenv("DB_PASS", ""),
        db=os.getenv("DB_NAME", ""),
    )
    return conn

store = PGVector(
    connection_string="postgresql+pg8000://",
    use_jsonb=True,
    engine_args=dict(
        creator=getconn,
    ),
    embedding_function=VertexAIEmbeddings(
        model_name="textembedding-gecko@003"
    ),
    pre_delete_collection=True  
)

# Save all release notes into the Cloud SQL database
texts = list(row["release_note"] for row in rows)
ids = store.add_texts(texts)

print(f"Done saving: {len(ids)} release notes")

Agrega las dependencias necesarias:

poetry add \
  "cloud-sql-python-connector[pg8000]" \
  langchain-google-vertexai==1.0.5 \
  langchain-community==0.2.5 \
  pgvector

Crea la base de datos y un usuario

Crea una base de datos release-notes en la instancia de Cloud SQL sql-instance:

gcloud sql databases create release-notes --instance sql-instance

Crea un usuario de base de datos llamado app:

gcloud sql users create app --instance sql-instance --password "myprecious"

Implementa y ejecuta el trabajo de indexación

Ahora, implementa y ejecuta el trabajo:

DB_INSTANCE_NAME=$(gcloud sql instances describe sql-instance --format="value(connectionName)")

gcloud run jobs deploy indexer \
  --source . \
  --command python \
  --args app/indexer.py \
  --set-env-vars=DB_INSTANCE_NAME=$DB_INSTANCE_NAME \
  --set-env-vars=DB_USER=app \
  --set-env-vars=DB_NAME=release-notes \
  --set-env-vars=DB_PASS=myprecious \
  --region=$REGION \
  --execute-now

Es un comando largo. Veamos qué sucede:

El primer comando recupera el nombre de la conexión (un ID único con el formato project:region:instance) y lo establece como la variable de entorno DB_INSTANCE_NAME.

El segundo comando implementa el trabajo de Cloud Run. Esto es lo que hacen las marcas:

  • --source .: Especifica que el código fuente de la tarea se encuentra en el directorio de trabajo actual (el directorio en el que ejecutas el comando).
  • --command python: Establece el comando que se ejecutará dentro del contenedor. En este caso, es para ejecutar Python.
  • --args app/indexer.py: Proporciona los argumentos al comando python. Esto le indica que ejecute la secuencia de comandos indexer.py en el directorio de la app.
  • --set-env-vars: Establece las variables de entorno a las que puede acceder la secuencia de comandos de Python durante la ejecución.
  • --region=$REGION: Especifica la región en la que se debe implementar el trabajo.
  • --execute-now: Le indica a Cloud Run que inicie el trabajo inmediatamente después de que se implemente.

Para verificar que el trabajo se haya completado correctamente, puedes hacer lo siguiente:

  • Lee los registros de la ejecución del trabajo a través de la consola web. Se debería mostrar el mensaje "Se guardó todo: notas de la versión xx" (donde xxx es el número de notas de la versión guardadas).
  • También puedes navegar a la instancia de Cloud SQL en la consola web y usar Cloud SQL Studio para consultar la cantidad de registros en la tabla langchain_pg_embedding.

6. Escribe la aplicación web

Abre el archivo app/server.py en tu editor. Verás una línea que dice lo siguiente:

# Edit this to add the chain you want to add

Reemplaza ese comentario por el siguiente fragmento:

# (1) Initialize VectorStore
connector = Connector()


def getconn() -> pg8000.dbapi.Connection:
    conn: pg8000.dbapi.Connection = connector.connect(
        os.getenv("DB_INSTANCE_NAME", ""),
        "pg8000",
        user=os.getenv("DB_USER", ""),
        password=os.getenv("DB_PASS", ""),
        db=os.getenv("DB_NAME", ""),
    )
    return conn


vectorstore = PGVector(
    connection_string="postgresql+pg8000://",
    use_jsonb=True,
    engine_args=dict(
        creator=getconn,
    ),
    embedding_function=VertexAIEmbeddings(
        model_name="textembedding-gecko@003"
    )
)

# (2) Build retriever


def concatenate_docs(docs):
    return "\n\n".join(doc.page_content for doc in docs)


notes_retriever = vectorstore.as_retriever() | concatenate_docs

# (3) Create prompt template
prompt_template = PromptTemplate.from_template(
    """You are a Cloud Run expert answering questions. 
Use the retrieved release notes to answer questions
Give a concise answer, and if you are unsure of the answer, just say so.

Release notes: {notes}

Here is your question: {query}
Your answer: """)

# (4) Initialize LLM
llm = VertexAI(
    model_name="gemini-1.0-pro-001",
    temperature=0.2,
    max_output_tokens=100,
    top_k=40,
    top_p=0.95
)

# (5) Chain everything together
chain = (
    RunnableParallel({
        "notes": notes_retriever,
        "query": RunnablePassthrough()
    })
    | prompt_template
    | llm
    | StrOutputParser()
)

También debes agregar estas importaciones:

import pg8000
import os
from google.cloud.sql.connector import Connector
from langchain_google_vertexai import VertexAI
from langchain_google_vertexai import VertexAIEmbeddings
from langchain_core.runnables import RunnablePassthrough, RunnableParallel
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import PromptTemplate
from langchain_community.vectorstores.pgvector import PGVector

Por último, cambia la línea que dice "NotImplemented" a lo siguiente:

# add_routes(app, NotImplemented)
add_routes(app, chain)

7. Implementa la aplicación web en Cloud Run

Desde el directorio run-rag, usa el siguiente comando para implementar la app en Cloud Run:

DB_INSTANCE_NAME=$(gcloud sql instances describe sql-instance --format="value(connectionName)")

gcloud run deploy run-rag \
  --source . \
  --set-env-vars=DB_INSTANCE_NAME=$DB_INSTANCE_NAME \
  --set-env-vars=DB_USER=app \
  --set-env-vars=DB_NAME=release-notes \
  --set-env-vars=DB_PASS=myprecious \
  --region=$REGION \
  --allow-unauthenticated

Este comando realiza las siguientes acciones:

  • Sube el código fuente a Cloud Build
  • Ejecuta docker build.
  • Envía la imagen de contenedor resultante a Artifact Registry.
  • Crea un servicio de Cloud Run con la imagen de contenedor.

Cuando se completa el comando, se muestra una URL HTTPS en el dominio run.app. Esta es la URL pública de tu nuevo servicio de Cloud Run.

8. Explora el área de juegos

Abre la URL del servicio de Cloud Run y navega a /playground. Aparecerá un campo de texto. Úsala para hacer preguntas sobre las notas de la versión de Cloud Run, como aquí:

9. Felicitaciones

Compilaste e implementaste correctamente una app de LangChain en Cloud Run. ¡Bien hecho!

Estos son los conceptos clave:

  • Usar el framework de LangChain para compilar una aplicación de generación mejorada por recuperación (RAG)
  • Usar PostgreSQL en Cloud SQL como una base de datos vectorial con pgvector, que se instala de forma predeterminada en Cloud SQL
  • Ejecuta un trabajo de indexación más largo como trabajos de Cloud Run y una aplicación web como servicio de Cloud Run.
  • Une una cadena de LangChain en una aplicación de FastAPI con LangServe, lo que proporciona una interfaz conveniente para interactuar con tu app de RAG.

Limpia

Sigue estos pasos para evitar que se apliquen cargos a tu cuenta de Google Cloud Platform para los recursos que se usaron en este instructivo:

  • En la consola de Cloud, ve a la página Administrar recursos.
  • En la lista de proyectos, selecciona tu proyecto y haz clic en Borrar.
  • En el diálogo, escribe el ID del proyecto y, luego, haz clic en Cerrar para borrarlo.

Si quieres conservar el proyecto, asegúrate de borrar los siguientes recursos:

  • Instancia de Cloud SQL
  • Servicio de Cloud Run
  • Trabajo de Cloud Run