多模型协同审查:用不同 LLM 交叉验证代码质量的工程方案
一、单一模型审查的局限性与多模型协同的必要性
单一 LLM 进行代码审查存在一个根本问题:模型的输出受限于其训练数据的分布、架构偏好和内置的偏置。一个主要训练于 Python 代码的模型可能在审查 React 组件时遗漏关键的渲染性能问题;一个偏向安全审计的模型可能对代码风格问题过于敏感却忽略架构层面的耦合。
更关键的是,不同的 LLM 在同一段代码上可能给出相互矛盾的审查意见。Claude 可能判定某个 useCallback 使用是可接受的,而 GPT 可能强烈建议移除。这种分歧不是缺陷——它恰恰反映了代码质量的多个维度需要不同的视角。
多模型协同审查的核心理念是:不依赖任何一个模型的单一判断,而是将多个模型的审查结果进行交叉验证,通过分歧点和共识点来提升审查结论的可信度。
flowchart TD A[代码提交] --> B[审查任务调度器] B --> C[模型 A: 主审查] B --> D[模型 B: 交叉审查] B --> E[模型 C: 专项审查] C --> F[性能 & 可维护性] D --> G[安全 & 可访问性] E --> H[架构 & 设计模式] F --> I[结果聚合器] G --> I H --> I I --> J{结果分析} J -->|三模型一致| K[高可信度问题] J -->|两模型一致| L[中可信度问题] J -->|仅单一模型| M[低可信度问题] K --> N[自动修复建议] L --> O[人工评审建议] M --> P[参考性建议] N --> Q[审查报告] O --> Q P --> Q二、多模型协同的架构设计
审查任务的分配策略
不是所有审查维度都需要多模型参与。将审查任务按类型分配给最合适的模型,在关键维度上使用多模型交叉验证,是平衡效果和成本的核心策略。
模型角色定义
/** * 多模型协同审查的模型角色与任务分配 */ // 审查模型的能力特征 interface ModelCapability { modelId: string; // 模型标识,如 "claude-4"、"gpt-4o" provider: 'anthropic' | 'openai' | 'google' | 'local'; strengths: ReviewDimension[]; // 该模型的优势审查维度 weaknesses: ReviewDimension[];// 该模型的已知弱项 costPer1kTokens: number; // 每千 token 的成本(用于成本控制) maxContextTokens: number; // 最大上下文长度 baseUrl: string; // API 基础地址 } type ReviewDimension = | 'performance' // 性能 | 'security' // 安全性 | 'maintainability' // 可维护性 | 'accessibility' // 可访问性 | 'architecture' // 架构 | 'code_style' // 代码风格 | 'testing'; // 测试质量 // 模型配置(实际项目从配置文件或环境变量读取) const MODEL_REGISTRY: ModelCapability[] = [ { modelId: 'claude-sonnet-4-20250514', provider: 'anthropic', strengths: ['architecture', 'maintainability', 'code_style'], weaknesses: ['accessibility'], costPer1kTokens: 0.003, maxContextTokens: 200000, baseUrl: process.env.CLAUDE_API_BASE ?? '', }, { modelId: 'gpt-4o', provider: 'openai', strengths: ['security', 'performance', 'testing'], weaknesses: ['accessibility'], costPer1kTokens: 0.005, maxContextTokens: 128000, baseUrl: process.env.OPENAI_API_BASE ?? '', }, { modelId: 'gemini-2.5-pro', provider: 'google', strengths: ['accessibility', 'code_style'], weaknesses: ['architecture'], costPer1kTokens: 0.0035, maxContextTokens: 1000000, baseUrl: process.env.GEMINI_API_BASE ?? '', }, ]; /** * 根据审查维度选择最优模型组合 * 策略:每个维度由 2 个模型交叉审查(一个主审、一个复核) */ interface ReviewAssignment { dimension: ReviewDimension; primaryModel: ModelCapability; // 主审模型 secondaryModel: ModelCapability; // 复核模型 } function assignReviewers( dimensions: ReviewDimension[] ): ReviewAssignment[] { const assignments: ReviewAssignment[] = []; for (const dimension of dimensions) { // 按该维度的能力评分排序 const ranked = [...MODEL_REGISTRY].sort((a, b) => { const aScore = a.strengths.includes(dimension) ? 2 : 0; const bScore = b.strengths.includes(dimension) ? 2 : 0; const aPenalty = a.weaknesses.includes(dimension) ? -2 : 0; const bPenalty = b.weaknesses.includes(dimension) ? -2 : 0; return (bScore + bPenalty) - (aScore + aPenalty); }); // 取前两名:一个主审、一个复核 if (ranked.length >= 2) { assignments.push({ dimension, primaryModel: ranked[0], secondaryModel: ranked[1], }); } else { // 模型不足时回退到单模型审查 assignments.push({ dimension, primaryModel: ranked[0], secondaryModel: ranked[0], // 同一模型作为复核(等同于单审) }); } } return assignments; }交叉验证的核心流程
sequenceDiagram participant Scheduler as 调度器 participant Primary as 主审模型 participant Secondary as 复核模型 participant Aggregator as 结果聚合器 participant Report as 审查报告 Scheduler->>Scheduler: 分析代码特征 Scheduler->>Scheduler: 确定审查维度和模型分配 par 并发审查 Scheduler->>Primary: 发送审查请求(代码 + Prompt) Scheduler->>Secondary: 发送审查请求(代码 + Prompt) end Primary-->>Aggregator: 返回审查结果 A Secondary-->>Aggregator: 返回审查结果 B Aggregator->>Aggregator: 对齐两份结果(去重 + 关联) Aggregator->>Aggregator: 识别共识问题(两模型都发现) Aggregator->>Aggregator: 识别分歧问题(仅单一模型发现) Aggregator->>Aggregator: 对分歧问题做可信度评分 Aggregator-->>Report: 输出分层审查报告 Report-->>Report: 高可信度 → 必须修复 Report-->>Report: 中可信度 → 建议修复 Report-->>Report: 低可信度 → 参考信息三、审查结果的聚合与仲裁策略
结果聚合器的实现
/** * 审查结果聚合引擎 * 对多模型的审查输出进行去重、关联、可信度评分 */ // 标准化的审查发现 interface ReviewFinding { id: string; // 统一标识(基于代码位置的哈希) severity: 'critical' | 'major' | 'minor' | 'info'; category: string; // 问题分类 file: string; // 文件路径 line: number; // 行号 column: number; // 列号 description: string; // 问题描述 suggestion: string; // 修复建议 // 多模型特有字段 foundBy: string[]; // 哪些模型发现了此问题 agreement: 'consensus' | 'majority' | 'single'; // 共识级别 modelConfidences: Record<string, number>; // 各模型对问题的置信度 } // 聚合后的审查报告 interface AggregatedReport { id: string; // 报告唯一标识 timestamp: string; codeSummary: { files: number; lines: number; languages: string[]; }; findings: { consensus: ReviewFinding[]; // 三模型以上(含)共识 majority: ReviewFinding[]; // 多模型(含双模型)共识 single: ReviewFinding[]; // 单模型发现 }; metrics: { totalFindings: number; consensusRate: number; // 共识率 = consensus / totalFindings modelCoverage: Record<string, number>; // 各模型的发现数量 averageConfidence: number; // 平均置信度 }; } class ReviewAggregator { /** * 聚合多个模型的审查结果 */ aggregate( modelResults: Array<{ modelId: string; findings: Omit<ReviewFinding, 'id' | 'foundBy' | 'agreement' | 'modelConfidences'>[]; }> ): AggregatedReport { // 第一步:为所有发现生成统一标识 const allFindings: ReviewFinding[] = []; for (const result of modelResults) { for (const finding of result.findings) { // 基于文件路径、行号和问题分类生成稳定 ID // 这允许不同模型发现的同一问题被关联 const id = this.generateFindingId( finding.file, finding.line, finding.category ); // 检查是否已存在相同 ID 的发现(来自其他模型) const existing = allFindings.find((f) => f.id === id); if (existing) { // 合并:将当前模型加入发现者列表 existing.foundBy.push(result.modelId); existing.modelConfidences[result.modelId] = 0.8; // 实际应从模型输出中提取 // 取最高严重级别 const severityOrder = ['critical', 'major', 'minor', 'info']; if ( severityOrder.indexOf(finding.severity) < severityOrder.indexOf(existing.severity) ) { existing.severity = finding.severity; } } else { allFindings.push({ ...finding, id, foundBy: [result.modelId], agreement: 'single', // 初始设为单模型,后续更新 modelConfidences: { [result.modelId]: 0.8, }, }); } } } // 第二步:根据发现者数量划分共识级别 for (const finding of allFindings) { const count = finding.foundBy.length; finding.agreement = count >= 3 ? 'consensus' : count >= 2 ? 'majority' : 'single'; } // 第三步:按共识级别分组 const consensus = allFindings.filter((f) => f.agreement === 'consensus'); const majority = allFindings.filter((f) => f.agreement === 'majority'); const single = allFindings.filter((f) => f.agreement === 'single'); // 第四步:计算指标 const modelCoverage: Record<string, number> = {}; for (const result of modelResults) { modelCoverage[result.modelId] = result.findings.length; } const consensusRate = allFindings.length > 0 ? Number(((consensus.length / allFindings.length) * 100).toFixed(1)) : 0; return { id: `report_${Date.now()}`, timestamp: new Date().toISOString(), codeSummary: { files: 0, // 由上游传入 lines: 0, languages: [], }, findings: { consensus, majority, single }, metrics: { totalFindings: allFindings.length, consensusRate, modelCoverage, averageConfidence: 0, }, }; } /** * 生成基于代码位置的稳定 ID * 使用文件路径 + 行号 + 分类的组合确保唯一性 */ private generateFindingId( file: string, line: number, category: string ): string { // 简单哈希:实际项目可使用 SHA-256 的一部分 const raw = `${file}:${line}:${category}`; let hash = 0; for (let i = 0; i < raw.length; i++) { const char = raw.charCodeAt(i); hash = (hash * 31 + char) & 0xFFFFFFFF; } return `FINDING_${Math.abs(hash).toString(36)}`; } }分歧问题的仲裁策略
当多个模型对同一个问题给出不同意见时,需要仲裁机制来决定最终建议。以下是推荐的仲裁策略:
/** * 分歧仲裁器 * 处理多模型之间对同一问题的不同判断 */ interface ArbitrationRule { // 仲裁规则的名称 name: string; // 适用的场景描述 appliesTo: (findings: ReviewFinding[], opinions: ModelOpinion[]) => boolean; // 仲裁逻辑:返回最终决定 arbitrate: (findings: ReviewFinding[], opinions: ModelOpinion[]) => ReviewFinding; } interface ModelOpinion { modelId: string; finding: ReviewFinding; reasoning: string; // 模型给出的判断理由 confidence: number; // 模型的自身置信度(0-1) } class DisputeArbitrator { private rules: ArbitrationRule[]; constructor() { this.rules = [ this.severityEscalationRule(), this.safetyFirstRule(), this.performanceConsensusRule(), ]; } /** * 执行仲裁 * @param findings 各方模型对同一问题的发现 * @returns 仲裁后的最终发现 */ arbitrate( findings: ReviewFinding[], opinions: ModelOpinion[] ): ReviewFinding { for (const rule of this.rules) { if (rule.appliesTo(findings, opinions)) { return rule.arbitrate(findings, opinions); } } // 默认仲裁:采用多数意见 // 如果一半一半,采用严重级别较高的一方 const agreeCount = findings.filter( (f) => f.severity !== 'info' ).length; const totalCount = findings.length; if (agreeCount > totalCount / 2) { // 多数模型同意存在问题 const nonInfo = findings.filter((f) => f.severity !== 'info'); return nonInfo[0]; } // 无法达成共识,降低严重级别为 info return { ...findings[0], severity: 'info', description: `[多模型意见不一致] ${findings[0].description}`, }; } /** * 规则一:安全问题的严重级别向上对齐 * 只要有任何模型认为存在安全问题,就按安全问题处理 */ private safetyFirstRule(): ArbitrationRule { return { name: '安全优先', appliesTo: (_findings, opinions) => { return opinions.some( (op) => op.finding.category === 'security' ); }, arbitrate: (findings) => { // 取所有安全相关的发现中严重级别最高的 const securityFindings = findings.filter( (f) => f.category === 'security' ); return securityFindings.sort( (a, b) => { const order = ['critical', 'major', 'minor', 'info']; return order.indexOf(a.severity) - order.indexOf(b.severity); } )[0]; }, }; } /** * 规则二:严重级别升级 * 当模型 A 认为 critical 而模型 B 认为 info 时,升级而不是取平均 */ private severityEscalationRule(): ArbitrationRule { return { name: '严重级别升级', appliesTo: (findings) => { const severities = new Set(findings.map((f) => f.severity)); return severities.size > 1 && severities.has('critical'); }, arbitrate: (findings) => { return findings.find((f) => f.severity === 'critical')!; }, }; } /** * 规则三:性能问题的共识优先 * 性能优化需要至少两个模型达成共识才建议实施 */ private performanceConsensusRule(): ArbitrationRule { return { name: '性能共识', appliesTo: (findings, opinions) => { return ( opinions.some((op) => op.finding.category === 'performance') && opinions.filter((op) => op.finding.severity !== 'info').length < opinions.length / 2 ); }, arbitrate: (findings) => { // 性能建议需要共识,单模型发现降级为 info return { ...findings[0], severity: 'info', description: `[待确认] ${findings[0].description} ` + '(仅单模型发现,建议人工复核)', }; }, }; } }四、成本控制与审查效率的平衡
多模型审查的首要挑战是成本。每次审查调用 3 个模型的 API,成本是单模型审查的 3 倍,耗时也可能更长。以下几个策略可以在效果和成本之间取得平衡。
分级审查策略
/** * 分级审查策略:根据代码变更的风险等级决定审查深度 */ type RiskLevel = 'low' | 'medium' | 'high' | 'critical'; interface RiskAssessment { level: RiskLevel; factors: string[]; // 触发高风险判断的因素 } class TieredReviewStrategy { /** * 根据风险等级选择审查强度 */ getReviewConfig(risk: RiskAssessment) { switch (risk.level) { case 'low': // 低风险:仅单模型快速审查 return { models: 1, dimensions: ['code_style', 'maintainability'] as ReviewDimension[], maxTokens: 4000, }; case 'medium': // 中风险:双模型标准审查 return { models: 2, dimensions: [ 'performance', 'security', 'maintainability', 'code_style', ] as ReviewDimension[], maxTokens: 8000, }; case 'high': // 高风险:三模型完整审查 return { models: 3, dimensions: [ 'performance', 'security', 'maintainability', 'accessibility', 'architecture', 'testing', ] as ReviewDimension[], maxTokens: 16000, }; case 'critical': // 最高风险:三模型 + 人工复核 return { models: 3, dimensions: [ 'performance', 'security', 'maintainability', 'accessibility', 'architecture', 'code_style', 'testing', ] as ReviewDimension[], maxTokens: 32000, requireHumanReview: true, }; } } /** * 评估代码变更的风险等级 */ assessRisk(changedFiles: string[], diffContent: string): RiskAssessment { const factors: string[] = []; // 规则一:涉及认证/授权的文件 → 高风险 const authPatterns = [/auth/, /login/, /session/, /token/, /permission/]; if ( changedFiles.some((file) => authPatterns.some((pattern) => pattern.test(file.toLowerCase())) ) ) { factors.push('涉及认证/授权逻辑'); } // 规则二:涉及支付/交易的文件 → 最高风险 const paymentPatterns = [/payment/, /billing/, /checkout/, /transaction/]; if ( changedFiles.some((file) => paymentPatterns.some((pattern) => pattern.test(file.toLowerCase())) ) ) { factors.push('涉及支付/交易逻辑'); return { level: 'critical', factors }; } // 规则三:大量代码变更 → 中等风险以上 const changedLines = diffContent.split('\n').length; if (changedLines > 500) { factors.push(`变更量较大 (${changedLines} 行)`); } // 规则四:修改了共享模块 → 中等风险 const sharedPatterns = [/shared/, /common/, /utils/, /hooks/]; if ( changedFiles.some((file) => sharedPatterns.some((pattern) => pattern.test(file.toLowerCase())) ) ) { factors.push('涉及共享模块'); } // 综合判定风险等级 if (factors.some((f) => f.includes('支付'))) { return { level: 'critical', factors }; } if (factors.some((f) => f.includes('认证'))) { return { level: 'high', factors }; } if (factors.length >= 2) { return { level: 'medium', factors }; } if (changedLines > 100) { return { level: 'medium', factors }; } return { level: 'low', factors }; } }审查结果缓存
相同的代码片段(或仅有微小改动)不应该被重复审查。通过内容哈希缓存审查结果,可以在增量提交中显著降低成本。
/** * 审查结果缓存 * 基于代码内容的哈希值缓存审查结果 * 注意:相同的函数名在不同位置可能语义不同,缓存键应包含文件路径 */ interface CacheEntry { reviewResult: ReviewFinding[]; codeHash: string; timestamp: number; ttl: number; // 存活时间(毫秒) } class ReviewCache { private cache = new Map<string, CacheEntry>(); private defaultTTL = 24 * 60 * 60 * 1000; // 24 小时 /** * 尝试从缓存获取审查结果 * @returns 缓存结果或 null(缓存未命中) */ get(filePath: string, code: string): ReviewFinding[] | null { const key = this.generateKey(filePath, code); const entry = this.cache.get(key); if (!entry) return null; // 检查 TTL 是否过期 if (Date.now() - entry.timestamp > entry.ttl) { this.cache.delete(key); return null; } console.log(`[缓存命中] ${filePath}`); return entry.reviewResult; } /** * 将审查结果存入缓存 */ set(filePath: string, code: string, findings: ReviewFinding[]): void { const key = this.generateKey(filePath, code); this.cache.set(key, { reviewResult: findings, codeHash: this.hashCode(code), timestamp: Date.now(), ttl: this.defaultTTL, }); // 限制缓存大小,防止内存溢出 if (this.cache.size > 500) { const oldestKey = this.cache.keys().next().value; if (oldestKey) { this.cache.delete(oldestKey); } } } /** * 生成缓存键:文件路径 + 内容哈希 */ private generateKey(filePath: string, code: string): string { return `${filePath}::${this.hashCode(code)}`; } /** * 简易哈希函数(生产环境应使用 crypto.createHash) */ private hashCode(input: string): string { let hash = 0; for (let i = 0; i < input.length; i++) { const char = input.charCodeAt(i); hash = ((hash << 5) - hash + char) | 0; } return Math.abs(hash).toString(36); } }五、总结
多模型协同审查不是简单的"多叫几个模型帮忙看看"。它需要一套完整的工程架构:审查维度的合理分配、模型角色的明确定义、审查结果的聚合与仲裁、成本与效果的分级控制。
多模型审查带来的核心价值是可信度的提升。共识发现的问题几乎可以确定是真实问题,可以直接进入自动修复流程;单一模型发现的问题则需要人工复核,降低了误报带来的信任消耗。
当前方案的一个明显局限是审查延迟——多模型串行调用的累计耗时。对于对响应速度有极高要求的场景,可以考虑异步审查模式:代码提交后立即合并,审查结果异步生成并回溯式地创建修复任务。这种模式的代价是可能让问题代码短暂进入主干,需要在团队内对风险评估达成共识。