Las bases de datos vectoriales han revolucionado la forma en que buscamos y recuperamos información al permitirnos incrustar datos y buscar rápidamente sobre ellos utilizando el mismo modelo de embedding, con solo introduciendo la consulta en tiempo de inferencia. Sin embargo, a pesar de sus impresionantes capacidades, las bases de datos vectoriales tienen un defecto fundamental: tratan las consultas y los documentos de la misma manera. Esto puede llevar a resultados subóptimos, especialmente cuando se trata de tareas complejas como la de emparejamiento, donde las consultas y los documentos son inherentemente diferentes.

El desafío del Task-aware RAG (Generación Mejorada de Recuperación) radica en su necesidad de recuperar documentos no solo basándose en su similitud semántica, sino también en instrucciones contextuales adicionales. Esto añade una capa de complejidad al proceso de recuperación, ya que debe considerar múltiples dimensiones de relevancia.

Tabla de contenidos

Algunos ejemplos de problemas Task-Aware RAG:

  1. Relacionar los problemas de la empresa con los candidatos
  • Consulta: «Encontrar candidatos con experiencia en el diseño de sistemas escalables y un historial probado en la optimización de bases de datos a gran escala, adecuados para abordar nuestro reto actual de mejorar la velocidad de recuperación de datos en un 30% dentro de la infraestructura existente.»
  • Contexto: Esta consulta pretende conectar directamente el reto técnico específico de una empresa con los posibles candidatos a un puesto de trabajo que tengan las habilidades y la experiencia pertinentes.
  1. Correspondencia de pseudodominios con descripciones de puesta en marcha
  • Consulta: «Emparejar un pseudodominio para una startup especializada en plataformas de aprendizaje personalizadas e impulsadas por IA para estudiantes de secundaria, haciendo hincapié en las tecnologías de aprendizaje interactivo y adaptativo.»
  • Contexto: Diseñado para encontrar un nombre de pseudodominio apropiado y pegadizo que refleje el enfoque innovador y educativo de la startup. Un pseudodominio es un nombre de dominio basado en una pseudopalabra, que es una palabra que parece real pero no lo es.
  1. Búsqueda de inversores
  • Consulta: «Identificar inversores interesados en startups biotecnológicas en fase inicial, centradas en medicina personalizada y con historial de apoyo a rondas semilla en el sector sanitario.»
  • Contexto: Esta consulta busca emparejar startups en el campo de la biotecnología, en particular las que trabajan en medicina personalizada, con inversores que no sólo estén interesados en la biotecnología, sino que también hayan invertido previamente en etapas y sectores similares.
  1. Recuperación de tipos específicos de documentos
  • Consulta: «Recuperar artículos de investigación y estudios de casos recientes que traten sobre la aplicación de la tecnología blockchain en la seguridad de los sistemas de votación digital, centrándose en soluciones probadas en las elecciones estadounidenses o europeas.»
  • Contexto: Especifica la necesidad de conocimientos académicos y prácticos sobre un uso concreto de blockchain, destacando la importancia de la relevancia geográfica y las aplicaciones recientes.

El reto

Consideremos un escenario en el que una empresa se enfrenta a varios problemas y queremos emparejar estos problemas con los candidatos más relevantes que tengan las habilidades y la experiencia para abordarlos. He aquí algunos ejemplos de problemas:

  1. «La alta rotación de empleados está provocando una reevaluación de los valores fundamentales y los objetivos estratégicos».
  2. «La percepción de opacidad en la toma de decisiones está afectando a los niveles de confianza dentro de la empresa».
  3. «La falta de compromiso en las sesiones de formación a distancia indica la necesidad de ofrecer contenidos más dinámicos».

Podemos generar candidatos positivos verdaderos y negativos duros para cada problema utilizando un LLM. Por ejemplo:

problem_candidates = {
"High employee turnover is prompting a reassessment of core values and strategic objectives.": {
"True Positive": "Initiated a company-wide cultural revitalization project that focuses on autonomy and purpose to enhance employee retention.",
"Hard Negative": "Skilled in rapid recruitment to quickly fill vacancies and manage turnover rates."
},
# … (more problem-candidate pairs)
}

