DEV Community

Cover image for Integrando Podcasts a Kiu: Cómo Construí un Agente Virtual Serverless con AWS Bedrock y Pinecone
Fernando Silva T
Fernando Silva T

Posted on

Integrando Podcasts a Kiu: Cómo Construí un Agente Virtual Serverless con AWS Bedrock y Pinecone

Introducción

Tuve la oportunidad de unirme al equipo de Kiu, un proyecto innovador que permite conocer charlas, meetups y eventos de la comunidad AWS. Descubrí Kiu en el AWS Community Day Chile 2024 y me fascinó desde el primer momento.

En febrero, Hazel Saenz me invitó a participar en el equipo. Mi primera tarea fue integrar contenido de podcasts dentro de Kiu, creando un subagente virtual capaz de consultar y procesar esta información para responder preguntas de forma precisa y eficiente.

En este blog compartiré cómo diseñé y desplegué una infraestructura serverless robusta usando AWS CDK en Python, integrando Pinecone para búsquedas vectoriales y Amazon Bedrock para inteligencia artificial. Además, te contaré los desafíos técnicos que enfrenté y cómo los superé para entregar una solución segura, escalable y automatizada.


El problema y el objetivo

Kiu es un agente virtual que responde consultas utilizando inteligencia artificial apoyada en bases de conocimiento actualizadas. Nuestro objetivo era incorporar el mundo de los podcasts para enriquecer la base y mejorar la experiencia del usuario.

Para esto, necesitábamos:

  • Extraer datos de podcasts desde una API externa y limpiarlos.
  • Almacenar esta información estructurada en un bucket S3, que funciona como base de conocimiento.
  • Usar Pinecone para búsquedas semánticas rápidas y eficientes.
  • Que el agente Bedrock acceda a esta base para responder consultas.
  • Automatizar la actualización periódica de los datos.

La solución técnica

Arquitectura Serverless con AWS CDK y Python

Me recomendaron implementar toda la infraestructura como código usando AWS CDK en Python, lo que facilitó mantener la solución versionada, segura y reproducible.

Los componentes clave fueron:

  • Amazon S3: Bucket cifrado y versionado para almacenar archivos JSON con la información procesada de los podcasts.
  • AWS Lambda: Función que consulta la API externa, procesa los datos limpiando y normalizando el texto, y actualiza el bucket S3.
  • Lambda Layers: Capas que contienen las librerías comunes (requests, unidecode) para optimizar despliegues.
  • AWS Secrets Manager: Almacena de forma segura las credenciales de la API externa, como URL y cookies.
  • AWS Scheduler: Programa la ejecución semanal automática de la función Lambda para mantener la base actualizada.
  • Amazon Bedrock: Configura el agente virtual Kiu para consultar la base de conocimiento.
  • Pinecone: Base de datos vectorial para búsquedas semánticas con un índice de dimensión 1024.

Flujo de datos

Diagrama

  1. El Scheduler activa semanalmente la función Lambda.
  2. Lambda obtiene las credenciales desde Secrets Manager.
  3. Lambda consulta y procesa los datos de la API externa.
  4. La información limpia y estructurada se almacena en S3.
  5. El agente Bedrock utiliza esta base para responder consultas.
  6. Pinecone facilita búsquedas semánticas rápidas y precisas.

Desafíos y aprendizajes

El proyecto implicó una curva de aprendizaje significativa y varios retos técnicos, que me ayudaron a crecer profesionalmente.

1. Aprender AWS CDK en Python

Aunque conocía CDK, tuve que aprender a usarlo correctamente en Python para alinearme con el equipo. Aquí un fragmento donde se define el bucket S3 y el secreto para las credenciales:

class PodcastKiuStack(Stack):
    def __init__(self, scope: Construct, construct_id: str, 
                 pinecone_api_key: str, pinecone_connection_url: str,
                 api_url: str, cookie: str, **kwargs) -> None:
        super().__init__(scope, construct_id, **kwargs)

        kb_bucket = s3.Bucket(
            self, "PodcastKnowledgeBase",
            versioned=True,
            encryption=s3.BucketEncryption.S3_MANAGED,
            removal_policy=RemovalPolicy.RETAIN
        )

        api_secret = secretsmanager.Secret(
            self, "PodcastApiSecret",
            secret_name="podcast-api-credentials",
            description="Credenciales para acceder a la API de podcasts",
            generate_secret_string=secretsmanager.SecretStringGenerator(
                secret_string_template=json.dumps({
                    "API_URL": api_url,
                    "COOKIE": cookie
                }),
                generate_string_key="dummy"
            )
        )
Enter fullscreen mode Exit fullscreen mode

2. Integrar Pinecone con Amazon Bedrock

Fue un reto configurar la knowledge base para usar Pinecone como base vectorial para búsquedas semánticas y configurar los roles necesarios para Bedrock:

bedrock_agent_role = iam.Role(
    self, "BedrockAgentRole",
    assumed_by=iam.ServicePrincipal("bedrock.amazonaws.com")
)

kb_bucket.grant_read(bedrock_agent_role)

