LLM como Juiz: Avaliação de Agentes de IA na Prática

Autor

Jorge Souza

Publicado em

Post Image 2

Sempre que pensava em um sistema que usa LLMs (Large Language Models) para alguma funcionalidade me pegava imaginando: como posso garantir a qualidade das respostas de um modelo? Afinal, LLMs têm uma natureza probabilística, não determinística, e mesmo se controlássemos essa aleatoriedade, os sistemas que fazem uso desses modelos, na maioria das vezes, não possuem uma única saída correta. Isso se tornou uma dor maior quando implementei pela primeira vez uma aplicação que fazia uso de LLMs. Na época não tinha conhecimento desse campo de estudo mas acabei caindo no LLM como juiz (LLM-as-a-judge ) sem conhecer o termo. Para entender por que isso é relevante e como aplicar na prática, precisamos voltar um pouco no passado.

Métodos Clássicos e Modernos

LLMs estão enquadrados nos algoritmos de NLG (Natural Language Generation) e ao longo dos anos de estudo surgiram diversas métricas de avaliação, como o BLEU e o ROUGE. Linguagem natural é difícil de avaliar. Existem muitos pontos: coerência, gramática, semântica, consistência, fluência e muitos outros, o que pode ser natural para um humano, mas extremamente complexo para um algoritmo. BLEU e ROUGE são métricas clássicas de avaliação para algoritmos de NLG, elas medem a sobreposição lexical de n-gramas entre o texto gerado e um texto de referência, ou seja, estão enquadradas nos métodos baseados em referência (reference-based). BLEU é orientado a precisão, ou seja, quanto do texto gerado aparece na referência, já ROUGE é orientado a recall, quanto da referência aparece no texto gerado.

Métodos baseados em referência são dependentes de um gabarito fornecido por humanos e funcionam bem quando a resposta certa é bem definida. Porém em sistemas modernos raramente existe apenas uma resposta correta, como em chatbots. E em sistemas agênticos a complexidade vai além: a saída não é apenas texto, é uma sequência de decisões, assunto que vamos abordar mais à frente. Assim surge a ideia de usar LLMs para julgar a saída de sistemas que utilizam LLMs, uma das abordagens livres de referências (reference-free) mais usadas atualmente: o LLM como juiz (LLM-as-a-judge). O paradigma de LLM como juiz se materializa em frameworks como o GPTScore e o G-Eval, que usam LLMs para julgar diretamente a qualidade da saída segundo critérios definidos.

Frameworks de Avaliação

GPTScore

GPTScore (Generative Pre-Trained Score) é um framework formulado como um problema de geração condicional (conditional generation problem) que utiliza a própria probabilidade que o LLM atribui ao texto dada uma instrução como referência de qualidade. O modelo recebe a instrução e o texto candidato, e o GPTScore calcula a soma das log-probabilidades de cada token do candidato dados os tokens anteriores e o prompt. Ou seja, mede o quanto o modelo "esperaria gerar" aquele texto se estivesse gerando do zero.


Figura 1: Arquitetura do GPTScore (Fu et al., 2023)

G-Eval

G-Eval é um framework modelado como um problema de preenchimento de formulário (form filling problem) composto por três elementos principais:

  1. Um prompt que contém a definição da task de avaliação e os critérios a serem avaliados
  2. Uma cadeia de pensamento (chain-of-thought), que nada mais é do que um conjunto de instruções intermediárias descrevendo detalhadamente os passos da avaliação
  3. Uma função de pontuação (scoring function) que extrai as probabilidades que o LLM atribuiu a cada nota possível (ex: 1 a 5) e calcula uma média ponderada. Isso gera pontuações contínuas (ex: 3.78) em vez de inteiras, melhorando a correlação com o julgamento humano.

Figura 2: arquitetura do G-Eval (Liu et al., 2023)

Ambos os frameworks utilizam LLM como juiz, mas se diferenciam na abordagem. Frameworks são definições conceituais: para utilizar na prática, precisamos implementá-los ou utilizar bibliotecas que os implementem. Vamos ver um exemplo do G-Eval mais adiante, implementado pela biblioteca Python deepeval.

Ferramentas

Podemos separar as ferramentas de avaliação em três grupos: bibliotecas de avaliação, ferramentas de teste/CLI (test harness) e plataformas de observabilidade.

