Skip to main content

rig_typesafeai/
lib.rs

1//! Experimental typed Jev evaluations alongside Rig agents.
2//!
3//! Build a [`Jev`] client, take its [`evaluation`](Jev::evaluation) model, then use [`Evaluate::evaluate`]
4//! with serializable state and a named [`Query`]. Its associated output type
5//! fixes the answer structure at compile time. Decisions
6//! retain distributions; routing and threshold policy remain in application code.
7//! Reported probabilities are preserved, including bounded hundredth-rounding
8//! error observed in live responses; they are not silently normalized.
9
10mod decode;
11mod dynamic;
12mod questions;
13pub mod types;
14mod validation;
15mod wire;
16
17pub use dynamic::DynamicQuery;
18pub use questions::{
19    Choice, ChoiceAnswer, DynamicScore, DynamicScoreAnswer, JoinedQuery, MappedQuery, NamedQuery,
20    Noul, NoulAnswer, Query, Score, ScoreAnswer,
21};
22use rig_core::driver::{Model, Transport};
23use rig_core::error::ProviderError;
24use rig_core::wasm_compat::{WasmCompatSend, WasmCompatSync};
25use serde::Serialize;
26pub use wire::{Evaluation, Jev, JevConfig};
27
28/// Typed answers together with provider metadata.
29#[derive(Debug, Clone)]
30pub struct EvaluationResult<A> {
31    /// Answers with the structure determined by the question set.
32    pub answers: A,
33    /// The model identifier returned by the provider.
34    pub model: String,
35    /// Token accounting when reported by the provider.
36    pub usage: Option<types::Usage>,
37    /// Transport request identifier for diagnostics.
38    pub provider_request_id: Option<String>,
39}
40
41/// Typed evaluation on Jev over a Rig transport.
42pub trait Evaluate {
43    /// Evaluate a shared state in one call and validate the typed answers.
44    /// The input question type determines the successful response type:
45    ///
46    /// ```compile_fail
47    /// use rig_typesafeai::{Evaluate, EvaluationResult, DynamicScoreAnswer, Noul, Query};
48    /// async fn wrong(client: impl Evaluate) -> Result<(), rig_core::error::ProviderError> {
49    ///     let question = Noul::new("Ready?")?.named("ready")?;
50    ///     let result: EvaluationResult<DynamicScoreAnswer> = client.evaluate(&"state", question).await?;
51    ///     Ok(())
52    /// }
53    /// ```
54    fn evaluate<S, Q>(
55        &self,
56        state: &S,
57        questions: Q,
58    ) -> impl std::future::Future<Output = Result<EvaluationResult<Q::Output>, ProviderError>>
59    + WasmCompatSend
60    where
61        S: Serialize + WasmCompatSync,
62        Q: Query;
63}
64impl<T: Transport<JevConfig>> Evaluate for Model<JevConfig, T> {
65    async fn evaluate<S, Q>(
66        &self,
67        state: &S,
68        questions: Q,
69    ) -> Result<EvaluationResult<Q::Output>, ProviderError>
70    where
71        S: Serialize + WasmCompatSync,
72        Q: Query,
73    {
74        let encoded = serde_json::value::to_raw_value(&questions)?;
75        let ids = validation::request_ids(&encoded)?;
76        let request = types::Request {
77            state: questions::state(state)?,
78            questions: encoded,
79        };
80        let response = self.call(request).await?;
81        if ids != validation::response_ids(&response.answers)? {
82            return Err(ProviderError::Response(
83                "response question IDs differ from request".into(),
84            ));
85        }
86        Ok(EvaluationResult {
87            answers: questions.decode(serde_json::from_str(response.answers.get())?)?,
88            model: response.model,
89            usage: response.usage,
90            provider_request_id: response.provider_request_id,
91        })
92    }
93}
94
95#[cfg(test)]
96mod tests;
97
98#[cfg(test)]
99mod query_tests;