Aller au contenu principal

Métriques personnalisées (MLflow 2)

important

Databricks recommande d’utiliser MLflow 3 pour l’évaluation et le monitoring des applications GenAI. Cette page décrit MLflow 2 Agent Evaluation.

Ce guide explique comment utiliser des métriques personnalisées pour évaluer les applications d'IA avec des agents personnalisés. Les métriques personnalisées offrent la flexibilité de définir des métriques d'évaluation adaptées à votre cas d'utilisation métier spécifique. Vous pouvez les baser sur des heuristiques simples, une logique avancée ou des évaluations programmatiques.

Présentation

Les métriques personnalisées sont écrites en Python et donnent aux développeurs un contrôle total pour évaluer les traces via une application d'IA. Les métriques suivantes sont prises en charge :

Les métriques personnalisées peuvent utiliser :

  • Tout champ dans la ligne d'évaluation.
  • Le champ custom_expected pour les valeurs attendues supplémentaires.
  • Accès complet à la trace MLflow, y compris les spans, les attributs et les sorties.

Utilisation

La métrique personnalisée est transmise au cadre d'évaluation à l'aide du champ extra_metrics dans mlflow.evaluate(). Exemple :

Python
import mlflow
from databricks.agents.evals import metric

@metric
def not_empty(response):
# "yes" for Pass and "no" for Fail.
return "yes" if response.choices[0]['message']['content'].strip() != "" else "no"

@mlflow.trace(span_type="CHAT_MODEL")
def my_model(request):
deploy_client = mlflow.deployments.get_deploy_client("databricks")
return deploy_client.predict(
endpoint="databricks-meta-llama-3-3-70b-instruct", inputs=request
)

with mlflow.start_run(run_name="example_run"):
eval_results = mlflow.evaluate(
data=[{"request": "Good morning"}],
model=my_model,
model_type="databricks-agent",
extra_metrics=[not_empty],
)
display(eval_results.tables["eval_results"])

décorateur @metric

Le décorateur @metric permet aux utilisateurs de définir des métriques d'évaluation personnalisées qui peuvent être transmises à mlflow.evaluate() en utilisant l'argument extra_metrics. Le harnais d'évaluation appelle la fonction de métrique avec des arguments nommés basés sur la signature ci-dessous :

Python
def my_metric(
*, # eval harness will always call it with named arguments
request: Dict[str, Any], # The agent's raw input as a serializable object
response: Optional[Dict[str, Any]], # The agent's raw output; directly passed from the eval harness
retrieved_context: Optional[List[Dict[str, str]]], # Retrieved context, either from input eval data or extracted from the trace
expected_response: Optional[str], # The expected output as defined in the evaluation dataset
expected_facts: Optional[List[str]], # A list of expected facts that can be compared against the output
guidelines: Optional[Union[List[str], Dict[str, List[str]]]] # A list of guidelines or mapping a name of guideline to an array of guidelines for that name
expected_retrieved_context: Optional[List[Dict[str, str]]], # Expected context for retrieval tasks
trace: Optional[mlflow.entities.Trace], # The trace object containing spans and other metadata
custom_expected: Optional[Dict[str, Any]], # A user-defined dictionary of extra expected values
tool_calls: Optional[List[ToolCallInvocation]],
) -> float | bool | str | Assessment

