• Featured post

Embeddings, Vector Search & BM25

Un ordenador no puede entender texto ni relaciónes semánticas o significados 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

C# Streams and Files

MemoryStream

Los streams en memoria se usan sobre todo para leer de ficheros, o para almacenar información que queremos guardar en ficheros.

Si se inicializa mediante el siguiente constructor no se puede cambiar su tamaño.

MemoryStream ms = new MemoryStream(150);
ms.Capacity;
ms.Length;
ms.Position;

// 0 bits from Begin
ms.Seek(0, SeekOrigin.Begin);
// 5 bits from current position
ms.Seek(5, SeekOrigin.Current);
// go back 10 bits from current position
ms.Seek(-10, SeekOrigin.Current);

Files

Ejemplo para leer y escribir un fichero.

write file

FileStream fsEscribir = new FileStream("miArchivo.txt", FileMode.Create);

string cadena = "this is an example";

fsEscribir.Write(ASCIIEncoding.ASCII.GetBytes(cadena), 0, cadena.Length);
fsEscribir.Close();

read file

byte[] infoArchivo = new byte[100];

FileStream fs = new FileStream("miArchivo.txt", FileMode.Open);
fs.Read(infoArchivo, 0, (int) fs.Length);

Console.WriteLine(ASCIIEncoding.ASCII.GetString(infoArchivo));
Console.ReadKey();

fs.Close();

Servicebus vs queue

ServiceBus

Sistema de mensajeria COMPLEJO que permite comunicacion entre MULTIPLES SERVICIOS usando patrones publication/subscription

Permite a un mensaje ser consumido por MULTIPLES CONSUMERS Ideal para flujos de trabajo complejos

Queue

Mecanismo mas simple y directo. Permite la transmision de mensajes de un punto a otro. Un mensaje enviado a la cola es recibido y procesado por un unico consumidor Se centra en GARANTIZAR LA ENTREGA del mensaje, pero es mas limitado.

C# Delegates

Un delegado es un placeholder para un método. Permite pasar una función como parámetro a un método.

Hay varios tipos de delegado:

  • Action<T> consume una clase, devuelve void Ejemplo Console.WriteLine();
  • Predicate<T> consume una clase, devuelve bool Por ejemplo para abstraer condiciones
  • Func<T, K> consume una clase y devuelve otra. Por ejemplo para abstraer mappings

Usaremos el struct Book para todos los ejemplos

public struct Book
{
	public string Title;        // Title of the book.
	public string Author;       // Author of the book.
	public decimal Price;       // Price of the book.
	public bool Paperback;      // Is it paperback?

	public Book(string title, string author, decimal price, bool paperBack)
	{
		Title = title;
		Author = author;
		Price = price;
		Paperback = paperBack;
	}
}

Action (Consumer)

Tenemos el ejemplo BookDBActionService

public class BookDBActionService
{
	// List of all books in the database
	List<Book> list = new List<Book>();

	// initialize test data on constructor
	public BookDBActionService()
	{
		list.Add(new Book("title1", "author1", 10, true));
		list.Add(new Book("title2", "author2", 20, false));
	}

	// Aqui tenemos el Action<Book> donde delegamos como se procesa cada libro
	public void ProcessPaperbackBooks(Action<Book> processBook)
	{
		foreach (Book b in list)
		{
			if (b.Paperback)
			{
				// delegate call
				processBook(b);
			}
		}
	}
}

Llamada donde ejecutamos el código y llamamos al Action<Book>

public static void Main(string[] args)
{
var bookDBAction = new BookDBActionService();
bookDBAction.ProcessPaperbackBooks(book => Console.WriteLine(book.Title));
}

Read More

Introduction to C#

Nullables

Check this post on c# nullables and how to use them

readonly

La palabra clave readonly hace que no se pueda asignar después de que el constructor acabe.

public class Age
{
	private readonly int _year;

	public Age(int year)
	{
		_year = year;
	}

	public void OverwriteYear()
	{
		_year = 1967; // Compilation error
	}
}

readonly vs const

readonly values can be computed dynamically, (!) but need to be assigned before the constructor exits (!).

In the other hand const are implicitly static.

(More info on this subject)

public class ReadonlyVsConst
{
	public const int I_VALUE = 2; // HAS TO be assigned on declaration.
	public readonly int I_RO_VALUE; // Can be assigned on the constructor.

	public ReadonlyVsConst()
	{
		I_RO_VALUE = 3;
	}
}

Read More

C# Methods

Pass by value or pass by reference

In C# you may choose how you want to pass a method’s variable.

by value example (default)

// by value
int number = 25;
PassByValue(number); 
Console.Write(number); // prints 25
public static void PassByValue(int number)
{
	// this won't take effect
	number = 12;
}

by reference example (out)

// by reference
int number;
PassByOutReference(out number);
Console.Write(number); // prints 12
public static void PassByOutReference(out int number)
{
	number = 12;
}

