typesayer/lib.rs
1// Copyright 2026 Thomas Santerre and Moderately AI Inc.
2//
3// SPDX-License-Identifier: MIT OR Apache-2.0
4
5#![cfg_attr(
6 not(test),
7 deny(clippy::unwrap_used, clippy::expect_used, clippy::panic)
8)]
9
10//! Native Rust predict engine for structured LLM prediction.
11//!
12//! Provides core types for building structured input/output contracts for language
13//! model calls, formatting prompts, parsing responses, and composing multi-step
14//! LLM programs with introspectable, serializable state.
15//!
16//! ## Prompt visibility for debugging
17//!
18//! `ChatAdapter::format` emits the fully
19//! assembled prompt at `TRACE` on the target
20//! `typesayer::adapter::chat::messages` — a `signature` shorthand
21//! (`"inputs -> outputs"`) plus `messages_json`, the provider-agnostic
22//! `Vec<Message>` serialized to JSON (the `[[ ## field ## ]]` envelope, demo
23//! turns, and cache-breakpoint markers). Off by default; an operator opts in:
24//!
25//! - Standalone binaries / examples (env filter reads `RUST_LOG`):
26//! `RUST_LOG=typesayer::adapter::chat::messages=trace`
27//! - Applications with a broader filter can pass a complete directive:
28//! `RUST_LOG="warn,typesayer=info,typesayer::adapter::chat::messages=trace"`
29//!
30//! Prompts can contain sensitive user data, so
31//! this is `TRACE`-gated and intended for `… 2>&1 | tee` inspection — never
32//! enable it in production log shipping.
33//!
34//! ## Quick Start
35//!
36//! ```rust
37//! use std::{collections::BTreeMap, sync::Arc};
38//!
39//! use modelplease::{DummyLM, ModelId};
40//! use typesayer::{ChatAdapter, Context, Predict};
41//! use typesayer_types::{FieldDef, FieldType, FieldValue, Signature};
42//!
43//! # async fn example() -> typesayer_types::Result<()> {
44//! let sig = Signature::builder("Answer the question.")
45//! .input(FieldDef::input("question", FieldType::String, "The question"))
46//! .output(FieldDef::output("answer", FieldType::String, "The answer"))
47//! .build()?;
48//!
49//! let lm = DummyLM::sequential(vec!["[[ ## answer ## ]]\nParis\n[[ ## completed ## ]]".into()]);
50//! let ctx = Context {
51//! provider: Arc::new(lm),
52//! model: ModelId::new("test"),
53//! adapter: Arc::new(ChatAdapter::default()),
54//! };
55//!
56//! let prediction = Predict::new(sig)
57//! .call(
58//! &BTreeMap::from([("question".into(), FieldValue::Str("Capital of France?".into()))]),
59//! &ctx,
60//! )
61//! .await?;
62//!
63//! assert_eq!(prediction.get::<String>("answer")?, "Paris");
64//! # Ok(())
65//! # }
66//! ```
67//!
68//! ## Modules
69//!
70//! Core types (`PredictError`, `Result`, `FieldType`, `FieldDef`, `FieldKind`,
71//! `FieldValue`, `ObjectField`, `Signature`, `SignatureBuilder`) live in the
72//! `typesayer-types` crate and are re-exported here.
73//!
74//! - [`format`] — `FieldSerializer`, `FieldDeserializer`, `JsonFieldSerializer`,
75//! `JsonFieldDeserializer`
76//! - `adapter` — `Adapter` trait, `ChatAdapter`, `Demo`
77//! - `prediction` — `Prediction`, `TryFromFieldValue`
78//! - `context` — `Context`
79//! - `predict` — `Predict`
80//! - `example` — `Example` (flat fields + input key separation)
81//! - `module` — `Module` trait (composition, introspection, state persistence)
82//! - `state` — module save/load helpers
83
84pub(crate) mod adapter;
85pub(crate) mod context;
86pub(crate) mod evaluate;
87pub(crate) mod example;
88pub(crate) mod format;
89pub(crate) mod module;
90pub(crate) mod optimizer;
91pub(crate) mod predict;
92pub(crate) mod prediction;
93pub(crate) mod propose;
94pub(crate) mod schema;
95pub(crate) mod state;
96pub(crate) mod trace;
97
98pub use adapter::{Adapter, ChatAdapter, Demo, chat::CachePlacement};
99pub use context::Context;
100pub use evaluate::{EvaluateConfig, EvaluationResult, evaluate};
101pub use example::Example;
102pub use format::{FieldDeserializer, FieldSerializer, JsonFieldDeserializer, JsonFieldSerializer};
103pub use modelplease::{
104 AwsAccountId, CapabilityError, ContentPart, HttpsUrl, MediaKind, MediaSource, MediaType,
105 Message, ModelId, ProviderFileId, Role, S3Uri, SourceKind,
106};
107pub use module::Module;
108pub use optimizer::{
109 AutoMode, BootstrapFewShot, CompileRequest, LabeledFewShot, MIPROv2, MetricFn,
110 MiproCompileRequest, MiproConfig, MiproDeps, Optimizer, Progress, ProgressFn,
111};
112pub use predict::Predict;
113pub use prediction::{Prediction, TryFromFieldValue};
114pub use typesayer_types::{
115 FieldDef, FieldKind, FieldType, FieldValue, ObjectField, OneOfDiscriminator, PredictError,
116 Result, Signature, SignatureBuilder, VariantArm,
117};
118// `is_field_required` is still `pub` in `schema` for any out-of-tree
119// consumer that imports it directly via `typesayer::schema::*`,
120// but it is no longer re-exported at the crate root — new code should
121// rely on standard JSON Schema nullability (parent `required: [...]`
122// array, `anyOf` with null, or `type: [..., "null"]`). The function
123// itself carries a `#[deprecated]` attribute that fires on use.
124#[expect(
125 deprecated,
126 reason = "explicit re-export retained for any out-of-tree caller that still imports it from the crate root; the deprecation warning will fire at each actual usage site"
127)]
128pub use schema::is_field_required;
129pub use schema::{
130 JsonSchemaDefinition, extract_description, field_type_from_schema, signature_from_json_schema,
131};
132pub use trace::{ExecutionTrace, TraceEntry};