Explication des arguments

  • request ** ** : L'entrée fournie à l'agent, formatée en tant qu'objet sérialisable arbitraire. Ceci représente la query ou l'invite de l'utilisateur.
  • response : sortie brute de l’agent, formatée comme un objet sérialisable arbitraire facultatif. Il contient la réponse générée par l’agent pour évaluation.
  • retrieved_context : Une liste de dictionnaires contenant le contexte récupéré pendant la tâche. Ce contexte peut provenir du dataset d'évaluation d'entrée ou de la trace, et les utilisateurs peuvent ignorer ou personnaliser son extraction via le champ trace.
  • expected_response ** ** : La chaîne représentant la réponse correcte ou souhaitée pour la tâche. Elle constitue la vérité terrain pour la comparaison avec la réponse de l'agent.
  • expected_facts : Une liste de faits devant apparaître dans la réponse de l'agent, utile pour les tâches de vérification des faits.
  • guidelines : Une liste de directives ou un mappage d'un nom de directive à un tableau de directives pour ce nom. Les directives vous permettent de fournir des contraintes sur n'importe quel champ qui peut ensuite être évalué par le juge de conformité aux directives.
  • expected_retrieved_context ** ** : une liste de dictionnaires représentant le contexte de récupération attendu. Ceci est essentiel pour les tâches d'augmentation par récupération où la justesse des données récupérées est importante.
  • trace : Un objet Trace MLflow facultatif contenant des portées, des attributs et d'autres métadonnées concernant l'exécution de l'agent. Cela permet une inspection approfondie des étapes internes effectuées par l'agent.
  • custom_expected : Un dictionnaire pour passer les valeurs attendues définies par l'utilisateur. Ce champ offre la flexibilité d'inclure des attentes personnalisées supplémentaires qui ne sont pas couvertes par les champs standard.
  • tool_calls : Une liste de ToolCallInvocation qui décrit les outils appelés et ce qu'ils ont renvoyé.

Valeur de retour

La valeur de retour de la métrique personnalisée est une évaluation par ligne. Si vous renvoyez une primitive, elle est enveloppée dans un Assessment avec une justification vide.

  • float : pour les métriques numériques (par exemple, les scores de similarité, les pourcentages de précision).
  • bool : Pour les métriques binaires.
  • Assessment ou list[Assessment]: un type de sortie plus riche qui prend en charge l'ajout d'une justification. Si vous renvoyez une liste d'évaluations, la même fonction de métrique peut être réutilisée pour renvoyer plusieurs évaluations.
    • name: Le nom de l'évaluation.
    • value: La valeur (un flottant, un entier, un booléen ou une chaîne de caractères).
    • rationale: (Facultative) Justification expliquant comment cette valeur a été calculée. Ceci peut être utile pour afficher un raisonnement supplémentaire dans l'interface utilisateur. Ce champ est utile, par exemple, lorsque l'on fournit un raisonnement provenant d'un LLM qui a généré cette évaluation.

Métriques de réussite/échec

Toute métrique de chaîne qui renvoie "yes" et "no" est traitée comme une métrique de succès/échec et bénéficie d'un traitement spécial dans l'interface utilisateur.

Vous pouvez également créer une métrique de réussite/échec avec le SDK Python du juge appelable. Cela vous donne plus de contrôle sur les parties de la trace à évaluer et les champs attendus à utiliser. Vous pouvez utiliser n'importe lequel des juges d'Agent Evaluation intégrés. Voir juges IA intégrés (MLflow 2).

Assurez-vous que le contexte récupéré ne contient pas d'informations personnelles identifiables (PII)

Cet exemple appelle le juge guideline_adherence pour s'assurer que le contexte récupéré ne contient aucune information d'identification personnelle (PII).

Python
import mlflow
import pandas as pd
from databricks.agents.evals import metric
from databricks.agents.evals import judges

evals = [
{
"request": "Good morning",
"response": "Good morning to you too!",
"retrieved_context": [{
"content": "The email address is noreply@databricks.com",
}],
}, {
"request": "Good afternoon",
"response": "This is actually the morning!",
"retrieved_context": [{
"content": "fake retrieved context",
}],
}
]

@metric
def retrieved_context_no_pii(request, response, retrieved_context):
retrieved_content = '\n'.join([c['content'] for c in retrieved_context])
return judges.guideline_adherence(
request=request,
# You can also pass in per-row guidelines by adding `guidelines` to the signature of your metric
guidelines=[
"The retrieved context must not contain personally identifiable information.",
],
# `guidelines_context` requires `databricks-agents>=0.20.0`
guidelines_context={"retrieved_context": retrieved_content},
)

