• 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

Validaciones C#

C# tiene los DataAnnotation

[Required] hace que sea un campo obligatorio

[Required(ErrorMessage = "Nombre es obligatorio")]
public string NombreCategoria { get; set; }

[Required(ErrorMessage = "Orden es obligatorio")]
[Range(1, int.MaxValue, ErrorMessage = "El orden debe de ser mayor a cero")]
public int Orden {get; set; }

Se controla mediante el siguiente codigo en el controller. El código de ModelState es código base de un Controller.

if(ModelState.IsValid) 
{
	// code if everything's valid
}

C# coding style

Interfaces must start by capital I

public interface IDataService 
{ 
	public Task SendData(DataModel model);
}

We use PascalCase for:

  • classes’ name
  • methods’ name
  • public variables

We use CamelCase for:

  • private or internal field names (they must include the prefix _)
  • methods’ paramters
public class DataService
{
	const int TAX = 7;
	
	public bool IsValid { get; private set; };
	private IWorkerQueue _workerQueue;
	
	public async Task SendData(DataModel model)
	{
		string someValue = "";
		// ... whatever
	}
}

async methods must end by Async

public async Task<string> GetUrlAsync()
{
	// ... whatever
}

Reference(s)

https://learn.microsoft.com/es-es/dotnet/csharp/fundamentals/coding-style/identifier-names

Introducción a .NET

CLR (Common language runtime)

Entorno de ejecucion para .NET. En tiempo de ejecucion el compilador de CLR convierte el codigo CIL en codigo nativo para el SO. Facilita la integración entre lenguajes.

CLR es la MV en la que se ejecutan nuestras apps. CLR se hizo para tener una capa de abstraccion entre las propias apps y el SO donde se ejecutaban.

El CLI se puede ejecutar en otros SO. El CLR se ejecuta solo en Windows.

.NET intro

Read More

.NET launchsettings vs appsettings

launchSettings

NO se despliega. Afecta a tu entorno local.

En él establecemos los perfiles con los que ejecutaremos nuestro proyecto y se usa para definir la variable ASPNETCORE_ENVIRONMENT. También permite establecer la url y puertos de ejecución.

