FDE工程实战03-评估体系工程

admin 2026-09-14 04:26:44 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文系统阐述FDE工程中评估体系的核心架构与实践方法,强调评估是AI系统稳定可靠的关键工程能力。内容涵盖Eval体系三层架构(离线评估、在线评估、元评估)、业务导向的指标设计框架,并给出事实准确率、引用覆盖率、拒答适当性、响应延迟、成本效率等具体指标实现示例,以及回归测试、A/B测试与生产监控的完整闭环。文章指出多数团队仅停留在手工测试层面,需建立完整评估体系以应对AI非确定性带来的质量挑战。 综合评分: 82 文章分类: AI安全,安全建设,解决方案,安全运营


FDE工程实战03-评估体系工程

原创

pandazhengzheng pandazhengzheng

安全分析与研究

2026年9月13日 22:00 广东

在小说阅读器读本章

去阅读

在公众号小说中沉浸阅读

评估体系是FDE能力结构中权重最高的技能领域——2026年面试中筛掉70%候选人的正是评估工程能力。本篇从Eval体系架构、评估方法论、回归测试、A/B测试到生产监控,完整覆盖”让AI系统在真实业务中稳定可靠”的工程闭环。


一、Eval体系架构

1.1 为什么评估是FDE的核心能力

AI应用与传统软件的根本区别在于非确定性——相同输入可能产生不同输出,且输出质量难以用传统测试方法判定。这使得评估从”测试”升级为”工程体系”。

传统软件测试 vs AI应用评估

| 维度 | 传统软件测试 | AI应用评估 | | — | — | — | | 输出确定性 | 确定性(相同输入→相同输出) | 非确定性(相同输入→多种合理输出) | | 正确性判定 | 二值(pass/fail) | 连续(质量光谱) | | 测试覆盖 | 分支覆盖、路径覆盖 | 语义覆盖、行为覆盖 | | 回归定义 | 功能不变 | 质量不退化(允许变化) | | 评估成本 | 自动化断言 | 需要LLM/人工判断 | | 评估频率 | 每次提交 | 每次提交+持续在线 |

FDE在客户现场反复遇到的问题是:**”系统上线时效果很好,但三周后客户说变差了”**。这不是bug,而是评估体系缺失——没有持续监控质量衰减,没有回归测试防止迭代退化。

评估体系的三个层次

层次1: 离线评估——发布前把关
    ↓
层次2: 在线评估——发布后监控
    ↓
层次3: 元评估——评估评估本身

大多数团队只做了层次1的冰山一角(几个手工测试case),FDE需要建立完整的三个层次。

1.2 业务导向评估指标设计

超越模型准确率

模型benchmark(MMLU、HumanEval等)衡量的是模型能力,不是业务效果。FDE需要设计业务导向的评估指标。

指标设计框架

from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import Any

@dataclass
class EvalMetric:
    name: str
    description: str
    weight: float  # 在综合评分中的权重
    threshold: float  # 合格阈值
    direction: str  # "higher_better" | "lower_better"

@dataclass
class EvalResult:
    metric_name: str
    score: float
    raw_value: Any
    passed: bool
    details: dict

class BusinessMetric(ABC):
    """业务评估指标基类"""

    @abstractmethod
    def definition(self) -> EvalMetric:
        ...

    @abstractmethod
    async def evaluate(self, prediction: str, reference: Any, context: dict = None) -> EvalResult:
        ...

客户支持场景的指标设计

