Hace unas semanas me pidieron que impartiera una breve clase sobre programación con IA. Me adelanté a preparar obedientemente las diapositivas y el código, pero de alguna manera los materiales crecieron y crecieron. Así que pensé ¿por qué no reunir todo ese material, y más, en una serie de artículos con él? Estoy seguro de que habrá otros más allá de la clase que puedan beneficiarse de ello.

También pueden convertirse en materiales de referencia para la clase después de la sesión.
Así que aquí está la primera parte de la clase (1 de 4), que es todo acerca de llamar a las API de AI proveedor a través de las API REST y bibliotecas.

  • Descargo de responsabilidad: esto se supone que es material introductorio por lo que hay un montón de cosas que he dejado fuera. Esto no es ni pretende ser exhaustivo.

Empecemos con un calentamiento. Con esto me refiero a llamar a algunas APIs. Llamar a APIs para hacer IA es la forma más común y sencilla de acceder a las capacidades de la IA. Mucha gente desprecia esto y lo considera «no lo suficientemente IA». Pero eso es una tontería: el valor del sistema es ofrecer capacidades a los usuarios, no cuánta IA se utiliza.

Actualmente existen muchos proveedores con API, como OpenAI (GPT), Google (Gemini), Anthropic (Claude), Mistral (Mistral), Cohere (Command), etc. Además de estos hay proveedores de plataformas que proporcionan varias capacidades como Replicate, Anyscale, Modal, Banana, etc.
En este artículo, vamos a empezar simplemente con la llamada a las REST API.

Tabla de contenidos

REST API

Que yo sepa, todos los proveedores tienen una REST API. Llamarlos es muy simple, sólo tenemos que utilizar curl con un punto final de la URL de la API, y pasarle la clave de la API, y la carga JSON.

Aquí hay un ejemplo de llamada a la API de OpenAI para completar chats.

$ curl https://api.openai.com/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-d '{
"model": "gpt-4o",
"messages": [
{
"role": "system",
"content": "You are a helpful assistant."
},
{
"role": "user",
"content": "Why is the sky blue?"
}
]
}'

Necesitas una clave API válida, que puede obtener de cada proveedor. La mayoría de las veces basta con registrarse para obtener una cuenta y crear una clave de API. Una vez que tengas la clave API, puedes introducirla directamente o establecerla como variable de entorno:

$ export OPENAI_API_KEY=<your API key>

Cuando llames a la API, deberías devolver algo como esto:

{
"id": "chatcmpl-9RWBzicE7v7A1ZRLWUMX3a6zwooWd",
"object": "chat.completion",
"created": 1716345631,
"model": "gpt-4o-2024-05-13",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "The sky appears blue due to a phenomenon called Rayleigh scattering. Here’s a more detailed explanation:\n\n1. **Sunlight Composition**: Sunlight, or white light, is composed of a spectrum of colors, each with different wavelengths. The visible spectrum ranges from shorter wavelengths (blue and violet) to longer wavelengths (red and orange).\n\n2. **Interaction with the Atmosphere**: When sunlight enters Earth's atmosphere, it encounters molecules and small particles. The shorter wavelengths of light (blue and violet) are scattered more effectively by these particles than the longer wavelengths (red, orange, and yellow). \n\n3. **Human Perception**: While violet light is scattered even more than blue light, our eyes are more sensitive to blue light and there is less violet light in sunlight to begin with. Moreover, some of the violet light is absorbed by the upper atmosphere. As a result, we see the sky as blue.\n\n4. **Resulting Blue Sky**: The scattered blue light is what predominantly reaches our eyes from various directions, making the sky look blue when viewed from the ground during the day.\n\nThis scattering effect is more pronounced when the sun is lower in the sky, which is why we see reddish colors at sunrise and sunset. In these cases, the light has to pass through more of the atmosphere, scattering out even more blue and green light, and leaving the reds and oranges to dominate the sky's appearance."
},
"logprobs": null,
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 23,
"completion_tokens": 286,
"total_tokens": 309
},
"system_fingerprint": "fp_729ea513f7"
}

Tal vez OpenAI fue el primero que ideó este tipo de API, o tal vez porque es el más popular, muchos otros proveedores utilizan también un formato similar en sus REST API. Por ejemplo, esta es la de Anthropic.

