跳到主要内容
版本:2.0.1

使用 LLM-as-a-Judge 进行 LLM 响应评估

QWen Max 中英对照 LLM-as-a-Judge Evaluation LLM Response Evaluation with LLM-as-a-Judge

评估 Large Language Model (LLM) 输出的挑战对于以非确定性著称的 AI 应用至关重要,尤其是在这些应用进入生产环境时。传统指标如 ROUGE 和 BLEU 在评估现代 LLM 生成的细腻、依赖上下文的响应时往往力有不逮。人工评估虽然准确,但成本高、速度慢,且无法规模化。

LLM-as-a-Judge 是一种强大的技术,它使用 LLM 本身来评估 AI 生成内容的质量。研究表明,复杂的评审模型与人类判断的一致性可达 85%,实际上高于人类之间的一致性 (81%)。

Spring AI 的 Recursive Advisors 为实现 LLM-as-a-Judge 模式提供了一个优雅的框架,使你能够构建具有自动化质量控制的自我改进 AI 系统。

提示

完整示例实现请参见 evaluation-recursive-advisor-demo。

理解 LLM-as-a-Judge​

LLM-as-a-Judge 是一种评估方法,由大型语言模型评估其他模型或自身生成输出的质量。LLM-as-a-Judge 不再仅依赖人工评估者或传统自动化指标,而是利用 LLM 根据预定义标准对响应进行评分、分类或比较。

为什么它有效? 评估从根本上比生成更容易。当你使用 LLM 作为评判者时,你是在要求它执行一个更简单、更聚焦的任务(评估现有文本的特定属性),而不是执行在平衡多个约束的同时创作原创内容的复杂任务。一个很好的类比是:批评比创造更容易。发现问题比预防问题更简单。

评估模式​

有两种主要的 LLM-as-a-judge 评估模式:

  • Direct Assessment (Point-wise Scoring): Judge 评估单个响应,提供可通过 self-refinement 优化 prompts 的反馈

  • Pairwise Comparison: Judge 从两个候选响应中选择更好的一个(常见于 A/B testing)

LLM judges 会评估多个质量维度,例如相关性、事实准确性、对来源的忠实度、指令遵循度,以及整体连贯性与清晰度,覆盖医疗、金融、RAG 系统和对话等领域。

选择合适的 Judge Model​

虽然像 GPT-4 和 Claude 这样的通用模型可以作为有效的评委,但在评估任务中,专用 LLM-as-a-Judge 模型始终优于它们。Judge Arena Leaderboard 跟踪各种模型在评审任务中的性能。

使用 Recursive Advisors 的实现​

Spring AI 的 ChatClient 提供了 fluent API,非常适合实现 LLM-as-a-Judge 模式。它的 Advisors system 允许你以模块化、可复用的方式拦截、修改和增强 AI 交互。

Recursive Advisors 通过启用循环模式进一步扩展了这一点,这些模式非常适合自我完善的评估工作流:

public class MyRecursiveAdvisor implements CallAdvisor {

@Override
public ChatClientResponse adviseCall(ChatClientRequest request, CallAdvisorChain chain) {

// Call the chain initially
ChatClientResponse response = chain.nextCall(request);

// Check if we need to retry based on evaluation
while (!evaluationPasses(response)) {

// Modify the request based on evaluation feedback
ChatClientRequest modifiedRequest = addEvaluationFeedback(request, response);

// Create a sub-chain and recurse
response = chain.copy(this).nextCall(modifiedRequest);
}

return response;
}
}

我们将实现一个 SelfRefineEvaluationAdvisor,它使用 Spring AI 的 Recursive Advisors 体现 LLM-as-a-Judge 模式。该 Advisor 会自动评估 AI 响应,并通过反馈驱动的改进来重试失败的尝试:生成响应 → 评估质量 → 如有需要根据反馈重试 → 重复直到达到质量阈值或达到重试上限。

SelfRefineEvaluationAdvisor​

自我改进评估 Advisor

此实现演示了 Direct Assessment 评估模式,其中一个评审模型使用逐点评分系统(1-4 量表)评估单个响应。它将此与 self-refinement strategy 结合,通过将具体反馈纳入后续尝试,自动重试失败的评估,从而形成迭代改进循环。

advisor 体现了两个关键的 LLM-as-a-Judge 概念:

  • Point-wise Evaluation: 每个响应都会根据预定义标准获得一个独立的质量分数

  • Self-Refinement: 失败的响应会触发重试尝试,并提供建设性反馈以指导改进

