• Featured post

Embeddings, Vector Search & BM25

Un ordenador no puede entender texto ni relación semántica o significado entre palabras. Solo puede entender números. Esto lo resolvemos mediante el uso de embeddings.

Un embedding es la representación de texto (en forma de números) en un espacio vectorial. Esto permite a los modelos de IA comparar y operar sobre el significado de las palabras.

flowchart TD
    A["perro"] --> B
    B --> C["[-0.003, 0.043, ..., -0.01]"]
    
    N1["(texto que queremos convertir)"]:::note --> A
    N2["(vectores con contenido semántico)"]:::note --> C
    
    classDef note fill:none,stroke:none,color:#777;    

Los vectores de cada palabra o documento capturan el significado semántico del texto.

  • perro estará cerca de mascota
  • contrato estará lejos de playa

Vector vs SQL databases

El problema con las BBDD típicas es que solo buscan matches exactos. Si yo busco por coche solo me sacará las entradas que contengan coche.

En cambio, como las BBDD vectoriales pueden interpretar la semántica de las palabras mediante los vectores, si busco por coche puede sacarme valores como sedán, SUV, Land Rover, etc.

Las BBDD vectoriales son muy buenas cuando necesitamos buscar items similares por proximidad uno respecto al otro. Un ejemplo de uso es buscar películas parecidas (Netflix). Otro ejemplo son los recomendadores de items parecidos en tiendas online (Amazon).

Como ejecutar una búsqueda (query) mediante vectores

(You can see the code here)

Necesitamos:

  • Una BBDD Vectorial (CosmosDB)
  • Un modelo para transformar los embeddings (text-embedding-3-large)

El flujo completo es el siguiente:

  1. Usar un embedding model para obtener los vectores del contenido que queremos indexar
  2. Insertar el texto original y los vectores del contenido en una BBDD vectorial
  3. Cuando queramos ejecutar una query usar el mismo embedding model de antes con la query a buscar. Con el embedding resultante buscamos vectores similares en la BBDD y sacamos el texto original de original_text

    Introducir vectores en CosmosDB

    Para poder buscar necesitamos rellenar antes la BBDD con contenido. Lo mantenemos simple. Metemos

    • un ID a mano
    • el texto original
    • los vectores resultado de hacer el embedding sobre el texto original

El pseudocódigo se ve así y se ejecuta de uno en uno

text = "A shiba walks alone in the park"
# this sends the text to the model text-embedding-3-large 
vectors = createEmbeddingsForText(text)
item = {
	"id": "1",
	"original_text": text,
	"vectors": vectors
}
uploadToCosmosDB(item)

ejemplos de los datos que guardo

{
	"id": "1",
	"original_text": "A shiba walks alone in the park",
	"vectors": [-0.003, 0.043, ..., -0.001]
}

Read More

MCP (Model Context Protocol)

Un MCP Server (Model Context Protocol) es un componente que implemente el protocolo MCP, un estándar abierto diseñado para conectar LLMs y agentes de IA con datos y herramientas externas de forma segura y estándar.

Dotan a los LLMs de capacidades adicionales permitiendo:

  • Acceso a datos en tiempo real: conectar la IA a APIs, BBDD, archivos locales…
  • Automatizaciones: permiten que los agentes no solo respondan, si no que ejecuten acciones (ejecutar flujos, crear registros, aprobar procesos)

Sin esto, los LLMs por sí solos están limitados a sus datos de entrenamiento.

(como funciona MCP - se conecta a diferentes fuentes de información independientemente de cuáles sean)

LLM Hyperparameters

Hyperparameters are configuration settings which are manually tuned to optimize efficiency or response creativity.

Los hyperparámetros son settings de configuración, los cuales se configuran manualmente para optimizar la eficiencia o la creatividad de la respuesta

Temperature (creatividad)

La temperatura controla la cuanto riesgo acepta el modelo a la hora de elegir las siguientes palabras. Controla la probabilidad de distribución de las siguientes palabras.

  • Low (0.2-0.3): El modelo es cauto y elige las palabras más probables. Output factual y predecible.
  • Medium (0.5-0.7): Un mix de confiabilidad y engagement
  • High (0.9-1.0): Toma riesgos y es impredecible

Respuesta posibles con la siguiente frase