$ curl https://api.anthropic.com/v1/messages \
-H "content-type: application/json" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-d '{
"model": "claude-3-opus-20240229",
"max_tokens": 1024,
"messages": [
{"role": "user", "content": "Why is the sky blue?"}
]
}'

Como puedes ver, la clave API se pasa a través de una cabecera diferente pero la carga útil es casi exactamente la misma aunque hay ligeras diferencias debido a cómo funciona el modelo. Por ejemplo, no puedes pasar el contenido del rol del sistema como parte de los mensajes en Anthropic.

Este es otro ejemplo, de Mistral.

$ curl https://api.mistral.ai/v1/chat/completions \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header "Authorization: Bearer $MISTRAL_API_KEY" \
--data '{
"model": "mistral-large-latest",
"messages": [
{
"role": "user",
"content": "Why is the sky blue?"
}
]
}'

Dicho esto, Google utiliza un enfoque ligeramente diferente de la API, y pasa la clave de la API como parte de la consulta de la URL, y el modelo está incrustado como parte de la URL. La carga útil también es diferente, pero la idea es prácticamente la misma.

$ curl "https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash-latest:generateContent?key=$API_KEY" \
-H 'Content-Type: application/json' \
-d '{ "contents":[
{ "parts":[{"text": "Why is the sky blue?"}]}
]
}'

Las REST API son realmente útiles y prácticamente universales. Si estás programando en un lenguaje que no está soportado directamente por el proveedor, puedes utilizar una librería cliente HTTP y llamar directamente a la API REST. Todos los lenguajes de programación razonables tienen una biblioteca cliente HTTP, ya sea en sus bibliotecas estándar o en las de terceros, así que ya está.

Sin embargo, la mayoría de los proveedores también ofrecen soporte para Python como mínimo, principalmente porque la mayoría de las cosas de IA se programan con Python.

Python

Por ejemplo, así es como se puede llamar a OpenAI utilizando su biblioteca Python.

from openai import OpenAI
client = OpenAI()

completion = client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Why is the sky blue?"}
]
)

print(completion.choices[0].message.content)

Es así de sencillo. Puedes configurar parámetros y opciones al crear el cliente, pero eso es todo. Habrás notado que ya no necesitamos especificar la clave de la API. Eso es porque si has configurado la clave de la API como una variable de entorno, la biblioteca Python la recogerá allí.

Lo mismo ocurre con Anthropic.

import anthropic

message = anthropic.Anthropic().messages.create(
model="claude-3-opus-20240229",
max_tokens=1024,
messages=[
{"role": "user", "content": "Why is the sky blue?"}
]
)

print(message.content)

Además de con Mistral.

from mistralai.client import MistralClient
from mistralai.models.chat_completion import ChatMessage

client = MistralClient()

chat_response = client.chat(
model="mistral-large-latest",
messages=[
ChatMessage(role="user", content="Why is the sky blue?")
],
)

print(chat_response.choices[0].message.content)

La biblioteca Python de Google también es muy fácil de usar, aunque es ligeramente diferente del resto.

import google.generativeai as genai

model = genai.GenerativeModel('gemini-1.5-flash-latest')
chat = model.start_chat(history=[])
response = chat.send_message("Why is the sky blue?")

print(response.text)

Como puedes ver, Python está muy bien soportado. El otro lenguaje bien soportado es Javascript.

Javascript

Aunque Python es el lenguaje mejor soportado, debido a su enorme popularidad, Javascript/Typescript también suele serlo. Echemos un vistazo a cómo acceder a las APIs de OpenAI usando Javascript con node.js.

import OpenAI from "openai";

const openai = new OpenAI();

const completion = await openai.chat.completions.create({
model: "gpt-4o",
messages: [
{ role: "system", content: "You are a helpful assistant." },
{ role: "user", content: "Why is the sky blue?" }
],
});

console.log(completion.choices[0]);

Como antes en las librerías Python, ya no necesitamos especificar la clave API. Eso es porque si has configurado la clave API como una variable de entorno la librería Javascript la recogerá ahí.

Esta es la salida del resultado JSON (sólo el completion.choices[0]).