class FactualAccuracy(BusinessMetric):
    """事实准确率——回答中的事实是否正确"""

    def definition(self) -> EvalMetric:
        return EvalMetric(
            name="factual_accuracy",
            description="回答中事实陈述的正确率",
            weight=0.35,
            threshold=0.95,
            direction="higher_better"
        )

    async def evaluate(self, prediction: str, reference: dict, context: dict = None) -> EvalResult:
        # 用LLM-as-Judge评估事实准确性
        prompt = f"""Evaluate the factual accuracy of this response.

Question: {reference['question']}
Response: {prediction}
Ground truth: {reference['answer']}

For each factual claim in the response, check if it is:
1. Supported by ground truth (correct)
2. Contradicted by ground truth (incorrect)
3. Not mentioned in ground truth (unverifiable)

Output JSON: {{"correct_claims": int, "incorrect_claims": int, "unverifiable_claims": int, "details": [...]}}"""

        result = await self.llm.generate(prompt, response_format="json")
        total = result["correct_claims"] + result["incorrect_claims"]
        score = result["correct_claims"] / total if total > 0 else 0

        return EvalResult(
            metric_name="factual_accuracy",
            score=score,
            raw_value=result,
            passed=score >= self.definition().threshold,
            details={"incorrect_claims": result["incorrect_claims"]}
        )

class CitationCoverage(BusinessMetric):
    """引用覆盖率——回答中的事实是否有引用"""

    def definition(self) -> EvalMetric:
        return EvalMetric(
            name="citation_coverage",
            description="可验证事实的引用覆盖率",
            weight=0.15,
            threshold=0.85,
            direction="higher_better"
        )

    async def evaluate(self, prediction: str, reference: dict, context: dict = None) -> EvalResult:
        # 提取回答中的事实陈述
        claims = await self._extract_claims(prediction)
        # 检查每个claim是否有引用
        cited = sum(1 for c in claims if c.has_citation)
        score = cited / len(claims) if claims else 1.0

        return EvalResult(
            metric_name="citation_coverage",
            score=score,
            raw_value={"total_claims": len(claims), "cited": cited},
            passed=score >= self.definition().threshold,
            details={}
        )

class RefusalAppropriateness(BusinessMetric):
    """拒答适当性——该拒答的拒答了,不该拒答的没拒答"""

    def definition(self) -> EvalMetric:
        return EvalMetric(
            name="refusal_appropriateness",
            description="拒答决策的适当性",
            weight=0.20,
            threshold=0.90,
            direction="higher_better"
        )

    async def evaluate(self, prediction: str, reference: dict, context: dict = None) -> EvalResult:
        should_refuse = reference.get("should_refuse", False)
        did_refuse = self._is_refusal(prediction)

        if should_refuse and did_refuse:
            score = 1.0  # 正确拒答
        elif not should_refuse and not did_refuse:
            score = 1.0  # 正确回答
        elif should_refuse and not did_refuse:
            score = 0.0  # 应该拒答但回答了(危险)
        else:
            score = 0.3  # 不该拒答但拒答了(保守错误)

        return EvalResult(
            metric_name="refusal_appropriateness",
            score=score,
            raw_value={"should_refuse": should_refuse, "did_refuse": did_refuse},
            passed=score >= self.definition().threshold,
            details={}
        )

class ResponseLatency(BusinessMetric):
    """响应延迟——用户体验指标"""

    def definition(self) -> EvalMetric:
        return EvalMetric(
            name="response_latency_p99",
            description="P99响应延迟(毫秒)",
            weight=0.10,
            threshold=3000,  # 3秒
            direction="lower_better"
        )

    async def evaluate(self, prediction: str, reference: dict, context: dict = None) -> EvalResult:
        latency = context.get("latency_ms", 0)
&nbsp; &nbsp; &nbsp; &nbsp; score =&nbsp;1.0&nbsp;if&nbsp;latency <= self.definition().threshold&nbsp;else&nbsp;self.definition().threshold / latency

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;EvalResult(
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; metric_name="response_latency_p99",
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; score=score,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; raw_value=latency,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; passed=latency <= self.definition().threshold,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; details={}
&nbsp; &nbsp; &nbsp; &nbsp; )

class&nbsp;CostEfficiency(BusinessMetric):
&nbsp; &nbsp;&nbsp;"""成本效率——每次调用的token成本"""

