Skip to main content

car_agents/
verifier.rs

1//! Verifier agent — check if output meets a specification.
2//!
3//! Takes work product + acceptance criteria, returns pass/fail with reasons.
4//! The quality gate for any pipeline — nothing ships without verification.
5
6use crate::{generate_with, AgentContext, AgentGenerator, AgentResult};
7use car_inference::{GenerateParams, GenerateRequest};
8use std::sync::Arc;
9
10/// Verifier configuration.
11#[derive(Debug, Clone)]
12pub struct VerifyConfig {
13    pub max_tokens: usize,
14    pub temperature: f64,
15    pub model: Option<String>,
16}
17
18impl Default for VerifyConfig {
19    fn default() -> Self {
20        Self {
21            max_tokens: 2048,
22            temperature: 0.1, // low temp for consistent judgment
23            model: None,
24        }
25    }
26}
27
28/// Verifier: output + spec → pass/fail with reasons.
29pub struct Verifier {
30    ctx: AgentContext,
31    config: VerifyConfig,
32    generator: Option<Arc<AgentGenerator>>,
33}
34
35impl Verifier {
36    pub fn new(ctx: AgentContext) -> Self {
37        Self {
38            ctx,
39            config: VerifyConfig::default(),
40            generator: None,
41        }
42    }
43
44    pub fn with_config(ctx: AgentContext, config: VerifyConfig) -> Self {
45        Self {
46            ctx,
47            config,
48            generator: None,
49        }
50    }
51
52    /// Construct a verifier with an injected generation boundary.
53    pub fn with_generator(
54        ctx: AgentContext,
55        config: VerifyConfig,
56        generator: Arc<AgentGenerator>,
57    ) -> Self {
58        Self {
59            ctx,
60            config,
61            generator: Some(generator),
62        }
63    }
64
65    /// Verify work output against acceptance criteria.
66    pub async fn verify(&self, output: &str, criteria: &str) -> AgentResult {
67        let prompt = format!(
68            "You are a verification agent. Your job is to determine if the output meets the criteria.\n\n\
69            ## Acceptance Criteria\n{criteria}\n\n\
70            ## Output to Verify\n{output}\n\n\
71            Respond with:\n\
72            VERDICT: PASS or FAIL\n\
73            REASONS:\n\
74            - (specific reasons for your verdict)\n\
75            ISSUES:\n\
76            - (specific issues found, or 'None' if passing)"
77        );
78
79        let start = std::time::Instant::now();
80        let req = GenerateRequest {
81            prompt,
82            model: self.config.model.clone(),
83            params: GenerateParams {
84                temperature: self.config.temperature,
85                max_tokens: self.config.max_tokens,
86                ..Default::default()
87            },
88            context: None,
89            context_stable_prefix: None,
90            tools: None,
91            images: None,
92            messages: None,
93            cache_control: false,
94            response_format: None,
95            intent: None,
96            client_ref: None,
97            expected_row_digest: None,
98            expected_catalog_revision: None,
99            caller: None,
100        };
101
102        match generate_with(&self.ctx, self.generator.as_ref(), req).await {
103            Ok(result) => {
104                let passed = result.text.contains("VERDICT: PASS");
105                AgentResult {
106                    agent: "verifier".into(),
107                    output: result.text,
108                    confidence: if passed { 0.9 } else { 0.8 },
109                    model_used: result.model_used,
110                    latency_ms: start.elapsed().as_millis() as u64,
111                }
112            }
113            Err(e) => AgentResult {
114                agent: "verifier".into(),
115                output: format!("Verification failed: {}", e),
116                confidence: 0.0,
117                model_used: String::new(),
118                latency_ms: start.elapsed().as_millis() as u64,
119            },
120        }
121    }
122}