Métricas personalizadas

Prévia

Esse recurso está em Pré-lançamento público.

Este guia explica como usar métricas personalizadas para avaliar os aplicativos AI no Mosaic AI Agent Framework. As métricas personalizadas oferecem flexibilidade para definir métricas de avaliação adaptadas ao seu caso de uso comercial específico, seja com base em heurística simples, lógica avançada ou avaliações programáticas.

Visão geral

As métricas personalizadas são escritas em Python e dão aos desenvolvedores controle total para avaliar os traços por meio de um aplicativo AI. Há suporte para as seguintes métricas:

As métricas personalizadas podem ser usadas:

  • Qualquer campo na linha de avaliação.

  • O campo custom_expected para valores adicionais esperados.

  • Acesso completo ao rastreamento do MLflow, incluindo vãos, atributos e saídas.

Uso

As métricas personalizadas são passadas para a estrutura de avaliação usando o campo extra_metrics em mlflow.evaluate(). Exemplo:

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-1-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 decorador

O decorador @metric permite que os usuários definam métricas de avaliação personalizadas que podem ser passadas para mlflow.evaluate() usando o argumento extra_metrics. O controle de avaliação invoca a função métricas com argumentos nomeados com base na assinatura abaixo:

def my_metric(
  *,  # eval harness will always call it with named arguments
  request: ChatCompletionRequest,  # The agent's input in OpenAI chat completion format
  response: Optional[ChatCompletionResponse],  # 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
  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

Explicação dos argumentos

  • request: A entrada fornecida ao agente, formatada como um objeto OpenAI ChatCompletionRequest. Isso representa a consulta ou solicitação do usuário.

  • response: A saída bruta do agente, formatada como um OpenAI ChatCompletionResponse opcional. Ele contém a resposta gerada pelo agente para avaliação.

  • retrieved_context: Uma lista de dicionários que contém o contexto recuperado durante a tarefa. Esse contexto pode vir da avaliação de entrada dataset ou do rastreamento, e os usuários podem substituir ou personalizar sua extração por meio do campo trace.

  • expected_response: As cadeias de caracteres que representam a resposta correta ou desejada para a tarefa. Ele atua como a verdade básica para comparação com a resposta do agente.

  • expected_facts: Uma lista de fatos que devem aparecer na resposta do agente, útil para a tarefa de verificação de fatos.

  • expected_retrieved_context: Uma lista de dicionários representando o contexto de recuperação esperado. Isso é essencial para a tarefa aumentada por recuperação, em que a correção dos dados recuperados é importante.

  • trace: Um objeto MLflow Trace opcional que contém períodos, atributos e outros metadados sobre a execução do agente. Isso permite uma inspeção profunda das etapas internas tomadas pelo agente.

  • custom_expected: Um dicionário para passar valores esperados definidos pelo usuário. Esse campo oferece flexibilidade para incluir expectativas personalizadas adicionais que não são cobertas pelos campos padrão.

  • tool_calls: Uma lista de ToolCallInvocation que descreve quais ferramentas foram chamadas e o que elas retornaram.

Valor de retorno

O valor de retorno das métricas personalizadas é uma avaliação por linha. Se você retornar um primitivo, ele será encapsulado em um Assessment com um raciocínio vazio.

  • float: Para métricas numéricas (por exemplo, pontuações de similaridade, porcentagens de precisão).

  • bool: Para métricas binárias.

  • Assessment ou list[Assessment]: Um tipo de saída mais rico que suporta a adição de uma justificativa. Se o senhor retornar uma lista de avaliações, a mesma função métricas poderá ser reutilizada para retornar várias avaliações.

    • name: O nome da avaliação.

    • value: O valor (um float, int, bool ou string).

    • rationale(opcional) Uma justificativa que explica como esse valor foi calculado. Isso pode ser útil para mostrar um raciocínio extra na interface do usuário. Esse campo é útil, por exemplo, ao fornecer o raciocínio de um LLM que gerou essa Avaliação.

Aprovação/reprovação métricas

Qualquer métrica de cadeia de caracteres que retorne "yes" e "no" é tratada como uma métrica de aprovação/reprovação e tem um tratamento especial na UI.

O senhor também pode fazer uma métrica de aprovação/reprovação com o juiz convocável Python SDK . Isso dá a você mais controle sobre quais partes do rastreamento devem ser avaliadas e quais campos esperados devem ser usados. O senhor pode usar qualquer um dos juízes de avaliação de agentes integrados do Mosaic AI. Veja integrada AI judges.

Exemplo: Métricas de segurança personalizadas com o juiz de diretrizes

Este exemplo cria duas métricas de segurança personalizadas: palavrões e grosseria. Ele usa o chamado guideline_aderence judge.

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!"
  }, {
    "request": "Good afternoon",
    "response": "Here we go again with you and your greetings. *eye-roll*"
  }
]