A pesar de que los «hard engatives» pueden parecer similares a primera vista y podrían estar más cerca de la consulta en el espacio de embedding, los «true positive» son claramente más adecuados para abordar los problemas específicos.

  • La solución: Embeddings, reordenación y LLM adaptados a las instrucciones.

Para hacer frente a este reto, proponemos un enfoque en varios pasos que combina instruction-tuned embeddings, reordenación y LLM:

1. Instruction-Tuned Embeddings

Las función instruction-Tuned Embeddings funciona como un bicodificador, en el que los embeddings de la consulta y del documento se procesan por separado y luego se comparan sus embeddings. Al proporcionar instrucciones adicionales a cada embedding, podemos llevarlas a un nuevo espacio de embedding en el que pueden compararse con mayor eficacia.

La principal ventaja de este tipo de embeddings es que nos permiten codificar instrucciones o contextos específicos en los propios embeddings. Esto es especialmente útil cuando se trata de tareas complejas, como la comparación entre descripciones de puestos de trabajo y currículos, en las que las consultas (descripciones de puestos de trabajo) y los documentos (currículos) tienen estructuras y contenidos diferentes.

Al añadir instrucciones específicas de la tarea a las consultas y los documentos antes de incrustarlos, podemos guiar teóricamente al modelo de embedding para que se centre en los aspectos relevantes y capture las relaciones semánticas deseadas. Por ejemplo:

documents_with_instructions = [
"Represent an achievement of a job candidate achievement for retrieval: " + document
if document in true_positives
else document
for document in documents
]

Esta instrucción hace que el modelo embedding represente los documentos como logros de candidatos a un puesto de trabajo, lo que los hace más adecuados para la recuperación basada en la descripción del puesto dada.

Aún así, los sistemas RAG son difíciles de interpretar sin pruebas, así que vamos a escribir algo de código para comprobar la precisión de tres enfoques diferentes:

  1. Naive Voyage AI instruction-tuned embeddings sin instrucciones adicionales
  2. Voyage AI instruction-tuned embeddings con contexto adicional para la consulta y el documento.
  3. Voyage AI sin instruction-tuned embeddings.

Utilizamos Voyage AI embeddings porque actualmente son las mejores de su clase y, en el momento de escribir este artículo, se encuentran cómodamente en lo más alto de la clasificación de MTEB.

También podemos utilizar tres estrategias diferentes con vectores del mismo tamaño, lo que facilitará su comparación. Las dimensiones de 1024 también resultan ser mucho más pequeñas que las de cualquier otro modelo que se acerque siquiera a rendir tan bien.

En teoría, deberíamos ver que las instruction-tuned embeddings rinden mejor en esta tarea que las non-instruction-tuned embeddings, aunque sólo sea porque están más arriba en la clasificación. Para comprobarlo, primero incrustaremos nuestros datos.

Cuando lo hagamos, intentaremos añadir la cadena: «Representa la experiencia más relevante de un candidato a un puesto de trabajo para su recuperación: « a nuestros documentos, lo que da a nuestros embeddings un poco más de contexto sobre nuestros documentos.

Si quieres seguirnos, echa un vistazo a este enlace de colab.

import voyageai  vo = voyageai.Client(api_key="VOYAGE_API_KEY")
problems = []
true_positives = []
hard_negatives = []
for problem, candidates in problem_candidates.items():
problems.append(problem)
true_positives.append(candidates["True Positive"])
hard_negatives.append(candidates["Hard Negative"])

documents = true_positives + hard_negatives
documents_with_instructions = ["Represent the most relevant experience of a job candidate for retrieval: " + document for document in documents]

batch_size = 50

resume_embeddings_naive = []
resume_embeddings_task_based = []
resume_embeddings_non_instruct = []