by reference example (ref)

// by reference (ref)
int number = -1;
PassByReference(out number);
Console.Write(number); // prints 12
public static void PassByRefReference(ref int number)
{
	number = 12;
}

out vs ref

(!) You should use out unless you explicitly need ref (!)

  • out doesn’t need the variable to be initialized first
  • ref needs the variable to be initialized first. This could confuse readers as it looks as if the initial values were relevant, but they’re not.

Read More

C# Classes

Auto properties

set vs private set

  • La variable que está como public x { get; set; } se puede llamar de manera externa e interna.
  • Mientras que la variable public x { get; private set; } se puede hacer un get de manera externa pero no un set

ejemplo

public class Person
{
	public string Name { get; set; }
	public string Surname { get; private set; }

	public Person()
	{
		Name = "ctor name";
		Surname = "ctor surname";
	}

	public void SetInternalValues()
	{
		Name = "name internal value";
		// if we'd have public string Surname { get; } without set; 
		//  then we couldn't set Surname inside its own class either 
		Surname = "surname internal value";
	}
}
var person = new Person(); 
person.Name = "name external value";
// person.Surname = "this isn't possible"; // cannot be set as we use { private set; }

Read More

Visual Studio (Code)

Shortcuts and QoL features

  • ctrl + F12 - over a method call -> go directly to the method inside the interface’s implementation
  • ctrl + - - go back where you where after entering a method
  • ctrl + G - go to line number
  • ctrl + T - search for a class. If you want to search for UserController.cs you can type UCont and it will find it
  • ctrl + shift + P - search inside Visual Studio options
  • ctrl + D - duplicate actual line
  • alt + ↑ / ↓ - move code lines

Auto-complete placeholders

  • prop - creates a new property for a class
  • ctor - creates a new constructor
  • cw - creates a new Console.WriteLine() statement
  • try - creates a try-catch statement

To create a new property, type prop, hit twice tab and it creates a property template which you can navegate and override

Multi-caret and multi-cursor editing

(check this for more details)

For lines that are aligned

  • alt + mouse click - selects a block to edit
  • alt + shift + arrow - same with keyboard

For multiple places that are not aligned

  • ctrl + alt + mouse click - click where you want to add a caret
  • (select the word you want to match) alt + shift + ; - vstudio selects all locations that match selected text in the current document and you may edit them

    Debug in VsCode

    Watch a variable

    En la pestaña watch se puede introducir el nombre de una variable y al hacer debug mostrará siempre el valor de esta variable.

Read More

Teoria Entity Framework Core

En el Program tenemos código que depende del nugget y la implementación de la base de datos que vamos a integrar.
Este usa una cadena del appsettings.json para conectar a la BBDD

"server=localhost;database=postgres;uid=postgres;password=thisisSomepassword"

Para crear una nueva entidad en la base de datos:

  • Creamos el Model pertinente (por ej.) Coche
  • Lo añadimos al ApplicationDbContext como un DbSet<Coche>
  • Añadimos una migration add-migration addedCoche
  • Ejecutamos update-database

Migration

Mecanismo para aplicar cambios en el modelo de datos de la aplicacion a la BBDD de manera incremental y versionable. Cada migracion contiene los pasos necesarios para llevar a cabo las modificaciones en la BBDD como agregar nuevas tablas, cambiar columnas existentes o eliminar indices.

Esto permite tener:

  • Despliegues consistentes: aplica mismos cambios en diferentes entornos.
  • Control de versiones
  • Evolucionar la BBDD

Read More

Entity Framework Core Scaffolding

Go to Tools > Nuget Package Manager > Package Manager Console

Adapt and paste the following code. This includes:

  • db’s connection string
  • name for the context it’s going to create
  • Where it pastes the data classes to
Scaffold-DBContext "Host=host_here;Database=database_name;Username=username_here;Password=pwd_here" Npgsql.EntityFrameworkCore.PostgreSQL -DataAnnotations -Context ContextNameHereDbContext -ContextDir Data -o Models/DB -force -NoOnConfiguring -verbose

This creates:

  • DbContext.cs class
  • all Model/Data classes

Remember to modify Startup.cs to set the service classes as AddTransient for the services which use this DbContext

services.AddTransient<SomethingService, SomethingService>(); 

Add into the service, the context you’ve just created and set it at the constructor.

private readonly ApplicationDbContext _context;

EFCore consultas solo lectura - gran volumen entidades

Cuando se van a realizar consultas de solo lectura, las cuales NO se van a usar para actualizar en la base de datos, se puede llamar al método .AsNoTracking() para mejorar la performance.
Es recomendable utilizarla para manejar grandes volumenes de entidades.

Las entidades de las queries donde se use .AsNoTracking() no se podrán actualizar, por lo que no es recomendable para ADD, UPDATE o DELETE

// operacion de solo lectura
var libros = context.Libros
	.AsNoTracking()
	.Where(l => l.Autor == "Something")
	.ToList();