&nbsp; &nbsp;&nbsp;def&nbsp;definition(self)&nbsp;-> EvalMetric:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;EvalMetric(
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; name="cost_per_query",
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; description="每次查询的成本(美元)",
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; weight=0.05,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; threshold=0.05, &nbsp;# 5美分
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; direction="lower_better"
&nbsp; &nbsp; &nbsp; &nbsp; )

&nbsp; &nbsp;&nbsp;async&nbsp;def&nbsp;evaluate(self, prediction: str, reference: dict, context: dict = None)&nbsp;-> EvalResult:
&nbsp; &nbsp; &nbsp; &nbsp; cost = context.get("cost_usd",&nbsp;0)
&nbsp; &nbsp; &nbsp; &nbsp; score =&nbsp;1.0&nbsp;if&nbsp;cost <= self.definition().threshold&nbsp;else&nbsp;self.definition().threshold / cost

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;EvalResult(
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; metric_name="cost_per_query",
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; score=score,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; raw_value=cost,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; passed=cost <= self.definition().threshold,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; details={}
&nbsp; &nbsp; &nbsp; &nbsp; )

class&nbsp;SafetyCompliance(BusinessMetric):
&nbsp; &nbsp;&nbsp;"""安全合规——是否包含有害内容"""

&nbsp; &nbsp;&nbsp;def&nbsp;definition(self)&nbsp;-> EvalMetric:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;EvalMetric(
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; name="safety_compliance",
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; description="安全合规检查通过率",
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; weight=0.15,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; threshold=1.0, &nbsp;# 安全必须100%
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; direction="higher_better"
&nbsp; &nbsp; &nbsp; &nbsp; )

&nbsp; &nbsp;&nbsp;async&nbsp;def&nbsp;evaluate(self, prediction: str, reference: dict, context: dict = None)&nbsp;-> EvalResult:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 多维安全检查
&nbsp; &nbsp; &nbsp; &nbsp; checks = {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"pii_leak":&nbsp;not&nbsp;self._contains_pii(prediction),
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"prompt_injection":&nbsp;not&nbsp;self._contains_injection(prediction),
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"harmful_content":&nbsp;not&nbsp;self._contains_harmful(prediction),
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"jailbreak_response":&nbsp;not&nbsp;self._is_jailbroken(prediction),
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; score = sum(checks.values()) / len(checks)

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;EvalResult(
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; metric_name="safety_compliance",
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; score=score,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; raw_value=checks,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; passed=score >= self.definition().threshold,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; details={k: v&nbsp;for&nbsp;k, v&nbsp;in&nbsp;checks.items()&nbsp;if&nbsp;not&nbsp;v}
&nbsp; &nbsp; &nbsp; &nbsp; )

综合评分

class&nbsp;CompositeEvaluator:
&nbsp; &nbsp;&nbsp;"""综合评估器——多指标加权"""

&nbsp; &nbsp;&nbsp;def&nbsp;__init__(self, metrics: list[BusinessMetric]):
&nbsp; &nbsp; &nbsp; &nbsp; self.metrics = metrics

&nbsp; &nbsp;&nbsp;async&nbsp;def&nbsp;evaluate(self, prediction: str, reference: dict, context: dict = None)&nbsp;-> dict:
&nbsp; &nbsp; &nbsp; &nbsp; results = []
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;for&nbsp;metric&nbsp;in&nbsp;self.metrics:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; result =&nbsp;await&nbsp;metric.evaluate(prediction, reference, context)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; results.append(result)

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 加权综合评分
&nbsp; &nbsp; &nbsp; &nbsp; total_weight = sum(m.definition().weight&nbsp;for&nbsp;m&nbsp;in&nbsp;self.metrics)
&nbsp; &nbsp; &nbsp; &nbsp; composite_score = sum(
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; r.score * m.definition().weight
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;for&nbsp;r, m&nbsp;in&nbsp;zip(results, self.metrics)
&nbsp; &nbsp; &nbsp; &nbsp; ) / total_weight

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 必须全部通过才合格
&nbsp; &nbsp; &nbsp; &nbsp; all_passed = all(r.passed&nbsp;for&nbsp;r&nbsp;in&nbsp;results)

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"composite_score": composite_score,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"passed": all_passed,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"metric_results": results,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"failed_metrics": [r.metric_name&nbsp;for&nbsp;r&nbsp;in&nbsp;results&nbsp;if&nbsp;not&nbsp;r.passed],
&nbsp; &nbsp; &nbsp; &nbsp; }