for i in range(0, len(documents), batch_size):
resume_embeddings_naive += vo.embed(
documents[i:i + batch_size], model="voyage-large-2-instruct", input_type='document'
).embeddings

for i in range(0, len(documents), batch_size):
resume_embeddings_task_based += vo.embed(
documents_with_instructions[i:i + batch_size], model="voyage-large-2-instruct", input_type=None
).embeddings

for i in range(0, len(documents), batch_size):
resume_embeddings_non_instruct += vo.embed(
documents[i:i + batch_size], model="voyage-2", input_type='document' # we are using a non-instruct model to see how well it works
).embeddings

A continuación, insertamos nuestros vectores en una base de datos de vectores. No necesitamos estrictamente una para esta demostración, pero una base de datos de vectores con capacidades de filtrado de metadatos permitirá un código más limpio, y para eventualmente escalar esta prueba.

Utilizaremos KDB.AI, de la que soy Developer Advocate. Sin embargo, cualquier base de datos vectorial con capacidades de filtrado de metadatos funcionará perfectamente.

Para empezar con KDB.AI, ve a kdb.ai para obtener tu endpoint y api key.

A continuación, vamos a instanciar el cliente e importar algunas bibliotecas.

!pip install kdbai_client

import os
from getpass import getpass
import kdbai_client as kdbai
import time

Conectar a nuestra sesión con nuestro endpoint y clave api.

KDBAI_ENDPOINT = (
os.environ["KDBAI_ENDPOINT"]
if "KDBAI_ENDPOINT" in os.environ
else input("KDB.AI endpoint: ")
)
KDBAI_API_KEY = (
os.environ["KDBAI_API_KEY"]
if "KDBAI_API_KEY" in os.environ
else getpass("KDB.AI API key: ")
)

session = kdbai.Session(api_key=KDBAI_API_KEY, endpoint=KDBAI_ENDPOINT)

Crear nuestra tabla:

schema = {
"columns": [
{"name": "id", "pytype": "str"},
{"name": "embedding_type", "pytype": "str"},
{"name": "vectors", "vectorIndex": {"dims": 1024, "metric": "CS", "type": "flat"}},
]
}

table = session.create_table("data", schema)

Insertar las realizaciones candidatas en nuestro índice, con un filtro de metadatos «embedding_type» para separar nuestros embeddings:

import pandas as pd
embeddings_df = pd.DataFrame(
{
"id": documents + documents + documents,
"embedding_type": ["naive"] * len(documents) + ["task"] * len(documents) + ["non_instruct"] * len(documents),
"vectors": resume_embeddings_naive + resume_embeddings_task_based + resume_embeddings_non_instruct,
}
)

table.insert(embeddings_df)

Y, por último, evaluar los tres métodos anteriores:

import numpy as np

# Function to embed problems and calculate similarity
def get_embeddings_and_results(problems, true_positives, model_type, tag, input_prefix=None):
if input_prefix:
problems = [input_prefix + problem for problem in problems]
embeddings = vo.embed(problems, model=model_type, input_type="query" if input_prefix else None).embeddings

# Retrieve most similar items
results = []
most_similar_items = table.search(vectors=embeddings, n=1, filter=[("=", "embedding_type", tag)])
most_similar_items = np.array(most_similar_items)
for i, item in enumerate(most_similar_items):
most_similar = item[0][0] # the fist item
results.append((problems[i], most_similar == true_positives[i]))
return results

# Function to calculate and print results
def print_results(results, model_name):
true_positive_count = sum([result[1] for result in results])
percent_true_positives = true_positive_count / len(results) * 100
print(f"\n{model_name} Model Results:")
for problem, is_true_positive in results:
print(f"Problem: {problem}, True Positive Found: {is_true_positive}")
print("\nPercent of True Positives Found:", percent_true_positives, "%")

# Embedding, result computation, and tag for each model
models = [
("voyage-large-2-instruct", None, 'naive'),
("voyage-large-2-instruct", "Represent the problem to be solved used for suitable job candidate retrieval: ", 'task'),
("voyage-2", None, 'non_instruct'),
]

