第三章:12、经验沉淀与自优化

主线闭环:失败 → 结构化 → 反哺 Prompt/评测集/路由 → 指标提升

1. 失败案例自动捕获

自动捕获和结构化工单失败案例,是持续优化的基础。通过合理设计数据结构,实现可检索、可聚合的案例沉淀。
核心流程:

  • 每次工单关闭后,自动判断是否为失败案例,并入库沉淀。
  • 失败判定标准包括:
  1. 人工审批被拒绝(方案不可用)
  2. 验证节点失败(方案无效)
  3. 重试次数 ≥ 2(Agent 兜圈子)
  4. 用户7天内重复提交同类工单(首次未根治)
  1. LLM-Judge 评分 ≤ 2
    数据结构设计要点:
  • 字段围绕"可检索、可聚合"展开,便于后续分析与优化。
java 复制代码
/**
 * 工单失败案例评审结果
 *
 * @param type          案例类型:MISROUTE / TOOL_FAILURE / SOLUTION_REJECTED / RETRY_LOOP / REPEATED_ISSUE
 * @param symptom       症状描述
 * @param rootCause      根因
 * @param resolution     处置方案
 * @param toolsUsedJson 使用的工具(JSON)
 * @param durationMs    总耗时(毫秒)
 */
public record CaseReview(
        String type,
        String symptom,
        String rootCause,
        String resolution,
        String toolsUsedJson,
        long durationMs
) {
    /**
     * 是否值得沉淀(命中任一失败判定即入库)
     */
    public boolean worthKeeping() {
        return type != null && !type.equals("OK");
    }
}
java 复制代码
/**
 * 失败案例自动捕获
 *
 * <p>每次工单关闭后,判断是否需要沉淀。
 * 命中任一失败判定即入库:
 * <ol>
 *   <li>人工审批被拒绝 → 方案不可用</li>
 *   <li>验证节点失败 → 方案无效</li>
 *   <li>重试次数 >= 2 → Agent 兜圈子</li>
 *   <li>用户重新提交同类工单(7天内同服务同关键词)→ 首次未根治</li>
 *   <li>LLM-Judge 评分 <= 2</li>
 * </ol>
 */
@Component
public class PostMortemRecorder {

    private static final Logger log = LoggerFactory.getLogger(PostMortemRecorder.class);

    @Resource
    private JdbcTemplate jdbc;

    /**
     * 工单关闭后调用
     */
    public void onWorkflowClosed(WorkflowContext ctx) {
        CaseReview review = classify(ctx);
        if (review.worthKeeping()) {
            try {
                jdbc.update("""
                        INSERT INTO experience_case
                        (case_type, symptom, root_cause, resolution, tools_used, duration_ms, trace_id, created_at)
                        VALUES (?, ?, ?, ?, ?, ?, ?, NOW())
                        """, review.type(), review.symptom(), review.rootCause(),
                        review.resolution(), review.toolsUsedJson(), review.durationMs(),
                        ctx.getWorkflowId());
                log.info("失败案例沉淀: type={}, symptom={}, traceId={}",
                        review.type(), review.symptom(), ctx.getWorkflowId());
            } catch (Exception e) {
                log.warn("写入失败案例失败: traceId={}, err={}", ctx.getWorkflowId(), e.getMessage());
            }
        }
    }