Bibliotecas de eval

  • DeepEval: avaliação de apps LLM no estilo Pytest.
  • Ragas: avaliação de RAG especificamente.

CLI / test harness

  • Promptfoo: A/B de prompts e modelos + red teaming.

Plataformas

  • TruLens: tracing + RAG Triad.
  • MLflow: AI engineering end-to-end (ML + LLM).
  • Langfuse: observability open-source (alternativa ao LangSmith).

Frequentemente, agentes rodando em produção utilizam RAG (Retrieval-Augmented Generation), uma técnica que recupera informações em bases de conhecimento (knowledge-bases), geralmente através de busca vetorial. Comumente RAG é utilizado como uma ferramenta (tool) que o agente decide usar dentro do seu loop de execução, assim ferramentas como o Ragas focadas em avaliar RAG se mostram extremamente úteis. Outras ferramentas são plataformas mais amplas mas fornecem um módulo de avaliação para agentes, como é o caso do MLflow e Langfuse.

Vamos usar a DeepEval mais adiante. Por ser uma biblioteca de avaliação, ela implementa métricas tanto para agentes quanto para RAG. Permite fazer avaliação em diferentes níveis (end-to-end e component level), tem integração nativa com pytest que encaixa muito bem em fluxos de CI/CD e oferece frameworks para criação de métricas customizadas, como o G-Eval, entre outros.

Tendo a ferramenta, agora precisamos entender o mais importante: o que avaliar? As métricas certas dependem do tipo de agente e do nível de avaliação.

Avaliando Agentes

Avaliar o desempenho de um sistema agêntico é diferente de avaliar um LLM, o que pode ser um ponto de confusão. Benchmarks como MMLU, SWE-bench e GAIA medem a capacidade bruta de um modelo isolado. O que queremos avaliar são os sistemas construídos em cima desses modelos. Para isso precisamos entender mais alguns conceitos.

Single-turn e Multi-turn

Agentes podem ser classificados de acordo com o número de interações de ponta a ponta entre o sistema e o usuário final durante a execução de uma tarefa. Um agente single-turn precisa de uma única interação do usuário para gerar uma saída e concluir uma tarefa, enquanto um agente multi-turn precisa de múltiplas interações. Em agentes single-turn a avaliação acontece dentro de uma única interação, considerando entrada, saída e chamadas de ferramentas realizadas nesse turno específico. Em multi-turn o cenário muda, o histórico é essencial: precisamos verificar se a tarefa foi concluída ao longo dos turnos e ainda olhar as chamadas de ferramentas individualmente a cada turno.

End-to-end e Component-Level

A avaliação ponta a ponta (end-to-end) analisa as entradas e saídas do sistema e o trata como uma caixa preta: importa o que entra e o que sai, não o processo interno.

Figura 3: diagrama da avaliação end-to-end

Já a avaliação em nível de componente (component-level) avalia os componentes internos do sistema (sub-agentes, chamadas de ferramentas, etc) em vez de tratar o sistema como uma caixa preta. O foco passa a ser o processo até o resultado.

Figura 4: diagrama da avaliação component-level

Onde agentes de IA falham?

Agentes de IA podem falhar a nível de ponta a ponta e a nível de componente. O agente finalizar uma tarefa corretamente é o mínimo esperado, ele ainda pode falhar na viabilidade econômica e na experiência do usuário. Alguns erros comuns são:

  • Não completar a tarefa
  • Ciclos infinitos de “reasoning / thinking”
  • Completar a tarefa mas com latência alta, custo elevado ou atrito conversacional
  • Ferramentas chamadas com parâmetros errados
  • Não utilização do resultado das ferramentas
  • Alucinações em diversos níveis

Resumindo, mesmo que a saída seja correta, o caminho para chegar nela é uma parte crítica da avaliação. Uma resposta pode esconder uma ferramenta nunca chamada, dados inventados ou um custo que inviabiliza um produto.

Avaliando um agente na prática

Até aqui discutimos conceitos e métricas de avaliação. Agora vamos aplicar essas ideias na prática. O objetivo não é construir um agente completo e sofisticado, mas um sistema pequeno o suficiente para conseguirmos observar claramente os tipos de falha que métricas de avaliação conseguem capturar.

Avaliar um agente envolve três passos: rastrear o que o agente faz, aplicar métricas sobre esses dados e logar os resultados.