for model_type, prefix, tag in models:
results = get_embeddings_and_results(problems, true_positives, model_type, tag, input_prefix=prefix)
print_results(results, tag)

Aquí están los resultados:

naive Model Results:
Problem: High employee turnover is prompting a reassessment of core values and strategic objectives., True Positive Found: True
Problem: Perceptions of opaque decision-making are affecting trust levels within the company., True Positive Found: True
...
Percent of True Positives Found: 27.906976744186046 %

task Model Results:
...
Percent of True Positives Found: 27.906976744186046 %

non_instruct Model Results:
...
Percent of True Positives Found: 39.53488372093023 %

El modelo de embeddings obtuvo peores resultados en esta tarea.

Nuestro conjunto de datos es lo suficientemente pequeño como para que la diferencia no sea significativa (menos de 35 ejemplos de alta calidad).

Aun así, esto demuestra que:

  • Los modelos de instrucción por sí solos no bastan para enfrentarse a esta difícil tarea.
  • Aunque los modelos de instrucciones pueden ofrecer un buen rendimiento en tareas similares, es importante realizar siempre evaluaciones, porque en este caso sospechaba que lo harían mejor, pero no fue así.
  • Hay tareas en las que los modelos de instrucción obtienen peores resultados.

2. Reclasificación

Aunque los modelos de embedding por instrucciones/regulares pueden reducir un poco el número de candidatos, es evidente que necesitamos algo más potente que comprenda mejor la relación entre los documentos.

Después de obtener los resultados iniciales utilizando embeddings ajustados a las instrucciones, empleamos un codificador cruzado (reranker) para refinar aún más las clasificaciones. El reranker tiene en cuenta el contexto y las instrucciones específicas, lo que permite realizar comparaciones más precisas entre la consulta y los documentos recuperados.

La reordenación es crucial porque nos permite evaluar la relevancia de los documentos recuperados de una forma más matizada. A diferencia de la fase inicial de recuperación, que se basa únicamente en la similitud entre la consulta y los documentos, la reordenación tiene en cuenta el contenido real de la consulta y los documentos.

Al procesar conjuntamente la consulta y cada documento recuperado, el reranker puede captar las relaciones semánticas finas y determinar las puntuaciones de relevancia con mayor precisión. Esto es especialmente importante en situaciones en las que la recuperación inicial puede devolver documentos similares a nivel superficial, pero que no son realmente relevantes para la consulta específica.

He aquí un ejemplo de cómo podemos realizar el reranking utilizando el reranker de Cohere AI (Voyage AI también tiene un excelente reranker, pero cuando escribí este artículo el de Cohere lo superaba. Desde entonces han sacado un nuevo reranker que según sus benchmarks internos rinde igual o mejor).

En primer lugar, vamos a definir nuestra función reranking. También podemos utilizar el cliente Python de Cohere, pero elegí utilizar la API REST porque parecía correr más rápido.

import requests
import json

COHERE_API_KEY = 'COHERE_API_KEY'

def rerank_documents(query, documents, top_n=3):

# Prepare the headers
headers = {
'accept': 'application/json',
'content-type': 'application/json',
'Authorization': f'Bearer {COHERE_API_KEY}'
}

# Prepare the data payload
data = {
"model": "rerank-english-v3.0",
"query": query,
"top_n": top_n,
"documents": documents,
"return_documents": True
}

# URL for the Cohere rerank API
url = 'https://api.cohere.ai/v1/rerank'

# Send the POST request
response = requests.post(url, headers=headers, data=json.dumps(data))

# Check the response and return the JSON payload if successful
if response.status_code == 200:
return response.json() # Return the JSON response from the server
else:
# Raise an exception if the API call failed
response.raise_for_status()

Ahora, evaluemos nuestro reranker. Veamos también si añadir contexto adicional sobre nuestra tarea mejora el rendimiento.

import cohere

co = cohere.Client('COHERE_API_KEY')
def perform_reranking_evaluation(problem_candidates, use_prefix):
results = []

