Partager via


Métriques personnalisées (MLflow 2)

Important

Databricks recommande d’utiliser MLflow 3 pour évaluer et surveiller les applications GenAI. Cette page décrit l’évaluation de l’agent MLflow 2.

Ce guide explique comment utiliser des métriques personnalisées pour l’évaluation des applications IA dans Mosaïque AI Agent Framework. Les métriques personnalisées offrent une flexibilité pour définir des métriques d’évaluation adaptées à votre cas d’usage métier spécifique, qu’elles soient basées sur des heuristiques simples, une logique avancée ou des évaluations programmatiques.

Vue d’ensemble

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 IA. Les métriques suivantes sont prises en charge :

Les métriques personnalisées peuvent utiliser :

  • Tout champ de la ligne d’évaluation.
  • Champ custom_expected pour des valeurs attendues supplémentaires.
  • Accès complet à la trace MLflow, y compris les étendues, les attributs et les sorties.

Utilisation

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

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"])

@metric décorateur

Le décorateur @metric permet aux utilisateurs de définir des métriques d’évaluation personnalisées qui peuvent être passées dans mlflow.evaluate() à l’aide de l’argument extra_metrics. L’harnais d’évaluation appelle la fonction d’indicateur avec des arguments nommés en fonction de la signature ci-dessous :

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: entrée fournie à l’agent, mise en forme comme objet sérialisable arbitraire. Cela représente la requête ou l’invite de l’utilisateur.
  • response: sortie brute de l’agent, mise en forme en tant qu’objet sérialisable arbitraire facultatif. Elle contient la réponse générée par l’agent pour l’évaluation.
  • retrieved_context: liste de dictionnaires contenant le contexte récupéré pendant la tâche. Ce contexte peut provenir du jeu de données d’évaluation d’entrée ou de la trace, et les utilisateurs peuvent remplacer ou personnaliser son extraction via le champ trace.
  • expected_response: chaîne représentant la réponse correcte ou souhaitée pour la tâche. Il agit comme la vérité de base pour la comparaison avec la réponse de l’agent.
  • expected_facts: liste des faits attendus dans la réponse de l’agent, utile pour les tâches de vérification des faits.
  • guidelines : une liste de directives ou bien un mappage d’un nom de directive vers un groupe 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 d’adhésion aux directives.
  • expected_retrieved_context: liste de dictionnaires représentant le contexte de récupération attendu. Cela est essentiel pour les tâches augmentées par récupération, où l’exactitude des données récupérées importe.
  • trace: objet MLflow Trace facultatif contenant des étendues, des attributs et d’autres métadonnées sur l’exécution de l’agent. Cela permet une inspection approfondie des mesures internes effectuées par l’agent.
  • custom_expected: dictionnaire permettant de transmettre des valeurs attendues définies par l’utilisateur. Ce champ offre la possibilité d’inclure des attentes personnalisées supplémentaires qui ne sont pas couvertes par les champs standard.
  • tool_calls: liste de ToolCallInvocation qui décrit les outils qui ont été appelés et ce qu’ils ont retournés.

Valeur retournée

La valeur de retour de l’indicateur personnalisé est une Évaluation par ligne. Si vous retournez une primitive, elle est encapsulée dans un Assessment avec une logique 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 enrichi qui prend en charge l’ajout d’une justification. Si vous retournez une liste d’évaluations, la même fonction de métrique peut être réutilisée pour retourner plusieurs évaluations.
    • name: nom de l’évaluation.
    • value: valeur (float, int, bool ou string).
    • rationale: (facultatif) Justification expliquant comment cette valeur a été calculée. Cela peut être utile pour montrer un raisonnement supplémentaire dans l’interface utilisateur. Ce champ est utile, par exemple, lorsqu'on fournit un raisonnement produit par un LLM qui a généré cette évaluation.

Métriques de réussite/échec

Toute métrique de chaîne qui retourne "yes" et "no" est traitée comme une métrique de passage/échec et a un traitement spécial dans l’interface utilisateur.

