¿Buscas nuestro logo?
Aquí te dejamos una copia, pero si necesitas más opciones o quieres conocer más, visita nuestra área de marca.
Conoce nuestra marca.¿Buscas nuestro logo?
Aquí te dejamos una copia, pero si necesitas más opciones o quieres conocer más, visita nuestra área de marca.
Conoce nuestra marca.dev
Leticia Martín-Fuertes y Miguel López Campos 09/12/2024 Cargando comentarios…
Continuamos esta serie de posts en la que comparamos las dos principales plataformas de LLM engineering: Langfuse y LangSmith. Si quieres saber más sobre la motivación de esta serie y cómo se configuran las distintas plataformas y qué hay que hacer en cada una de ellas para versionar los prompts y tracear las llamadas al LLM, échale un vistazo al primer post.
La definición de datasets es clave para poder tener un control del ciclo de vida de nuestros prompts. Los datasets son conjuntos de inputs y las correspondientes respuestas esperadas del LLM a esos inputs, y se emplean para evaluar la aplicación o la parte de ella que nos interese (el prompt, el LLM, la base de datos…).
Hay que tener en cuenta que para crear un dataset necesitamos las ground truths, las respuestas correctas a las preguntas que queremos que pueda responder el LLM, con las que comparar sus respuestas reales. Pero tanto en LangSmith como en Langfuse, los datasets se pueden crear de forma iterativa, pudiendo insertar en ellos los casos reales ocurridos en entornos productivos que escojamos. Pueden crearse tanto desde la interfaz gráfica como desde código.
Para construir un dataset en LangSmith, es tan sencillo como usar el método .create_dataset() con el nombre que deseemos darle. Después, para insertar ejemplos, hay que usar el método .create_examples() y completar los parámetros:
dataset_name = "Dataset1"
dataset = client.create_dataset(dataset_name)
client.create_examples(
inputs = [
{"question": "¿De qué color era la caperucita?"},
{"question": "¿Qué quiere el lobo?"},
],
outputs = [
{"answer": "La caperucita era roja"},
{"answer": "El lobo quiere comerse a Caperucita"},
],
dataset_id = dataset.id,
)
Dentro de LangSmith, un dataset se ve como a continuación:
En Langfuse también existe un método .create_dataset():
langfuse.create_dataset(
name = "dataset_sencillo",
description = "Dataset de prueba",
# Metadata opcional
metadata = {
"author": "Miguel",
"date": "2024-08-06",
"type": "tutorial"
}
)
Sin embargo, para insertar items en el dataset, es necesario hacerlo de uno en uno:
langfuse.create_dataset_item(
dataset_name = "dataset_sencillo",
input = {
"text": "¿De qué color era la caperucita?"
},
expected_output = {
"text": "La caperucita era roja"
},
)
langfuse.create_dataset_item(
dataset_name = "dataset_sencillo",
input = {
"text": "¿Qué quiere el lobo?"
},
expected_output = {
"text": "El lobo quiere comerse a Caperucita"
},
)
Los datasets también permiten tener un control del deterioro de un prompt asociado a, por ejemplo, un cambio en la versión del LLM o, al poder ser incrementados, un deterioro a lo largo del tiempo debido a cambios en los datos de entrada.
Como hemos dicho, el objetivo de crear un dataset es evaluar una parte de la aplicación.
Tanto LangSmith como Langfuse se integran fácilmente con el framework de evaluación de LangChain y también con RAGAS (una librería de métricas automáticas para evaluar el caso de uso RAG). Sin embargo, en este caso crearemos un prompt propio de evaluación, para evaluar si el contenido generado contiene la información del contenido de referencia (respuesta generada vs. respuesta correcta).
Para ello volveremos a echar mano de un ChatPromptTemplate, pero esta vez también le pasaremos algunos ejemplos de comportamiento (técnica que se llama few shot), ya que necesitamos que el formato sea más predecible (que diga “True” o “False”).
Para empezar, vamos a escribir código común para las dos plataformas: la plantilla de sistema, la plantilla donde se insertará el mensaje de la persona humana y una plantilla con los ejemplos:
from langchain_core.prompts.few_shot import FewShotChatMessagePromptTemplate
from langchain_core.messages.human import HumanMessage
evaluation_prompt_system = f"""
Recibirás dos trozos de texto, uno que será la referencia
y otro que será un texto a evaluar. Tu tarea consiste en
ver si el texto a evaluar contiene semánticamente todo lo
que contiene el texto referencia y contestar solo con 'True'
o 'False'. Ten en cuenta lo siguiente:
1. El texto a evaluar no tiene que ser literalmente
igual que el texto de referencia.
2. El texto a evaluar tiene que contener toda la
información dada en el texto de referencia.
3. El texto a evaluar puede contener más información
que el texto de referencia.
4. Devuelve solamente 'True' o 'False' dependiendo de
si se cumplen las condiciones o no.
Lo que recibirás será:
reference_text: texto de referencia
evaluate_text: texto a evaluar
"""
human_prompt = """
reference_text: {reference_text}
evaluate_text: {evaluate_text}
"""
# Ejemplos few shot
examples = [{
"reference": "La casa es azul",
"evaluate": "La casa es azul y tiene una puerta marrón",
"response": "True"
}, {
"reference": "La revolución rusa empieza en 1328",
"evaluate": "La revolución rusa empieza en 1917",
"response": "False"
}, {
"reference": "Mi primo es rubio y tiene ojos azules",
"evaluate": "Mi primo Pedro tiene ojos azules",
"response": "False"
}, {
"reference": "Tengo dos casas en Formentera",
"evaluate": "Tengo dos casas preciosas en Formentera",
"response": "True"
}]
example_prompt = ChatPromptTemplate.from_messages([
('human', 'reference_text: {reference}\nevaluate_text: {evaluate}'),
('ai', '{response}')
])
# Creación del prompt few shot
few_shot_template = FewShotChatMessagePromptTemplate(
example_prompt = example_prompt,
examples = examples
)
Una vez tenemos las tres plantillas, tenemos que crear nuestro prompt_to_evaluate para subirlo a la plataforma. Empezamos por LangSmith:
examples_msgs = [
("human", i.content) if type(i) == HumanMessage
else ("ai", i.content)
for i in few_shot_template.format_messages()
]
prompt_to_evaluate = ChatPromptTemplate.from_messages(
[("system", evaluation_prompt_system)] +
examples_msgs +
[("human", human_prompt)]
)
client.push_prompt(
"prompt_for_evaluation",
object = prompt_to_evaluate
)
Para la etapa de evaluación personalizada, es necesario definir:
A continuación definimos ambas funciones. La función de generación (generate_response()) instancia el LLM, instancia el prompt a evaluar e invoca el modelo. Debe recibir como input un diccionario, que corresponderá a los diccionarios definidos como inputs a la hora de crear el dataset. Este input contendrá los campos necesarios que hemos definido en la propia creación del dataset (question en nuestro caso). La salida debe de ser también un diccionario cuyos campos luego serán referenciados desde la función de evaluación (en nuestro caso, output).
La función de evaluación (evaluate_shot()) recibe como argumentos un objeto Run y un objeto Example de LangSmith. Dentro de la función, instanciamos el prompt de evaluación y obtenemos por un lado el output del ejemplo (que en el almacenamiento del dataset hemos definido como answer) y la salida de la ejecución, que seguiría la misma estructura que la definida en el return de la función de generación (en nuestro caso solo tendría el campo output).
from langsmith.schemas import Example, Run
from langsmith.evaluation import evaluate
from langchain.chains.question_answering import load_qa_chain
def generate_response(inputs: dict):
llm = ChatOpenAI(model = "gpt-4o", temperature = 0.1)
prompt = client.pull_prompt("prompt_rag")
chain = load_qa_chain(
llm = llm,
chain_type = "stuff",
prompt = prompt,
document_variable_name = "contexto"
)
# Se realiza la generación
response = chain.invoke({
"input_documents": chunks,
"pregunta": inputs["question"]
})
return {"output": response["output_text"]}
def evaluate_shot(run: Run, example: Example):
prompt_evaluation = client.pull_prompt(
"prompt_for_evaluation"
)
reference = example.outputs["answer"]
output_to_evaluate = run.outputs["output"]
chain = prompt_evaluation | ChatOpenAI(
model = "gpt-4o"
)
response = chain.invoke({
"evaluate_text": output_to_evaluate,
"reference_text": reference
}).content
return {"key": "accuracy", "score" : float(eval(response))}
Finalmente, usamos la función evaluate() para crear un experimento, indicando cuál es la función de generación, cual la de evaluación y el dataset sobre el que realizaremos el experimento.
from langsmith.evaluation import evaluate
results = evaluate(
generate_response,
data = "Dataset1",
evaluators = [evaluate_shot]
)
Y ya podemos ir a LangSmith a ver nuestro experimento, que ha recibido automáticamente un nombre compuesto por dos nombres y un número aleatorios separados por guiones. En este caso, advanced-exchange-25.
Cuando entramos en el experimento, vemos que podemos añadir otro para compararlos. Como ves, la columna advanced-exchange-25 contiene las respuestas reales y el score que se les ha dado. En este caso, la primera respuesta tiene un score de 0 porque la respuesta real dice "El lobo quiere comerse a la abuela, a Caperucita Roj..." cuando la de referencia es "El lobo quiere comerse a Caperucita", y la segunda respuesta tiene un score de 1 porque dice "La caperucita es de color rojo" cuando la de referencia es "La caperucita es roja".
Ahora vamos a crear el prompt_to_evaluate necesario para subirlo a Langfuse, usando las mismas plantillas que definimos para LangSmith.
examples_msgs = [
{
"role": "human",
"content": i.content
} if type(i) == HumanMessage
else {"role": "ai", "content": i.content}
for i in few_shot_template.format_messages()
]
prompt_to_evaluate = [{
"role": "system", "content": evaluation_prompt_system
}] + examples_msgs + [{
"role": "human", "content": human_prompt
}]
langfuse.create_prompt(
name = "evaluation_prompt",
prompt = prompt_to_evaluate,
labels = ["production"],
tags = ["evaluation", "simple_evaluation_prompt"],
config = {"llm": "gpt-4o"},
type = "chat"
)
A continuación definimos la función que gestionará el prompt de evaluación. Esta, al ser también una generación, se traceará también, indicando que se trata de una traza de evaluación:
@observe()
def generate_evaluation(llm_model, reference_text, evaluate_text , model_kwargs, metadata):
langfuse_handler = langfuse_context.get_current_langchain_handler()
langfuse_context.update_current_trace(
name = "evaluation_trace",
tags = ["evaluacion", "primera_evaluacion"],
metadata = metadata
)
llm = ChatOpenAI(model = llm_model, **model_kwargs)
# Se realiza la generación
chain = evaluation_prompt | llm
response = chain.invoke(
{
"reference_text": reference_text,
"evaluate_text": evaluate_text
},
config = {"callbacks": [langfuse_handler]}
)
return response
Instanciamos el dataset y el prompt de evaluación:
dataset = langfuse.get_dataset("dataset_sencillo")
evaluation_prompt = ChatPromptTemplate.from_messages(
langfuse.get_prompt(
"evaluation_prompt"
).get_langchain_prompt())
Y, por último, iteramos sobre el dataset, aplicamos la evaluación y guardamos el score.
for item in dataset.items:
with item.observe(
run_name = "evaluation",
run_description = "Primera evaluación",
run_metadata = {"model": "gpt4-o"}
) as trace_id:
question = item.input['text']
expected_output = item.expected_output['text']
# Se realiza la generación
response = generate_response(
"gpt-4o",
question,
chunks,
{"temperature": 0.1}
)['output_text']
# Evaluación
evaluation_value = generate_evaluation(
"gpt-4o",
expected_output,
response,
{"temperature": 0.1},
metadata = {"date": "2024-08-06"}
).content
# Se registra la puntuación
langfuse.score(
trace_id = trace_id,
name = "evaluate_" + item.id,
value = float(eval(evaluation_value))
)
Y ya podemos observar en Langfuse las trazas de las evaluaciones, con el nombre y los tags que les hemos dado y con sus scores.
También podemos ir directamente a la pestaña de “scores” si solo nos interesa ver cómo han ido cambiando los scores.
Y, por último, también queda registrado en la vista del dataset, en la pestaña de “runs”, si queremos comparar evaluaciones con ese mismo dataset de un vistazo.
Como hemos visto, se trata de dos plataformas muy parecidas, pero resumimos aquí sus diferencias:
Ambas son muy buenas opciones para seguir monitorizando tus aplicaciones basadas en LLMs, solo tienes que dar con la que mejor se ajuste a tus necesidades.
Los comentarios serán moderados. Serán visibles si aportan un argumento constructivo. Si no estás de acuerdo con algún punto, por favor, muestra tus opiniones de manera educada.
Cuéntanos qué te parece.