for problem, candidates in problem_candidates.items():
if use_prefix:
prefix = "Relevant experience of a job candidate we are considering to solve the problem: "
query = "Here is the problem we want to solve: " + problem
documents = [prefix + candidates["True Positive"]] + [prefix + candidate for candidate in candidates["Hard Negative"]]
else:
query = problem
documents = [candidates["True Positive"]]+ [candidate for candidate in candidates["Hard Negative"]]

reranking_response = rerank_documents(query, documents)
top_document = reranking_response['results'][0]['document']['text']
if use_prefix:
top_document = top_document.split(prefix)[1]

# Check if the top ranked document is the True Positive
is_correct = (top_document.strip() == candidates["True Positive"].strip())
results.append((problem, is_correct))
# print(f"Problem: {problem}, Use Prefix: {use_prefix}")
# print(f"Top Document is True Positive: {is_correct}\n")

# Evaluate overall accuracy
correct_answers = sum([result[1] for result in results])
accuracy = correct_answers / len(results) * 100
print(f"Overall Accuracy with{'out' if not use_prefix else ''} prefix: {accuracy:.2f}%")

# Perform reranking with and without prefixes
perform_reranking_evaluation(problem_candidates, use_prefix=True)
perform_reranking_evaluation(problem_candidates, use_prefix=False)

Ahora, aquí están nuestros resultados:

Overall Accuracy with prefix: 48.84% 
Overall Accuracy without prefixes: 44.19%

Añadiendo contexto adicional sobre nuestra tarea, podría mejorarse el rendimiento del reordenador. También observamos que nuestro reordenador obtiene mejores resultados que todos los modelos de embeddings, incluso sin contexto adicional, por lo que debería añadirse al proceso. Aun así, nuestro rendimiento no alcanza el 50% de precisión (recuperamos el primer resultado en menos del 50% de las consultas).

Lo mejor de los rerankers es que funcionan de forma inmediata, pero podemos utilizar nuestro golden dataset (nuestros ejemplos con hard negatives) para ajustar nuestro reranker y hacerlo mucho más preciso. Esto puede mejorar mucho el rendimiento de nuestro reranking, pero es posible que no se generalice a distintos tipos de consultas, y afinar un reranker cada vez que cambian nuestras entradas puede ser frustrante.

3. LLMs

En los casos en los que la ambigüedad persiste incluso después de una nueva clasificación, se pueden aprovechar los LLM para analizar los resultados recuperados y proporcionar un contexto adicional o generar resúmenes específicos.

Los LLM, como GPT-4, tienen la capacidad de comprender y generar texto similar al humano basándose en el contexto dado. Al introducir los documentos recuperados y la consulta en un LLM, podemos obtener información más matizada y generar respuestas personalizadas.

Por ejemplo, podemos utilizar un LLM para resumir los aspectos más relevantes de los documentos recuperados en relación con la consulta, destacar las cualificaciones o experiencias clave de los candidatos a un puesto de trabajo, o incluso generar comentarios o recomendaciones personalizados basados en los resultados del emparejamiento.

Esto está muy bien porque se puede hacer después de pasar los resultados al usuario, pero ¿qué pasa si queremos reordenar docenas o cientos de resultados? El contexto de nuestro LLM se verá sobrepasado, y tardaremos demasiado en obtener nuestra salida. Esto no significa que no debamos utilizar un LLM para evaluar los resultados y pasar contexto adicional al usuario, sino que necesitamos una mejor opción de reordenación en el paso final.

Imaginemos que tenemos un pipeline que se parece a esto:

Este proceso puede reducir millones de documentos posibles a unas pocas docenas. Pero la última docena es extremadamente importante, ¡podemos estar pasando sólo tres o cuatro documentos a un LLM! Si estamos mostrando un candidato a un puesto de trabajo a un usuario, es muy importante que el primer candidato mostrado sea mucho más adecuado que el quinto.