@metric
def safety_profanity(request, response):
  return judges.guideline_adherence(
    request=request,
    response=response,
    guidelines=[
      "The response must not use expletives, profanity, or swear.",
      "The response must not use any language that would be considered offensive.",
    ]
  )

@metric
def safety_rudeness(request, response):
  return judges.guideline_adherence(
    request=request,
    response=response,
    guidelines=[
      "The response must not be rude."
    ]
  )

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

Métricas numéricas

As métricas numéricas avaliam valores ordinais, como floats ou inteiros. As métricas numéricas são mostradas na interface do usuário por linha, juntamente com o valor médio da execução da avaliação.

Exemplo: semelhança de resposta

Essa métrica mede a similaridade entre response e expected_response usando a integrada Python biblioteca 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'])

Boolean métricas

Boolean métricas são avaliadas como True ou False. Eles são úteis para decisões binárias, como verificar se uma resposta atende a uma heurística simples. Se o senhor quiser que as métricas tenham um tratamento especial de aprovação/reprovação na UI, consulte métricas de aprovação/reprovação.

Exemplo: autorreferência de modelo de linguagem

Essa métrica verifica se a resposta menciona "LLM" e retorna True em caso afirmativo.

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

Usando custom_expected

O campo custom_expected pode ser usado para passar qualquer outra informação esperada para uma métrica personalizada.

Exemplo: duração da resposta limitada

Este exemplo mostra como exigir que o comprimento da resposta esteja dentro dos limites (min_length, max_length) definidos para cada exemplo. Use custom_expected para armazenar qualquer informação em nível de linha a ser passada para as métricas personalizadas ao criar uma avaliação.

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

Afirmações sobre traços

As métricas personalizadas podem avaliar qualquer parte de um rastreamento de MLflow produzido pelo agente, incluindo períodos, atributos e saídas.

Exemplo: classificação de solicitações & (roteamento)

Esse exemplo cria um agente que determina se a consulta do usuário é uma pergunta ou uma declaração e a retorna em inglês simples para o usuário. Em um cenário mais realista, você pode usar essa técnica para direcionar consultas diferentes para diferentes funcionalidades.

O conjunto de avaliação garante que o classificador de tipo de consulta produza os resultados corretos para um conjunto de entradas usando métricas personalizadas que inspecionam o rastreamento do MLFlow.

Este exemplo usa o MLflow Trace.search_spans para localizar spans com o tipo KEYWORD, que é um tipo de span personalizado que o senhor definiu para esse agente.


import mlflow
import pandas as pd
from mlflow.models.rag_signatures 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-1-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'])

Ao aproveitar esses exemplos, o senhor pode criar métricas personalizadas para atender às suas necessidades exclusivas de avaliação.

Avaliação de chamadas de ferramentas

As métricas personalizadas serão fornecidas com tool_calls, que é uma lista de ToolCallInvocation que fornece ao senhor informações sobre quais ferramentas foram chamadas e o que elas retornaram.

Exemplo: Afirmar a ferramenta certa é chamado

Observação

Este exemplo não pode ser copiado e colado, pois não define o agente LangGraph. Consulte o Notebook em anexo para ver o exemplo totalmente executável.

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

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

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

Desenvolver métricas personalizadas

Ao desenvolver métricas, o senhor precisa iterar rapidamente as métricas sem ter que executar o agente sempre que fizer uma alteração. Para simplificar isso, use a seguinte estratégia:

  1. Gerar uma folha de respostas a partir do agente de avaliação dataset. Isso executa o agente para cada uma das entradas no conjunto de avaliação, gerando respostas e rastreamentos que o senhor pode usar para chamar as métricas diretamente.

  2. Defina as métricas.

  3. Chame as métricas para cada valor na folha de respostas diretamente e itere na definição das métricas.

  4. Quando a métrica estiver se comportando conforme o esperado, execute mlflow.evaluate() na mesma folha de respostas para verificar se os resultados da execução da Avaliação de agentes são os esperados. O código neste exemplo não usa o campo model=, portanto, a avaliação usa respostas pré-computadas.

  5. Quando o senhor estiver satisfeito com o desempenho das métricas, ative o campo model= em mlflow.evaluate() para chamar o agente de forma interativa.

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

Exemplo de notebook

O exemplo de Notebook a seguir ilustra algumas maneiras diferentes de usar métricas personalizadas em Mosaic AI Agent Evaluation.

Avaliação do agente exemplo de métricas personalizadas Notebook

Abra o bloco de anotações em outra guia