Potencia tus hojas de cálculo con la API de los modelos Claude 3 (Anthropic) usando Apps Script

En un artículo reciente te contaba cómo invocar la API de Gemini desde Europa en tus hojas de cálculo de Google usando un pellizco de Apps Script. Hoy veremos cómo hacer algo similar usando Claude, que está propulsado por los sorprendentes modelos de lenguaje de gran tamaño (o LLM, como se suele decir) de Anthropic, una empresa de base tecnológica de San Francisco creada en 2021 por gente que salió rebotada de OpenAI cuando esta última ya le hacía ojitos descaradamente a Microsoft

Parece que todo queda en casa, aunque esa casa siempre está al otro lado del Atlántico 🙄.

Y es que no hay nada como los malos entendidos. O las creencias erróneas, más bien. Tenía asumido que Claude.ai  —el asistente conversacional— no estaba disponible en Europa (para variar).

Intento de inicio de sesión en Claude, se muestra un mensaje de error indicando que no está disponible por restricciones regionales.
¡Bendito RGPD, que nos libras de todo mal!

Pero no se me había ocurrido en ningún momento ni siquiera hacer un triste intento de uso de su API. Ni leerme su documentación, donde dice bien clarito que eso no lo tenemos prohibido en Europa, lo cual no deja de resultarme intrigante. ¿API para uso comercial sí y asistente conversacional no 🤔? En fin, cosas más raras se han visto.

El caso es que hablar con Claude a través de su API es inmediato. Ni siquiera es necesario recurrir a una VPN para registrarse como desarrollador y obtener una de esas flamantes y  súper secretas claves de la API, como nos vimos forzados a hacer con Gemini.

Ni tampoco utilizar una plataforma de desarrollo que corra en servidores allende los mares (léase Apps Script). De hecho, la típica prueba del curl, que fallaba de inmediato al atacar a Gemini, funciona perfectamente con Claude sin necesidad de dar saltos triples saltos mortales hacia atrás con una VPN atada a la espalda.

Captura de un intento exitoso de conexión con la API de Gemini usando la instrucción curl en la consola de comandos de Linux.
Mi PC, que sale a Internet con una IP pública española, hablando sin obstáculos con la API de Claude.

Pero, por supuesto, también podremos usar sin cortapisas Apps Script. Y hacerlo en nuestras adoradas (como suelo decir) hojas de cálculo de Google.

Animación que muestra una función personalizada Apps Script para Google Sheets.
Claude y Apps Script también hacen una buena pareja.

👇 ¡En resumen! 👇

Vamos a tomar contacto con la API de Claude 3 y a desarrollar sobre ella una sencilla función personalizada Apps Script para explotar sus funciones de generación de textos y análisis de imágenes.

TABLA DE CONTENIDO

Claude 3: Opus, Sonnet y Haiku

Antropic presentó la familia de modelos Claude 3 hace un suspiro (4 de marzo de 2024), y lo cierto es que todos ellos han sido muy bien recibidos por la crítica especializada, divulgadores y desarrolladores, como puedes comprobar tú mismo en esta interesante tabla de LMSYS Org que recoge las impresiones de cientos de miles de usuarios.

Tabla comparativa de LLMS, según los usuarios de LMSYS Org.
Tabla comparativa de modelos de lenguaje grandes, según los usuarios de LMSYS Org.

Claude 3 se compone de tres modelos distintos, de tamaño, potencia y capacidades crecientes, denominados Haiku, Sonnet y Opus. Este último parece ser el nuevo estado del arte (me pregunto durante cuánto tiempo 🤭).

Todos ellos son multimodales (texto e imagen), muy rápidos, cuentan con una enorme ventana de contexto inicial de 200.000 tokens y un coste por token extremadamente competitivo. Ni tan mal, ¿verdad?

☝ En context.ai puedes comparar diversos modelos para hacerte una idea rápida de sus capacidades, aquí por ejemplo Claude 3 Haiku frente a GPT-3.5 Turbo. Sí, Haiku es más potente y mucho más barato. Y estamos hablando del pequeño de la familia Claude.

Tabla comparativa de Haiku frente a GPT-3.5 Turbo
Fuente: https://context.ai/compare/claude-3-haiku/gpt-3-5-turbo

Me encantaría animarte a que probarás Claude (el asistente conversacional) ahora mismo. Ya sabes que vas a necesitar una VPN. Pero más vale que lo hagas bien, porque los términos de uso de Anthropic son muy contundentes al respecto. Y además los aplican a rajatabla.