{
index: 0,
message: {
role: 'assistant',
content: "The sky appears blue because of a phenomenon called Rayleigh scattering. This scattering occurs when sunlight enters Earth's atmosphere and interacts with the molecules and small particles in the air.\n" +
'\n' +
"Here's a step-by-step breakdown of why the sky looks blue:\n" +
'\n' +
'1. **Sunlight Composition**: Sunlight, or white light, is made up of multiple colors, each with different wavelengths. The colors range from violet and blue (shorter wavelengths) to red and orange (longer wavelengths).\n' +
'\n' +
'2. **Scattering**: As sunlight passes through the atmosphere, it collides with gas molecules and small particles. The shorter wavelengths of light (blue and violet) are scattered in all directions by these molecules and particles much more than the longer wavelengths (red and orange).\n' +
'\n' +
'3. **Human Perception**: Even though violet light is scattered even more than blue light, our eyes are more sensitive to blue light and less sensitive to violet light. Additionally, some of the violet light is absorbed by the upper atmosphere. Therefore, the sky predominantly appears blue to us.\n' +
'\n' +
'4. **Viewing Angle**: When you look up at the sky, you are seeing this scattered blue light coming from all parts of the sky, giving it its characteristic color.\n' +
'\n' +
"In summary, the sky is blue because the shorter blue wavelengths of sunlight are scattered more widely in all directions by the molecules in Earth's atmosphere, and our eyes are better equipped to see blue light."
},
logprobs: null,
finish_reason: 'stop'
}

Del mismo modo, así es como se puede llamar a la API antrópica utilizando Javascript con node.js.

import Anthropic from '@anthropic-ai/sdk';

const anthropic = new Anthropic();

const completion = await anthropic.messages.create({
model: "claude-3-haiku-20240307",
max_tokens: 1024,
messages: [
{"role": "user", "content": "Why is the sky blue?"}
]
});

console.log(completion);

No voy a seguir con los otros proveedores, pero más o menos te haces una idea. Si prefieres trabajar con las librerías soportadas oficialmente, Python y Javascript son el camino a seguir. Además de la API REST, Python y Javascript, la mayoría de los proveedores no tienen soporte oficial para otros lenguajes, aunque Google tiene una gama mucho más amplia de lenguajes soportados.

Sin embargo, esto no significa que no haya bibliotecas para otros lenguajes.

Bibliotecas de terceros

Existen multitud de librerías de terceros. Si echas un vistazo a la documentación de OpenAI, verás que hay librerías de terceros para casi todos los lenguajes populares. Por ejemplo, el paquete go-openai permite llamar a las bibliotecas de OpenAI en Go.

package main

import (
"context"
"fmt"
"os"

openai "github.com/sashabaranov/go-openai"
)

func main() {
client := openai.NewClient(os.Getenv("OPENAI_API_KEY"))
resp, err := client.CreateChatCompletion(
context.Background(),
openai.ChatCompletionRequest{
Model: openai.GPT4o,
Messages: []openai.ChatCompletionMessage{
{
Role: openai.ChatMessageRoleUser,
Content: "Why is the sky blue?",
},
},
},
)

if err != nil {
fmt.Printf("ChatCompletion error: %v\n", err)
return
}

fmt.Println(resp.Choices[0].Message.Content)
}

Aquí tienes una librería OpenAI de terceros para Swift, llamada SwiftOpenAI. Para usarla, añádela como dependencia del paquete en tu proyecto XCode. Este es un ejemplo de una función para llamar a las APIs de OpenAI.

    func sendMessage() async {
let input = userInput.trimmingCharacters(in: .whitespacesAndNewlines)
guard !input.isEmpty else { return }

let message = Message(content: input, isUser: true)
messages.append(message)
userInput = ""

let openAI = SwiftOpenAI(apiKey: Config.openAIKey)
let msgs: [MessageChatGPT] = [
MessageChatGPT(text: "You are a helpful assistant.", role: .system),
MessageChatGPT(text: input, role: .user)
]

let optionalParameters = ChatCompletionsOptionalParameters(
temperature: 0.7,
stream: true,
maxTokens: 1024
)

do {
let stream = try await openAI.createChatCompletionsStream(
model: .gpt4o(.base),
messages: msgs,
optionalParameters: optionalParameters
)

let resp = Message(content: "", isUser: false)
messages.append(resp)

for try await response in stream {
let content = response.choices[0].delta?.content ?? ""
if let lastMessage = messages.last, !lastMessage.isUser {
let updatedContent = lastMessage.content + content
messages[messages.count - 1] = Message(content: updatedContent, isUser: false)
}
}
} catch {
print("Error: \(error)")
if let lastMessage = messages.last, !lastMessage.isUser {
messages[messages.count - 1] = Message(content: "Cannot get response from OpenAI: \(error)", isUser: false)
}
}
}

