大模型应用的日常检查方法

用户投诉比线上监控告警先到,这是大模型应用上线后最让人头疼的事情。

过去做传统软件服务,巡检的核心指标非常清晰:HTTP 200 响应率、P99 延迟、CPU 内存占用。然而大模型应用(Prompt + LLM)上线后,即使所有 API 的 HTTP 状态码全是 200,响应延迟也完全正常,输出的语义质量却可能已经在暗中发生了严重的坍塌。

大模型 API 供应商常常会静默更新底层模型版本(例如从 v1.2-patch1 升到 v1.2-patch2);团队内部工程师也可能为了优化某类特定场景而微调了全局 Prompt 提示词。

没有任何自动化评估巡检机制保障的 Prompt Engineering,就像在没有单元测试的代码库里裸奔。

1. 线上语义质量默不作声地下滑:用户投诉比监控告警先到

前段时间发生过一次非常典型的隐式故障。

在一个负责自动生成技术文档摘要的在线服务中,客服渠道突然接到了大量用户反馈:最近导出的摘要变得越来越啰嗦,原本要求的“三句点总结”变成了洋洋洒洒上千字的长文。

调出 APM 监控大盘,没有触发任何错误告警。网络 Latency 甚至还因为 Token 变长而略有微涨,但在系统阈值之内。

排查日志才发现,由于供应商在深夜对大模型做了一次热更新,微调了对格式指令的响应权重;加上两天前有同事在 Prompt 结尾加了一句看似无害的提示“请尽可能详细地阐述关键细节”,两者叠加在一起,直接导致模型对“简明摘要”指令的遵从度降为了零。

这种语义级别的退化与漂移,传统基于 HTTP 状态码的巡检体系根本无法感知。

2. 定位隐式漂移:API 供应商暗摸摸更新版本带来的 Prompt 失效

大模型应用的日常巡检,难点在于输出结果的非唯一性。同一个 Prompt,即便把 Temperature 设为 0,两次请求返回的字符串字面量也未必完全一致。

很多团队尝试过用正则匹配或 BLEU / ROUGE 得分做巡检,但很快就发现这套方法走不通:

正则表达式太死板,稍有词汇替换就会误报;而 BLEU / ROUGE 原本是为机器翻译设计的,无法衡量逻辑严密性、事实准确性以及格式遵从度。

常规监控 vs 大模型语义巡检 覆盖空隙:
传统 APM 监控: [HTTP 200 OK] ➔ [响应耗时 800ms] ➔ [判定服务健康]
语义巡检维度: [格式遵从率 40%] ➔ [幻觉发生率 18%] ➔ [判定模型输出坍塌!]

如果缺乏一套常态化运行的评估集(Eval Dataset)与自动化巡检 Pipeline,算法团队每天就会陷入盲目响应用户投诉的拉锯战中。

3. 自动化 Prompt 巡检与基准对比套件架构

少走弯路的解法,是构建一套每日定时触发的轻量级评估巡检套件。

我们设计了一套包含“规则硬校验”与“LLM-as-a-Judge 语义软评估”的双层巡检流水线。

这套架构不需要复杂的标注工程,核心是维护一个包含 50~100 条典型业务场景的“黄金评估集”(Golden Dataset),并在每次变更或每日凌晨自动跑一遍回归基线。

4. 基于 LLM-as-a-Judge 与规则双重校验的生产巡检脚本

以下是在 Python 环境下集成的自动化 Prompt 巡检脚本,支持多维度打分与指标漂移预警。

import json
import time
from typing import List, Dict, Any
from pydantic import BaseModel, Field

class BenchmarkItem(BaseModel):
    id: str
    input_text: str
    expected_keywords: List[str]
    max_length_limit: int

class EvaluationResult(BaseModel):
    item_id: str
    format_passed: bool
    judge_score: float = Field(..., description="裁判模型打分 1.0 - 5.0")
    reason: str