Si se dan cuenta de que estás intentado saltarte el bloqueo regional, aunque sea solo una vez, posiblemente te bloqueen la cuenta inmediatamente, lo que también te impediría acceder al panel de control de la API de Claude (un fastidio).

Mensaje de error por cuenta suspendida al iniciar sesión en Anthropic.
Digo yo que no hacía falta ser tan antipático, gente de Anthropic. Un «no lo vuelvas a intentar» seguro que hubiera bastado.

Le ha pasado a un amigo 😉.

La API de Claude

Como te decía al principio de este breve artículo, Afortunadamente no vamos a necesitar hacer trampas para exprimir la API de Claude.

Puedes obtener tu clave de la API de Claude siguiente estos sencillos pasos:

1️⃣ Dirige tu navegador de confianza hacia console.anthropic.com y regístrate.

Cuadro de diálogo de registro en la consola de Anthropic.
Que no, que no necesitas una VPN.

2️⃣ Para poder hacer algo de provecho con la API de Claude necesitarás créditos (money talks). Aprovéchate de los cinco pavos que Anthropic tiene el detalle de regalarte para empezar a jugar.

Menú principal de la consola de Anthropic, con un aviso que indica que disponemos de un cupón gratuito de 5 dólares para usar la API de Claude.
Pues esto es todo un detalle.

Necesitarás introducir tu número de teléfono móvil para recibir un SMS con un código de verificación y canjear el cupón.

⚠️ Cuidado, porque estos créditos gratuitos se esfuman pasados 14 días desde el momento en que los obtienes, aunque no los hayas agotado aún.

Imagen del cupón de 5 dólares junto a un mensaje que indica que caduca a los 15 días.
¡Date prisa en probar la API de Claude!

Si todo va bien, encontrarás a continuación el crédito de bienvenida disponible en Settings → Plans and Billing.

Aspecto de la sección de planes y facturación de la consola de Anthropic.
Aquí es donde gestionarás tu suscripción a Claude y controlarás el gasto realizado.

3️⃣ Ahora regresa a la sección Dashboard de la consola y haz clic en Get API Keys. Fíjate en el aviso que te indica que estás en lo que Anthropic denomina modo de evaluación, que adolece de ciertas limitaciones (más sobre ellas en el apartado final de este artículo).

Sección de la consola donde se crear y gestionan las claves de la API.
¡Bienvenido al modo de evaluación de Claude!

4️⃣ Pero nosotros a lo nuestro. Haz clic en Create Key para generar una nueva clave de la API.

Cuadro de diálogo introducción del nombre de la clave de la API que se va a generar.
Dado que puedes crear múltiples claves conviene que utilices nombres significativos para ellas.

Ponla seguidamente a buen recaudo puesto que, como suele ser habitual, no la podrás visualizar de nuevo.

Cuadro de diálogo que muestra la clave recién creada.
Ni lo intentes, esa clave ya no existe.

Si ya tienes claro que vas a ir en serio con Claude puedes acceder ya mismo a uno de sus dos planes comerciales (Settings → Plans and Billing → Select Plan).

Cuadro de diálogo de selección de uno de los dos planes de uso de la API de Claude.
¿Soy yo o ese cuadro de diálogo parece invitarte a seleccionar el plan de la derecha?

☝ Una de las diferencias más significativas entre ambos es que el plan Build es de prepago (recargas tus créditos manualmente cuando lo necesites), en tanto que si estás en Scale, diseñado para el usuario empresarial, se te facturará mensualmente el consumo realizado.

Resulta curioso que la API de Claude esté también disponible a través del descomunal e inacabable Vertex AI de Google. Digo yo que la pasta que ha metido Google en Anthropic algo habrá tenido que ver.

Al igual que Gemini, Claude tiene su propia caja de arena para pruebas (Workbench), que puedes utilizar para prototipar tus prompts o simplemente familiarizarte con Claude.

Animación que muestra una secuencia de introducción de un prompt en el workbench de Claude.
Espero que Claude sea más lista que graciosa.

Y seguramente también querrás tener a mano el consumo (tokens y dólares) que estás haciendo.

Panel de control de uso (tokens) de Claude.
Settings → Usage
Panel de planes y facturación de Claude.
Settings → Plans & Billing

Dicho todo esto, que está muy bien pero no es a lo que hemos venido, vamos a ensuciarnos un poco las manos con la grasa del motor de Apps Script.

La función personalizada HDCP_CLAUDE

Vamos a construir ahora algo así como un clon de la función personalizada HDCP_GEMINI_VISION_DESCRIBIR que creamos en el artículo previo sobre Gemini,.