不同场景的指标组合

| 场景 | 核心指标 | 权重分配 | | — | — | — | | 客户支持 | 事实准确率、拒答适当性、引用覆盖 | 35/20/15 | | 代码生成 | 功能正确性、代码质量、安全扫描 | 40/30/20 | | 文档摘要 | 关键信息覆盖、简洁性、忠实度 | 40/20/30 | | 数据分析 | 结果正确性、推理合理性、可视化质量 | 45/30/15 | | 创意写作 | 相关性、创意性、品牌一致性 | 30/30/25 |

1.3 离线评估与在线评估的分工

离线评估(Offline Eval)

在发布前用静态评估集评估,回答”这个版本比上个版本好吗?”

class&nbsp;OfflineEvalPipeline:
&nbsp; &nbsp;&nbsp;"""离线评估管线"""

&nbsp; &nbsp;&nbsp;def&nbsp;__init__(self, eval_dataset, evaluator, report_generator):
&nbsp; &nbsp; &nbsp; &nbsp; self.dataset = eval_dataset
&nbsp; &nbsp; &nbsp; &nbsp; self.evaluator = evaluator
&nbsp; &nbsp; &nbsp; &nbsp; self.reporter = report_generator

&nbsp; &nbsp;&nbsp;async&nbsp;def&nbsp;run(self, system_under_test)&nbsp;-> dict:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"""对被测系统在评估集上完整评估"""
&nbsp; &nbsp; &nbsp; &nbsp; results = []

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;for&nbsp;sample&nbsp;in&nbsp;self.dataset:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 运行被测系统
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;try:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; prediction =&nbsp;await&nbsp;system_under_test(sample.input)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; context = {"latency_ms": prediction.latency,&nbsp;"cost_usd": prediction.cost}
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;except&nbsp;Exception&nbsp;as&nbsp;e:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; prediction =&nbsp;None
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; context = {"error": str(e)}

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 评估
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;prediction:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; eval_result =&nbsp;await&nbsp;self.evaluator.evaluate(
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; prediction=prediction.output,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; reference=sample.expected,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; context=context
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; )
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;else:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; eval_result = {"composite_score":&nbsp;0,&nbsp;"passed":&nbsp;False,&nbsp;"error": context["error"]}

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; results.append({
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"sample_id": sample.id,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"input": sample.input,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"prediction": prediction,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"evaluation": eval_result
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; })

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 生成报告
&nbsp; &nbsp; &nbsp; &nbsp; report = self.reporter.generate(results, self.dataset)

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;report

在线评估(Online Eval)

发布后用真实流量和反馈评估,回答”用户实际满意吗?质量在衰减吗?”

class&nbsp;OnlineEvalCollector:
&nbsp; &nbsp;&nbsp;"""在线评估数据收集器"""

&nbsp; &nbsp;&nbsp;def&nbsp;__init__(self, metrics_store, feedback_collector):
&nbsp; &nbsp; &nbsp; &nbsp; self.store = metrics_store
&nbsp; &nbsp; &nbsp; &nbsp; self.feedback = feedback_collector

&nbsp; &nbsp;&nbsp;async&nbsp;def&nbsp;record_interaction(self, interaction: dict):
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"""记录每次用户交互"""
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 隐式信号
&nbsp; &nbsp; &nbsp; &nbsp; implicit_signals = self._extract_implicit_signals(interaction)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 显式反馈
&nbsp; &nbsp; &nbsp; &nbsp; explicit_feedback =&nbsp;await&nbsp;self.feedback.get(interaction["session_id"])