{
  "profiles": {
    "my.project.namespace": {
      "commandName": "Project",
      "launchBrowser": false,
      "applicationUrl": "https://localhost:5011;http://localhost:5010",
	  "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    }
  }
}

ASPNETCORE_ENVIRONMENT Indica el entorno - soporta los siguientes valores

  • Development
  • Staging
  • Production valor por defecto si se omite el valor

appsettings

Se utiliza para almacenar la configuración de la aplicación como por ej. cadenas de conexión de BBDD.
Se utiliza tanto en entornos de desarrollo como de producción.

{
	"LocalDirectory": "/opt/data-download",
	"MyServiceConfig": {
		"Uri": "http://localhost:8800",
		"Endpoint": "/some-endpoint",
		"Timeout": 30
	}
}

appsettings.{Environment}.json

Son archivos adicionales opcionales, donde {Environment} corresponde al valor de la variable ASPNETCORE_ENVIRONMENT del launchSettings.json.

El orden de carga es:

  1. appsettings.json
  2. appsettings.{Environment}.json (si existe)
  3. launchSettings.json

Hay varias maneras de leer la configuración.

leer de la raiz

Las que se encuentran en la raiz las podemos leer inyectando la config en la clase.

public class MyService(IConfiguration _config) : IMyService
{
	public void MyMethod()
	{
		var localDirectory = _config["LocalDirectory"];
	}
}

leer de una clase de config custom

por un lado tenemos la clase de config

public class MyServiceConfig
{
	public const string Section = "MyServiceConfig";

	public string Uri { get; set; }
	public string Endpoint { get; set; }
	public int Timeout { get; set; }
}

y por otro lado la inyectamos

public class MyService(MyServiceConfig _config) : IMyService
{
	public void MyMethod()
	{
		var uri = _config.Uri;
		var endpoint = _config.Endpoint;
		var timeout = _config.Timeout;
	}
}

la tendremos que poner también en el Startup

public void ConfigureServices(IServiceCollection services)
{
	// ...
	services.Configure<MyServiceConfig>(config.GetSection(MyServiceConfig.Section));
	// ...
}

Buenas prácticas

Usar appsettings.json como base y en cada appsettings.{Environment].json poner solo los datos que cambien.

No poner secretos en appsettings.json ni sus variantes:

  • En local (para development) utilizar User secrets
  • Para los entornos con Kubernetes inyectar secretos via ConfigMaps o Secrets

Reference(s)

https://learn.microsoft.com/es-es/aspnet/core/fundamentals/environments?view=aspnetcore-8.0
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?view=aspnetcore-8.0

.NET Middleware

Middleware is software assembled into a pipeline to handle request and responses. Each component:

  • Chooses if he passes the request to the next component.
  • Can perform work before and after the next component.

Common use cases:

  • Logging
  • Authentication & Authorization
  • Request/Response processing
  • Caching
  • Error handling

.NET middleware

Implementation w. DI

For more details check this project

We create our own custom middleware with an internal dependency, which is the class it’s going to process before/after our request.

// interface and its class, which do the processing
public interface IMiddlewareService
{
	Task ProcessRequest(string request);
}

// interface implementation
public class MiddlewareService : IMiddlewareService
{
	public async Task ProcessRequest(string request)
	{
		// do something with the request
		Console.WriteLine(request);
	}
}

Then we have our middleware class as such

public class CustomMiddleware(IMiddlewareService _service) : IMiddleware
{
	public async Task InvokeAsync(HttpContext context, RequestDelegate next)
	{
		// custom logic to be executed BEFORE next middleware
		await _service.ProcessRequest("middleware processing request");
		await next(context);
		// custom logic to be executed AFTER next middleware
	}
}

Declaration inside Startup.cs

public void ConfigureServices(IServiceCollection services)
{
	// ... whatever

	// register both: our custom middleware, and its internal dependency
	services.AddTransient<IMiddlewareService, MiddlewareService>();
	services.AddTransient<CustomMiddleware>();
	
	// ... whatever else
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
	// ... whatever

	// declaration to use middleware
	app.UseMiddleware<CustomMiddleware>();
	// if we use this, the middleware declaration has to be BEFORE or else it won't work
	app.UseEndpoints(endpoints => 
	{
		endpoints.MapControllers();
	})

	// ... whatever else
}

Reference(s)

https://medium.com/@dushyanthak/best-practices-for-writing-custom-middlewares-in-asp-net-core-97b58c50cf9c
https://learn.microsoft.com/es-es/aspnet/core/fundamentals/middleware/write?view=aspnetcore-8.0
https://sardarmudassaralikhan.medium.com/custom-middleware-in-asp-net-core-web-api-70c2ffbbc095

Test APIs with Postman - Scripting

We can set pre-request scripts (run before the request) & tests (after execution) at several levels:

  • Collection
  • Folder
  • Request

Snippets

Inside pre-request Script and Tests we have a SNIPPETS column with templates we may use for our code.

postman scripting

Get / Set variables

console.log("Hello world");

// work with local vars
let urlVar = pm.variables.get("protocol");
console.log("value for protocol: " + urlVar);

pm.variables.set("protocol", "http");
console.log(pm.variables.get("protocol"));  

// work with global vars
let globalVar = pm.globals.get("env");
console.log(globalVar);

Read More

Test APIs with Postman - GUI

To test postman I use https://reqres.in. You can use it to learn how to write Postman tests.

Collections

Collections have metadata which you can set up. This includes:

  • Authorization so you don’t need to set it for every single request.
  • Variables, for variables only for this collection (so you don’t use environment vars).
  • Collection tests.

postman gui

Read More

Error al crear imagenes en local (Minikube)

Hay un problema al intentar correr una imagen tuya propia en local con minikube. Minikube tiene su propio repositorio de imagenes y si no encuentra una imagen alli, la intenta descargar siempre del repositorio.

Solución

Set imagePullPolicy: IfNotPresent

error crear imagenes minikube en local

Run the following command:

eval $(minikube docker-env)
# build again the image
# try to run it again

Reference(s)

https://medium.com/bb-tutorials-and-thoughts/how-to-use-own-local-doker-images-with-minikube-2c1ed0b0968

K8s config commands

Config

Azure

log-in into a tenant

az login --tenant 7f2c3a6b-e849-4d6f-b65d-1c0ef8b0f41c

configure local kubectl .kube config file

az aks get-credentials --resource-group my-aks --name aksname --admin

(the –admin part is optional but useful)

Contexts & Namespaces

see all configured contexts

kubectl config get-contexts

configure a context with a default namespace

kubectl config set-context <context_here> --namespace=<namespace_here>
# example
kubectl config set-context my-aks-context --namespace=aks-dev

Read More