Como de costumbre, lo primero y principal es pegarle una buena leída a la documentación para desarrolladores de la API de Claude. La tienes aquí.

En Gemini precisábamos de un modelo diferenciado para utilizar sus capacidades multimodales (gemini-pro-vision). Todos los modelos de Claude, sin embargo, son capaces de trabajar con imágenes, por lo que no es necesario recurrir a versiones o variantes especiales.

Por esa razón, en esta ocasión diseñaremos una única función personaliza para hojas de cálculo, a la que le podamos facilitar de manera opcional el URL de una imagen como parte de las instrucciones que le damos al modelo.

Y comenzaremos con el clásico bloque de declaraciones.

const CLAUDE = {
  claveApi: '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',
  modelo: 'claude-3-haiku-20240307',
  temperatura: 0.5,
  topK: 32,
  topP: 1.0,
  maxTokens: 4096,
  puntoFinal: 'https://api.anthropic.com/v1/messages'
}
 

Nada fuera de lo habitual, por ahora:

  • Clave de la API en la línea 2. Claude nos pedirá que se la enviemos al modelo usando la cabecera x-api-key de todas las petición POST que le hagamos para autenticarnos ante él.
  • Designación del modelo a utilizar en la línea 3. He optado por usar Haiku: bueno, bonito y barato, aunque inicialmente no nos va a costa nada, recuerda que hemos obtenido un crédito de 5 dólares. Anthropic recomienda indicar también la versión de la API deseada, aunque yo no lo he hecho en esta sencilla función.
  • Los clásicos parámetros de generación en las líneas 4 - 7.
  • El punto final de la API de Messages de Claude. Ojo porque sus métodos sustituyen a los ya obsoletos pertenecientes a la API de Text Completions.

Esta es la declaración de nuestra función. El parámetro urlImagen puede ser omitido si no estamos interesados en trabajar con imágenes.


/**
 * Envía un prompt de texto a Claude y devuelve su respuesta.
 * @param {"Claude, háblame de ti"} prompt Instrucciones para Claude.
 * @param {"https://www.komar.de/media/catalog/product/cache/13/image/9df78eab33525d08d6e5fb8d27136e95/I/A/IADX10-065.jpg"} urlImagen [OPIONAL] URL de una imagen JPG/PNG/WEBP/GIF.
 * @customfunction
 */
function HDCP_CLAUDE(prompt = 'Claude, háblame de ti', urlImagen) {

Lo primero que tenemos que hacer es determinar si la función ha recibido el URL de una imagen. Aunque la API admite hasta 20 imágenes en cada prompt, nuestra función quedará limitada a una sola.

En las líneas 19 - 43 es donde montaremos el objeto JSON que contendrá las instrucciones para Claude.


  if (prompt) {

    // Prompt de texto
    let messages;

    // El prompt no incluye (url de) imagen
    if (!urlImagen) {
      messages = [{ role: 'user', content: prompt }];
    } else {
      // El prompt sí incluye (url de) imagen
      const blob = UrlFetchApp.fetch(urlImagen).getBlob();
      const tipoMime = blob.getContentType();
      const b64datos = Utilities.base64Encode(blob.getBytes());
      if (b64datos && ['image/png', 'image/jpeg', 'image/webp', 'image/gif'].includes(tipoMime)) {
        messages = [
          {
            role: 'user',
            content: [
              { type: 'image', source: { type: 'base64', media_type: tipoMime, data: b64datos } },
              { type: 'text', text: prompt }
            ]
          }
        ]
      } else throw 'Tipo de imagen no soportado.';
    }
    

Primero se comprueba si hay instrucciones de texto (línea  19).

A continuación determinaremos si estas son de tipo multimodal (incluyen una imagen) o no. En el primer caso introduciremos en la propiedad content únicamente el texto de las instrucciones facilitadas (líneas 25 - 26). En el segundo, montaremos en su lugar un vector de bloques de contenido para caracterizar debidamente texto e imagen (líneas 27 - 43).

Al igual que hicimos con la función personalizada previa para Gemini, utilizaremos una cadena codificada en Base64 para enviarle a Claude la imagen a partir del URL facilitado:

  1. Se obtiene un blob a partir del URL de la imagen (línea 29).
  2. Se identifica su tipo MIME (línea 30).
  3. Se codifica el blob en el formato Base64 que espera la API (línea 31).

Y también del mismo modo, en este prototipo tampoco estamos controlando los posibles errores que pueden producirse al recuperar la imagen a partir de su URL mediante el método fetch(), algo que aunque solo sea por cuestiones estéticas sí deberíamos hacer en un producto final.

☝ La documentación de Claude recomienda insertar las imágenes antes del texto, en el vector de bloques de contenido, aunque no parece ser esto algo crítico. De un modo u otro, no dejes de echarle un vistazo a estos tips multimodales para hacerte una idea de todas las posibilidades, especialmente en el contexto de una secuencia conversacional, aunque nuestra función personalizada, puramente generativa, no va a admitir esta posibilidad.

Si la imagen recuperada es del tipo correcto (Claude soporta en estos momentos JPEG, PNG, GIF y WebP), seguiremos adelante con la petición, en caso contrario la función lanzará una excepción y finalizara de inmediato (línea 42).

Vamos finalmente con la petición, que ya es cosa de sota, caballo y rey.


    const solicitud = {
      model: CLAUDE.modelo,
      max_tokens: CLAUDE.maxTokens,
      temperature: CLAUDE.temperatura,
      top_p: CLAUDE.topP,
      top_k: CLAUDE.topK,
      messages
    };

    const respuesta = UrlFetchApp.fetch(
      CLAUDE.puntoFinal,
      {
        headers: { 'x-api-key': CLAUDE.claveApi, 'anthropic-version': '2023-06-01' },
        method: 'POST',
        payload: JSON.stringify(solicitud),
        contentType: 'application/json',
        muteHttpExceptions: true
      }
    );

    const codigoResultado = respuesta.getResponseCode();
    const resultado = JSON.parse(respuesta.getContentText());
    if (codigoResultado == 200) {
      return resultado.content[0].text;
    } else throw `Claude no responde (${codigoResultado}): ${resultado.error.message}`;

  }

}