Once upon a time, there was a dragon…

  • Temp 0.2: …who lived in a cave guarding treasure
  • Temp 0.7: …who dreamed of becoming friends with the villagers
  • Temp 1.0: …who loved baking cookies and singing karaoke

Cuanta más alta la temperatura, más imaginativo, creativo y loco es el modelo.

Top-k (candidate pool fija)

Top-k es un filtro de candidatos más probables. Fuerza al modelo a elegir el siguiente token sobre una pool k de candidatos. Límite de tamaño fijo.

Candidatos posibles con la siguiente frase

I like to drink…

  • small k (5): El modelo solo tiene unas pocas elecciones posibles, las cuales serán las más seguras posibles. Ej: [water, coffee, tea, juice, milk]
  • large k (50k): El modelo puede elegir de unas opciones muy ampliar. Amplía la variedad pero también la posibilidad de que salgan resultados inesperados Ej: […, smothies, kombucha, cocktails, hot chocolate, …]

La contra que tiene es que si el modelo está muy seguro, puede incluir tokens con una baja probabilidad solo por el hecho que tiene que llegar hasta k candidatos.

Read More

AI EU Legislation

European Regulation 2024/1689

La IA se clasifica por riesgos:

| riesgo | medidas | ejemplo | | :————-: | —————————————————————————————————————– | ————————————— | | inaceptable | prohibido | social scoring systems; manipulative AI | | alto riesgo | regulado | | | riesgo limitado | obligaciones de transparencia; los usuarios finales deben tener conocimiento que interactuan con un sistema de IA | chatbots; deepfakes | | riesgo mínimo | sin regular | juegos; filtros de spam; | Sobre todo establece obligaciones para providers (developers) de sistemas de alto riesgo que operen en EU, independientemente de dónde se encuentren localizados.