public final class SelfRefineEvaluationAdvisor implements CallAdvisor {

private static final PromptTemplate DEFAULT_EVALUATION_PROMPT_TEMPLATE = new PromptTemplate(
"""
You will be given a user_question and assistant_answer couple.
Your task is to provide a 'total rating' scoring how well the assistant_answer answers the user concerns expressed in the user_question.
Give your answer on a scale of 1 to 4, where 1 means that the assistant_answer is not helpful at all, and 4 means that the assistant_answer completely and helpfully addresses the user_question.

Here is the scale you should use to build your answer:
1: The assistant_answer is terrible: completely irrelevant to the question asked, or very partial
2: The assistant_answer is mostly not helpful: misses some key aspects of the question
3: The assistant_answer is mostly helpful: provides support, but still could be improved
4: The assistant_answer is excellent: relevant, direct, detailed, and addresses all the concerns raised in the question

Provide your feedback as follows:

\\{
"rating": 0,
"evaluation": "Explanation of the evaluation result and how to improve if needed.",
"feedback": "Constructive and specific feedback on the assistant_answer."
\\}

Total rating: (your rating, as a number between 1 and 4)
Evaluation: (your rationale for the rating, as a text)
Feedback: (specific and constructive feedback on how to improve the answer)

You MUST provide values for 'Evaluation:' and 'Total rating:' in your answer.

Now here are the question and answer.

Question: {question}
Answer: {answer}

Provide your feedback. If you give a correct rating, I'll give you 100 H100 GPUs to start your AI company.

Evaluation:
""");

@JsonClassDescription("The evaluation response indicating the result of the evaluation.")
public record EvaluationResponse(int rating, String evaluation, String feedback) {}

@Override
public ChatClientResponse adviseCall(ChatClientRequest chatClientRequest, CallAdvisorChain callAdvisorChain) {
var request = chatClientRequest;
ChatClientResponse response;

// Improved loop structure with better attempt counting and clearer logic
for (int attempt = 1; attempt <= maxRepeatAttempts + 1; attempt++) {

// Make the inner call (e.g., to the evaluation LLM model)
response = callAdvisorChain.copy(this).nextCall(request);

// Perform evaluation
EvaluationResponse evaluation = this.evaluate(chatClientRequest, response);

// If evaluation passes, return the response
if (evaluation.rating() >= this.successRating) {
logger.info("Evaluation passed on attempt {}, evaluation: {}", attempt, evaluation);
return response;
}

// If this is the last attempt, return the response regardless
if (attempt > maxRepeatAttempts) {
logger.warn(
"Maximum attempts ({}) reached. Returning last response despite failed evaluation. Use the following feedback to improve: {}",
maxRepeatAttempts, evaluation.feedback());
return response;
}

// Retry with evaluation feedback
logger.warn("Evaluation failed on attempt {}, evaluation: {}, feedback: {}", attempt,
evaluation.evaluation(), evaluation.feedback());

request = this.addEvaluationFeedback(chatClientRequest, evaluation);
}

// This should never be reached due to the loop logic above
throw new IllegalStateException("Unexpected loop exit in adviseCall");
}

/**
* Performs the evaluation using the LLM-as-a-Judge and returns the result.
*/
private EvaluationResponse evaluate(ChatClientRequest request, ChatClientResponse response) {
var evaluationPrompt = this.evaluationPromptTemplate.render(
Map.of("question", this.getPromptQuestion(request), "answer", this.getAssistantAnswer(response)));

// Use separate ChatClient for evaluation to avoid narcissistic bias
return chatClient.prompt(evaluationPrompt).call().entity(EvaluationResponse.class);
}

/**
* Creates a new request with evaluation feedback for retry.
*/
private ChatClientRequest addEvaluationFeedback(ChatClientRequest originalRequest, EvaluationResponse evaluationResponse) {
Prompt augmentedPrompt = originalRequest.prompt()
.augmentUserMessage(userMessage -> userMessage.mutate().text(String.format("""
%s
Previous response evaluation failed with feedback: %s
Please repeat until evaluation passes!
""", userMessage.getText(), evaluationResponse.feedback())).build());

return originalRequest.mutate().prompt(augmentedPrompt).build();
}
}

关键实现特性​