    /**
     * 判定是否值得沉淀
     */
    private CaseReview classify(WorkflowContext ctx) {
        List<NodeExecution> log_ = ctx.getExecutionLog();
        String symptom = ctx.getString("symptom");
        if (symptom == null) {
            symptom = ctx.getString("description");
        }
        if (symptom == null) {
            symptom = "未知症状";
        }

        // 判定1:人工审批被拒绝
        boolean approvalRejected = log_.stream()
                .anyMatch(e -> e.nodeId().contains("approval") && "FAILED".equals(e.status()));
        if (approvalRejected) {
            return new CaseReview("SOLUTION_REJECTED", symptom, "人工审批拒绝", "",
                    extractTools(log_), calcDuration(log_));
        }

        // 判定2:验证节点失败
        boolean verificationFailed = log_.stream()
                .anyMatch(e -> e.nodeId().contains("verification") && "FAILED".equals(e.status()));
        if (verificationFailed) {
            return new CaseReview("VERIFICATION_FAILED", symptom, "方案验证失败", "",
                    extractTools(log_), calcDuration(log_));
        }

        // 判定3:重试次数 >= 2(同一节点出现2次以上执行)
        long maxRetries = log_.stream()
                .collect(java.util.stream.Collectors.groupingBy(NodeExecution::nodeId, java.util.stream.Collectors.counting()))
                .values().stream()
                .max(Long::compare)
                .orElse(0L);
        if (maxRetries >= 2) {
            return new CaseReview("RETRY_LOOP", symptom, "Agent兜圈子(重试" + maxRetries + "次)", "",
                    extractTools(log_), calcDuration(log_));
        }

        // 判定4:流程失败
        if (ctx.getStatus() == WorkflowStatus.FAILED) {
            return new CaseReview("WORKFLOW_FAILED", symptom, "工作流执行失败", "",
                    extractTools(log_), calcDuration(log_));
        }

        // 成功完成,不需要沉淀
        return new CaseReview("OK", symptom, "", "", "", calcDuration(log_));
    }

    /**
     * 从执行日志中提取使用的工具列表(JSON 格式)
     */
    private String extractTools(List<NodeExecution> log_) {
        var tools = log_.stream()
                .map(NodeExecution::nodeId)
                .filter(id -> id.contains("tool") || id.contains("diagnosis") || id.contains("solution"))
                .distinct()
                .toList();
        return tools.isEmpty() ? "[]" : "[\"" + String.join("\",\"", tools) + "\"]";
    }

    /**
     * 从执行日志计算总耗时
     */
    private long calcDuration(List<NodeExecution> log_) {
        if (log_ == null || log_.isEmpty()) {
            return 0;
        }
        var first = log_.getFirst().timestamp();
        var last = log_.getLast().timestamp();
        return java.time.Duration.between(first, last).toMillis();
    }
}

2. 从失败案例生成 Few-Shot(路由专项)

针对"路由不准"问题,自动从典型失败案例中挖掘 few-shot 样本,动态注入路由 Prompt,实现错例收敛与持续进化。
实践要点:

  • 每周自动从路由错误工单中筛选典型样本,生成 few-shot 片段。
  • 限定每类样本数量,防止膨胀。
  • 动态挂载到路由 Prompt,自动纠正历史混淆。
java 复制代码
/**
 * 从失败案例生成 few-shot(路由专项)
 *
 * <p>应对"路由不准"最直接的解法:从路由错误的工单里挑典型样本,
 * 生成 few-shot 片段注入到路由 Prompt 里。
 *
 * <p>效果:错例会自动收敛------同一个混淆对被纠正后不再出现,
 * 新的混淆对才会冒出来。这比手工维护 few-shot 可持续得多。
 */
@Component
public class FewShotMiner {

    private static final Logger log = LoggerFactory.getLogger(FewShotMiner.class);

    @Resource
    private JdbcTemplate jdbc;

    /**
     * 每类最多保留的样本数,防膨胀
     */
    private static final int MAX_PER_CATEGORY = 8;

    /**
     * 每周执行:从路由错误的工单里挑典型样本,生成 few-shot 片段
     */
    public String mineRoutingFewShots() {
        try {
            List<Map<String, Object>> misroutes = jdbc.queryForList("""
                    SELECT symptom, root_cause as expected_agent, case_type
                    FROM experience_case
                    WHERE case_type IN ('MISROUTE', 'RETRY_LOOP')
                      AND created_at > NOW() - INTERVAL '30 DAY'
                    ORDER BY created_at DESC
                    LIMIT 20
                    """);

            if (misroutes.isEmpty()) {
                return "";
            }

            // 按症状分组,每组取最高频的一条
            return misroutes.stream()
                    .limit(MAX_PER_CATEGORY)
                    .map(r -> "用户:\"%s\" → %s (置信度0.90)"
                            .formatted(r.get("symptom"), r.get("expected_agent")))
                    .reduce("", (a, b) -> a + "\n" + b);
        } catch (Exception e) {
            log.warn("挖掘路由 few-shot 失败: {}", e.getMessage());
            return "";
        }
    }
}
java 复制代码
String dynamicExamples = fewShotMiner.mineRoutingFewShots();
// 注入到 routerAgent 的 system prompt 尾部