O agente utilizado no exemplo é um chatbot para uma loja de peças automotivas. A partir dele vamos percorrer todo o fluxo de avaliação:

  • definição do comportamento esperado;
  • captura das execuções;
  • avaliação single-turn;
  • avaliação multi-turn;
  • por fim, integração com testes automatizados.

Esse agente foi implementado em Python, usando o framework pydantic_ai com o modelo gpt-4.1-mini da OpenAI. O código fonte está disponível em https://github.com/jvsouzx/agent_eval.

O comportamento do agente é definido pelo prompt de sistema, que define seu domínio de atuação e incentiva o uso de ferramentas em vez de respostas inventadas.

1SYSTEM_PROMPT = (
2 "Você é um atendente de uma loja de peças automotivas. "
3 "Ajude o cliente a consultar disponibilidade no estoque, conferir preços,"
4 "verificar compatibilidade com o veículo e registrar itens no pedido. "
5 "Sempre use as ferramentas disponíveis em vez de adivinhar valores, "
6 "e responda de forma cordial e objetiva, em uma ou duas frases."
7)

Como o agente precisa consultar informações externas, ele opera sobre um conjunto pequeno de ferramentas. Esse é um ponto importante para a avaliação, que não será ferita apenas sobre a resposta final, mas também sobre os passos realizados durante a execução.

  • search_product busca produtos pelo nome no catálogo
  • check_stock verifica a quantidade de peças em estoque por SKU
  • check_compatibility verifica se uma peça serve em um dado modelo de automóvel
  • check_price retorna o preço unitário de uma peça
  • add_to_order adiciona o item ao pedido atual
  • order_summary lista itens e o valor total do pedido

Cada ferramenta encapsula uma operação específica. Abaixo temos a implementação da ferramenta responsável por buscar produtos no catálogo:

1@agent.tool
2async def search_product(ctx: RunContext[AttendantContext], query: str) -> str:
3"""Busca produtos no catálogo pelo nome ou parte do nome."""
4 context = ctx.deps
5 matches = context.catalog.search(query)
6 if not matches:
7 return f"Nenhum produto encontrado para '{query}'."
8 return "\n".join(f"{p.sku} - {p.name}" for p in matches)

Para simplificar o exemplo, as informações do catálogo são carregadas a partir de um arquivo JSON local, e expostas através de um model Pydantic:

1class Catalog(BaseModel):
2 products: list[Product]
3
4 def search(self, query: str) -> list[Product]:
5 q = query.lower()
6 return [p for p in self.products if q in p.name.lower()]

O catálogo utilizado no exemplo contém informações simples como SKU, preço, estoque e modelos compatíveis.

1{
2 "products": [
3 {
4 "sku": "FO-1023",
5 "name": "Filtro de óleo",
6 "price": 28.90,
7 "stock": 12,
8 "compatible_models": ["Gol 1.0", "Palio 1.4", "HB20 1.0"]
9 },
10 ]
11}

Golden Dataset

Para conseguir avaliar um agente precisamos antes definir o que deve ser considerado como comportamento “correto”. O golden dataset serve para esse propósito, ele é a nossa fonte de verdade. Nele escrevemos os cenários ideais de execução contendo:

  • a entrada do usuário;
  • a resposta esperada do agente;
  • e as ferramentas que deveriam ser utilizadas em cada turno.

Abaixo temos um cenário chamado search_and_order, onde o usuário busca um produto, consulta preço e estoque e por fim adiciona o item ao pedido.

1
2 {
3 "name":"search_and_order",
4 "turns":[
5 {
6 "input":"Vocês têm filtro de óleo?",
7 "expected_output":"Confirma que há filtro de óleo no catálogo (FO-1023).",
8 "expected_tools":[
9 "search_product"
10 ]
11 },
12 {
13 "input":"Qual o preço do FO-1023?",
14 "expected_output":"Informa o preço de R$ 28,90 para o FO-1023.",
15 "expected_tools":[
16 "check_price"
17 ]
18 },
19 ]
20 }
21

Além do search_and_order, o exemplo também possui cenários como compatibility_check e off_topic_redirect.

O cenário off_topic_redirect ilustra um detalhe importante: em alguns casos o comportamento correto é simplesmente não utilizar ferramentas. Nesse cenário o campo expected_tools permanece vazio, indicando que o agente deve apenas redirecionar a conversa sem executar operações desnecessárias.