with mlflow.start_run(run_name="safety"):
eval_results = mlflow.evaluate(
data=pd.DataFrame.from_records(evals),
model_type="databricks-agent",
extra_metrics=[retrieved_context_no_pii],
# Disable built-in judges.
evaluator_config={
'databricks-agent': {
"metrics": [],
}
}
)
display(eval_results.tables['eval_results'])

Métriques numériques

Les métriques numériques évaluent les valeurs ordinales, telles que les nombres à virgule flottante ou les entiers. Les métriques numériques sont affichées dans l’interface utilisateur par ligne, ainsi que la valeur moyenne pour l’exécution d’évaluation.

Exemple : similarité de la réponse

Cette métrique mesure la similarité entre response et expected_response à l'aide de la bibliothèque Python intégrée SequenceMatcher.

Python
import mlflow
import pandas as pd
from databricks.agents.evals import metric
from difflib import SequenceMatcher

evals = [
{
"request": "Good morning",
"response": "Good morning to you too!",
"expected_response": "Hello and good morning to you!"
}, {
"request": "Good afternoon",
"response": "I am an LLM and I cannot answer that question.",
"expected_response": "Good afternoon to you too!"
}
]

@metric
def response_similarity(response, expected_response):
s = SequenceMatcher(a=response, b=expected_response)
return s.ratio()

with mlflow.start_run(run_name="response_similarity"):
eval_results = mlflow.evaluate(
data=pd.DataFrame.from_records(evals),
model_type="databricks-agent",
extra_metrics=[response_similarity],
evaluator_config={
'databricks-agent': {
"metrics": [],
}
}
)
display(eval_results.tables['eval_results'])

Mesures booléennes

Les métriques booléennes évaluent à True ou False. Elles sont utiles pour les décisions binaires, telles que vérifier si une réponse répond à une heuristique simple. Si vous souhaitez que la métrique ait un traitement spécial de réussite/échec dans l'UI, consultez métriques de réussite/échec.

Exemple : vérifiez que les demandes d'entrée sont correctement formatées

Cette métrique vérifie si l’entrée arbitraire est formatée comme prévu et renvoie True si c'est le cas.

Python
import mlflow
import pandas as pd
from databricks.agents.evals import metric

evals = [
{
"request": {"messages": [{"role": "user", "content": "Good morning"}]},
}, {
"request": {"inputs": ["Good afternoon"]},
}, {
"request": {"inputs": [1, 2, 3, 4]},
}
]

@metric
def check_valid_format(request):
# Check that the request contains a top-level key called "inputs" with a value of a list
return "inputs" in request and isinstance(request.get("inputs"), list)

with mlflow.start_run(run_name="check_format"):
eval_results = mlflow.evaluate(
data=pd.DataFrame.from_records(evals),
model_type="databricks-agent",
extra_metrics=[check_valid_format],
# Disable built-in judges.
evaluator_config={
'databricks-agent': {
"metrics": [],
}
}
)
eval_results.tables['eval_results']

Exemple : auto-référence des modèles de langage

Cette métrique vérifie si la réponse mentionne « LLM » et renvoie True si c'est le cas.

Python
import mlflow
import pandas as pd
from databricks.agents.evals import metric

evals = [
{
"request": "Good morning",
"response": "Good morning to you too!"
}, {
"request": "Good afternoon",
"response": "I am an LLM and I cannot answer that question."
}
]

@metric
def response_mentions_llm(response):
return "LLM" in response

with mlflow.start_run(run_name="response_mentions_llm"):
eval_results = mlflow.evaluate(
data=pd.DataFrame.from_records(evals),
model_type="databricks-agent",
extra_metrics=[response_mentions_llm],
evaluator_config={
'databricks-agent': {
"metrics": [],
}
}
)
display(eval_results.tables['eval_results'])

Utilisation custom_expected

Le champ custom_expected peut être utilisé pour transmettre toute autre information attendue à une métrique personnalisée.

Exemple : longueur de réponse limitée