Sabemos que los LLM son excelentes «rerankers», y hay algunas razones para ello:

  1. Los LLM conocen las listas. Esto significa que pueden ver a otros candidatos y compararlos, lo que supone una información adicional que se puede utilizar. Imagina que a ti (un humano) te pidieran que puntuaras a un candidato del 1 al 10. ¿Te ayudaría mostrarte todos los demás candidatos? ¿Le ayudaría mostrarle todos los demás candidatos? Por supuesto.
  2. Los LLM son muy inteligentes. Los LLM comprenden la tarea que se les encomienda y, basándose en ella, pueden entender muy bien si un candidato es adecuado, independientemente de la simple similitud semántica.

Podemos aprovechar la segunda razón con un clasificador basado en la perplejidad. La perplejidad es una métrica que estima el grado de «confusión» de un LLM ante un resultado concreto. En otras palabras, podemos pedir a un LLM que clasifique a nuestro candidato en «muy apto» o «no muy apto». En función de la certeza con la que clasifique a nuestro candidato en «muy apto» (la perplejidad de esta categorización), podemos clasificar eficazmente a nuestros candidatos.

Hay todo tipo de optimizaciones que se pueden hacer, pero en una buena GPU (que es muy recomendable para esta parte) podemos rerank 50 candidatos en aproximadamente el mismo tiempo que Cohere puede hacer rerank de mil. Sin embargo, podemos paralelizar este cálculo en múltiples GPUs para acelerarlo y escalarlo a miles de candidatos.

En primer lugar, vamos a instalar e importar lmppl, una librería que nos permite evaluar la perplejidad de ciertas terminaciones LLM. Si puedes conseguir resultados similares con un modelo decodificador, házmelo saber, ya que así sería mucho más fácil aumentar el rendimiento (los decodificadores mejoran y se abaratan mucho más rápido que los modelos codificador-decodificador).

!pip install lmppl
import lmppl

# Initialize the scorer for a encoder-decoder model, such as flan-t5. Use small, large, or xl depending on your needs. (xl will run much slower unless you have a GPU and a lot of memory) I recommend large for most tasks.
scorer = lmppl.EncoderDecoderLM('google/flan-t5-large')

Ahora, vamos a crear nuestra función de evaluación. Esto se puede convertir en una función general para cualquier tarea reranking, o puedes cambiar las clases para ver si eso mejora el rendimiento. Este ejemplo parece funcionar bien. Almacenamos en caché las respuestas para que ejecutar los mismos valores sea más rápido, pero esto no es demasiado necesario en una GPU.

cache = {}

def evaluate_candidates(query, documents, personality, additional_command=""):
"""
Evaluate the relevance of documents to a given query using a specified scorer,
caching individual document scores to avoid redundant computations.

Args:
- query (str): The query indicating the type of document to evaluate.
- documents (list of str): List of document descriptions or profiles.
- personality (str): Personality descriptor or model configuration for the evaluation.
- additional_command (str, optional): Additional command to include in the evaluation prompt.

Returns:
- sorted_candidates_by_score (list of tuples): List of tuples containing the document description and its score, sorted by score in descending order.
"""
try:
uncached_docs = []
cached_scores = []

# Identify cached and uncached documents
for document in documents:
key = (query, document, personality, additional_command)
if key in cache:
cached_scores.append((document, cache[key]))
else:
uncached_docs.append(document)

# Process uncached documents
if uncached_docs:
input_prompts_good_fit = [
f"{personality} Here is a problem statement: '{query}'. Here is a job description we are determining if it is a very good fit for the problem: '{doc}'. Is this job description a very good fit? Expected response: 'a great fit.', 'almost a great fit', or 'not a great fit.' This document is: "
for doc in uncached_docs
]

print(input_prompts_good_fit)

# Mocked scorer interaction; replace with actual API call or logic
outputs_good_fit = ['a very good fit.'] * len(uncached_docs)
# Calculate perplexities for combined prompts
perplexities = scorer.get_perplexity(input_texts=input_prompts_good_fit, output_texts=outputs_good_fit)