Capturando o uso de ferramentas

Métricas relacionadas ao uso de ferramentas precisam observar não apenas a resposta final do agente, mas também o processo de execução. Para isso, precisamos capturar quais tools foram chamadas durante cada turno da conversa.

Em ambientes reais esse processo normalmente é feito através de ferramentas de observabilidade e tracing. Para simplificar o exemplo, vamos recuperar as chamadas de ferramenta diretamente das mensagens retornadas pelo pydantic_ai, utilizando o tipo ToolCallPart.

1def extract_tool_calls(messages: list) -> list[ToolCall]:
2"""Coleta as tools chamadas (nome e argumentos) em uma lista de mensagens do pydantic-ai."""
3return [
4 ToolCall(
5 name=part.tool_name,
6 input_parameters=part.args_as_dict()
7 )
8 for msg in messages
9 if isinstance(msg, ModelResponse)
10 for part in msg.parts
11 if isinstance(part, ToolCallPart)
12]

O resultado dessa função é uma lista contendo o nome da ferramenta utilizada e os parâmetros enviados pelo agente. Essas informações serão usadas mais adiante pelas métricas de avaliação single-turn.

Métricas single-turn

Com os dados de execução capturados, agora podemos avaliar cada turno individualmente. Antes de entrarmos nas métricas em si, precisamos entender o conceito de threshold. Threshold é o limiar mínimo a partir do qual uma pontuação é considerada aceitável. Na prática, ele funciona como o critério de aprovação ou reprovação da avaliação. Neste exemplo utilizamos 0.7 para todas as métricas.

Nenhuma métrica isolada é suficiente para avaliar um agente. Cada métrica observa uma dimensão diferente do comportamento do sistema: algumas avaliam o uso correto de ferramentas, enquanto outras verificam a qualidade da resposta final produzida pelo agente.

Tool Correctness

Tool Correctness verifica se o agente utilizou as ferramentas corretas para um determinado cenário. Essa métrica é importante porque agentes frequentemente conseguem produzir respostas plausíveis mesmo executando fluxos incorretos internamente.

Essa é uma métrica baseada em referência (reference-based), ou seja, depende de uma lista pré-definida contendo as ferramentas esperadas para cada turno.

1ToolCorrectnessMetric(threshold=settings.eval_threshold)

Na Figura 5 temos um exemplo de execução bem-sucedida de Tool Correctness em um dos turnos do cenário search_and_order. Nesse caso, o comportamento esperado era que o agente utilizasse a ferramenta check_price para consultar o preço do item antes de responder ao usuário.

Figura 5: exemplo de execução de Tool Correctness

Argument Correctness

Argument Correctness avalia se o agente utilizou os parâmetros corretos ao chamar uma ferramenta. Essa métrica complementa a Tool Correctness, já que utilizar a ferramenta correta não significa necessariamente utilizá-la corretamente.

Esse tipo de falha é comum em sistemas agênticos: o agente entende qual operação precisa executar, mas envia parâmetros incompletos, inválidos ou inconsistentes para a ferramenta.

1ArgumentCorrectnessMetric(
2 model=settings.judge_model,
3 threshold=settings.eval_threshold
4)

A Figura 6 mostra um exemplo onde Tool Correctness passou com sucesso, indicando que o agente escolheu a ferramenta correta. Porém, Argument Correctness identificou que a chamada foi feita sem o parâmetro esperado.

Figura 6: exemplo de execução de Argument Correctness

Response Correctness

Mesmo com ferramentas e parâmetros corretos, ainda precisamos verificar se a resposta produzida pelo agente realmente atende ao que o usuário pediu. Para isso implementei uma métrica customizada chamada ResponseCorrectness, utilizando o framework G-Eval disponível no DeepEval.

Aqui estamos interessados em avaliar a qualidade semântica da resposta gerada pelo agente.

Quando apresentei o G-Eval anteriormente, mencionei que ele é composto por três elementos principais:

  • definição da tarefa;
  • passos de avaliação (evaluation steps);
  • função de pontuação (scoring function).

Em vez de fornecer apenas um criteria textual e deixar o DeepEval gerar automaticamente a cadeia de pensamento do juiz, defini explicitamente os evaluation_steps. Isso me dá mais controle sobre o processo de avaliação e reduz ambiguidades no julgamento.