效果:

  • 错例自动收敛,历史混淆被纠正后不再反复出现。
  • 新的混淆对被及时暴露,持续优化路由准确率。

3. 工具描述的自我修复

工具调用准确率是核心指标。实践中,工具调用错误往往源于 @Tool(description=...) 描述不清晰。
自我修复机制:

  • 统计每个工具的"空参调用"和"调用失败"次数。
  • 空参率高或失败次数多的工具,自动生成改进建议。
    优化建议举例:
  • 空参率高 → 明确参数为必填,并举例取值范围。
  • 失败次数多 → 检查 description 是否清晰,参数约束是否明确。
java 复制代码
/**
 * 工具描述的自我修复
 *
 * <p>统计每个工具的"空参调用"和"调用失败"次数,
 * 空参率高的工具 → 自动生成改进建议。
 *
 * <p>实践中常见情况:工具调用错误往往源于 @Tool(description=...) 写得不够清楚,
 * 而非模型能力问题。
 */
@Component
public class ToolHealthReport {

    private static final Logger log = LoggerFactory.getLogger(ToolHealthReport.class);

    /**
     * 工具调用记录(简化版,实际从 DecisionLogger 的 TOOL_CALL 记录中提取)
     */
    public record ToolCallRecord(
            String toolName,
            String toolArgsJson,
            boolean success
    ) {}

    /**
     * 工具健康度
     */
    public record ToolHealth(
            String toolName,
            int totalCalls,
            long emptyArgsCalls,
            long failedCalls,
            double emptyArgsRate
    ) {}

    /**
     * 统计每个工具的"空参调用"和"调用失败"次数
     */
    public Map<String, ToolHealth> toolHealthReport(List<ToolCallRecord> calls) {
        return calls.stream()
                .collect(Collectors.groupingBy(ToolCallRecord::toolName))
                .entrySet().stream()
                .collect(Collectors.toMap(Map.Entry::getKey, e -> {
                    var list = e.getValue();
                    long badArgs = list.stream()
                            .filter(r -> r.toolArgsJson() == null || r.toolArgsJson().equals("{}"))
                            .count();
                    long failed = list.stream()
                            .filter(r -> !r.success())
                            .count();
                    return new ToolHealth(
                            e.getKey(),
                            list.size(),
                            badArgs,
                            failed,
                            list.isEmpty() ? 0.0 : (double) badArgs / list.size()
                    );
                }));
    }

    /**
     * 生成工具描述改进建议
     */
    public void reportAndSuggest(Map<String, ToolHealth> healthMap) {
        healthMap.forEach((tool, health) -> {
            if (health.emptyArgsRate() > 0.2) {
                log.warn("【工具健康】{} 空参率{}% → 建议在 @ToolParam 中明确标注参数为必填,并举例取值范围",
                        tool, String.format("%.0f", health.emptyArgsRate() * 100));
            }
            if (health.failedCalls() > 3) {
                log.warn("【工具健康】{} 失败{}次/{}次 → 建议检查 description 是否清晰、参数约束是否明确",
                        tool, health.failedCalls(), health.totalCalls());
            }
        });
    }
}

实际收获:

  • 快速定位被反复误调的工具,精准改进 description,提升整体调用成功率。

4. 评测集自动生长

保持评测集的"鲜活度",防止指标虚高但线上效果翻车。
自动生长机制:

  • 持续从线上真实问题中挖掘新用例,剔除已覆盖和低价值样本。
  • 候选用例经人工或 LLM 审核后晋升为正式评测用例。
  • 规则过滤:短于6字或命中寒暄词的输入直接丢弃;与现有用例精确去重。
    维护节奏建议:
  • 每周新增 5~10 条,淘汰 5 条过时用例。
  • 评测集规模建议维持在 60~80 条,重点是持续更新。