Vous pouvez également créer un indicateur de réussite/échec avec le juge SDK Python joignable. Cela vous donne plus de contrôle sur les parties de la trace à évaluer et quels champs attendus utiliser. Vous pouvez utiliser l’un des juges d’évaluation d’assistant IA de Mosaic intégrés. Consultez les juges d’IA intégrés (MLflow 2).

Vérifiez que le contexte récupéré n’a pas d’informations d’identification personnelle

Cet exemple appelle le juge guideline_adherence pour s’assurer que le contexte récupéré n’a pas d’informations personnelles identifiantes.

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 floats ou les entiers. Les métriques numériques sont affichées dans l’interface utilisateur par ligne, ainsi que la valeur moyenne de l'exécution de l'évaluation.

Exemple : similarité de réponse

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

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'])

métriques booléennes

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

Exemple : Vérifier que les demandes d’entrée sont correctement mises en forme

Cette métrique vérifie si l’entrée arbitraire est mise en forme comme prévu et retourne True si elle est.

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 du modèle de langage

Cette métrique vérifie si la réponse mentionne « LLM » et retourne True si elle le fait.

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 de custom_expected

Le champ custom_expected peut être utilisé pour transmettre toutes les autres informations attendues à une métrique personnalisée.

Exemple : Longueur de réponse limitée

Cet exemple montre comment exiger que la longueur de la réponse se trouve dans les limites (min_length, max_length) définies pour chaque exemple. Utilisez custom_expected pour stocker les informations au niveau des lignes à transmettre à des métriques personnalisées lors de la création d’une évaluation.

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': {
                "metrics": [],
            }
        }
    )
    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 : Routage et classification des requêtes

Cet exemple génère un agent qui détermine si la requête utilisateur est une question ou une instruction et la retourne en anglais brut à l’utilisateur. Dans un scénario plus réaliste, vous pouvez utiliser cette technique pour router différentes requêtes vers différentes fonctionnalités.

Le jeu d’évaluation garantit que le classifieur de type requête produit les résultats appropriés pour un ensemble d’entrées à l’aide de métriques personnalisées qui inspectent la trace MLFlow.

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


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': {
                "metrics": [],
            }
        }
    )
    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’outils

Les indicateurs personnalisés sont fournis avec tool_calls, qui sont une liste de ToolCallInvocation vous donnant des informations sur les outils appelés et ce qu’ils ont retourné.

Exemple : l’assertion de l’outil approprié est appelée

Remarque

Cet exemple n’est pas copiable, car il ne définit pas l’agent LangGraph. Consultez le cahier attaché pour un exemple entièrement exécutable.

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={
      "available_tools": str(tool_calls[0].available_tools),
      "chosen_tools": 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 effectuer rapidement une itération sur la métrique sans avoir à exécuter l’agent chaque fois que vous apportez une modification. Pour simplifier cette opération, utilisez la stratégie suivante :

  1. Générez une feuille de réponses à partir de l'agent du jeu de données d'évaluation. Cette opération exécute l’agent pour chacune des entrées du jeu d’évaluation, en 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. Appliquez la métrique pour chaque valeur de la feuille de réponses directement et itérez sur la définition de la métrique.
  4. Lorsque la métrique se comporte comme prévu, exécutez mlflow.evaluate() sur la même feuille de réponses pour vérifier que les résultats de l’évaluation de l’agent sont ce que vous attendez. Le code de cet exemple n’utilise pas le champ model=. L’évaluation utilise donc des réponses précalcalé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.
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={"databricks-agent": {"metrics": []}
  }
).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': {
        #         "metrics": [],
        #     }
        # }
    )
    display(eval_results.tables['eval_results'])

Exemple de notebook

L’exemple de notebook suivant illustre différentes façons d’utiliser des indicateurs personnalisés dans l’évaluation d’assistant IA de Mosaic.

Exemple de carnet sur les métriques personnalisées de l'évaluation de l'agent

Obtenir le notebook