Skip to main content

runifold_model/
request.rs

1use std::collections::BTreeMap;
2
3use schemars::{JsonSchema, schema_for};
4use serde::{Deserialize, Serialize};
5use serde_json::Value;
6
7use crate::{ExtensionMap, Message};
8
9/// A provider-qualified model identity.
10#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
11pub struct ModelRef {
12    /// Provider namespace.
13    pub provider: String,
14    /// Provider model name.
15    pub name: String,
16}
17
18impl ModelRef {
19    /// Creates a model reference.
20    pub fn new(provider: impl Into<String>, name: impl Into<String>) -> Self {
21        Self {
22            provider: provider.into(),
23            name: name.into(),
24        }
25    }
26}
27
28/// Behavior when a requested feature is not natively supported.
29#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
30#[serde(rename_all = "snake_case")]
31#[non_exhaustive]
32pub enum FeaturePolicy {
33    /// Reject unsupported, unknown, or emulated features.
34    #[default]
35    Strict,
36    /// Permit documented emulation but reject ignored features.
37    AllowEmulation,
38    /// Permit degradation when it is reported as a warning.
39    BestEffort,
40}
41
42/// Sampling and output-length options common to providers.
43#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
44pub struct GenerationOptions {
45    /// Sampling temperature.
46    pub temperature: Option<f64>,
47    /// Nucleus-sampling probability.
48    pub top_p: Option<f64>,
49    /// Maximum output tokens.
50    pub max_output_tokens: Option<u64>,
51    /// Optional deterministic seed.
52    pub seed: Option<u64>,
53    /// Stop sequences.
54    pub stop: Vec<String>,
55}
56
57/// Desired final-output format.
58#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
59#[serde(tag = "type", rename_all = "snake_case")]
60#[non_exhaustive]
61pub enum OutputFormat {
62    /// Unconstrained text.
63    #[default]
64    Text,
65    /// Any valid JSON value.
66    Json,
67    /// JSON constrained by a schema.
68    JsonSchema {
69        /// Schema name sent to providers that require one.
70        name: String,
71        /// JSON Schema.
72        schema: Value,
73        /// Whether the provider should enforce its strictest mode.
74        strict: bool,
75    },
76}
77
78impl OutputFormat {
79    /// Builds a strict JSON-schema format from a Rust type.
80    ///
81    /// Provider enforcement is only one boundary. Callers should still decode
82    /// the response locally with [`crate::ModelResponse::structured`].
83    pub fn typed<T>(name: impl Into<String>) -> Self
84    where
85        T: JsonSchema,
86    {
87        Self::JsonSchema {
88            name: name.into(),
89            schema: schema_for!(T).to_value(),
90            strict: true,
91        }
92    }
93
94    /// Builds a JSON-schema format from a Rust type with explicit provider
95    /// strictness.
96    pub fn typed_with_strictness<T>(name: impl Into<String>, strict: bool) -> Self
97    where
98        T: JsonSchema,
99    {
100        Self::JsonSchema {
101            name: name.into(),
102            schema: schema_for!(T).to_value(),
103            strict,
104        }
105    }
106}
107
108/// A model-facing tool definition.
109#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
110pub struct ToolSpec {
111    /// Tool name.
112    pub name: String,
113    /// Model-facing description.
114    pub description: String,
115    /// JSON Schema for arguments.
116    pub input_schema: Value,
117    /// Optional JSON Schema for results.
118    pub output_schema: Option<Value>,
119    /// Namespaced metadata not automatically exposed to a provider.
120    pub metadata: ExtensionMap,
121}
122
123/// How a model may select tools.
124#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
125#[serde(tag = "type", rename_all = "snake_case")]
126#[non_exhaustive]
127pub enum ToolChoice {
128    /// The model decides whether to call a tool.
129    #[default]
130    Auto,
131    /// The model must not call tools.
132    None,
133    /// The model must call at least one tool.
134    Required,
135    /// The model must call a named tool.
136    Named {
137        /// Required tool name.
138        name: String,
139    },
140}
141
142/// A complete provider-neutral model request.
143#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
144pub struct ModelRequest {
145    /// Selected model.
146    pub model: ModelRef,
147    /// Ordered conversation messages.
148    pub messages: Vec<Message>,
149    /// Model-facing tools.
150    pub tools: Vec<ToolSpec>,
151    /// Tool-selection behavior.
152    pub tool_choice: ToolChoice,
153    /// Desired final-output format.
154    pub output_format: OutputFormat,
155    /// Common generation options.
156    pub generation: GenerationOptions,
157    /// Feature-degradation behavior.
158    pub feature_policy: FeaturePolicy,
159    /// Typed adapters serialize options into their provider namespace.
160    pub provider_options: BTreeMap<String, Value>,
161    /// Host-only namespaced metadata.
162    pub metadata: ExtensionMap,
163}
164
165impl ModelRequest {
166    /// Creates a request with one initial message.
167    pub fn new(model: ModelRef, message: Message) -> Self {
168        Self {
169            model,
170            messages: vec![message],
171            tools: Vec::new(),
172            tool_choice: ToolChoice::Auto,
173            output_format: OutputFormat::Text,
174            generation: GenerationOptions::default(),
175            feature_policy: FeaturePolicy::Strict,
176            provider_options: BTreeMap::new(),
177            metadata: BTreeMap::new(),
178        }
179    }
180
181    /// Appends a conversation message.
182    #[must_use]
183    pub fn message(mut self, message: Message) -> Self {
184        self.messages.push(message);
185        self
186    }
187
188    /// Adds a model-facing tool.
189    #[must_use]
190    pub fn tool(mut self, tool: ToolSpec) -> Self {
191        self.tools.push(tool);
192        self
193    }
194
195    /// Sets the desired output format.
196    #[must_use]
197    pub fn output_format(mut self, output_format: OutputFormat) -> Self {
198        self.output_format = output_format;
199        self
200    }
201
202    /// Requests strict structured output described by the Rust type `T`.
203    #[must_use]
204    pub fn structured_output<T>(self, name: impl Into<String>) -> Self
205    where
206        T: JsonSchema,
207    {
208        self.output_format(OutputFormat::typed::<T>(name))
209    }
210
211    /// Sets the feature-degradation policy.
212    #[must_use]
213    pub const fn feature_policy(mut self, feature_policy: FeaturePolicy) -> Self {
214        self.feature_policy = feature_policy;
215        self
216    }
217}