java 复制代码
/**
 * 评测集自增长
 *
 * <p>线上真实问题是评测集最宝贵的来源------手工维护的用例总会滞后于真实场景。
 * 本组件从工单中挖掘新问题,剔除已覆盖和低价值样本,把候选写入 {@code eval_case_candidate} 表,
 * 由人工或 LLM 筛选后晋升为正式评测用例。
 *
 * <p>关键约束:
 * <ul>
 *   <li>已存在于 EvalDataset 的输入不重复收录(基于输入文本精确去重;相似去重交给 LLM 判定)</li>
 *   <li>短于 6 字或命中寒暄词的输入视为闲聊,直接丢弃</li>
 *   <li>晋升后的候选标记为 PROMOTED,避免重复晋升</li>
 * </ul>
 */
@Component
public class DatasetGrower {

    private static final Logger log = LoggerFactory.getLogger(DatasetGrower.class);

    /**
     * 寒暄关键词,命中即视为低价值
     */
    private static final Set<String> CHITCHAT_KEYWORDS = Set.of(
            "你好", "在吗", "谢谢", "帮我", "请问", "hello", "hi", "thanks"
    );

    @Resource
    private JdbcTemplate jdbc;

    /**
     * 候选筛选器:判断是否值得加入评测集,并给出建议字段
     */
    private final ChatClient curator;

    public DatasetGrower(ChatClient.Builder builder) {
        this.curator = builder
                .defaultSystem("""
                        你是评测集策展人。判断一条线上工单是否值得固化为评测用例。
                        只返回 JSON:{"worth": true/false, "category": "routing|tool-call|rag|hallucination|workflow", "reason": "简短说明"}
                        """)
                .build();
    }

    /**
     * 从一条工单输入挖掘候选评测用例
     *
     * @param userInput      线上真实问题
     * @param sourceTraceId  来源 traceId
     */
    public void consider(String userInput, String sourceTraceId) {
        if (userInput == null || userInput.isBlank()) {
            return;
        }
        String trimmed = userInput.trim();

        // 1. 规则过滤:闲聊与过短输入
        if (trimmed.length() <= 6 || isChitchat(trimmed)) {
            return;
        }

        // 2. 精确去重:与现有评测集重复则跳过
        Set<String> existing = EvalDataset.opsCases().stream()
                .map(EvalCase::input)
                .map(String::trim)
                .collect(Collectors.toSet());
        if (existing.contains(trimmed)) {
            return;
        }

        // 3. 与候选表去重(避免同一输入多次入库)
        try {
            Integer dupCount = jdbc.queryForObject(
                    "SELECT COUNT(*) FROM eval_case_candidate WHERE input = ?",
                    Integer.class, trimmed);
            if (dupCount != null && dupCount > 0) {
                return;
            }
        } catch (Exception e) {
            log.debug("候选表去重查询失败,继续后续流程: {}", e.getMessage());
        }

        // 4. LLM 策展:判定价值并建议分类
        String category = "rag"; // 兜底分类
        boolean worth = true;
        try {
            String json = curator.prompt()
                    .user("""
                            判断以下工单是否值得固化为评测用例:
                            "%s"
                            只返回JSON:{"worth": true/false, "category": "...", "reason": "..."}
                            """.formatted(trimmed))
                    .call()
                    .content();
            if (json != null && json.contains("\"worth\":false")) {
                worth = false;
            }
            String parsed = extractJsonField(json, "category");
            if (parsed != null && !parsed.isBlank()) {
                category = parsed;
            }
        } catch (Exception e) {
            log.debug("LLM 策展失败,按默认价值入库: {}", e.getMessage());
        }

        if (!worth) {
            return;
        }

        // 5. 写入候选表,待后续晋升
        try {
            jdbc.update("""
                    INSERT INTO eval_case_candidate
                    (input, source_trace_id, suggested_category, status, created_at)
                    VALUES (?, ?, ?, 'PENDING', NOW())
                    """, trimmed, sourceTraceId, category);
            log.info("评测候选入库: category={}, traceId={}", category, sourceTraceId);
        } catch (Exception e) {
            log.warn("评测候选入库失败: traceId={}, err={}", sourceTraceId, e.getMessage());
        }
    }