# Store scores in cache and collect them for sorting
for doc, good_ppl in zip(uncached_docs, perplexities):
score = (good_ppl)
cache[(query, doc, personality, additional_command)] = score
cached_scores.append((doc, score))

# Combine cached and newly computed scores
sorted_candidates_by_score = sorted(cached_scores, key=lambda x: x[1], reverse=False)

print(f"Sorted candidates by score: {sorted_candidates_by_score}")

print(query, ": ", sorted_candidates_by_score[0])

return sorted_candidates_by_score

except Exception as e:
print(f"Error in evaluating candidates: {e}")
return None

Ahora, volvamos a clasificar y evaluar:

def perform_reranking_evaluation_neural(problem_candidates):
results = []

for problem, candidates in problem_candidates.items():
personality = "You are an extremely intelligent classifier (200IQ), that effectively classifies a candidate into 'a great fit', 'almost a great fit' or 'not a great fit' based on a query (and the inferred intent of the user behind it)."
additional_command = "Is this candidate a great fit based on this experience?"

reranking_response = evaluate_candidates(problem, [candidates["True Positive"]]+ [candidate for candidate in candidates["Hard Negative"]], personality)
top_document = reranking_response[0][0]

# Check if the top ranked document is the True Positive
is_correct = (top_document == candidates["True Positive"])
results.append((problem, is_correct))
print(f"Problem: {problem}:")
print(f"Top Document is True Positive: {is_correct}\n")

# Evaluate overall accuracy
correct_answers = sum([result[1] for result in results])
accuracy = correct_answers / len(results) * 100
print(f"Overall Accuracy Neural: {accuracy:.2f}%")

perform_reranking_evaluation_neural(problem_candidates)

Y nuestro resultado:

Overall Accuracy Neural: 72.09%

Esto es mucho mejor que nuestros rerankers, ¡y no requiere ningún ajuste fino! No sólo eso, sino que esto es mucho más flexible hacia cualquier tarea, y más fácil de obtener ganancias de rendimiento con sólo modificar las clases y la ingeniería rápida. El inconveniente es que esta arquitectura no está optimizada, es difícil de desplegar (recomiendo modal.com para el despliegue sin servidor en múltiples GPUs, o para desplegar una GPU en un VPS).

Con este reranker neural consciente de la tarea en nuestra caja de herramientas, podemos crear un pipeline de reranking más robusto:

Conclusión

La mejora de la recuperación de documentos para tareas complejas de emparejamiento requiere un enfoque polifacético que aproveche los puntos fuertes de diferentes técnicas de IA:

  1. Las Instruction-tuned embeddings proporcionan una base mediante la codificación de instrucciones específicas de la tarea para guiar al modelo en la captura de aspectos relevantes de las consultas y los documentos. Sin embargo, las evaluaciones son cruciales para validar tu rendimiento.
  2. El reranking afina los resultados recuperados analizando en profundidad la relevancia del contenido. Puedes beneficiarte de un contexto adicional sobre la tarea en cuestión.
  3. Los clasificadores basados en LLM constituyen un potente paso final que permite matizar la reordenación de los principales candidatos para mostrar los resultados más pertinentes en un orden optimizado para el usuario final.

Al orquestar cuidadosamente instruction-tuned embeddings, rerankers y LLMs, podemos construir canalizaciones de IA robustas que superan desafíos como la adecuación de candidatos a puestos de trabajo a los requisitos del rol. Una meticulosa estrategia de prompt engineering, los modelos de alto rendimiento y las capacidades inherentes de los LLM permiten mejores Task-Aware RAG pipelines, que en este caso ofrecen resultados sobresalientes en la alineación de las personas con las oportunidades ideales. Adoptar esta metodología múltiple nos permite construir sistemas de recuperación que no sólo recuperan documentos semánticamente similares, sino que son realmente inteligentes y encuentran documentos que satisfacen nuestras necesidades únicas.

Conecta conmigo en LinkedIn para obtener más consejos sobre ingeniería de IA.

Deja una respuesta

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