1iwfvt Jvxd2ynew95ddwlw.png

Los resultados presentados en la Tabla 1 me parecen muy atractivos, al menos a mí. El simple La evolución funciona muy bien. En el caso de la evolución del razonamiento, la primera parte de la pregunta se responde perfectamente, pero la segunda parte queda sin respuesta. Inspeccionando la página de Wikipedia [3] Es evidente que en el propio documento no hay respuesta a la segunda parte de la pregunta, por lo que también puede interpretarse como la restricción de las alucinaciones, algo bueno en sí mismo. El multicontexto El par de preguntas y respuestas parece muy bueno. El tipo de evolución condicional es aceptable si nos fijamos en el par pregunta-respuesta. Una forma de ver estos resultados es que siempre hay espacio para una mejor ingeniería rápida que está detrás de las evoluciones. Otra forma es utilizar mejores LLM, especialmente para el rol crítico, como es el predeterminado en la biblioteca ragas.

Métrica

La biblioteca ragas no solo puede generar conjuntos de evaluación sintética, sino que también nos proporciona métricas integradas para la evaluación de componentes, así como la evaluación de un extremo a otro de los RAG.

Imagen 2: Métricas de Evaluación RAG en RAGAS. Imagen creada por el autor en draw.io.

Al momento de escribir este artículo, RAGAS proporciona ocho métricas listas para usar para la evaluación de RAG (consulte la Imagen 2) y es probable que se agreguen otras nuevas en el futuro. En general, está a punto de elegir las métricas más adecuadas para su caso de uso. Sin embargo, recomiendo seleccionar la métrica más importante, es decir:

Corrección de la respuesta — la métrica de un extremo a otro con puntuaciones entre 0 y 1, cuanto más altas, mejor, y mide la precisión de la respuesta generada en comparación con la verdad básica.

Centrarse en una métrica de un extremo a otro ayuda a iniciar la optimización de su sistema RAG lo más rápido posible. Una vez que logre algunas mejoras en la calidad, podrá observar las métricas de los componentes, centrándose en la más importante para cada componente de RAG:

Fidelidad — la métrica de generación con puntuaciones entre 0 y 1, cuanto más altas, mejor, midiendo la coherencia fáctica de la respuesta generada en relación con el contexto proporcionado. Se trata de fundamentar la respuesta generada tanto como sea posible en el contexto proporcionado y, al hacerlo, prevenir las alucinaciones.

Relevancia del contexto — la métrica de recuperación con puntuaciones entre 0 y 1, cuanto más altas, mejor, midiendo la relevancia del contexto recuperado en relación con la pregunta.

Fábrica de trapos

Bien, entonces tenemos un RAG listo para la optimización… no tan rápido, esto no es suficiente. Para optimizar RAG necesitamos la función de fábrica para generar cadenas RAG con un conjunto dado de hiperparámetros RAG. Aquí definimos esta función de fábrica en 2 pasos:

Paso 1: Una función para almacenar documentos en la base de datos vectorial.

# Defining a function to get document collection from vector db with given hyperparemeters
# The function embeds the documents only if collection is missing
# This development version as for production one would rather implement document level check
def get_vectordb_collection(chroma_client,
documents,
embedding_model="text-embedding-ada-002",
chunk_size=None, overlap_size=0) -> ChromaCollection:

if chunk_size is None:
collection_name = "full_text"
docs_pp = documents
else:
collection_name = f"{embedding_model}_chunk{chunk_size}_overlap{overlap_size}"

text_splitter = CharacterTextSplitter(
separator=".",
chunk_size=chunk_size,
chunk_overlap=overlap_size,
length_function=len,
is_separator_regex=False,
)

docs_pp = text_splitter.transform_documents(documents)

embedding = OpenAIEmbeddings(model=embedding_model)

langchain_chroma = Chroma(client=chroma_client,
collection_name=collection_name,
embedding_function=embedding,
)

existing_collections = [collection.name for collection in chroma_client.list_collections()]

if chroma_client.get_collection(collection_name).count() == 0:
langchain_chroma.from_documents(collection_name=collection_name,
documents=docs_pp,
embedding=embedding)
return langchain_chroma

Paso 2: Una función para generar RAG en LangChain con recopilación de documentos, o la función de fábrica RAG adecuada.

# Defininig a function to get a simple RAG as Langchain chain with given hyperparemeters
# RAG returns also the context documents retrieved for evaluation purposes in RAGAs

def get_chain(chroma_client,
documents,
embedding_model="text-embedding-ada-002",
llm_model="gpt-3.5-turbo",
chunk_size=None,
overlap_size=0,
top_k=4,
lambda_mult=0.25) -> RunnableSequence:

vectordb_collection = get_vectordb_collection(chroma_client=chroma_client,
documents=documents,
embedding_model=embedding_model,
chunk_size=chunk_size,
overlap_size=overlap_size)

retriever = vectordb_collection.as_retriever(top_k=top_k, lambda_mult=lambda_mult)

template = """Answer the question based only on the following context.
If the context doesn't contain entities present in the question say you don't know.

{context}

Question: {question}
"""
prompt = ChatPromptTemplate.from_template(template)
llm = ChatOpenAI(model=llm_model)

def format_docs(docs):
return "\n\n".join([doc.page_content for doc in docs])

chain_from_docs = (
RunnablePassthrough.assign(context=(lambda x: format_docs(x["context"])))
| prompt
| llm
| StrOutputParser()
)

chain_with_context_and_ground_truth = RunnableParallel(
context=itemgetter("question") | retriever,
question=itemgetter("question"),
ground_truth=itemgetter("ground_truth"),
).assign(answer=chain_from_docs)

return chain_with_context_and_ground_truth

La función anterior get_vectordb_collection se incorpora a este último función obtener_cadena, que genera nuestra cadena RAG para un conjunto de parámetros dado, es decir: embedding_model, llm_model, chunk_size, Temporary_size, top_k, lambda_mult. Con nuestra función de fábrica, solo estamos arañando la superficie de las posibilidades de qué hiperparámetros de nuestro sistema RAG optimizamos. Tenga en cuenta también que la cadena RAG requerirá 2 argumentos: pregunta y verdad_terrenodonde este último simplemente pasa a través de la cadena RAG, ya que es necesario para la evaluación utilizando RAGA.

# Setting up a ChromaDB client
chroma_client = chromadb.EphemeralClient()

# Testing full text rag

with warnings.catch_warnings():
rag_prototype = get_chain(chroma_client=chroma_client,
documents=news,
chunk_size=1000,
overlap_size=200)

rag_prototype.invoke({"question": 'What happened in Minneapolis to the bridge?',
"ground_truth": "x"})["answer"]

Evaluación del GAR

Para evaluar nuestro RAG utilizaremos el conjunto de datos diverso de artículos de noticias de CNN y Daily Mail, que está disponible en abrazando la cara [4]. La mayoría de los artículos de este conjunto de datos tienen menos de 1000 palabras. Además, utilizaremos un pequeño extracto del conjunto de datos de sólo 100 artículos de noticias. Todo esto se hace para limitar los costos y el tiempo necesarios para ejecutar la demostración.

# Getting the tiny extract of CCN Daily Mail dataset
synthetic_evaluation_set_url = "https://gist.github.com/gox6/0858a1ae2d6e3642aa132674650f9c76/raw/synthetic-evaluation-set-cnn-daily-mail.csv"
synthetic_evaluation_set_pl = pl.read_csv(synthetic_evaluation_set_url, separator=",").drop("index")
# Train/test split
# We need at least 2 sets: train and test for RAG optimization.

shuffled = synthetic_evaluation_set_pl.sample(fraction=1,
shuffle=True,
seed=6)
test_fraction = 0.5

test_n = round(len(synthetic_evaluation_set_pl) * test_fraction)
train, test = (shuffled.head(-test_n),
shuffled.head( test_n))

Como consideraremos muchos prototipos de RAG diferentes más allá del definido anteriormente, necesitamos una función para recopilar respuestas generadas por el RAG en nuestro conjunto de evaluación sintética:

# We create the helper function to generate the RAG ansers together with Ground Truth based on synthetic evaluation set
# The dataset for RAGAS evaluation should contain the columns: question, answer, ground_truth, contexts
# RAGAs expects the data in Huggingface Dataset format

def generate_rag_answers_for_synthetic_questions(chain,
synthetic_evaluation_set) -> pl.DataFrame:

df = pl.DataFrame()

for row in synthetic_evaluation_set.iter_rows(named=True):
rag_output = chain.invoke({"question": row["question"],
"ground_truth": row["ground_truth"]})
rag_output["contexts"] = [doc.page_content for doc
in rag_output["context"]]
del rag_output["context"]
rag_output_pp = {k: [v] for k, v in rag_output.items()}
df = pl.concat([df, pl.DataFrame(rag_output_pp)], how="vertical")

return df

Optimización RAG con RAGA y Optuna

En primer lugar, vale la pena enfatizar que la optimización adecuada del sistema RAG debe implicar una optimización global, donde todos los parámetros se optimizan a la vez, en contraste con el enfoque secuencial o codicioso, donde los parámetros se optimizan uno por uno. El enfoque secuencial ignora el hecho de que puede haber interacciones entre los parámetros, lo que puede dar como resultado una solución subóptima.