    /**
     * 拉取待晋升的候选(status=PENDING),供人工或 LLM 审阅
     */
    public List<Candidate> pendingCandidates(int limit) {
        try {
            return jdbc.query("""
                    SELECT id, input, suggested_category, source_trace_id
                    FROM eval_case_candidate
                    WHERE status = 'PENDING'
                    ORDER BY created_at DESC
                    LIMIT ?
                    """, (rs, rowNum) -> new Candidate(
                            rs.getLong("id"),
                            rs.getString("input"),
                            rs.getString("suggested_category"),
                            rs.getString("source_trace_id")
                    ), limit);
        } catch (Exception e) {
            log.warn("查询候选失败: {}", e.getMessage());
            return List.of();
        }
    }

    /**
     * 晋升一条候选为正式评测用例(写入 EvalDataset 由人工维护,此处仅更新状态)
     */
    public void promote(long candidateId) {
        try {
            jdbc.update("""
                    UPDATE eval_case_candidate
                    SET status = 'PROMOTED', reviewed_at = NOW()
                    WHERE id = ?
                    """, candidateId);
            log.info("候选 {} 已晋升为正式评测用例", candidateId);
        } catch (Exception e) {
            log.warn("候选晋升失败: id={}, err={}", candidateId, e.getMessage());
        }
    }

    /**
     * 拒绝一条候选
     */
    public void reject(long candidateId, String reason) {
        try {
            jdbc.update("""
                    UPDATE eval_case_candidate
                    SET status = 'REJECTED', reviewed_at = NOW()
                    WHERE id = ?
                    """, candidateId);
            log.info("候选 {} 被拒绝:{}", candidateId, reason);
        } catch (Exception e) {
            log.warn("候选拒绝失败: id={}, err={}", candidateId, e.getMessage());
        }
    }

    /**
     * 候选用例
     */
    public record Candidate(long id, String input, String suggestedCategory, String sourceTraceId) {}

    /**
     * 简易判定是否为寒暄
     */
    private boolean isChitchat(String text) {
        String lower = text.toLowerCase();
        return CHITCHAT_KEYWORDS.stream().anyMatch(lower::contains);
    }

    /**
     * 简易 JSON 字段提取(与 MemoryConsolidator 同风格,避免引入 JSON 库)
     */
    private static final java.util.regex.Pattern FIELD_PATTERN =
            java.util.regex.Pattern.compile("\"(\\w+)\"\\s*:\\s*\"([^\"]*)\"");

    private String extractJsonField(String json, String field) {
        if (json == null) {
            return null;
        }
        var matcher = FIELD_PATTERN.matcher(json);
        while (matcher.find()) {
            if (matcher.group(1).equals(field)) {
                return matcher.group(2);
            }
        }
        return null;
    }
}

5. 裁判模型校准

监控 LLM-Judge 与人工复核评分的一致性,防止评分标准随时间"漂移"。
校准方法:

  • 按月抽样人工复核 LLM-Judge 评分,统计一致率趋势。
  • 指标分级:
    • 完全一致:人工分 = LLM 分
    • 可接受偏差:|人工分 - LLM 分| ≤ 1
    • 显著偏差:|人工分 - LLM 分| ≥ 2
  • 告警阈值:
  • 一致率 < 70% 或显著偏差 > 30% 时,提示重新校准 rubric 或更换裁判模型。
java 复制代码
/**
 * 裁判模型校准 ------ 监控 LLM-Judge 与人工复核的一致性
 *
 * <p>LLM-as-a-Judge 最怕的不是评分偏低,而是评分"漂移"------同一类用例的严格程度随时间变化。
 * 本组件按月抽样人工复核 LLM-Judge 的评分,统计一致率趋势:
 * <ul>
 *   <li>完全一致:人工分 == LLM 分</li>
 *   <li>偏差可接受:|人工分 - LLM 分| ≤ 1</li>
 *   <li>显著偏差:|人工分 - LLM 分| ≥ 2</li>
 * </ul>
 *
 * <p>当月一致率 < 0.7 或显著偏差占比 > 30% 时告警,提示需要重新校准 rubric 或更换裁判模型。
 */