Essa abordagem difere parcialmente do G-Eval original proposto por Yang Liu et al. (2023), onde a cadeia de pensamento é gerada automaticamente a partir da descrição da tarefa.

A função de pontuação foi customizada através de objetos Rubric, que definem faixas de pontuação e descrevem o que caracteriza cada nível de qualidade da resposta.

1GEval(
2 name="ResponseCorrectness",
3 threshold=settings.eval_threshold,
4 evaluation_steps=[
5 "Identifique o que o cliente está pedindo no campo input.",
6 "Compare actual_output com expected_output e verifique se o conteúdo essencial (preços, SKUs, compatibilidade, confirmação de pedido) está coberto.",
7 "Verifique se actual_output não inventa dados (preços, estoque, modelos) que contradigam ou extrapolem expected_output.",
8 "Penalize respostas que ignoram parte da pergunta ou trazem informação irrelevante.",
9 "Considere clareza e concisão: idealmente uma ou duas frases, em tom cordial.",
10 ],
11 rubric=[
12 Rubric(
13 score_range=(0, 2),
14 expected_outcome="Resposta contradiz o catálogo ou inventa informações que não vieram das tools.",
15 ),
16 Rubric(
17 score_range=(3, 5),
18 expected_outcome="Resposta parcialmente correta, com lacunas ou imprecisões em relação ao esperado.",
19 ),
20 Rubric(
21 score_range=(6, 8),
22 expected_outcome="Resposta correta e alinhada com a referência esperada.",
23 ),
24 Rubric(
25 score_range=(9, 10),
26 expected_outcome="Resposta correta, clara, concisa e diretamente endereça a pergunta.",
27 ),
28 ],
29 evaluation_params=[
30 SingleTurnParams.INPUT,
31 SingleTurnParams.ACTUAL_OUTPUT,
32 SingleTurnParams.EXPECTED_OUTPUT,
33 ],
34 model=settings.judge_model,
35)

Na Figura 7 temos um exemplo onde ResponseCorrectness não atingiu o threshold mínimo. Apesar da resposta estar parcialmente correta, ela omitiu uma informação considerada importante no cenário esperado: o código do item solicitado.

Figura 7: exemplo de execução de Response Correctness - falha na resposta

A Figura 8 reforça um ponto importante sobre avaliação de agentes: uma resposta final aparentemente correta ainda pode esconder falhas internas no processo de execução. Nesse caso, a ferramenta correta foi chamada e a resposta final parecia adequada, mas o parâmetro esperado nunca foi enviado corretamente para a ferramenta.

Figura 8: exemplo de execução de Response Correctness - falha no parâmetro da ferramenta

Métricas multi-turn

As métricas single-turn analisam interações isoladas. Porém, em chatbots reais, muitos problemas só aparecem ao longo da conversa: perda de contexto, desvios de assunto e falhas em concluir o objetivo do usuário.

É nesse ponto que entram as métricas multi-turn, responsáveis por avaliar o comportamento do agente considerando o histórico completo da conversa.

O DeepEval oferece diversas métricas desse tipo, como KnowledgeRetention, RoleAdherence e TurnFaithfulness. Neste exemplo vamos utilizar ConversationCompleteness e TurnRelevancy, já que elas cobrem dois problemas clássicos de chatbots:

  • não concluir o que o usuário pediu;
  • perder relevância ao longo da conversa.

Conversation Completeness

Conversation Completeness avalia se o agente conseguiu satisfazer o objetivo do usuário ao longo da conversa inteira, considerando todos os turnos do diálogo.

Aqui o a avaliação já é feita considerando o processo como uma caixa preta, avaliamos se o resultado final condiz com a entrada fornecida.

Internamente, o modelo juiz infere quais eram as intenções do usuário durante a conversa e verifica se elas foram efetivamente atendidas pelo agente.

1ConversationCompletenessMetric(
2 model=settings.judge_model,
3 threshold=settings.eval_threshold
4)

A Figura 9 mostra um exemplo onde ConversationCompleteness não atingiu a pontuação mínima. Segundo a intenção inferida pelo modelo juiz, o Atendente não conseguiu cumprir completamente o que o usuário desejava ao longo da conversa.

Figura 9: exemplo de execução de Conversation Completeness

Turn Relevancy

Turn Relevancy avalia se as respostas do agente permanecem relevantes em relação ao contexto construído ao longo da conversa.