Cet exemple montre comment exiger que la longueur de la réponse soit comprise entre les limites (min_length, max_length) définies pour chaque exemple. Utilisez custom_expected pour stocker toute information au niveau des lignes à transmettre à des métriques personnalisées lors de la création d'une évaluation.

Python
import mlflow
import pandas as pd
from databricks.agents.evals import metric
from databricks.agents.evals import judges

evals = [
{
"request": "Good morning",
"response": "Good night.",
"custom_expected": {
"max_length": 100,
"min_length": 3
}
}, {
"request": "What is the date?",
"response": "12/19/2024",
"custom_expected": {
"min_length": 10,
"max_length": 20,
}
}
]

# The custom metric uses the "min_length" and "max_length" from the "custom_expected" field.
@metric
def response_len_bounds(
request,
response,
# This is the exact_expected_response from your eval dataframe.
custom_expected
):
return len(response) <= custom_expected["max_length"] and len(response) >= custom_expected["min_length"]

with mlflow.start_run(run_name="response_len_bounds"):
eval_results = mlflow.evaluate(
data=pd.DataFrame.from_records(evals),
model_type="databricks-agent",
extra_metrics=[response_len_bounds],
# Disable built-in judges.
evaluator_config={
'databricks-agent': {
&quot;metrics&quot;: [],
}
}
)
display(eval_results.tables['eval_results'])

Assertions sur les traces

Les métriques personnalisées peuvent évaluer n’importe quelle partie d’une trace MLflow produite par l’agent, y compris les étendues, les attributs et les sorties.

Exemple : Classification des requêtes & routage

Cet exemple crée un agent qui détermine si la query de l'utilisateur est une question ou une déclaration et la renvoie en anglais simple à l'utilisateur. Dans un scénario plus réaliste, vous pourriez utiliser cette technique pour acheminer différentes requêtes vers différentes fonctionnalités.

L'ensemble d'évaluation garantit que le classificateur de type de query produit les bons résultats pour un ensemble d'entrées en utilisant des métriques personnalisées qui inspectent la trace MLFlow.

Cet exemple utilise le Trace.search_spans de MLflow pour trouver des étendues de type KEYWORD, qui est un type d'étendue personnalisé que vous avez défini pour cet agent.

Python

import mlflow
import pandas as pd
from mlflow.types.llm import ChatCompletionResponse, ChatCompletionRequest
from databricks.agents.evals import metric
from databricks.agents.evals import judges
from mlflow.evaluation import Assessment
from mlflow.entities import Trace
from mlflow.deployments import get_deploy_client

# This agent is a toy example that returns simple statistics about the user's request.
# To get the stats about the request, the agent calls methods to compute stats before returning the stats in natural language.

deploy_client = get_deploy_client("databricks")
ENDPOINT_NAME="databricks-meta-llama-3-3-70b-instruct"

@mlflow.trace(name="classify_question_answer")
def classify_question_answer(request: str) -> str:
system_prompt = """
Return "question" if the request is formed as a question, even without correct punctuation.
Return "statement" if the request is a statement, even without correct punctuation.
Return "unknown" otherwise.

Do not return a preamble, only return a single word.
"""
request = {
"messages": [
{"role": "system", "content": system_prompt},
{"role": "user", "content": request},
],
"temperature": .01,
"max_tokens": 1000
}

result = deploy_client.predict(endpoint=ENDPOINT_NAME, inputs=request)
return result.choices[0]['message']['content']

@mlflow.trace(name="agent", span_type="CHAIN")
def question_answer_agent(request: ChatCompletionRequest) -> ChatCompletionResponse:
user_query = request["messages"][-1]["content"]

request_type = classify_question_answer(user_query)
response = f"The request is a {request_type}."

return {
"messages": [
*request["messages"][:-1], # Keep the chat history.
{"role": "user", "content": response}
]
}

# Define the evaluation set with a set of requests and the expected request types for those requests.
evals = [
{
"request": "This is a question",
"custom_expected": {
"request_type": "statement"
}
}, {
"request": "What is the date?",
"custom_expected": {
"request_type": "question"
}
},
]

