Skip to main content

rig_typesafeai/
dynamic.rs

1//! Explicit runtime-defined evaluation queries.
2use crate::{Query, types::Question};
3use rig_core::error::ProviderError;
4use std::collections::BTreeMap;
5
6/// Runtime-defined questions with runtime-keyed answers. Choose this explicitly
7/// when the application cannot know the question set at compile time.
8#[derive(Debug, Clone, serde::Serialize)]
9#[serde(transparent)]
10pub struct DynamicQuery {
11    definitions: BTreeMap<String, Question>,
12}
13impl DynamicQuery {
14    /// Validate runtime question definitions before accepting them.
15    pub fn new(definitions: BTreeMap<String, Question>) -> Result<Self, ProviderError> {
16        if definitions.is_empty() {
17            return Err(ProviderError::request("at least one question is required"));
18        }
19        for (id, question) in &definitions {
20            crate::questions::validate_definition(id, question)?;
21        }
22        Ok(Self { definitions })
23    }
24}
25impl Query for DynamicQuery {
26    type Output = BTreeMap<String, crate::types::Answer>;
27    type Response = BTreeMap<String, crate::types::Answer>;
28    fn decode(&self, response: Self::Response) -> Result<Self::Output, ProviderError> {
29        if !self.definitions.keys().eq(response.keys()) {
30            return Err(ProviderError::Response(
31                "response question IDs differ from request".into(),
32            ));
33        }
34        self.definitions
35            .iter()
36            .map(|(id, question)| {
37                let answer = response
38                    .get(id)
39                    .ok_or_else(|| ProviderError::Response(format!("missing answer: {id}")))?;
40                crate::questions::validate(question, answer)?;
41                Ok((id.clone(), answer.clone()))
42            })
43            .collect()
44    }
45}