Ahora por fin estamos listos para optimizar nuestro sistema RAG. Usaremos un marco de optimización de hiperparámetros. optuna. Para este fin, definimos la función objetivo para el estudio de Optuna especificando el espacio de hiperparámetro permitido y calculando la métrica de evaluación; consulte el código a continuación:

def objective(trial):

embedding_model = trial.suggest_categorical(name="embedding_model",
choices=["text-embedding-ada-002", 'text-embedding-3-small'])

chunk_size = trial.suggest_int(name="chunk_size",
low=500,
high=1000,
step=100)

overlap_size = trial.suggest_int(name="overlap_size",
low=100,
high=400,
step=50)

top_k = trial.suggest_int(name="top_k",
low=1,
high=10,
step=1)

challenger_chain = get_chain(chroma_client,
news,
embedding_model=embedding_model,
llm_model="gpt-3.5-turbo",
chunk_size=chunk_size,
overlap_size= overlap_size ,
top_k=top_k,
lambda_mult=0.25)

challenger_answers_pl = generate_rag_answers_for_synthetic_questions(challenger_chain , train)
challenger_answers_hf = Dataset.from_pandas(challenger_answers_pl.to_pandas())

challenger_result = evaluate(challenger_answers_hf,
metrics=[answer_correctness],
)

return challenger_result['answer_correctness']

Finalmente, teniendo la función objetivo definimos y ejecutamos el estudio para optimizar nuestro sistema RAG en Optuna. Vale la pena señalar que podemos agregar al estudio nuestras conjeturas fundamentadas de hiperparámetros con el método enqueue_trialasí como limitar el estudio por tiempo o número de ensayos, consulte la Documentos de Optuna para más consejos.

sampler = optuna.samplers.TPESampler(seed=6)
study = optuna.create_study(study_name="RAG Optimisation",
direction="maximize",
sampler=sampler)
study.set_metric_names(['answer_correctness'])

educated_guess = {"embedding_model": "text-embedding-3-small",
"chunk_size": 1000,
"overlap_size": 200,
"top_k": 3}

study.enqueue_trial(educated_guess)

print(f"Sampler is {study.sampler.__class__.__name__}")
study.optimize(objective, timeout=180)

En nuestro estudio no se confirmó la suposición fundamentada, pero estoy seguro de que con un enfoque riguroso como el propuesto anteriormente las cosas mejorarán.

Best trial with answer_correctness: 0.700130617593832
Hyper-parameters for the best trial: {'embedding_model': 'text-embedding-ada-002', 'chunk_size': 700, 'overlap_size': 400, 'top_k': 9}

Limitaciones de los RAGA

Después de experimentar con la biblioteca ragas para sintetizar conjuntos de evaluaciones y evaluar RAG, tengo algunas advertencias:

  • La pregunta puede contener la respuesta.
  • La verdad fundamental es sólo el extracto literal del documento.
  • Problemas con RateLimitError y desbordamientos de red en Colab.
  • Las evoluciones integradas son pocas y no existe una manera fácil de agregar otras nuevas.
  • Hay margen de mejora en la documentación.

Las dos primeras advertencias están relacionadas con la calidad. La causa principal de ellos puede estar en el LLM utilizado y, obviamente, GPT-4 da mejores resultados que GPT-3.5-Turbo. Al mismo tiempo, parece que esto podría mejorarse mediante alguna ingeniería rápida para las evoluciones utilizadas para generar conjuntos de evaluación sintéticos.

En cuanto a los problemas con la limitación de velocidad y los desbordamientos de la red, es recomendable utilizar: 1) puntos de control durante la generación de conjuntos de evaluación sintética para evitar la pérdida de datos creados y 2) retroceso exponencial para asegurarse de completar toda la tarea.

Por último, y lo más importante, más evoluciones integradas serían bienvenidas para el paquete ragas. Por no hablar de la posibilidad de crear evoluciones personalizadas más fácilmente.

Otras características útiles de los RAGA

  • Avisos personalizados. El paquete ragas le brinda la opción de cambiar las indicaciones utilizadas en las abstracciones proporcionadas. Se describe el ejemplo de solicitudes personalizadas para métricas en la tarea de evaluación. en los documentos. A continuación utilizo indicaciones personalizadas para modificar evoluciones y mitigar los problemas de calidad.
  • Adaptación automática del idioma. RAGAs lo tiene cubierto para idiomas distintos del inglés. Tiene una excelente característica llamada adaptación automática del idioma que admite la evaluación RAG en otros idiomas además del inglés; consulte los documentos para obtener más información.

Conclusiones

A pesar de las limitaciones de RAGA NO te pierdas lo más importante:

RAGAs ya es una herramienta muy útil a pesar de su corta edad. Permite la generación de un conjunto de evaluaciones sintéticas para una evaluación RAG rigurosa, un aspecto crítico para el desarrollo exitoso de RAG.