Skip to content

课 2 · LLM-as-a-judge

本课目标

用 LLM 自动评估答案质量,实现 Faithfulness、Answer Relevance、Context Precision 三个核心指标。

关键理解:LLM-as-a-judge 不是让 LLM 猜答案对不对,而是给它明确的评分标准和结构化输出格式。

为什么需要答案质量评测

检索指标只能说"找到了正确文档",但找到了文档不代表答案就好:

  • 找到了相关文档,但答案从其他地方瞎编了(低 Faithfulness
  • 文档相关,答案也忠实,但没回答用户真正的问题(低 Answer Relevance
  • 检索到了 5 个文档,但真正有用的只有 1 个(低 Context Precision

这三个指标是 RAGAS 框架提出的,业界广泛使用。

Faithfulness(忠实度)

定义:答案中的每个声明都能在检索到的文档中找到依据。

Faithfulness=有文档支撑的声明数答案中的声明总数
typescript
// packages/eval-core/src/answer-eval.ts
import { generateObject } from 'ai'
import { z } from 'zod'

const faithfulnessSchema = z.object({
  statements: z.array(
    z.object({
      statement: z.string(),
      supported: z.boolean(),
      evidence: z.string().optional(),
    })
  ),
  score: z.number().min(0).max(1),
})

export async function evaluateFaithfulness(
  answer: string,
  contexts: string[],
) {
  const { object } = await generateObject({
    model: getModel(),
    schema: faithfulnessSchema,
    prompt: `
你是一个答案忠实度评估助手。判断答案中每个声明是否有文档支撑。

参考文档:
${contexts.join('\n\n---\n\n')}

答案:
${answer}

对答案中每个独立声明判断:它是否可以从参考文档中直接推断出来?
计算 score = 有文档支撑的声明数 / 总声明数。
    `.trim(),
  })

  return {
    score: object.score,
    statements: object.statements,
  }
}

Answer Relevance(答案相关性)

定义:答案是否真正回答了用户的问题。

typescript
const relevanceSchema = z.object({
  score: z.number().min(0).max(1),
  reason: z.string(),
  isAnswered: z.boolean(),
})

export async function evaluateAnswerRelevance(
  question: string,
  answer: string,
) {
  const { object } = await generateObject({
    model: getModel(),
    schema: relevanceSchema,
    prompt: `
评估以下答案是否真正回答了问题。

问题:${question}

答案:${answer}

打分标准:
1.0 = 完整、直接地回答了问题
0.5 = 部分回答,或回答了但不够直接
0.0 = 没有回答问题,或答非所问
    `.trim(),
  })

  return object
}

Context Precision(上下文精度)

定义:检索到的文档中,真正有用的比例。

Context Precision=真正相关的文档数总检索文档数
typescript
const contextPrecisionSchema = z.object({
  relevanceScores: z.array(
    z.object({
      contextIndex: z.number(),
      isRelevant: z.boolean(),
      reason: z.string(),
    })
  ),
  score: z.number().min(0).max(1),
})

export async function evaluateContextPrecision(
  question: string,
  contexts: string[],
) {
  const { object } = await generateObject({
    model: getModel(),
    schema: contextPrecisionSchema,
    prompt: `
判断以下每个检索到的文档片段对于回答问题是否真正有帮助。

问题:${question}

${contexts.map((ctx, i) => `文档片段 ${i + 1}:\n${ctx}`).join('\n\n---\n\n')}

对每个片段判断是否真正有助于回答问题(而不只是主题相关)。
计算 score = 相关片段数 / 总片段数。
    `.trim(),
  })

  return object
}

批量评测

typescript
// packages/eval-core/src/batch-eval.ts
export interface AnswerEvalResult {
  questionId: string
  question: string
  faithfulness: number
  answerRelevance: number
  contextPrecision: number
}

export async function batchEvalAnswers(
  cases: Array<{
    id: string
    question: string
    answer: string
    contexts: string[]
  }>,
): Promise<AnswerEvalResult[]> {
  const results: AnswerEvalResult[] = []

  for (const c of cases) {
    const [faithfulness, relevance, precision] = await Promise.all([
      evaluateFaithfulness(c.answer, c.contexts),
      evaluateAnswerRelevance(c.question, c.answer),
      evaluateContextPrecision(c.question, c.contexts),
    ])

    results.push({
      questionId: c.id,
      question: c.question,
      faithfulness: faithfulness.score,
      answerRelevance: relevance.score,
      contextPrecision: precision.score,
    })
  }

  return results
}

评测报告

typescript
// scripts/run-answer-eval.ts
const results = await batchEvalAnswers(testCases)

const avg = (arr: number[]) => arr.reduce((a, b) => a + b, 0) / arr.length

console.log('=== 答案质量评测 ===')
console.log(`Faithfulness:       ${(avg(results.map(r => r.faithfulness)) * 100).toFixed(1)}%`)
console.log(`Answer Relevance:   ${(avg(results.map(r => r.answerRelevance)) * 100).toFixed(1)}%`)
console.log(`Context Precision:  ${(avg(results.map(r => r.contextPrecision)) * 100).toFixed(1)}%`)

// 写入报告文件
const report = { timestamp: new Date().toISOString(), metrics: {...}, details: results }
writeFileSync('datasets/eval/report.json', JSON.stringify(report, null, 2))

本节产物

packages/eval-core/src/
  answer-eval.ts          # Faithfulness / Answer Relevance / Context Precision
  batch-eval.ts           # 批量评测
scripts/
  run-answer-eval.ts
datasets/eval/
  report.json             # 评测报告

面试追问

LLM-as-a-judge 本身会不会产生幻觉导致评分不准?

会。LLM 评判结果有偏差,不能完全信任。实践中的缓解方式:1)用强力模型(GPT-4o、Claude 3.5)当裁判,比被评测的模型能力更强;2)给明确的评分标准,用结构化输出避免模棱两可;3)对关键判断加 double-check(正反两问比较);4)抽样人工复核,校准自动评分准确率。

问答机器人准确率如何保证?

分两层:检索准确率(Recall@K、Hit Rate)和答案准确率(Faithfulness、Answer Relevance)。提升路径:先看 Bad Case 属于哪层问题(是没找到文档,还是找到了但回答错了),针对性改进检索策略或 prompt,用评测集量化改进效果,建立回归测试防止退化。

面向前端工程师和独立开发者的 AI 应用工程课程