class PromptEvalPipeline:
    def __init__(self, target_llm_client, judge_llm_client, golden_dataset_path: str):
        self.target_llm = target_llm_client
        self.judge_llm = judge_llm_client
        self.dataset = self._load_dataset(golden_dataset_path)

    def _load_dataset(self, path: str) -> List[BenchmarkItem]:
        with open(path, "r", encoding="utf-8") as f:
            data = json.load(f)
        return [BenchmarkItem(**item) for item in data]

    def _rule_based_check(self, output: str, item: BenchmarkItem) -> bool:
        """第一防线:规则硬校验"""
        # 1. 长度限制检查
        if len(output) > item.max_length_limit:
            return False
        # 2. 必含关键词检查
        for kw in item.expected_keywords:
            if kw not in output:
                return False
        return True

    def _judge_evaluation(self, input_text: str, output_text: str) -> tuple[float, str]:
        """第二防线:LLM-as-a-Judge 裁判模型评估"""
        judge_prompt = f"""你是一个严谨的 AI 输出质量裁判。请对以下生成内容进行客观打分(1到5分)。

原始输入:
{input_text}

模型输出内容:
{output_text}

打分标准:
5分:完美遵循指令,逻辑严密,表达简洁无幻觉。
3分:基本符合指令,但包含部分冗余废话。
1分:完全违背指令要求,格式破坏或包含严重事实错误。

请严格按 JSON 格式返回:
{{"score": 4.5, "reason": "逻辑清晰,但多了一句客套话"}}
"""
        response = self.judge_llm.generate(judge_prompt)
        try:
            res_json = json.loads(response.text)
            return float(res_json["score"]), res_json["reason"]
        except Exception:
            return 1.0, "裁判模型响应解析失败"

    def run_daily_inspection(self, baseline_score: float = 4.2) -> Dict[str, Any]:
        """执行日常巡检流"""
        results: List[EvaluationResult] = []
        passed_rules_count = 0
        total_scores = 0.0

        for item in self.dataset:
            # 1. 调用当前生产环境 Prompt 生成结果
            generated_text = self.target_llm.generate_with_prod_prompt(item.input_text)
            
            # 2. 执行规则校验
            rule_passed = self._rule_based_check(generated_text, item)
            if rule_passed:
                passed_rules_count += 1
            
            # 3. 执行裁判打分
            score, reason = self._judge_evaluation(item.input_text, generated_text)
            total_scores += score
            
            results.append(EvaluationResult(
                item_id=item.id,
                format_passed=rule_passed,
                judge_score=score,
                reason=reason
            ))

        avg_score = total_scores / len(self.dataset)
        rule_pass_rate = passed_rules_count / len(self.dataset)

        is_alert_triggered = avg_score < (baseline_score * 0.95) # 下降超过 5%
        
        report = {
            "timestamp": time.strftime("%Y-%m-%d %H:%M:%S"),
            "total_samples": len(self.dataset),
            "rule_pass_rate": f"{rule_pass_rate * 100:.1f}%",
            "average_judge_score": round(avg_score, 2),
            "baseline_score": baseline_score,
            "alert_triggered": is_alert_triggered,
            "details": [r.dict() for r in results]
        }

        if is_alert_triggered:
            self._send_alert_notification(report)

        return report

    def _send_alert_notification(self, report: dict):
        print(f"[巡检预警触发!]: 平均分从基线 {report['baseline_score']} 掉至 {report['average_judge_score']}")

代码将巡检结果量化为规则通过率与裁判均分,一旦发现均分相较于历史基线(Baseline)下滑超过 5%,立即触发警报,从而在用户觉察之前捕获语义退化问题。

5. 巡检常态化:让 Prompt 像单元测试一样可回归

建立高效的 Prompt 巡检机制,需要落地三条工程守则。

第一,黄金测试集必须来源于真实的线上坏case(Bad Cases)。每当线上遇到用户反馈提取失败或回答偏离的样本,清洗脱敏后第一时间补充进评估集,确保同一个错误不能据此保证默默犯第二次。

第二,裁判模型(Judge Model)必须与目标模型保持独立。如果目标模型使用的是轻量级模型,建议使用能力更强的大模型(如 GPT-4 或 Claude 3.5 Sonnet)作为裁判,避免“低水平模型评价自己”带来的误判。

第三,将 Prompt 修改纳入 Git 版本控制与 CI / CD 流程。禁止任何人在线上配置后台直接手改提示词。每一次修改必须提交 Pull Request,并通过 Prompt 巡检脚本的回归测试后方可上线发布。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