Una vez más, y hablando de código en producción, probablemente sería aconsejable tener en cuenta los distintos códigos de error de la API de Anthropic (4xx, 5xx) para realizar un mejor tratamiento, aunque solo fuera informativo, de las situaciones de error.

Imagen estática que demuestra el funcionamiento de la función personalizada HDCP_CLAUDE().
Pero oiga, a mí solo me salen 7 personajes, a menos que el Mjölnir de Thor cuente como uno más. ¿O me falla
la vista?

 

Tira de este hilo si te apetece conocer mis primeras impresiones usando Claude:

Aquí tienes una hoja de cálculo con el código de la función que hemos desarrollado juntos y algunas pruebas multimodales. Solo tienes que obtener tu propia clave de la API  de Claude e inicializar con ella la constante claveApi en Extensiones → Apps Script.

👉 Claude API test | publico 👈

Comentarios finales y siguientes pasos

Me quedan aún muchas cosas por contarte antes de terminar por hoy.

Y es que tanto la implementación de esta función personalizada que habla con Claude como la que te presentaba en el ya mencionado artículo previo que hacía lo propio con Gemini son poco más que pruebas de concepto.

Mi intención con estos dos artículos era poner de manifiesto la facilidad con la que Apps Script permite integrar capacidades generativas en las hojas de cálculo, utilizando para ello dos modelos de lenguaje distintos de diferentes fabricantes: Gemini y Claude 3.

Pero este es solo el primer paso. Vamos a necesitar esforzarnos un poco más para construir herramientas basadas en modelos generativos que realmente puedan escalar en entornos reales, donde es fundamental un comportamiento robusto y eficiente.

Y no me refiero solo a controlar situaciones de error fácilmente tratables, como la que mencionaba más arriba al analizar el modo en que nuestro código obtiene las imágenes por medio de su URL.

¿Entonces?

Si te haces una copia de la hoja de cálculo que te facilito y juegas un poco con la función personalizada que vive en su interior, te vas a encontrar muy a menudo con esta situación:

Mensaje de error devuelto por la API de Claude por haber excedido el número máximo de peticiones por minuto: "Number of requests has exceeded your rate limit (https://docs.anthropic.com/claude/reference/rate-limits). Please try again later or contact sales at https://www.anthropic.com/contact-sales to discuss your options for a rate limit increase. (línea 70)".
Hay una vida mejor, pero es más cara. Siempre.

O con esta otra, que es parecida pero no idéntica (a ver si encuentras la diferencia):

Mensaje de error devuelto por la API de Claude por haber excedido el número máximo de peticiones simultáneas: "Number of concurrent connections has exceeded your rate limit. Please try again later or contact sales at https://www.anthropic.com/contact-sales to discuss your options for a rate limit increase. (línea 70)".
Hay una vida mejor, pero es más cara. Siempre. Te lo digo otra vez para que no se te olvide, es una Verdad Universal.