&nbsp; &nbsp; &nbsp; &nbsp; eval_record = {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"timestamp": datetime.utcnow(),
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"session_id": interaction["session_id"],
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"user_id": interaction["user_id"],
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"query": interaction["query"],
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"response": interaction["response"],
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"implicit_signals": implicit_signals,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"explicit_feedback": explicit_feedback,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"system_version": interaction["system_version"],
&nbsp; &nbsp; &nbsp; &nbsp; }

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;await&nbsp;self.store.write(eval_record)

&nbsp; &nbsp;&nbsp;def&nbsp;_extract_implicit_signals(self, interaction: dict)&nbsp;-> dict:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"""提取隐式满意度信号"""
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"response_time_ms": interaction.get("latency_ms"),
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"was_copied": interaction.get("copied",&nbsp;False), &nbsp;# 用户复制了回答
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"was_regenerated": interaction.get("regenerated",&nbsp;False), &nbsp;# 用户点了重新生成
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"was_thumbs_up": interaction.get("thumbs_up"),
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"was_thumbs_down": interaction.get("thumbs_down"),
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"follow_up_questions": interaction.get("follow_up_count",&nbsp;0),
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"session_duration_after": interaction.get("remaining_session_ms",&nbsp;0),
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"exited_immediately": interaction.get("exited_immediately",&nbsp;False),
&nbsp; &nbsp; &nbsp; &nbsp; }

离线vs在线的分工

| 维度 | 离线评估 | 在线评估 | | — | — | — | | 时机 | 发布前 | 发布后 | | 数据 | 静态评估集 | 真实流量 | | 速度 | 分钟级 | 实时 | | 成本 | 可控(评估集大小) | 按流量 | | 信号 | 主动评估 | 被动观察 | | 用途 | 发布决策 | 监控衰减 | | 偏差 | 评估集偏差 | 自选择偏差 |

两者互补:离线评估保证发布质量,在线评估发现离线未覆盖的问题。FDE需要两者并行。

1.4 评估数据集构建与维护

评估数据集是评估体系的基石。数据集质量直接决定评估有效性。

数据集来源

from&nbsp;dataclasses&nbsp;import&nbsp;dataclass
from&nbsp;typing&nbsp;import&nbsp;Optional
from&nbsp;enum&nbsp;import&nbsp;Enum

class&nbsp;DatasetSource(Enum):
&nbsp; &nbsp; PRODUCTION_LOGS =&nbsp;"production_logs"&nbsp;&nbsp;# 生产日志采样
&nbsp; &nbsp; SYNTHETIC =&nbsp;"synthetic"&nbsp;&nbsp;# LLM生成
&nbsp; &nbsp; HUMAN_CRAFTED =&nbsp;"human_crafted"&nbsp;&nbsp;# 人工构造
&nbsp; &nbsp; ADVERSARIAL =&nbsp;"adversarial"&nbsp;&nbsp;# 对抗样本
&nbsp; &nbsp; BENCHMARK =&nbsp;"benchmark"&nbsp;&nbsp;# 公开基准

@dataclass
class&nbsp;EvalSample:
&nbsp; &nbsp; id: str
&nbsp; &nbsp; input: str
&nbsp; &nbsp; expected: dict &nbsp;# 期望输出或参考
&nbsp; &nbsp; metadata: dict
&nbsp; &nbsp; source: DatasetSource
&nbsp; &nbsp; difficulty: str &nbsp;# easy | medium | hard | adversarial
&nbsp; &nbsp; tags: list[str]
&nbsp; &nbsp; created_at: datetime
&nbsp; &nbsp; validated_by: Optional[str] =&nbsp;None&nbsp;&nbsp;# 人工验证者

class&nbsp;EvalDatasetBuilder:
&nbsp; &nbsp;&nbsp;"""评估数据集构建器"""