@Component
public class JudgeCalibration {

    private static final Logger log = LoggerFactory.getLogger(JudgeCalibration.class);

    /**
     * 人工复核抽样比例:默认 20%
     */
    private static final double SAMPLE_RATIO = 0.2;

    /**
     * 一致率告警阈值
     */
    private static final double CONSISTENCY_THRESHOLD = 0.7;

    /**
     * 显著偏差占比告警阈值
     */
    private static final double SIGNIFICANT_DEVIATION_THRESHOLD = 0.3;

    @Resource
    private JdbcTemplate jdbc;

    /**
     * 记录一次 LLM-Judge 评分,等待人工抽样复核
     *
     * @param caseId      用例 ID
     * @param result      LLM-Judge 评分结果
     * @param sourceTraceId 来源 traceId
     */
    public void recordJudgeResult(String caseId, JudgeResult result, String sourceTraceId) {
        if (!result.valid()) {
            return;
        }
        try {
            // 按抽样比例决定是否需要人工复核
            boolean needsHumanReview = Math.random() < SAMPLE_RATIO;
            jdbc.update("""
                    INSERT INTO judge_calibration
                    (case_id, llm_score, llm_reason, human_score, human_reviewed, source_trace_id, created_at)
                    VALUES (?, ?, ?, NULL, ?, ?, NOW())
                    """, caseId, result.score(), result.reason(),
                    needsHumanReview, sourceTraceId);
        } catch (Exception e) {
            log.warn("记录 Judge 评分失败: caseId={}, err={}", caseId, e.getMessage());
        }
    }

    /**
     * 提交一次人工复核评分
     *
     * @param recordId    judge_calibration.id
     * @param humanScore  人工评分(0~5)
     */
    public void submitHumanReview(long recordId, int humanScore) {
        try {
            jdbc.update("""
                    UPDATE judge_calibration
                    SET human_score = ?, human_reviewed = TRUE, reviewed_at = NOW()
                    WHERE id = ?
                    """, humanScore, recordId);
            log.info("人工复核提交: recordId={}, humanScore={}", recordId, humanScore);
        } catch (Exception e) {
            log.warn("人工复核提交失败: recordId={}, err={}", recordId, e.getMessage());
        }
    }

    /**
     * 月度校准报告:统计本月 LLM-Judge 与人工复核的一致性
     * <p>每月 1 日凌晨执行
     */
    @Scheduled(cron = "0 0 2 1 * ?")
    public void monthlyCalibrationReport() {
        try {
            Map<String, Object> stats = jdbc.queryForMap("""
                    SELECT
                        COUNT(*) AS total,
                        COUNT(CASE WHEN human_score = llm_score THEN 1 END) AS exact_match,
                        COUNT(CASE WHEN ABS(human_score - llm_score) <= 1 THEN 1 END) AS acceptable,
                        COUNT(CASE WHEN ABS(human_score - llm_score) >= 2 THEN 1 END) AS significant_deviation,
                        AVG(ABS(human_score - llm_score)) AS avg_deviation
                    FROM judge_calibration
                    WHERE human_reviewed = TRUE
                      AND created_at >= date_trunc('month', NOW()) - INTERVAL '1 MONTH'
                      AND created_at < date_trunc('month', NOW())
                    """);

            long total = asLong(stats.get("total"));
            if (total == 0) {
                log.info("[月度校准] 本月无人工复核样本,跳过校准");
                return;
            }

            long exactMatch = asLong(stats.get("exact_match"));
            long acceptable = asLong(stats.get("acceptable"));
            long significant = asLong(stats.get("significant_deviation"));
            double avgDeviation = asDouble(stats.get("avg_deviation"));

            double consistencyRate = (double) acceptable / total;
            double significantRate = (double) significant / total;

            log.info("[月度校准] 样本数={}, 完全一致={}, 一致率(偏差≤1)={}, 显著偏差(≥2)={}, 平均偏差={}",
                    total, exactMatch,
                    String.format("%.0f%%", consistencyRate * 100),
                    String.format("%.0f%%", significantRate * 100),
                    String.format("%.2f", avgDeviation));

            if (consistencyRate < CONSISTENCY_THRESHOLD) {
                log.warn("[月度校准告警] 一致率 {}% 低于阈值 {}% → 建议重新审视 rubric 或更换裁判模型",
                        String.format("%.0f", consistencyRate * 100),
                        String.format("%.0f", CONSISTENCY_THRESHOLD * 100));
            }
            if (significantRate > SIGNIFICANT_DEVIATION_THRESHOLD) {
                log.warn("[月度校准告警] 显著偏差占比 {}% 超过阈值 {}% → 裁判模型可能存在系统性偏差",
                        String.format("%.0f", significantRate * 100),
                        String.format("%.0f", SIGNIFICANT_DEVIATION_THRESHOLD * 100));
            }
        } catch (Exception e) {
            log.warn("月度校准报告生成失败: {}", e.getMessage());
        }
    }