Todas estas librerías de terceros son buenas pero en su mayoría sólo soportan 1 proveedor. Si quieres acceder a múltiples proveedores normalmente necesitas usar varias librerías a la vez, o también puedes probar frameworks LLM.

LLM Frameworks

Los frameworks de LLM son un tipo de marco de desarrollo que nos permite escribir aplicaciones basadas en LLM. Estos frameworks proveen soporte y estructura, y usualmente una forma determinada de escribir aplicaciones basadas en LLM.

Hay un gran número de frameworks LLM, y algunos de ellos son muy populares, más que las librerías con soporte oficial o de terceros. Esto se debe a que estos frameworks proporcionan a los desarrolladores un montón de capacidades. Nos permiten conectarnos a múltiples proveedores LLM al mismo tiempo, tiene conectores a múltiples fuentes de datos e incluso implementar agentes sobre los LLM básicos.

Hay un gran número de frameworks incluyendo Langchain, LlamaIndex, Haystack, etc. pero en este artículo sólo hablaré de Langchain y LlamaIndex, ya que actualmente son los dos frameworks más populares para construir aplicaciones basadas en LLM.

Langchain 🦜️🔗

El framework más popular es probablemente Langchain, que fue publicado por primera vez en octubre de 2022. Ha evolucionado rápidamente desde entonces para cubrir casi todo en el mundo LLM, culminando en cerca de 400 versiones hasta la fecha. En un momento dado, las versiones eran casi diarias, ¡e incluso a veces dos al día!

Durante el último año Langchain ha pasado de ser una librería Python relativamente simple a un ecosistema de capacidades que van desde la librería central a un servidor de despliegue y un conjunto de herramientas de observabilidad.

Aquí está lo básico de cómo utilizar Langchain para conectarse a OpenAI y llamar a su API de chat.

from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser

llm = ChatOpenAI(model_name="gpt-4o")
prompt = ChatPromptTemplate.from_messages([
("system", "You are a helpful assistant."),
("user", "{input}")
])
output_parser = StrOutputParser()

chain = prompt | llm | output_parser
results = chain.invoke({"input": "why is the sky blue?"})

print(results)

El código no parece muy diferente a simple vista, pero te habrás dado cuenta de que hemos encadenado el prompt del chat, LLM y el parser de salida para producir los resultados. Este es un ejemplo de una de las características más poderosas de Langchain y la que le dio a Langchain su nombre – la cadena.

Una cadena es una secuencia de llamadas enlazadas entre sí. Las cadenas se crean usando el Lenguaje de Expresión Langchain (LCEL) y la cadena más básica es la que se muestra arriba:

chain = prompt | llm | output_parser

Para ejecutar la cadena, simplemente llamamos a uno de los pocos métodos de la cadena (en el caso del código anterior, usamos invoke) con la entrada apropiada, y obtendremos la salida.

IA

Las cadenas son potentes y configurables. Veamos cómo queremos realizar una sencilla generación aumentada de recuperación (RAG) pasando el contexto junto con la pregunta al LLM.

Utilizaremos un documento de texto, en este caso, los procedimientos parlamentarios en Singapur sobre el Comité de Suministros para el Ministerio de Comunicaciones e Información (MCI) en enero de 2024. Saqué el texto del sitio y lo guardé como un archivo de texto llamado hansard.txt.

from langchain_community.vectorstores import DocArrayInMemorySearch
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnableParallel, RunnablePassthrough
from langchain_openai import OpenAIEmbeddings
from langchain_openai import ChatOpenAI

def extract(file_path):
with open(file_path, 'r') as file:
return [line.strip() for line in file if line.strip()]