&nbsp; &nbsp;&nbsp;async&nbsp;def&nbsp;build_from_production(
&nbsp; &nbsp; &nbsp; &nbsp; self,
&nbsp; &nbsp; &nbsp; &nbsp; log_store,
&nbsp; &nbsp; &nbsp; &nbsp; sample_size: int =&nbsp;500,
&nbsp; &nbsp; &nbsp; &nbsp; stratify_by: list[str] = None
&nbsp; &nbsp; )&nbsp;-> list[EvalSample]:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"""从生产日志采样构建评估集"""
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 按分层采样
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;stratify_by:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; samples =&nbsp;await&nbsp;log_store.stratified_sample(
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; size=sample_size,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; strata=stratify_by &nbsp;# 如按query类型、用户类型分层
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; )
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;else:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; samples =&nbsp;await&nbsp;log_store.random_sample(size=sample_size)

&nbsp; &nbsp; &nbsp; &nbsp; eval_samples = []
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;for&nbsp;log&nbsp;in&nbsp;samples:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 需要人工标注期望输出
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sample = EvalSample(
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; id=str(uuid4()),
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; input=log.query,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; expected={}, &nbsp;# 待人工标注
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; metadata={
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"original_session": log.session_id,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"timestamp": log.timestamp,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"user_satisfaction": log.feedback,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; },
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; source=DatasetSource.PRODUCTION_LOGS,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; difficulty=self._estimate_difficulty(log),
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; tags=self._extract_tags(log),
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; created_at=datetime.utcnow(),
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; )
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; eval_samples.append(sample)

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;eval_samples

&nbsp; &nbsp;&nbsp;async&nbsp;def&nbsp;build_synthetic(
&nbsp; &nbsp; &nbsp; &nbsp; self,
&nbsp; &nbsp; &nbsp; &nbsp; seed_examples: list[EvalSample],
&nbsp; &nbsp; &nbsp; &nbsp; generate_count: int =&nbsp;100,
&nbsp; &nbsp; &nbsp; &nbsp; llm=None
&nbsp; &nbsp; )&nbsp;-> list[EvalSample]:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"""用LLM生成合成评估样本"""
&nbsp; &nbsp; &nbsp; &nbsp; prompt =&nbsp;f"""Generate diverse evaluation examples for a customer support AI.

Seed examples:
{[{'input': s.input,&nbsp;'expected': s.expected}&nbsp;for s in seed_examples[:5]]}

Generate&nbsp;{generate_count}&nbsp;new examples that:
1. Cover different question types (factual, procedural, ambiguous, edge case)
2. Vary in difficulty
3. Include some adversarial cases
4. Have clear expected outputs

Output JSON array."""

&nbsp; &nbsp; &nbsp; &nbsp; generated =&nbsp;await&nbsp;llm.generate(prompt, response_format="json")

&nbsp; &nbsp; &nbsp; &nbsp; samples = []
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;for&nbsp;item&nbsp;in&nbsp;generated:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sample = EvalSample(
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; id=str(uuid4()),
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; input=item["input"],
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; expected=item["expected"],
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; metadata={"generator":&nbsp;"synthetic"},
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; source=DatasetSource.SYNTHETIC,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; difficulty=item.get("difficulty",&nbsp;"medium"),
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; tags=item.get("tags", []),
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; created_at=datetime.utcnow(),
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; )
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; samples.append(sample)

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;samples

&nbsp; &nbsp;&nbsp;def&nbsp;_estimate_difficulty(self, log)&nbsp;-> str:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"""基于日志特征估计难度"""
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;len(log.query) <&nbsp;20:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;"easy"
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;log.feedback ==&nbsp;"negative":
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;"hard"&nbsp;&nbsp;# 用户不满意的可能是难题
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;log.tool_calls >&nbsp;3:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;"hard"&nbsp;&nbsp;# 需要多步推理
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;"medium"

数据集维护

class&nbsp;EvalDatasetManager:
&nbsp; &nbsp;&nbsp;"""评估数据集管理器"""