    /**
     * 拉取待人工复核的样本
     */
    public List<Map<String, Object>> pendingHumanReviews(int limit) {
        try {
            return jdbc.queryForList("""
                    SELECT id, case_id, llm_score, llm_reason, source_trace_id, created_at
                    FROM judge_calibration
                    WHERE human_reviewed = FALSE
                    ORDER BY created_at DESC
                    LIMIT ?
                    """, limit);
        } catch (Exception e) {
            log.warn("查询待复核样本失败: {}", e.getMessage());
            return List.of();
        }
    }

    /**
     * 兜底 LlmJudge 引用,确保模块依赖 observability(避免被误删)
     */
    @SuppressWarnings("unused")
    private LlmJudge judgeRef;

    /**
     * Spring 注入 LlmJudge,保持对 observability 模块的编译期依赖
     */
    @Resource
    public void setJudgeRef(LlmJudge judgeRef) {
        this.judgeRef = judgeRef;
    }

    private static long asLong(Object v) {
        if (v == null) {
            return 0L;
        }
        if (v instanceof Number n) {
            return n.longValue();
        }
        try {
            return Long.parseLong(v.toString());
        } catch (NumberFormatException e) {
            return 0L;
        }
    }

    private static double asDouble(Object v) {
        if (v == null) {
            return 0.0;
        }
        if (v instanceof Number n) {
            return n.doubleValue();
        }
        try {
            return Double.parseDouble(v.toString());
        } catch (NumberFormatException e) {
            return 0.0;
        }
    }
}

总结

  • 建立自动化闭环,让失败案例反哺 Prompt、评测集和路由,驱动指标持续提升。
  • 通过结构化数据、自动挖掘、健康度自检和模型校准,实现体系化、可持续优化。
    建议:
  • 关注数据可检索、可聚合,便于分析和持续改进。
  • 保持评测集和路由 few-shot 的动态更新,避免"过拟合"历史场景。
  • 定期校准裁判模型,确保评测指标的客观性与权威性。

相关推荐
Ven%2 小时前
在魔搭 Notebook 容器里跑 Qwen Code:从零配置到省魔粒的完整实践
人工智能·python·ai·ai编程·modelscope
学长毕设2 小时前
基于springboot的实验室设备管理系统
java·spring boot·后端
学长毕设3 小时前
基于springboot的社区养老监护系统的设计与实现
java·spring boot·后端
会编程的吕洞宾3 小时前
RAG检索质量怎么评估?3个指标+LLM裁判实战
java·开发语言·人工智能·后端
卢总霸气3 小时前
上海交大机动学院学位论文参考文献Zotero CSL 样式-2026
java
段一凡-华北理工大学3 小时前
高炉能源系统智能化:热风炉、煤气与极致能效~系列文章07:高炉煤气:产生规律与全厂用户平衡
java·前端·能源
咖啡八杯3 小时前
Redis 工具封装:RedisCache 对 Operations 体系的二次封装
java·数据库·redis·缓存·框架·springboot·若依
wno7043 小时前
Springboot国际化信息(i18n)解析
java·spring boot·后端
eleven_4u3 小时前
QPS如何计算,如何部署架构
java·linux·运维·jvm·中间件
牧瀬クリスだ3 小时前
Redis Hash实战:高效存储对象与购物车
java·数据库·redis·算法·哈希算法