# The custom metric checks the expected request type against the actual request type produced by the agent trace.
@metric
def correct_request_type(request, trace, custom_expected):
classification_span = trace.search_spans(name="classify_question_answer")[0]
return classification_span.outputs == custom_expected['request_type']

with mlflow.start_run(run_name="multiple_assessments_single_metric"):
eval_results = mlflow.evaluate(
data=pd.DataFrame.from_records(evals),
model=question_answer_agent,
model_type="databricks-agent",
extra_metrics=[correct_request_type],
evaluator_config={
'databricks-agent': {
&quot;metrics&quot;: [],
}
}
)
display(eval_results.tables['eval_results'])

En tirant parti de ces exemples, vous pouvez concevoir des métriques personnalisées pour répondre à vos besoins d'évaluation uniques.

Évaluation des appels d'outil

Des métriques personnalisées seront fournies avec tool_calls, qui sont une liste de ToolCallInvocation vous donnant des informations sur les outils appelés et ce qu'ils ont renvoyé.

Exemple : Affirmer que le bon outil est appelé

remarque

Cet exemple n'est pas copiable-collable car il ne définit pas l'agent LangGraph. Voir le Notebook joint pour l'exemple entièrement exécutable.

Python
import mlflow
import pandas as pd
from databricks.agents.evals import metric
from databricks.agents.evals import judges

eval_data = pd.DataFrame(
[
{
"request": "what is 3 * 12?",
"expected_response": "36",
"custom_expected": {
"expected_tool_name": "multiply"
},
},
{
"request": "what is 3 + 12?",
"expected_response": "15",
"custom_expected": {
"expected_tool_name": "add"
},
},
]
)

@metric
def is_correct_tool(tool_calls, custom_expected):
# Metric to check whether the first tool call is the expected tool
return tool_calls[0].tool_name == custom_expected["expected_tool_name"]

@metric
def is_reasonable_tool(request, trace, tool_calls):
# Metric using the guideline adherence judge to determine whether the chosen tools are reasonable
# given the set of available tools. Note that `guidelines_context` requires `databricks-agents >= 0.20.0`

return judges.guideline_adherence(
request=request["messages"][0]["content"],
guidelines=[
"The selected tool must be a reasonable tool call with respect to the request and available tools.",
],
# `guidelines_context` requires `databricks-agents>=0.20.0`
guidelines_context={
&quot;available_tools&quot;: str(tool_calls[0].available_tools),
&quot;chosen_tools&quot;: str([tool_call.tool_name for tool_call in tool_calls]),
},
)

results = mlflow.evaluate(
data=eval_data,
model=tool_calling_agent,
model_type="databricks-agent",
extra_metrics=[is_correct_tool]
)
results.tables["eval_results"].display()

Développer des métriques personnalisées

Lorsque vous développez des métriques, vous devez itérer rapidement sur la métrique sans avoir à exécuter l'agent chaque fois que vous apportez une modification. Pour rendre cela plus simple, utilisez la stratégie suivante :

  1. Générez une feuille de réponses à partir de l'agent du dataset d'évaluation. Ceci exécute l'agent pour chacune des entrées de l'ensemble d'évaluation, générant des réponses et des traces que vous pouvez utiliser pour appeler directement la métrique.
  2. Définissez la métrique.
  3. Appeler la métrique pour chaque valeur de la feuille de réponses directement et itérer sur la définition de la métrique.
  4. Lorsque la métrique se comporte comme vous l'attendez, exécutez mlflow.evaluate() sur la même feuille de réponses pour vérifier que les résultats de l'exécution de l'Agent Evaluation sont ceux que vous attendez. Le code de cet exemple n'utilise pas le champ model=, l'évaluation utilise donc des réponses précalculées.
  5. Lorsque vous êtes satisfait des performances de la métrique, activez le champ model= dans mlflow.evaluate() pour appeler l'agent de manière interactive.