knowledge_base = bedrock.CfnKnowledgeBase(
            self, "MyKnowledgeBaseKiuPodcast",
            name="KnowledgeBasePineconeKiuPodcast",
            description="Knowledge base for the podcast content with pinecone",
            role_arn=bedrock_role.role_arn,
            knowledge_base_configuration=bedrock.CfnKnowledgeBase.KnowledgeBaseConfigurationProperty(
                type="VECTOR",
                vector_knowledge_base_configuration=bedrock.CfnKnowledgeBase.VectorKnowledgeBaseConfigurationProperty(
                    embedding_model_arn="arn:aws:bedrock:us-east-1::foundation-model/amazon.titan-embed-text-v2:0"
                )
            ),
            storage_configuration=bedrock.CfnKnowledgeBase.StorageConfigurationProperty(
                type="PINECONE",
                pinecone_configuration=bedrock.CfnKnowledgeBase.PineconeConfigurationProperty(
                    connection_string=pinecone_connection_url,
                    credentials_secret_arn=pinecone_url_secret.secret_arn,
                    field_mapping=bedrock.CfnKnowledgeBase.PineconeFieldMappingProperty(
                        text_field="text",
                        metadata_field="metafield"
                    ),
                )
            )
        )
Enter fullscreen mode Exit fullscreen mode

3. Función Lambda para Procesamiento de Datos

Diseñé una función Lambda que consulta la API, procesa los datos limpiando texto con expresiones regulares y unidecode, y almacena la información en S3:

def lambda_handler(event, context):
    secrets_client = boto3.client('secretsmanager')
    secret_response = secrets_client.get_secret_value(SecretId=os.environ['API_SECRET_ARN'])
    secret = json.loads(secret_response['SecretString'])

    api_url = secret['API_URL']
    cookie = secret['COOKIE']

    response = requests.get(api_url, headers={'Cookie': cookie})
    podcasts = response.json()
    processed_data = []

    for podcast in podcasts:
        title = clean_text(podcast.get('title', ''))
        description = clean_text(podcast.get('description', ''))
        processed_data.append({
            'id': podcast.get('id'),
            'title': title,
            'description': description,
            'url': podcast.get('url'),
            'published_date': podcast.get('published_date'),
            'processed_timestamp': context.invoked_function_arn
        })

    s3_client = boto3.client('s3')
    s3_client.put_object(
        Bucket=os.environ['KB_BUCKET_NAME'],
        Key='processed_podcasts.json',
        Body=json.dumps(processed_data),
        ContentType='application/json'
    )
Enter fullscreen mode Exit fullscreen mode

4. Seguridad: Roles IAM y Políticas granulares

Configuré roles con permisos mínimos necesarios para Lambda y Bedrock, garantizando la seguridad del sistema:

lambda_role = iam.Role(
    self, "PodcastProcessorRole",
    assumed_by=iam.ServicePrincipal("lambda.amazonaws.com")
)

lambda_role.add_managed_policy(
    iam.ManagedPolicy.from_aws_managed_policy_name("service-role/AWSLambdaBasicExecutionRole")
)

lambda_role.add_to_policy(
    iam.PolicyStatement(
        actions=["s3:PutObject", "s3:GetObject", "s3:ListBucket"],
        resources=[kb_bucket.bucket_arn, f"{kb_bucket.bucket_arn}/*"]
    )
)

lambda_role.add_to_policy(
    iam.PolicyStatement(
        actions=["secretsmanager:GetSecretValue"],
        resources=[api_secret.secret_arn]
    )
)
Enter fullscreen mode Exit fullscreen mode

5. Pruebas Unitarias para asegurar calidad

Implementé pruebas unitarias que simulan la interacción con Secrets Manager, S3 y la API externa para validar el procesamiento de datos:

class TestLambdaFunction(unittest.TestCase):
    @patch('lambda_function.boto3.client')
    @patch('lambda_function.requests.get')
    def test_successful_processing(self, mock_get, mock_boto3_client):
        mock_context = MagicMock()
        mock_context.invoked_function_arn = 'test-arn'
        # mocks configurados...

        result = lambda_function.lambda_handler({}, mock_context)
        self.assertEqual(result['statusCode'], 200)
        self.assertIn('Procesados 1 podcasts', result['body'])
        mock_s3.put_object.assert_called_once()
Enter fullscreen mode Exit fullscreen mode

6. Emojis en los títulos de los episodios

Durante las pruebas unitarias descubrí un problema curioso: si el título de un pódcast incluía un emoji, los metadatos no se almacenaban correctamente y fallaba el envío a S3. Este detalle, aunque pequeño, tenía un impacto directo en la integridad del flujo de datos. Con la ayuda de Chris Gonzalez, logramos identificar el origen del error y validamos el manejo de emojis usando la tabla oficial de Unicode. Esta corrección fue clave para asegurar que todos los títulos —emoji incluidos— se procesaran de forma segura y consistente.


Conclusión

Este proyecto ha sido para mí una gran curva de aprendizaje, no solo en términos técnicos, sino también en el trabajo colaborativo. Integrar tecnologías como AWS CDK en Python, Pinecone y Amazon Bedrock me llevó a salir de mi zona de confort y a descubrir nuevas formas de construir soluciones serverless escalables y seguras.

Además, trabajar en equipo ha sido fundamental. Cada compañero aporta desde distintos frentes, y esa sinergia nos está permitiendo avanzar con velocidad y calidad. Un agradecimiento muy especial a Hazel Saenz, quien ha sido una guía constante, enseñándome mucho, apoyándome en cada error y alentándome a seguir mejorando. Su paciencia y conocimiento han sido claves para mi crecimiento dentro del proyecto y para la entrega exitosa de esta funcionalidad.

Estoy emocionado por lo que viene y orgulloso de ser parte de este equipo que está construyendo el futuro de Kiu con tecnologías punteras.


Recursos

Top comments (0)