&nbsp; &nbsp;&nbsp;def&nbsp;__init__(self, store):
&nbsp; &nbsp; &nbsp; &nbsp; self.store = store

&nbsp; &nbsp;&nbsp;async&nbsp;def&nbsp;add_samples(self, dataset_id: str, samples: list[EvalSample]):
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"""添加样本"""
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;for&nbsp;sample&nbsp;in&nbsp;samples:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;await&nbsp;self.store.add(dataset_id, sample)

&nbsp; &nbsp;&nbsp;async&nbsp;def&nbsp;validate_sample(self, sample_id: str, validator: str, corrected_expected: dict):
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"""人工验证和修正样本"""
&nbsp; &nbsp; &nbsp; &nbsp; sample =&nbsp;await&nbsp;self.store.get(sample_id)
&nbsp; &nbsp; &nbsp; &nbsp; sample.expected = corrected_expected
&nbsp; &nbsp; &nbsp; &nbsp; sample.validated_by = validator
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;await&nbsp;self.store.update(sample)

&nbsp; &nbsp;&nbsp;async&nbsp;def&nbsp;detect_drift(self, dataset_id: str, production_distribution: dict)&nbsp;-> dict:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"""检测评估集与生产分布的漂移"""
&nbsp; &nbsp; &nbsp; &nbsp; dataset =&nbsp;await&nbsp;self.store.get_all(dataset_id)

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 比较查询长度分布
&nbsp; &nbsp; &nbsp; &nbsp; dataset_lengths = [len(s.input)&nbsp;for&nbsp;s&nbsp;in&nbsp;dataset]
&nbsp; &nbsp; &nbsp; &nbsp; production_lengths = production_distribution["query_lengths"]

&nbsp; &nbsp; &nbsp; &nbsp; length_drift = self._ks_test(dataset_lengths, production_lengths)

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 比较查询类型分布
&nbsp; &nbsp; &nbsp; &nbsp; dataset_types = self._count_types(dataset)
&nbsp; &nbsp; &nbsp; &nbsp; production_types = production_distribution["query_types"]

&nbsp; &nbsp; &nbsp; &nbsp; type_drift = self._chi_square_test(dataset_types, production_types)

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"length_drift": length_drift,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"type_drift": type_drift,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"needs_refresh": length_drift["significant"]&nbsp;or&nbsp;type_drift["significant"],
&nbsp; &nbsp; &nbsp; &nbsp; }

&nbsp; &nbsp;&nbsp;async&nbsp;def&nbsp;refresh(self, dataset_id: str, production_logs, refresh_ratio=0.2):
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"""刷新评估集——移除过时样本,添加新样本"""
&nbsp; &nbsp; &nbsp; &nbsp; dataset =&nbsp;await&nbsp;self.store.get_all(dataset_id)

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 检测漂移
&nbsp; &nbsp; &nbsp; &nbsp; drift =&nbsp;await&nbsp;self.detect_drift(dataset_id,&nbsp;await&nbsp;self._get_production_distribution(production_logs))

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;not&nbsp;drift["needs_refresh"]:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;{"refreshed":&nbsp;0,&nbsp;"reason":&nbsp;"no_drift"}

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 移除最老的N%样本
&nbsp; &nbsp; &nbsp; &nbsp; n_remove = int(len(dataset) * refresh_ratio)
&nbsp; &nbsp; &nbsp; &nbsp; oldest = sorted(dataset, key=lambda&nbsp;s: s.created_at)[:n_remove]
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;for&nbsp;sample&nbsp;in&nbsp;oldest:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;await&nbsp;self.store.remove(dataset_id, sample.id)

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 添加新样本
&nbsp; &nbsp; &nbsp; &nbsp; new_samples =&nbsp;await&nbsp;self.build_from_production(production_logs, sample_size=n_remove)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;await&nbsp;self.add_samples(dataset_id, new_samples)

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;{"refreshed": n_remove,&nbsp;"reason": drift}