Anthropic aplica una serie de límites de uso a su API para garantizar el correcto funcionamiento de sus sistemas y evitar su abuso. Y eso nos deja en estos momentos con 5 tristes peticiones por minuto en su plan de evaluación.

GIF animado que muestra los distintos límites de uso de la API de Claude que afectan a la frecuencia de peticiones y tokens consumidos por periodos de tiempo.
Cada plan de uso de Claude presenta diferentes restricciones por lo que hace a la frecuencia de uso y al consumo de tokens. Ascender a planes menos restrictivo requiere la compra de paquetes de créditos cada vez mayores.

Esto de establecer límites de servicio es una práctica habitual, incluso en nuestro reverenciado Apps Script, como seguro que ya sabes. Y Gemini, que no iba a ser menos, también los aplica en sus modelos de un modo análogo (busca rate limit en esta página). Y es natural.

GIF animado que muestra los distintos límites de uso de la API de Gemini que afectan a la frecuencia de peticiones y tokens consumidos por periodos de tiempo.
Fuente: https://ai.google.dev/pricing

¡Qué barbaridad, Gemini 1.5 Pro tan solo admite 2 peticiones por minuto en el plan gratuito actual! Se diría que este modelo aún supone unos costes operativos prohibitivos para Google. 

Así que si tienes el gatillo de la API fácil y envías un número excesivo de peticiones en un cierto intervalo de tiempo, obtendrás un simpático mensaje de error por respuesta sugiriendo que te moderes. O que pases por caja para optar a una vida mejor, por supuesto 😉. Otra vez money talks.

Y aquí tenemos, amigos y amigas, un problema ineludible, especialmente  —pero no únicamente— cuando nos emperramos en interactuar con estos modelos generativos de terceros utilizando funciones personalizas.

Piensa en una hoja de cálculo en la que necesitas arrastrar una de estas fórmulas generativas a decenas o cientos de celdas contiguas, algo que se hace en un santiamén y sin pensar. O imagínate que podría ocurrir cuando de repente Google Sheets decide que todos esos cientos de fórmulas que dialogan con nuestros parlanchines modelos deben actualizarse al mismo tiempo.

Creo que la respuesta ya la conoces.

—«Pero Sheets cuenta con un mecanismo de caché interna para evitar recálculos cuando las celdas referenciadas en una fórmula no varían» —te escucho decir.

Bien visto, es evidente que sabes de hojas de cálculo. Pero ese mecanismo tan estupendo, que por otra parte no controlamos, no solo no es infalible sino que además es incompleto. Prueba a insertar una nueva fila o una columna en una hoja y comprueba qué pasa con todas las fórmulas que quedan por debajo o a su derecha.

Y hablando de funciones personalizadas, los consejos de optimización que Google ofrece en su documentación para desarrolladores —me refiero a diseñarlas de modo que acepten argumentos matriciales— no contribuyen de manera significativa en esta ocasión a resolver nuestro problema.

Resumiendo:

🚨 Necesitamos evitar a toda costa que nuestras fórmulas fallen escupiendo un feo error como consecuencia de la aplicación de los límites de servicio que sean de aplicación, algo aún más horrible cuando ya mostraban un resultado previo válido que ha desaparecido tras un recálculo tal vez innecesario.

🚨 Y al mismo tiempo, también debemos reducir todo lo posible el número de peticiones a la API para evitar costes innecesarios. Después de todo, vas a pagar por todos y cada uno de los tokens que envíes en tus peticiones y recibas en las respuestas generadas por los modelos.

Existen diversas técnicas para afrontar estos problemas, algunas de tipo preventivo en tanto que otras son más bien correctivas, por ejemplo:

  • Mecanismos de contención del cálculo (con alguna que otra magia sorprendente 😏).
  • Reparación automática de fórmulas con error.
  • Serialización y estrangulamiento de peticiones.
  • Reintento de peticiones con tiempo de espera variable.
  • Almacenamiento en una caché local de resultados.

El problema es ciertamente apasionante. Te adelanto que vamos necesitar cierta ingeniería —o fontanería, según se mire— para aproximarnos a una solución aceptable.

Pero de todo eso hablaremos largo y tendido en un próximo artículo, el tercero en esta serie que explora el uso de las APIs de estos inefables modelos generativos que hoy en día nos rodean.

Mientras tanto, te invito a que le eches un vistazo a este complemento para hojas de cálculo de Anthropic que demuestra algunos de los principios de diseño que te acabo de adelantar.


Comentarios