Esse tipo de métrica é especialmente importante em chatbots multi-turn, onde um único desvio de contexto pode comprometer completamente a experiência do usuário. Problemas desse tipo são frequentemente chamados de context drift.

1TurnRelevancyMetric(
2 model=settings.judge_model,
3 threshold=settings.eval_threshold
4)

Na Figura 10 temos um exemplo interessante: embora o modelo juiz tenha concluído que o Atendente não satisfez completamente o objetivo do usuário, as respostas continuaram relevantes para o contexto da conversa.

Isso mostra que diferentes métricas avaliam dimensões distintas do comportamento do agente. Um chatbot pode permanecer no tópico e ainda assim falhar em concluir a tarefa corretamente.

Figura 10: exemplo de execução de Turn Relevancy

Enquanto métricas single-turn avaliam decisões locais do agente, métricas multi-turn avaliam o comportamento global da conversa. Na prática, ambos os níveis são importantes: um agente pode executar corretamente um turno isolado e ainda assim falhar em concluir a interação como um todo.

pytest + CI/CD

Uma das vantagens mais interessantes do DeepEval é sua integração nativa com o pytest, amplamente utilizado no ecossistema Python. Isso permite tratar avaliações de agentes da mesma forma que tratamos testes tradicionais de software: com execução automatizada, critérios de aprovação e detecção de regressões.

Na prática, o threshold definido para cada métrica funciona como critério de aprovação ou reprovação do teste. Se a pontuação ficar abaixo do limiar configurado, o teste falha automaticamente.

1def test_single_turn(scenario, turn_idx, captured_by_scenario):
2"""Avalia cada turno isolado: Tool/Argument/Response correctness."""
3 golden, output, tools = captured_by_scenario[scenario.name][turn_idx]
4 test_case = LLMTestCase(
5 name=golden.name,
6 input=golden.input,
7 actual_output=output,
8 expected_output=golden.expected_output,
9 expected_tools=golden.expected_tools,
10 tools_called=tools,
11 )
12assert_test(test_case, SINGLE_TURN_METRICS)

Esse modelo se encaixa naturalmente em pipelines de CI/CD, permitindo executar avaliações automaticamente a cada alteração no agente. Em vez de depender apenas de testes manuais ou inspeção visual das respostas, passamos a ter uma suíte de regressão capaz de validar continuamente o comportamento do sistema

A implementação completa dos testes pode ser consultada no repositório em:

Conclusão

Mais do que avaliar o agente em um dado momento, todo esse processo cria uma garantia de evolução contínua. Cada modificação no sistema precisa provar que não regrediu o que já estava funcionando.

Existe ainda uma vantagem menos óbvia nesse tipo de abordagem: com avaliações automatizadas e thresholds bem definidos, conseguimos utilizar modelos menores e mais baratos sem abrir mão da qualidade do sistema. Isso permite otimizar custo e latência mantendo um nível mínimo de confiabilidade.

Por outro lado, esses ganhos não vêm de graça. A avaliação também depende de LLMs atuando como juízes, e o modelo responsável pela avaliação precisa ter capacidade suficiente para julgar com critério. Na prática, existe sempre um equilíbrio entre:

  1. custo do agente
  2. custo da avaliação
  3. qualidade do sistema final

Retomando a pergunta que motivou este artigo: como garantir a qualidade das respostas de um sistema que utiliza LLMs?

Assim como acontece na saída de um agente, aqui também não existe uma única resposta correta. A escolha das métricas, frameworks e ferramentas depende do tipo de sistema que queremos avaliar.

Mas uma coisa é certa: tratar avaliação como parte do ciclo de desenvolvimento é o que separa um agente experimental de um agente pronto para produção.

Referências

https://arxiv.org/abs/2303.16634

https://arxiv.org/abs/2302.04166

https://arxiv.org/abs/2309.15217

https://arxiv.org/html/2501.08208v1

https://arxiv.org/abs/2306.05685

https://www.confident-ai.com/blog/why-llm-as-a-judge-is-the-best-llm-evaluation-method

https://www.confident-ai.com/blog/llm-evaluation-metrics-everything-you-need-for-llm-evaluation

https://www.confident-ai.com/blog/definitive-ai-agent-evaluation-guide

https://www.trulens.org/

https://deepeval.com/

https://github.com/promptfoo/promptfoo

https://www.ragas.io/

https://mlflow.org/

https://langfuse.com/