Py
import mlflow
import pandas as pd
from databricks.agents.evals import metric
from databricks.agents.evals import judges
from mlflow.evaluation import Assessment
from mlflow.entities import Trace

evals = [
{
"request": "What is Databricks?",
"custom_expected": {
"keywords": ["databricks"],
},
"expected_response": "Databricks is a cloud-based analytics platform.",
"expected_facts": ["Databricks is a cloud-based analytics platform."],
"expected_retrieved_context": [{"content": "Databricks is a cloud-based analytics platform.", "doc_uri": "https://databricks.com/doc_uri"}]
}, {
"request": "When was Databricks founded?",
"custom_expected": {
"keywords": ["when", "databricks", "founded"]
},
"expected_response": "Databricks was founded in 2012",
"expected_facts": ["Databricks was founded in 2012"],
"expected_retrieved_context": [{"content": "Databricks is a cloud-based analytics platform.", "doc_uri": "https://databricks.com/doc_uri"}]
}, {
"request": "How do I convert a timestamp_ms to a timestamp in dbsql?",
"custom_expected": {
"keywords": ["timestamp_ms", "timestamp", "dbsql"]
},
"expected_response": "You can convert a timestamp with...",
"expected_facts": ["You can convert a timestamp with..."],
"expected_retrieved_context": [{"content": "You can convert a timestamp with...", "doc_uri": "https://databricks.com/doc_uri"}]
}
]
## Step 1: Generate an answer sheet with all of the built-in judges turned off.
## This code calls the agent for all the rows in the evaluation set, which you can use to build the metric.
answer_sheet_df = mlflow.evaluate(
data=evals,
model=rag_agent,
model_type="databricks-agent",
# Turn off built-in judges to just build an answer sheet.
evaluator_config={&quot;databricks-agent&quot;: {&quot;metrics&quot;: []}
}
).tables['eval_results']
display(answer_sheet_df)

answer_sheet = answer_sheet_df.to_dict(orient='records')

## Step 2: Define the metric.
@metric
def custom_metric_consistency(
request,
response,
retrieved_context,
expected_response,
expected_facts,
expected_retrieved_context,
trace,
# This is the exact_expected_response from your eval dataframe.
custom_expected
):
print(f"[custom_metric] request: {request}")
print(f"[custom_metric] response: {response}")
print(f"[custom_metric] retrieved_context: {retrieved_context}")
print(f"[custom_metric] expected_response: {expected_response}")
print(f"[custom_metric] expected_facts: {expected_facts}")
print(f"[custom_metric] expected_retrieved_context: {expected_retrieved_context}")
print(f"[custom_metric] trace: {trace}")

return True

## Step 3: Call the metric directly before using the evaluation harness to iterate on the metric definition.
for row in answer_sheet:
custom_metric_consistency(
request=row['request'],
response=row['response'],
expected_response=row['expected_response'],
expected_facts=row['expected_facts'],
expected_retrieved_context=row['expected_retrieved_context'],
retrieved_context=row['retrieved_context'],
trace=Trace.from_json(row['trace']),
custom_expected=row['custom_expected']
)

## Step 4: After you are confident in the signature of the metric, you can run the harness with the answer sheet to trigger the output validation and make sure the UI reflects what you intended.
with mlflow.start_run(run_name="exact_expected_response"):
eval_results = mlflow.evaluate(
data=answer_sheet,
## Step 5: Re-enable the model here to call the agent when we are working on the agent definition.
# model=rag_agent,
model_type="databricks-agent",
extra_metrics=[custom_metric_consistency],
# Uncomment to turn off built-in judges.
# evaluator_config={
# 'databricks-agent': {
# &quot;metrics&quot;: [],
# }
# }
)
display(eval_results.tables['eval_results'])

Exemple de Notebook

L'exemple de notebook suivant illustre différentes façons d'utiliser des métriques personnalisées dans l'Agent Evaluation.

Notebook d'exemple de mesures personnalisées d'Agent Evaluation