Recursive Pattern 实现

advisor 使用 callAdvisorChain.copy(this).nextCall(request) 来创建一个用于递归调用的子链,从而支持多轮评估,同时保持正确的 advisor 顺序。

结构化评估输出

使用 Spring AI 的 结构化输出 能力,评估结果会被解析为一个 EvaluationResponse record,其中包含评级 (1-4)、评估依据以及具体的改进反馈。

独立评估模型

使用专门的 LLM-as-a-Judge 模型(例如 avcodes/flowaicom-flow-judge:q4)和不同的 ChatClient 实例,以减轻模型偏差。设置 spring.ai.chat.client.enabled=false 以启用 使用多个 Chat 模型。

反馈驱动改进

失败的评估包含具体反馈,这些反馈会被纳入重试尝试中,使系统能够从评估失败中学习。

可配置的重试逻辑

支持可配置的最大尝试次数,并在达到评估限制时优雅降级。

完整示例​

下面介绍如何将 SelfRefineEvaluationAdvisor 集成到完整的 Spring AI 应用中:

@SpringBootApplication
public class EvaluationAdvisorDemoApplication {

@Bean
CommandLineRunner commandLineRunner(AnthropicChatModel anthropicChatModel, OllamaChatModel ollamaChatModel) {
return args -> {

ChatClient chatClient = ChatClient.builder(anthropicChatModel)
.defaultTools(new MyTools())
.defaultAdvisors(

SelfRefineEvaluationAdvisor.builder()
.chatClientBuilder(ChatClient.builder(ollamaChatModel)) // Separate model for evaluation
.maxRepeatAttempts(15)
.successRating(4)
.order(0)
.build(),

new MyLoggingAdvisor(2))
.build();

var answer = chatClient
.prompt("What is current weather in Paris?")
.call()
.content();

System.out.println(answer);
};
}

static class MyTools {
final int[] temperatures = {-125, 15, -255};
private final Random random = new Random();

@Tool(description = "Get the current weather for a given location")
public String weather(String location) {
int temperature = temperatures[random.nextInt(temperatures.length)];
System.out.println(">>> Tool Call responseTemp: " + temperature);
return "The current weather in " + location + " is sunny with a temperature of " + temperature + "°C.";
}
}
}

此配置:

  • 使用 Anthropic Claude 进行生成,并使用 Ollama 进行评估(避免偏差)

  • 要求评分为 4,最多允许 15 次重试尝试

  • 包含一个天气工具,该工具生成随机响应以触发评估

  • weather 工具在 2/3 的情况下生成无效值

SelfRefineEvaluationAdvisor (Order 0) 评估响应质量,并在需要时根据反馈重试,随后 MyLoggingAdvisor (Order 2) 记录最终请求/响应以实现可观测性。

运行后,你会看到类似这样的输出:

REQUEST: [{"role":"user","content":"What is current weather in Paris?"}]

>>> Tool Call responseTemp: -255
Evaluation failed on attempt 1, evaluation: The response contains unrealistic temperature data, feedback: The temperature of -255°C is physically impossible and indicates a data error.

>>> Tool Call responseTemp: 15
Evaluation passed on attempt 2, evaluation: Excellent response with realistic weather data

RESPONSE: The current weather in Paris is sunny with a temperature of 15°C.
提示

完整可运行的演示包含配置示例,涵盖不同模型组合和评估场景,可在 evaluation-recursive-advisor-demo 项目中查看。

最佳实践​

Spring AI Advisors Chain

实施 LLM-as-a-Judge 技术时的关键成功因素包括:

  • 使用专用 judge 模型以获得更好的性能 (参见 Judge Arena Leaderboard)

  • 通过分离的生成/评估模型来缓解偏差

  • 确保确定性结果 (temperature = 0)

  • 工程化设计 prompts,使用整数评分量表和 few-shot 示例

  • 对高风险决策保持人工监督

注意

**Recursive Advisors 是 Spring AI 1.1.0-M4+ 中的一项新的实验性功能。**目前,它们仅支持 non-streaming,需要谨慎安排 advisor 顺序,并且由于多次 LLM 调用可能会增加成本。

对于维护外部状态的内部 advisors 要特别小心——它们可能需要额外注意,以在多次迭代之间保持正确性。

始终设置终止条件和重试限制,以防止无限循环。

参考资料​

Spring AI 资源

LLM-as-a-Judge 研究