[Read more on this]([High-level summary of the AI Act EU Artificial Intelligence Act](https://artificialintelligenceact.eu/high-level-summary/))

ISO/IEC 42001

Standard internacional que especifica los requerimientos para establecer, implementar, mantener y mejorar un Artificial Intelligence Management Systen (AIMS) dentro de organizaciones.

Read more on this

LangChain

LangChain es un framework open-source implementado en Python. Es un orquestador de aplicaciones basadas en LLMs que proporciona diferentes abstracciones de alto nivel para construir pipelines sobre LLMs.

La gran ventaja que nos introduce es abstraernos de la manera de consumir los diferentes LLMs para no tener que reinventar el código cada vez que queramos cambiar de modelo.

Ahora mismo LangChain dispone de 6 módulos diferentes

  • Model I/O - Handle input/output ops related to the model
  • Retrieval - Retrieves relevant texts for the LLM
  • Chains (Runnables) - Enables construction of sequences of LLM operations or function calls
  • Agents - Allows chains to make decisions on which tools to use based on high-level instructions
  • Memory - perstist the state of an application between different runs of a chain
  • Callbacks - for running additional code on specific events

Install

poetry add langchain
poetry add langchain-openai
poetry add openai # this is in case we want to use OpenAI's models

ChatModels

We have the following classes to interact with LLMs in the context of LangChain

  • SystemMessage - instructions for the AI System (Behaviour)
  • HumanMessage - messages coming from an human to interact with the LLM (questions, commands…)
  • AIMessage - information coming from the AI itself. This is typically the AI’s response

Chunking techniques (RAG)

En el contexto de RAG, chunking es el proceso de partir la información que contienen nuestros documentos en pedazos digestibles. Estos chunks los usaremos después para hacer queries contra ellos.

A veces si parece que no obtenemos los resultados que queremos de nuestras queries no es por falta de información, si no por un mal chunking. Si hacemos una query y por cómo hemos partido nuestros documentos tenemos la pregunta en un chunk y partes de la respuesta divididas entre múltiples chunks sin solapamiento, no obtendremos buenas respuestas.

La idea es simple, el proceso no tanto. Hay varias técnicas según lo que nos queramos complicar y los resultados que obtenemos varían entre ellas.

Chunking estático

El más simple. Tenemos un documento y partimos cada (por ejemplo) 500 tokens con 100 tokens de solapamiento entre chunks.

Tenemos la siguiente frase de prueba

La casa grande es bonita aunque cara, pero tiene un buen garaje. En el futuro quiero comprarme un coche

Visualizando los tokens a nivel de subword con test-multilingual-embedding me salen con 28 tokens aprox. ![[Pasted image 20260306132554.png]]

Si establecemos un limite de 10 tokens por chunk, con 3 de solapamiento, cada chunk se podría ver así:

La casa grande es bonita aunque cara
aunque cara, pero tiene un buen garaje
buen garaje. En el futuro quiero comprar
quiero comprarme un coche

El problema es que puede cortar frases a media palabra según como coincida ya que la palabra bonita son dos tokens bonit y a

Esto se puede aliviar un poco con librerías de chunking inteligente que existen que parten usando separadores como espacios o saltos de línea, pero el contexto se puede perder mucho si cae entre varios chunks.

Chunking semántico

Una técnica que solventa esto es hacer el chunking mediante un modelo de embedding, el cual parte los chunks por contenido semántico cuando detecta que el tema principal cambia. Es más caro porque un modelo tiene que analizar los documentos, pero da mejores resultados.

De esta manera el mismo texto nos quedaría en dos chunks que aunque tienen distinto tamaño, están mucho más contenidos, por lo que a la hora de buscar con una query nos dará mejores resultados.

La casa grande es bonita aunque cara, pero tiene un buen garaje.
En el futuro quiero comprarme un coche

Limpieza

Limpiar los chunks es tan importante como el dónde partir. En mis primeras pruebas se metía mucho ruido en cosmos por como \n o \t o chunks que solo contenían un número sin contexto ninguno.

Para sistemas grandes o genéricos puede ser importante meter metadatos junto a los chunks para filtrar luego al buscar. Cosas como el nombre del documento al que pertenece un chunk o la fecha de creación del documento ayudan a filtrar luego y favorecer documentación reciente o saber de dónde viene algún dato erróneo o con menor calidad.

RAG implementation

El código para mi implementación está aquí en GitHub

Requerimientos

  • LangChain
  • Un modelo de embeddings (text-embedding-3-large)
  • Un modelo de chat (gpt-4.1)
  • Una base de datos vectorial (CosmosDB)
  • PyPDF (libreria para chunking inteligente)
  • Documentos como base de conocimiento para el RAG (documentos sobre una herramienta propietaria)

Sobre CosmosDB for NoSQL: Se puede usar como una base NoSQL tradicional pero también soporta búsqueda nativa con vectores (hay que habilitarlo primero y establecer una policy vectorial). Yo la uso en vez de ChromaDB por estar en Azure y ser entorno Microsoft ya que personalmente me entra dentro del tier gratuito. Si no, ChromaDB es una buena opción gratuita.

Implementación

Se divide en tres procesos:

Habilitar búsqueda vectorial para CosmosDB. Tras hacer el siguiente paso, podemos ejecutar el script para recrear el contenedor con una policy para vectores

(dentro de CosmsoDB) Settings < Features < Vector Search for NoSQL API < Enable

Cargar documentos en CosmosDB:

  • Cargamos los documentos en memoria
  • Los dividimos en chunks
  • Limpiamos los chunks de caracteres especiales (\n, \t, \r)
  • Creamos los embeddings en batches
  • Para cada chunk subimos el original y su embeddings

Realizar una query:

  • Obtenemos los embeddings para la query (tiene que ser el mismo modelo que se usó para crear los embeddings de los ficheros)
  • Hacer una búsqueda vectorial en CosmosDB para sacar el texto original de los chunks con contexto relevante a nuestra query
  • Invocar el modelo de chat a través de LangChain, pasándole ambos pregunta y chunks con contexto en la misma llamada

Conceptos aprendidos

Calidad del dato

La calidad del dato es de máxima importancia. Hay que revisar manualmente los datos que se meten y ver que sean aporten valor. Si metemos datos que no aporten, solo generamos ruido.

También hay que revisar que los PDFs sean texto puro, ya que si son puramente imágenes o contienen imágenes importantes habrá que pasarlos por algún tipo de OCR para extraer texto.

Chunking

La parte de dónde y cómo hacer chunking es complicada. Lo importante es que haya un buen overlap entre chunks para que la respuesta a tu pregunta no caiga en tierra de nadie. Yo estoy probando con un chunk_size de 500 tokens y un overlap de 100 tokens.

Limpiar los chunks después de partirlos es igual de importante. En mis primeras pruebas se metían en cosmos muchos caracteres como \n o \t y esto genera mucho ruido.

Para sistemas grandes o genéricos puede ser importante meter metadatos junto a los chunks para filtrar luego al buscar. Cosas como el nombre del documento al que pertenece un chunk o la fecha de creación del documento ayudan a filtrar luego y favorecer documentación reciente o saber de dónde viene algún dato erróneo o con menor calidad.

Debilidades BBDD Vectorial

Una base de datos vectorial es muy buena para hacer búsquedas relacionadas o búsquedas semánticas, pero se queda corta para hacer búsquedas por keywords. En esas situaciones se puede implementar una búsqueda híbrida (algoritmo BM25).

Referencia(s)

RAG Systems in 5 Levels of Difficulty (With Full Code) | Data Science Collective *RAG vs Fine Tuning. The Great LLM Showdown | by Agneya Pathare | Medium

AI Data Grouding (RAG vs Fine Tuning)

TLDR:

  • RAG: complementar el prompt referenciando conocimiento concreto o actualizado
  • Fine-tuning: reentrenar el modelo para que cambie su estilo, tono o formato

El objetivo de customizar los modelos es mejorar aspectos de su performance, calidad y/o seguridad de sus respuestas.

Data Grounding

Proceso de enriquecer una respuesta generada por IA con datos externos o específicos para mejorar su calidad y/o seguridad de sus respuestas. Asegura que el output del modelo está alineado con datos factuales, contextuales y que son confiables.

Data Grounding no es una técnica, es el objetivo.

Para responder al prompt la IA no se basará sólo y únicamente en los datos de su entrenamiento, si no que se le proporciona información adicional de una fuente externa (documentos, BBDD, APIs, etc.) para que sus respuestas estén basadas en datos reales y actualizados y no se los invente ni alucine.

RAG (Retrieval Augmented Generation)

Técnica de data grounding. Enriquece el conocimiento de un modelo conectándolo a bases de conocimiento externas. En lugar de confiar solo en el conocimiento entrenado del modelo, busca datos relevantes en tiempo real y los usa como contexto para generar la respuesta.

Por ej: BBDD propietarias, documentos internos de la compañía…

Casos de uso

  • Cuando necesites respuestas basadas en información muy reciente (hoy, news, current stock prices)
  • Cuando necesites información de fuentes de datos privadas o propietarias (documentos, internal company policies, support logs)
  • Cuando quieras que la IA cite sus fuentes

Ejemplo

Si le pregunto a un asistente cualquiera ¿Cuánto dinero puedo pedir en gastos de desplazamiento? Seguramente me responderá que varía según mi empresa y sus políticas

Si al mismo modelo le aporto la documentación de la empresa como RAG y le pregunto lo mismo, el modelo irá al apartado correspondiente y lo consultará por mí, dándome como respuesta puedes pedir hasta x, según las fuentes y

Read More

C# parallelize code

Example on how to write parallel code

public List<string> ProcessX(CancellationToken cancToken = default)
{
	// sequential code
	// ...
	
	// list that we want to be able to process faster
	List<Animal> animals = // ... 
	
	// multithread safe collection
	var animalsDTO = new ConcurrentBag<AnimalDTO>();
	var options = new ParallelOptions
	{
		CancellationToken = cancToken,
		MaxDegreeOfParallelism = GetMaxDegreeOfParallelism()
	};
	
	Parallel.ForEach(animals, options, animal => 
	{
		// process single animal
		// this is just an example to map animal to animalDTO
		// ...
		animalsDTO.Add(Map(animal));
	});
}

private int GetMaxDegreeOfParallelism()
{
	string configured = config["ANIMALS_PARALLELISM"] ?? "4";
	int degree;
	if(!int.TryParse(configured, out degree))
	{
		degree = 4;
	}
	return degree;
}

Install Poetry Script Globally

# only the first time we install something 
pipx ensurepath
# close and open terminal again

# build, install and run to make sure it works
poetry build
poetry install
poetry run

# install localy with pipx
pipx install .

# now we're able to run it from anywhere
file-enlarger

in this case we invoke it as file-enlarger as the .toml declares it as such

[tool.poetry.scripts]
file-enlarger = "FileEnlarger:main"

Read More