mod types;
pub use types::*;
use serde::de::DeserializeOwned;
use serde_json::Value;
use crate::error::{Result, TinyAgentsError};
use crate::harness::model::{ModelResponse, ResponseFormat};
impl StructuredOutput {
pub fn as_value(&self) -> &Value {
&self.value
}
pub fn parse<T: DeserializeOwned>(&self) -> Result<T> {
serde_json::from_value(self.value.clone())
.map_err(|e| TinyAgentsError::StructuredOutput(format!("deserialisation failed: {e}")))
}
}
impl StructuredExtractor {
pub fn new(
strategy: StructuredStrategy,
schema_name: impl Into<String>,
schema: Value,
) -> Self {
Self {
strategy,
schema_name: schema_name.into(),
schema,
}
}
pub fn schema(&self) -> &Value {
&self.schema
}
pub fn extract(&self, response: &ModelResponse) -> Result<StructuredOutput> {
match self.strategy {
StructuredStrategy::ProviderSchema => self.extract_provider_schema(response),
StructuredStrategy::ToolCall => self.extract_tool_call(response),
}
}
fn extract_provider_schema(&self, response: &ModelResponse) -> Result<StructuredOutput> {
let raw = response.text();
let value: Value = serde_json::from_str(&raw).map_err(|e| {
TinyAgentsError::StructuredOutput(format!(
"schema '{}': response text is not valid JSON: {e}",
self.schema_name
))
})?;
Ok(StructuredOutput {
value,
raw_text: Some(raw),
})
}
fn extract_tool_call(&self, response: &ModelResponse) -> Result<StructuredOutput> {
let call = response
.tool_calls()
.iter()
.find(|tc| tc.name == self.schema_name)
.ok_or_else(|| {
TinyAgentsError::Validation(format!(
"schema '{}': no tool call with that name found in response",
self.schema_name
))
})?;
Ok(StructuredOutput {
value: call.arguments.clone(),
raw_text: None,
})
}
}
pub fn response_format_for_strategy(
strategy: StructuredStrategy,
name: impl Into<String>,
schema: Value,
) -> ResponseFormat {
match strategy {
StructuredStrategy::ProviderSchema => ResponseFormat::json_schema(name, schema),
StructuredStrategy::ToolCall => ResponseFormat::Text,
}
}
#[cfg(test)]
mod test;