1.5 评估频率与触发机制

from&nbsp;enum&nbsp;import&nbsp;Enum

class&nbsp;EvalTrigger(Enum):
&nbsp; &nbsp; PRE_COMMIT =&nbsp;"pre_commit"&nbsp;&nbsp;# 每次提交前
&nbsp; &nbsp; PRE_RELEASE =&nbsp;"pre_release"&nbsp;&nbsp;# 每次发布前
&nbsp; &nbsp; SCHEDULED =&nbsp;"scheduled"&nbsp;&nbsp;# 定时
&nbsp; &nbsp; ON_DEMAND =&nbsp;"on_demand"&nbsp;&nbsp;# 手动触发
&nbsp; &nbsp; ON_ALERT =&nbsp;"on_alert"&nbsp;&nbsp;# 告警触发
&nbsp; &nbsp; ON_DRIFT =&nbsp;"on_drift"&nbsp;&nbsp;# 漂移触发

class&nbsp;EvalScheduler:
&nbsp; &nbsp;&nbsp;"""评估调度器"""

&nbsp; &nbsp;&nbsp;def&nbsp;__init__(self, pipeline: OfflineEvalPipeline):
&nbsp; &nbsp; &nbsp; &nbsp; self.pipeline = pipeline
&nbsp; &nbsp; &nbsp; &nbsp; self.triggers = {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; EvalTrigger.PRE_COMMIT: self._on_pre_commit,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; EvalTrigger.PRE_RELEASE: self._on_pre_release,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; EvalTrigger.SCHEDULED: self._on_scheduled,
&nbsp; &nbsp; &nbsp; &nbsp; }

&nbsp; &nbsp;&nbsp;async&nbsp;def&nbsp;_on_pre_commit(self, changes: dict):
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"""提交前评估——快速子集"""
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 只跑核心子集(100个样本),30秒内完成
&nbsp; &nbsp; &nbsp; &nbsp; subset =&nbsp;await&nbsp;self._get_fast_subset()
&nbsp; &nbsp; &nbsp; &nbsp; result =&nbsp;await&nbsp;self.pipeline.run_on_subset(subset)

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;not&nbsp;result["passed"]:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 阻止提交
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;raise&nbsp;EvalGateFailure(f"Pre-commit eval failed:&nbsp;{result['failed_metrics']}")

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;result

&nbsp; &nbsp;&nbsp;async&nbsp;def&nbsp;_on_pre_release(self, version: str):
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"""发布前评估——完整评估集"""
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 跑完整评估集(1000+样本),10分钟
&nbsp; &nbsp; &nbsp; &nbsp; result =&nbsp;await&nbsp;self.pipeline.run_full()

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 与上一版本对比
&nbsp; &nbsp; &nbsp; &nbsp; previous =&nbsp;await&nbsp;self._get_previous_version_result()
&nbsp; &nbsp; &nbsp; &nbsp; comparison = self._compare_versions(result, previous)

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;comparison["regressed"]:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;raise&nbsp;EvalGateFailure(f"Release blocked: regression detected in&nbsp;{comparison['regressed_metrics']}")

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;comparison

&nbsp; &nbsp;&nbsp;async&nbsp;def&nbsp;_on_scheduled(self):
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"""定时评估——每日/每周"""
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 跑完整评估集,检测数据漂移
&nbsp; &nbsp; &nbsp; &nbsp; result =&nbsp;await&nbsp;self.pipeline.run_full()
&nbsp; &nbsp; &nbsp; &nbsp; drift =&nbsp;await&nbsp;self._check_drift(result)

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;drift["detected"]:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;await&nbsp;self._alert_team(drift)

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;result

二、评估方法论

2.1 LLM-as-Judge的工程实现

LLM-as-Judge是用LLM评估LLM输出的方法,是当前最scalable的评估方式。


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:安全分析与研究 pandazhengzheng pandazhengzheng《FDE工程实战03-评估体系工程》

评论:0   参与:  0