AI Agent 的工程化实践:从 prompt 调试到系统化评测的方法论

一、prompt 调参的地狱:为什么试了 50 遍还是不行

agent 的第一个版本很简单:把 PR 的 diff 文本拼进 prompt,让 GPT-4o 给 review 意见。第一个 prompt 大概长这样:

你是一个代码审查专家。请审查以下代码变更,找出潜在问题。

{diff}

结果是什么样的呢?agent 会指出"变量名可以改得更语义化"(对),但漏掉了"这段代码有 SQL 注入风险"(致命),还会对着完全正确的代码凭空捏造 bug(幻觉)。

我尝试了各种 prompt 技巧:加 system prompt、给几个示例、要求结构化输出……效果时好时坏。改一个 prompt 词,某些 case 变好了,另一些 case 又变差了。这就是 prompt 调参地狱:

两周后我意识到:没有评测集的 prompt 调参就是在盲飞。你必须先有一个固定的测试集,每次改 prompt 后跑一遍全量测试,才能知道到底有没有改进。

二、构建评测集:最难也是最必要的一步

我花了整整一周手工构建了一个 200 条代码片段的评测集。每条包含:

  • 代码 diff:模拟真实 PR 的变更
  • 正确 review:由我和一位 senior 工程师共同标注(关键!)
  • 分类标签:安全漏洞 / 性能问题 / 代码规范 / 逻辑错误 / 无问题
// ============================================================
// 评测集的数据结构
// ============================================================
use serde::{Deserialize, Serialize};

/// 一条评测用例
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EvalCase {
    /// 用例 ID(方便追踪哪条没过)
    pub id: String,
    /// PR diff 内容
    pub diff: String,
    /// 预期 review 结果(人工标注的正确答案)
    pub expected: Vec<ReviewIssue>,
    /// 这条用例的难度(easy / medium / hard)
    pub difficulty: Difficulty,
    /// 问题类别标签
    pub category: IssueCategory,
}

/// Agent 输出的 review 意见
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReviewIssue {
    /// 严重程度
    pub severity: Severity,
    /// 问题描述
    pub description: String,
    /// 涉及的文件和行号
    pub location: Option<SourceLocation>,
    /// 修复建议
    pub suggestion: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Difficulty { Easy, Medium, Hard }

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum IssueCategory {
    Security,
    Performance,
    CodeStyle,
    LogicError,
    NoIssue,
}

光有数据集还不够,还需要定义评测指标:

// ============================================================
// 评测指标计算
// ============================================================
pub struct EvalMetrics {
    /// 召回率:真实问题中被 agent 找出来的比例
    pub recall: f64,
    /// 精确率:agent 说的问题中真正是问题的比例
    pub precision: f64,
    /// F1 分数:recall 和 precision 的调和平均
    pub f1: f64,
    /// 幻觉率:agent 凭空捏造问题的比例
    pub hallucination_rate: f64,
}

impl EvalMetrics {
    pub fn compute(predictions: &[ReviewIssue], ground_truth: &[ReviewIssue]) -> Self {
        let true_positives = /* 计算真正例 */;
        let false_positives = /* 计算假正例(幻觉) */;
        let false_negatives = /* 计算假反例(漏报) */;
        
        let precision = true_positives as f64 / (true_positives + false_positives) as f64;
        let recall = true_positives as f64 / (true_positives + false_negatives) as f64;
        let f1 = 2.0 * precision * recall / (precision + recall);
        
        EvalMetrics {
            recall,
            precision,
            f1,
            hallucination_rate: false_positives as f64 / predictions.len() as f64,
        }
    }
}

三、有了评测集后,prompt 优化变成科学实验

评测集 build 好之后,prompt 优化就不再是盲飞了。我遵循了一个标准流程:

举个例子:通过分析评测结果,我发现 agent 在"性能问题"类别上的 recall 只有 35%。分析失败用例后,发现很多性能问题是关于"N+1 查询"模式的,agent 需要在更广的上下文中才能发现。于是我在 prompt 里加入了一条规则:

"当看到循环中包含数据库或 API 调用时,必须检查是否存在 N+1 查询问题,并标记为 Performance / High 严重度。"

加上这一条后,性能问题的 recall 从 35% 跳到了 68%。

// ============================================================
// Prompt 版本管理和 A/B 测试的简单实现
// ============================================================
pub struct PromptVersion {
    pub version: String,
    pub system_prompt: String,
    pub user_prompt_template: String,
    pub metrics: Option<EvalMetrics>,
}

impl PromptVersion {
    /// A/B 对比:判断新版本在所有维度上是否都优于旧版本
    pub fn is_strictly_better_than(&self, other: &PromptVersion) -> bool {
        match (&self.metrics, &other.metrics) {
            (Some(a), Some(b)) => {
                a.f1 > b.f1 
                && a.recall > b.recall 
                && a.precision > b.precision
            }
            _ => false,
        }
    }
}

四、从 prompt 调优到多阶段 pipeline

随着评测指标逐步提升,我发现单次 LLM 调用的天花板到了——precision 到 85% 就上不去了。于是我把 agent 拆成了多阶段 pipeline:

  1. 第一阶段(分类器):判断这条 PR 是否需要 review(跳过纯文档或配置变更)。
  2. 第二阶段(问题检测):扫描代码 diff,列出所有潜在问题。
  3. 第三阶段(严重度判断):对每个问题做 severity 分级。
  4. 第四阶段(去幻觉):用代码静态分析工具(Clippy、cargo check)验证 agent 发现的问题是否真的存在。

加了第四阶段后,幻觉率从 12% 降到了 3.5%。

但第四阶段也有代价:每次审查多了 200-400ms 的 clippy 执行开销。后来我们做了缓存——同一个 commit hash 的代码不再重复跑 clippy,增量耗时降到 50ms 以内。

多说一点:Claude 3.5 做代码 review 的幻觉率(3.5%)比 GPT-4(5.1%)低不少,选模型本身就是一个重要的优化手段。

五、总结

从 prompt 调参地狱到系统化评测,我总结的方法论是:

  1. 没有评测集就不要调 prompt。评测集是唯一的"锚点",没有它你永远不知道是在进步还是退步。
  2. 评测集必须人工标注。不能让 LLM 自己给自己出题改卷,那叫自欺欺人。
  3. 单次 LLM 调用的天花板很低。要提升到 85%+ 的准确率,必须用多阶段 pipeline + 代码分析工具作为"安全网"。
  4. Prompt 版本管理要像管理代码一样严肃。每个版本记录 prompt 内容 + 评测指标,确保可以回溯。

这套方法论不只适用于代码 review agent,任何需要"用 LLM 做判断"的场景都适用。

Logo

中国智能体开发者社区,聚焦智能体与大模型开发,提供前沿资讯、实用工具链、开源项目及行业案例。通过技术沙龙、开发者大赛等活动,促进经验交流与协作,助力开发者快速构建创新智能应用。

更多推荐