model = ChatOpenAI(model="gpt-4o")
vectorstore = DocArrayInMemorySearch.from_texts(
texts=extract('data/hansard.txt'),
embedding=OpenAIEmbeddings(),
)
retriever = vectorstore.as_retriever()
template = """Answer the question based only on the following context:
{context}

Question: {question}
"""
prompt = ChatPromptTemplate.from_template(template)
output_parser = StrOutputParser()
setup_and_retrieval = RunnableParallel(
{"context": retriever, "question": RunnablePassthrough()}
)
chain = setup_and_retrieval | prompt | model | output_parser

results = chain.invoke("How has Smart Nation improved citizen's lives?")
print(results)

En el código anterior, primero creamos un almacén de vectores en memoria a partir de cada línea del documento hansard.txt, utilizando incrustaciones de OpenAI. A partir del almacén de vectores, creamos un recuperador, que nos permite obtener las líneas apropiadas como entrada para el prompt.

Ahora, dada una entrada del usuario, la pasamos al recuperador para obtener las líneas del almacén de vectores. La misma entrada del usuario también se pasa al prompt. Estos 2 son ejecutados al mismo tiempo por RunnableParallel, y la salida es enviada al prompt como la pregunta y el contexto.

El resto es más o menos lo mismo, pero aquí está la salida.

% python langchain_test_rag.py
The Smart Nation initiative has improved citizens' lives in Singapore by increasing satisfaction with government services, which rose from 73% to 83% between 2014 and 2023. Additionally, 84% of Singaporeans feel that digital technologies have made their lives easier. The initiative aims to enhance everyday convenience and quality of life, empower people to live more meaningful and fulfilled lives, and ensure that no one is left behind.

Como puedes ver, las cadenas son un mecanismo muy potente. Este mecanismo de encadenamiento no es exclusivo de Langchain. El framework Haystack, que también es un framework LLM bastante popular por sí mismo, lo llama pipeline, mientras que otros frameworks como LLMFlows lo llaman flow.

LlamaIndex

Otro framework LLM popular es LlamaIndex. LlamaIndex comenzó en noviembre de 2022 como un framework llamado GPTIndex . La premisa de LlamaIndex es conectar LLMs a sus datos. Puedes notar que tanto LlamaIndex como Langchain empezaron más o menos al mismo tiempo. De hecho, ambos creadores de Langchain (Harrison Chase) y LlamaIndex (Jerry Liu) eran compañeros en Robust Intelligence, una empresa de seguridad de IA.

Echemos un vistazo rápido a cómo utilizar LlamaIndex para completar chats.

from llama_index.core import Settings
from llama_index.core.llms import ChatMessage
from llama_index.llms.openai import OpenAI

Settings.llm = OpenAI(model="gpt-4o")
messages = [
ChatMessage(
role="system", content="You are a helpful assistant."
),
ChatMessage(role="user", content="Why is the sky blue?"),
]
resp = OpenAI().chat(messages)
print(resp)

Como se puede ver, esto no es muy diferente de Langchain o cualquier API, pero la ventaja LlamaIndex tiene es su enfoque en la conexión con los datos. Como era de esperar, el código para hacer un RAG sencillo es bastante simple.

from llama_index.core import Settings, VectorStoreIndex, SimpleDirectoryReader
from llama_index.llms.openai import OpenAI

Settings.llm = OpenAI(model="gpt-4o")

documents = SimpleDirectoryReader("data").load_data()
index = VectorStoreIndex.from_documents(documents)
query_engine = index.as_query_engine()
response = query_engine.query("How has Smart Nation improved citizen's lives?")
print(response)

En primer lugar, tomamos los archivos del directorio de datos (que no es más que nuestro archivo hansard.txt) y los almacenamos en un almacén vectorial. Después usamos ese almacén vectorial como motor de consulta y le enviamos una consulta, y usará los datos de nuestro documento para formar su respuesta.

Langchain y LlamaIndex son dos potentes frameworks tras el rápido ritmo de evolución. Cada uno de ellos tiene sus propias fortalezas y en este momento, el uso de cualquiera de ellos depende principalmente de la preferencia personal.

Resumen

Esta es la primera parte de la clase que impartiré sobre programación con IA. En el próximo artículo, profundizaré en los LLM locales, es decir, los LLM que puedes implementar en tus propias máquinas, incluso en tu propio portátil.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *