starweaver_runtime/output/
validation.rs1use async_trait::async_trait;
2use thiserror::Error;
3
4use crate::run::AgentRunState;
5
6use super::{OutputSchema, OutputValue};
7
8#[derive(Debug, Error)]
10pub enum OutputValidationError {
11 #[error("output is not valid JSON: {0}")]
13 InvalidJson(String),
14 #[error("output schema validation failed: {0}")]
16 Schema(String),
17 #[error("output retry requested: {0}")]
19 Retry(String),
20 #[error("output validation failed: {0}")]
22 Failed(String),
23}
24
25impl OutputValidationError {
26 #[must_use]
28 pub fn retry(message: impl Into<String>) -> Self {
29 Self::Retry(message.into())
30 }
31
32 #[must_use]
34 pub fn failed(message: impl Into<String>) -> Self {
35 Self::Failed(message.into())
36 }
37}
38
39pub type OutputValidationResult<T> = Result<T, OutputValidationError>;
41
42#[async_trait]
44pub trait OutputValidator: Send + Sync {
45 async fn validate(
51 &self,
52 state: &mut AgentRunState,
53 output: &OutputValue,
54 ) -> OutputValidationResult<()>;
55}
56
57pub struct FunctionOutputValidator<F> {
59 function: F,
60}
61
62impl<F> FunctionOutputValidator<F> {
63 #[must_use]
65 pub const fn new(function: F) -> Self {
66 Self { function }
67 }
68}
69
70#[async_trait]
71impl<F, Fut> OutputValidator for FunctionOutputValidator<F>
72where
73 F: Send + Sync + Fn(&mut AgentRunState, &OutputValue) -> Fut,
74 Fut: Send + std::future::Future<Output = OutputValidationResult<()>>,
75{
76 async fn validate(
77 &self,
78 state: &mut AgentRunState,
79 output: &OutputValue,
80 ) -> OutputValidationResult<()> {
81 (self.function)(state, output).await
82 }
83}
84
85pub fn parse_output(
91 raw_output: &str,
92 schema: Option<&OutputSchema>,
93) -> OutputValidationResult<OutputValue> {
94 match schema {
95 Some(schema) => {
96 let value = serde_json::from_str(raw_output)
97 .map_err(|error| OutputValidationError::InvalidJson(error.to_string()))?;
98 validate_json_value(&value, schema)?;
99 Ok(OutputValue::Json(value))
100 }
101 None => Ok(OutputValue::Text(raw_output.to_string())),
102 }
103}
104
105fn validate_json_value(
106 value: &serde_json::Value,
107 schema: &OutputSchema,
108) -> OutputValidationResult<()> {
109 let validator = jsonschema::validator_for(&schema.schema).map_err(|error| {
110 OutputValidationError::Schema(format!("invalid output schema {}: {error}", schema.name))
111 })?;
112 validator.validate(value).map_err(|error| {
113 let path = error.instance_path.as_str();
114 let detail = error.masked().to_string();
115 if path.is_empty() {
116 OutputValidationError::Schema(detail)
117 } else {
118 OutputValidationError::Schema(format!("{path}: {detail}"))
119 }
120 })
121}