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::{AgentContext, AgentResult};
7use car_inference::{GenerateParams, GenerateRequest};
8
9/// Verifier configuration.
10#[derive(Debug, Clone)]
11pub struct VerifyConfig {
12    pub max_tokens: usize,
13    pub temperature: f64,
14    pub model: Option<String>,
15}
16
17impl Default for VerifyConfig {
18    fn default() -> Self {
19        Self {
20            max_tokens: 2048,
21            temperature: 0.1, // low temp for consistent judgment
22            model: None,
23        }
24    }
25}
26
27/// Verifier: output + spec → pass/fail with reasons.
28pub struct Verifier {
29    ctx: AgentContext,
30    config: VerifyConfig,
31}
32
33impl Verifier {
34    pub fn new(ctx: AgentContext) -> Self {
35        Self {
36            ctx,
37            config: VerifyConfig::default(),
38        }
39    }
40
41    pub fn with_config(ctx: AgentContext, config: VerifyConfig) -> Self {
42        Self { ctx, config }
43    }
44
45    /// Verify work output against acceptance criteria.
46    pub async fn verify(&self, output: &str, criteria: &str) -> AgentResult {
47        let prompt = format!(
48            "You are a verification agent. Your job is to determine if the output meets the criteria.\n\n\
49            ## Acceptance Criteria\n{criteria}\n\n\
50            ## Output to Verify\n{output}\n\n\
51            Respond with:\n\
52            VERDICT: PASS or FAIL\n\
53            REASONS:\n\
54            - (specific reasons for your verdict)\n\
55            ISSUES:\n\
56            - (specific issues found, or 'None' if passing)"
57        );
58
59        let start = std::time::Instant::now();
60        let req = GenerateRequest {
61            prompt,
62            model: self.config.model.clone(),
63            params: GenerateParams {
64                temperature: self.config.temperature,
65                max_tokens: self.config.max_tokens,
66                ..Default::default()
67            },
68            context: None,
69            tools: None,
70            images: None,
71            messages: None,
72            cache_control: false,
73            response_format: None,
74            intent: None,
75        };
76
77        match self.ctx.inference.generate_tracked(req).await {
78            Ok(result) => {
79                let passed = result.text.contains("VERDICT: PASS");
80                AgentResult {
81                    agent: "verifier".into(),
82                    output: result.text,
83                    confidence: if passed { 0.9 } else { 0.8 },
84                    model_used: result.model_used,
85                    latency_ms: start.elapsed().as_millis() as u64,
86                }
87            }
88            Err(e) => AgentResult {
89                agent: "verifier".into(),
90                output: format!("Verification failed: {}", e),
91                confidence: 0.0,
92                model_used: String::new(),
93                latency_ms: start.elapsed().as_millis() as u64,
94            },
95        }
96    }
97}