providers_kit/generation.rs
1//! Portable generation, messages, function continuation, and streaming values.
2//!
3//! Construct a [`GenerateRequest`] for an already selected [`crate::ModelBackend`].
4//! Returned assistant [`Message`] values retain the exact associated continuation;
5//! append them unchanged, then append correlated [`FunctionResult`] values in a
6//! tool message. providers-kit declares functions but never executes them.
7//!
8//! [`GenerateResponse`] is authoritative at completion. [`GenerateEventStream`]
9//! provides bounded incremental observation from supplied Models; partial text or
10//! hosted projections cannot reconstruct opaque replay state. Serialization rejects
11//! messages carrying continuation instead of silently dropping it.
12//!
13//! # Consume a stream and retain the authoritative response
14//!
15//! This requires OpenAI credentials and access to the exact selected text model.
16//! A complete stream contains one `Completed`; earlier text is only incremental
17//! observation. Keep the returned assistant message, not a reconstruction from deltas.
18//! A terminal reason such as `Length` or `ContentFilter` is not an ordinary successful
19//! answer. This example accepts only `Stop` and does not compare wording to its prompt.
20//!
21//! ```no_run
22//! use futures_util::StreamExt;
23//! use providers_kit::{Provider, ProviderBackend, ProviderConfig, ModelBackend, ModelId};
24//! use providers_kit::config::{ApiKey, OpenAiConfig, ProviderKind, TransportConfig};
25//! use providers_kit::generation::{ContentPart, FinishReason, GenerateEvent, GenerateRequest, Message};
26//! # #[tokio::main(flavor = "current_thread")]
27//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
28//! let provider = Provider::from_config(ProviderConfig::new(
29//! ProviderKind::OpenAi(OpenAiConfig::new(ApiKey::new("replace-with-your-key")?)),
30//! TransportConfig::default(),
31//! ))?;
32//! let work = async {
33//! let model = provider.model(ModelId::new("gpt-6-luna")?)?;
34//! let request = GenerateRequest::new(vec![Message::user_text("Explain a rainbow briefly.")?])?;
35//! let mut stream = model.generate_stream(&request).await?;
36//! let mut text = String::new();
37//! let mut completed = None;
38//! while let Some(event) = stream.next().await {
39//! match event? {
40//! GenerateEvent::Started { model: returned } => assert_eq!(returned, *model.model_id()),
41//! GenerateEvent::ContentDelta { delta } => text.push_str(&delta),
42//! GenerateEvent::Completed(response) => {
43//! assert!(completed.is_none(), "duplicate completion");
44//! completed = Some(response);
45//! }
46//! GenerateEvent::ThinkingDelta { .. } | GenerateEvent::HostedTool(_) | GenerateEvent::Usage(_) => {}
47//! }
48//! }
49//! let response = completed.ok_or("stream ended before Completed")?;
50//! assert_eq!(response.model, *model.model_id());
51//! assert_eq!(response.finish_reason, Some(FinishReason::Stop));
52//! let final_text: String = response.message.content().iter().filter_map(|part| match part {
53//! ContentPart::Text { text } => Some(text.as_str()), _ => None,
54//! }).collect();
55//! assert!(!final_text.trim().is_empty());
56//! assert_eq!(text, final_text); // Fidelity within Kit output, not native parity.
57//! Ok::<_, Box<dyn std::error::Error>>(response.message)
58//! }.await;
59//! let cleanup = provider.shutdown().await;
60//! let _unchanged_assistant = work?;
61//! cleanup?;
62//! # Ok(())
63//! # }
64//! ```
65//!
66//! # Correlated function continuation
67//!
68//! providers-kit declares functions; the application executes them. This bounded
69//! two-generation example requires the selected OpenAI model, credentials, and
70//! function support. Validate the call before dispatching application behavior. The
71//! result copies its exact id/name, and the assistant is moved unchanged into history.
72//! It includes opaque reasoning/replay state that visible text cannot reconstruct.
73//!
74//! ```no_run
75//! use providers_kit::{Provider, ProviderBackend, ProviderConfig, ModelBackend, ModelId};
76//! use providers_kit::config::{ApiKey, OpenAiConfig, ProviderKind, TransportConfig};
77//! use providers_kit::generation::{ContentPart, FinishReason, FunctionResult, GenerateRequest,
78//! GenerationConfig, JsonSchema, Message, ProviderFunctionTool, ProviderToolSpec, ToolChoice};
79//! # #[tokio::main(flavor = "current_thread")]
80//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
81//! let provider = Provider::from_config(ProviderConfig::new(
82//! ProviderKind::OpenAi(OpenAiConfig::new(ApiKey::new("replace-with-your-key")?)),
83//! TransportConfig::default(),
84//! ))?;
85//! let work = async {
86//! let model = provider.model(ModelId::new("gpt-6-luna")?)?;
87//! let lookup = ProviderFunctionTool::new("lookup", Some("Read the local item".into()),
88//! JsonSchema::new(serde_json::json!({"type":"object","properties":{},"additionalProperties":false}))?)?;
89//! let initial = Message::user_text("Look up the local item and summarize it.")?;
90//! let request = GenerateRequest::new(vec![initial.clone()])?
91//! .with_tools(vec![ProviderToolSpec::Function(lookup)])?
92//! .with_generation(GenerationConfig::default().with_tool_choice(ToolChoice::Required))?;
93//! let first = model.generate(&request).await?;
94//! assert_eq!(first.model, *model.model_id());
95//! assert_eq!(first.finish_reason, Some(FinishReason::ToolCall));
96//! let [call] = first.message.function_calls() else { return Err("expected one lookup".into()); };
97//! assert_eq!(call.name(), "lookup");
98//! assert_eq!(call.arguments(), &serde_json::json!({}));
99//! let local_item = serde_json::json!({"name":"paper","quantity":3});
100//! let result = FunctionResult::new(call.id().map(str::to_owned), call.name(),
101//! vec![ContentPart::Json { value: local_item }], false)?;
102//! assert_eq!(result.call_id(), call.id());
103//! let followup = GenerateRequest::new(vec![initial, first.message, Message::tool(vec![result])?])?;
104//! let second = model.generate(&followup).await?;
105//! assert_eq!(second.model, *model.model_id());
106//! assert_eq!(second.finish_reason, Some(FinishReason::Stop));
107//! assert!(second.message.function_calls().is_empty());
108//! assert!(second.message.content().iter().any(|part| matches!(part,
109//! ContentPart::Text { text } if !text.trim().is_empty())));
110//! Ok::<(), Box<dyn std::error::Error>>(())
111//! }.await;
112//! let cleanup = provider.shutdown().await;
113//! work?;
114//! cleanup?;
115//! # Ok(())
116//! # }
117//! ```
118//!
119//! # Structured output and reasoning
120//!
121//! Request shape is local and testable. Model compatibility is checked when the
122//! request executes: an omitted reasoning override remains omitted; unsupported
123//! sampling/reasoning/schema combinations fail rather than silently changing settings.
124//! A schema constrains shape; ordinary prompt wording is not an equality guarantee.
125//!
126//! ```
127//! use providers_kit::generation::{GenerateRequest, GenerationConfig, JsonSchema, Message,
128//! ReasoningConfig, ReasoningEffort, ResponseFormat};
129//! let schema = JsonSchema::new(serde_json::json!({
130//! "type":"object", "properties":{"summary":{"type":"string"}},
131//! "required":["summary"], "additionalProperties":false
132//! }))?;
133//! let request = GenerateRequest::<()>::new(vec![Message::user_text("Summarize the supplied note.")?])?
134//! .with_response_format(ResponseFormat::JsonSchema(schema))
135//! .with_reasoning(ReasoningConfig { effort: Some(ReasoningEffort::Low), ..Default::default() })
136//! .with_generation(GenerationConfig::default()
137//! .with_max_output_tokens(std::num::NonZeroU32::new(4096).unwrap()))?;
138//! assert!(matches!(request.response_format(), Some(ResponseFormat::JsonSchema(_))));
139//! assert_eq!(request.reasoning().unwrap().effort, Some(ReasoningEffort::Low));
140//! assert!(request.generation().temperature().is_none());
141//! # Ok::<(), providers_kit::KitError>(())
142//! ```
143
144//! The same request pattern can be executed on an exact supporting model. This
145//! requires credentials and current access to `gpt-6-luna`; no sampling override
146//! is inferred from the reasoning setting. Inspect both terminal outcome and the
147//! returned JSON rather than assuming the prompt's prose guarantees a value.
148//!
149//! ```no_run
150//! use providers_kit::{Provider, ProviderBackend, ProviderConfig, ModelBackend, ModelId};
151//! use providers_kit::config::{ApiKey, OpenAiConfig, ProviderKind, TransportConfig};
152//! use providers_kit::generation::{ContentPart, FinishReason, GenerateRequest, JsonSchema,
153//! Message, ReasoningConfig, ReasoningEffort, ResponseFormat};
154//! # #[tokio::main(flavor = "current_thread")]
155//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
156//! let provider = Provider::from_config(ProviderConfig::new(
157//! ProviderKind::OpenAi(OpenAiConfig::new(ApiKey::new("replace-with-your-key")?)),
158//! TransportConfig::default(),
159//! ))?;
160//! let work = async {
161//! let model = provider.model(ModelId::new("gpt-6-luna")?)?;
162//! let schema = JsonSchema::new(serde_json::json!({"type":"object",
163//! "properties":{"summary":{"type":"string"}}, "required":["summary"], "additionalProperties":false}))?;
164//! let request = GenerateRequest::new(vec![Message::user_text("Summarize: rain supports plant growth.")?])?
165//! .with_response_format(ResponseFormat::JsonSchema(schema))
166//! .with_reasoning(ReasoningConfig { effort: Some(ReasoningEffort::Low), ..Default::default() });
167//! let response = model.generate(&request).await?;
168//! assert_eq!(response.model, *model.model_id());
169//! assert_eq!(response.finish_reason, Some(FinishReason::Stop));
170//! let text: String = response.message.content().iter().filter_map(|part| match part {
171//! ContentPart::Text { text } => Some(text.as_str()), _ => None,
172//! }).collect();
173//! let value: serde_json::Value = serde_json::from_str(&text)?;
174//! assert!(value["summary"].is_string());
175//! assert_eq!(value.as_object().ok_or("expected an object")?.len(), 1);
176//! Ok::<(), Box<dyn std::error::Error>>(())
177//! }.await;
178//! let cleanup = provider.shutdown().await;
179//! work?;
180//! cleanup?;
181//! # Ok(())
182//! # }
183//! ```
184
185use std::collections::BTreeSet;
186use std::fmt;
187use std::num::NonZeroU32;
188use std::pin::Pin;
189use std::task::{Context, Poll};
190
191use futures_util::Stream;
192use serde::{Deserialize, Deserializer, Serialize, Serializer, ser::Error as _};
193use serde_json::Value;
194use url::Url;
195
196use crate::KitError;
197use crate::core::content::base64_bytes;
198use crate::core::{GenerateRequestView, selected_tools_for_choice};
199use crate::error::ValidationViolation;
200use crate::hosted::{HostedTool, HostedToolOutput};
201use crate::models::ModelId;
202
203/// Reference that can be submitted as provider-managed or remote model input.
204#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
205#[serde(tag = "type", rename_all = "snake_case", deny_unknown_fields)]
206pub enum FileReference {
207 /// Provider-managed resource identifier.
208 Provider {
209 /// Exact provider-assigned identifier or input URI.
210 id: String,
211 /// Optional media type for selecting the reusable input block kind.
212 media_type: Option<String>,
213 },
214 /// Externally hosted resource.
215 Url {
216 /// Exact resource URL.
217 url: Url,
218 /// Optional media-type hint.
219 media_type: Option<String>,
220 },
221}
222
223/// Artifact returned by generation or hosted execution.
224#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
225#[serde(tag = "type", rename_all = "snake_case", deny_unknown_fields)]
226pub enum Artifact {
227 /// Inline artifact bytes.
228 Inline {
229 /// Media type describing the bytes.
230 media_type: String,
231 /// Optional human-readable file name.
232 file_name: Option<String>,
233 /// Optional model-authored description.
234 description: Option<String>,
235 /// Raw artifact bytes, serialized as base64.
236 #[serde(with = "base64_bytes")]
237 data: Vec<u8>,
238 },
239 /// Provider-managed or remote artifact reference.
240 File {
241 /// Reference usable by a later request.
242 reference: FileReference,
243 /// Optional human-readable file name.
244 file_name: Option<String>,
245 /// Optional model-authored description.
246 description: Option<String>,
247 },
248}
249
250/// Ordered portable message content.
251#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
252#[serde(tag = "type", rename_all = "snake_case", deny_unknown_fields)]
253pub enum ContentPart {
254 /// Plain UTF-8 text.
255 Text {
256 /// Text content.
257 text: String,
258 },
259 /// Provider-authored thinking replayed with continuation state.
260 Thinking {
261 /// Provider-visible thinking text, when returned.
262 text: Option<String>,
263 },
264 /// Remote image input.
265 ImageUrl {
266 /// Absolute image URL.
267 url: Url,
268 },
269 /// Inline image bytes.
270 Image {
271 /// Image media type.
272 media_type: String,
273 /// Raw image bytes.
274 #[serde(with = "base64_bytes")]
275 data: Vec<u8>,
276 },
277 /// Inline audio bytes.
278 Audio {
279 /// Audio media type.
280 media_type: String,
281 /// Raw audio bytes.
282 #[serde(with = "base64_bytes")]
283 data: Vec<u8>,
284 },
285 /// Inline document bytes.
286 Document {
287 /// Document media type.
288 media_type: String,
289 /// Raw document bytes.
290 #[serde(with = "base64_bytes")]
291 data: Vec<u8>,
292 /// Optional file name presented to the provider.
293 file_name: Option<String>,
294 },
295 /// Provider-managed or remote file input.
296 File {
297 /// Exact provider or remote file reference.
298 reference: FileReference,
299 },
300 /// Structured function-result content.
301 Json {
302 /// Structured JSON value.
303 value: Value,
304 },
305}
306
307/// Portable message role.
308#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
309#[serde(rename_all = "snake_case")]
310pub enum Role {
311 /// System instruction.
312 System,
313 /// Developer instruction.
314 Developer,
315 /// User input.
316 User,
317 /// Model-authored assistant output.
318 Assistant,
319 /// Caller-supplied function output.
320 Tool,
321}
322
323/// Opaque provider-authored state required to continue one model turn.
324///
325/// Returned assistant messages carry the selected Model's exact continuation type.
326/// Append that message unchanged before the corresponding tool-result message.
327/// Native continuation contents have no public constructor or serialization contract.
328/// Serializing any message that carries continuation fails rather than discarding it.
329/// Caller models may choose their own Clone + Send + Sync continuation type.
330///
331/// # Examples
332///
333/// ```
334/// use providers_kit::generation::{GenerationContinuation, Message};
335/// let message = Message::<GenerationContinuation>::assistant_text("Hello")?;
336/// assert!(message.continuation().is_none());
337/// assert!(serde_json::to_value(&message).is_ok());
338///
339/// # Ok::<(), providers_kit::KitError>(())
340/// ```
341///
342#[derive(Clone, PartialEq)]
343pub struct GenerationContinuation {
344 pub(crate) model_id: ModelId,
345 pub(crate) inner: NativeContinuation,
346}
347
348#[derive(Clone, PartialEq)]
349pub(crate) enum NativeContinuation {
350 OpenAi { output_items: Vec<Value> },
351 Anthropic { content_blocks: Vec<Value> },
352 Gemini { signed_parts: Vec<Value> },
353 Ollama { thinking: String },
354}
355
356impl fmt::Debug for GenerationContinuation {
357 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
358 formatter
359 .debug_struct("GenerationContinuation")
360 .field("model_id", &self.model_id)
361 .field("state", &"<redacted>")
362 .finish()
363 }
364}
365
366/// Portable message with private function and continuation state.
367///
368/// A caller Model can return its own continuation type. Preserve that exact
369/// assistant value before appending the matching function-result message:
370///
371/// ```
372/// use providers_kit::generation::{ContentPart, FunctionCall, FunctionResult, GenerateRequest, Message};
373/// let user = Message::<u32>::user_text("Look up record a")?;
374/// // Representative output constructed by a caller Model with Continuation = u32.
375/// let call = FunctionCall::new(Some("call-1".into()), "lookup", serde_json::json!({"key":"a"}))?;
376/// let assistant = Message::assistant(Vec::new(), vec![call])?.with_continuation(7)?;
377/// let result = FunctionResult::new(Some("call-1".into()), "lookup",
378/// vec![ContentPart::Json { value: serde_json::json!({"value":2}) }], false)?;
379/// let next = GenerateRequest::new(vec![user, assistant, Message::tool(vec![result])?])?;
380/// assert_eq!(next.messages()[1].continuation(), Some(&7));
381/// assert_eq!(next.messages()[2].function_results()[0].call_id(), Some("call-1"));
382/// assert!(serde_json::to_value(&next).is_err());
383///
384/// # Ok::<(), providers_kit::KitError>(())
385/// ```
386///
387/// Role, function calls/results, and continuation are validated together. Supplied
388/// continuations stay opaque; caller Models use their own exact associated type.
389/// Assistant messages returned by a Model must be replayed unchanged.
390///
391/// # Examples
392///
393/// ```
394/// use providers_kit::generation::{GenerationContinuation, Message};
395/// let message = Message::<GenerationContinuation>::assistant_text("Hello")?;
396/// assert!(message.continuation().is_none());
397/// assert!(serde_json::to_value(&message).is_ok());
398///
399/// # Ok::<(), providers_kit::KitError>(())
400/// ```
401///
402#[derive(Clone, PartialEq)]
403pub struct Message<C = GenerationContinuation> {
404 role: Role,
405 content: Vec<ContentPart>,
406 function_calls: Vec<FunctionCall>,
407 function_results: Vec<FunctionResult>,
408 continuation: Option<C>,
409}
410
411impl<C> fmt::Debug for Message<C> {
412 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
413 formatter
414 .debug_struct("Message")
415 .field("role", &self.role)
416 .field("content", &self.content)
417 .field("function_calls", &self.function_calls)
418 .field("function_results", &self.function_results)
419 .field(
420 "continuation",
421 &self.continuation.is_some().then_some("<redacted>"),
422 )
423 .finish()
424 }
425}
426
427impl<C> Message<C> {
428 fn from_role(role: Role, content: Vec<ContentPart>) -> Self {
429 Self {
430 role,
431 content,
432 function_calls: Vec::new(),
433 function_results: Vec::new(),
434 continuation: None,
435 }
436 }
437
438 /// Creates a text-only system instruction.
439 ///
440 /// # Errors
441 ///
442 /// Returns an input error for empty or invalid content, or content incompatible with the role. Instruction messages permit only text; user messages reject thinking and raw JSON.
443 ///
444 /// # Examples
445 ///
446 /// ```
447 /// use providers_kit::generation::{Message, ContentPart, Role};
448 /// let message = Message::<()>::system(vec![ContentPart::Text { text: "Hello".into() }])?;
449 /// assert_eq!(message.role(), Role::System);
450 ///
451 /// # Ok::<(), providers_kit::KitError>(())
452 /// ```
453 ///
454 pub fn system(content: Vec<ContentPart>) -> Result<Self, KitError> {
455 Self::instruction(Role::System, content)
456 }
457
458 /// Creates a text-only developer instruction.
459 ///
460 /// # Errors
461 ///
462 /// Returns an input error for empty or invalid content, or content incompatible with the role. Instruction messages permit only text; user messages reject thinking and raw JSON.
463 ///
464 /// # Examples
465 ///
466 /// ```
467 /// use providers_kit::generation::{Message, ContentPart, Role};
468 /// let message = Message::<()>::developer(vec![ContentPart::Text { text: "Hello".into() }])?;
469 /// assert_eq!(message.role(), Role::Developer);
470 ///
471 /// # Ok::<(), providers_kit::KitError>(())
472 /// ```
473 ///
474 pub fn developer(content: Vec<ContentPart>) -> Result<Self, KitError> {
475 Self::instruction(Role::Developer, content)
476 }
477
478 fn instruction(role: Role, content: Vec<ContentPart>) -> Result<Self, KitError> {
479 let message = Self::from_role(role, content);
480 message.validate_shape()?;
481 Ok(message)
482 }
483
484 /// Creates a user message.
485 ///
486 /// # Errors
487 ///
488 /// Returns an input error for empty or invalid content, or content incompatible with the role. Instruction messages permit only text; user messages reject thinking and raw JSON.
489 ///
490 /// # Examples
491 ///
492 /// ```
493 /// use providers_kit::generation::{Message, ContentPart, Role};
494 /// let message = Message::<()>::user(vec![ContentPart::Text { text: "Hello".into() }])?;
495 /// assert_eq!(message.role(), Role::User);
496 ///
497 /// # Ok::<(), providers_kit::KitError>(())
498 /// ```
499 ///
500 pub fn user(content: Vec<ContentPart>) -> Result<Self, KitError> {
501 let message = Self::from_role(Role::User, content);
502 message.validate_shape()?;
503 Ok(message)
504 }
505
506 /// Creates an assistant message with visible content and/or function calls.
507 ///
508 /// # Errors
509 ///
510 /// Returns an input error if both content and calls are empty, content is malformed, or the role cannot represent the supplied values.
511 ///
512 /// # Examples
513 ///
514 /// ```
515 /// use providers_kit::generation::FunctionCall;
516 /// let call = FunctionCall::new(Some("call-1".into()), "lookup", serde_json::json!({"key":"a"}))?;
517 /// use providers_kit::generation::Message;
518 /// let message = Message::<()>::assistant(Vec::new(), vec![call])?;
519 /// assert_eq!(message.function_calls().len(), 1);
520 ///
521 /// # Ok::<(), providers_kit::KitError>(())
522 /// ```
523 ///
524 pub fn assistant(
525 content: Vec<ContentPart>,
526 function_calls: Vec<FunctionCall>,
527 ) -> Result<Self, KitError> {
528 let message = Self {
529 function_calls,
530 ..Self::from_role(Role::Assistant, content)
531 };
532 message.validate_shape()?;
533 Ok(message)
534 }
535
536 /// Creates a tool message containing validated function results.
537 ///
538 /// # Errors
539 ///
540 /// Returns an input error when no function results are supplied or the message shape is invalid.
541 ///
542 /// # Examples
543 ///
544 /// ```
545 /// use providers_kit::generation::{FunctionResult, ContentPart};
546 /// let result = FunctionResult::new(Some("call-1".into()), "lookup", vec![ContentPart::Json { value: serde_json::json!({"value":2}) }], false)?;
547 /// use providers_kit::generation::Message;
548 /// let message = Message::<()>::tool(vec![result])?;
549 /// assert_eq!(message.function_results()[0].call_id(), Some("call-1"));
550 ///
551 /// # Ok::<(), providers_kit::KitError>(())
552 /// ```
553 ///
554 pub fn tool(results: Vec<FunctionResult>) -> Result<Self, KitError> {
555 let message = Self {
556 function_results: results,
557 ..Self::from_role(Role::Tool, Vec::new())
558 };
559 message.validate_shape()?;
560 Ok(message)
561 }
562
563 /// Creates one user text message.
564 ///
565 /// # Errors
566 ///
567 /// Returns an input error when the text is empty or whitespace-only.
568 ///
569 /// # Examples
570 ///
571 /// ```
572 /// use providers_kit::generation::{Message, Role};
573 /// let message = Message::<()>::user_text("Hello")?;
574 /// assert_eq!(message.role(), Role::User);
575 /// assert_eq!(message.content().len(), 1);
576 ///
577 /// # Ok::<(), providers_kit::KitError>(())
578 /// ```
579 ///
580 pub fn user_text(text: impl Into<String>) -> Result<Self, KitError> {
581 Self::user(vec![ContentPart::Text { text: text.into() }])
582 }
583
584 /// Creates one assistant text message.
585 ///
586 /// # Errors
587 ///
588 /// Returns an input error when the text is empty or whitespace-only.
589 ///
590 /// # Examples
591 ///
592 /// ```
593 /// use providers_kit::generation::{Message, Role};
594 /// let message = Message::<()>::assistant_text("Hello")?;
595 /// assert_eq!(message.role(), Role::Assistant);
596 /// assert_eq!(message.content().len(), 1);
597 ///
598 /// # Ok::<(), providers_kit::KitError>(())
599 /// ```
600 ///
601 pub fn assistant_text(text: impl Into<String>) -> Result<Self, KitError> {
602 Self::assistant(vec![ContentPart::Text { text: text.into() }], Vec::new())
603 }
604
605 /// Attaches continuation state to an assistant message.
606 ///
607 /// Continuation is valid only on assistant messages and is never silently serialized.
608 /// Use this constructor for a caller Model's continuation; preserve supplied returned
609 /// messages unchanged rather than reconstructing their private replay state.
610 ///
611 /// # Errors
612 ///
613 /// Returns an input error when the message role is not Assistant.
614 ///
615 /// # Examples
616 ///
617 /// ```
618 /// use providers_kit::generation::Message;
619 /// let message = Message::assistant_text("continue")?.with_continuation(7_u32)?;
620 /// assert_eq!(message.continuation(), Some(&7));
621 /// assert!(serde_json::to_value(&message).is_err());
622 /// assert!(Message::user_text("hello")?.with_continuation(7_u32).is_err());
623 ///
624 /// # Ok::<(), providers_kit::KitError>(())
625 /// ```
626 ///
627 pub fn with_continuation(mut self, continuation: C) -> Result<Self, KitError> {
628 if self.role != Role::Assistant {
629 return Err(KitError::invalid_input(
630 ValidationViolation::Incompatible {
631 field: "message.continuation",
632 other: "message.role",
633 },
634 "continuation can be attached only to assistant messages",
635 ));
636 }
637 self.continuation = Some(continuation);
638 Ok(self)
639 }
640
641 /// Returns the speaker role.
642 ///
643 /// # Examples
644 ///
645 /// ```
646 /// use providers_kit::generation::{Message, ContentPart, Role};
647 /// let message = Message::<()>::user_text("Hello")?;
648 /// assert_eq!(message.role(), Role::User);
649 ///
650 /// # Ok::<(), providers_kit::KitError>(())
651 /// ```
652 ///
653 #[must_use]
654 pub const fn role(&self) -> Role {
655 self.role
656 }
657
658 /// Returns ordered visible content.
659 ///
660 /// # Examples
661 ///
662 /// ```
663 /// use providers_kit::generation::{Message, ContentPart, Role};
664 /// let message = Message::<()>::user_text("Hello")?;
665 /// assert!(matches!(&message.content()[0], ContentPart::Text { text } if text == "Hello"));
666 ///
667 /// # Ok::<(), providers_kit::KitError>(())
668 /// ```
669 ///
670 #[must_use]
671 pub fn content(&self) -> &[ContentPart] {
672 &self.content
673 }
674
675 /// Returns model-requested function calls.
676 ///
677 /// # Examples
678 ///
679 /// ```
680 /// use providers_kit::generation::FunctionCall;
681 /// let call = FunctionCall::new(Some("call-1".into()), "lookup", serde_json::json!({"key":"a"}))?;
682 /// let message = providers_kit::generation::Message::<()>::assistant(Vec::new(), vec![call])?;
683 /// assert_eq!(message.function_calls()[0].name(), "lookup");
684 ///
685 /// # Ok::<(), providers_kit::KitError>(())
686 /// ```
687 ///
688 #[must_use]
689 pub fn function_calls(&self) -> &[FunctionCall] {
690 &self.function_calls
691 }
692
693 /// Returns caller-supplied function results.
694 ///
695 /// # Examples
696 ///
697 /// ```
698 /// use providers_kit::generation::{FunctionResult, ContentPart};
699 /// let result = FunctionResult::new(Some("call-1".into()), "lookup", vec![ContentPart::Json { value: serde_json::json!({"value":2}) }], false)?;
700 /// use providers_kit::generation::Message;
701 /// let message = Message::<()>::tool(vec![result])?;
702 /// assert_eq!(message.function_results()[0].call_id(), Some("call-1"));
703 ///
704 /// # Ok::<(), providers_kit::KitError>(())
705 /// ```
706 ///
707 #[must_use]
708 pub fn function_results(&self) -> &[FunctionResult] {
709 &self.function_results
710 }
711
712 /// Returns opaque continuation state when this is a returned assistant message.
713 ///
714 /// # Examples
715 ///
716 /// ```
717 /// use providers_kit::generation::Message;
718 /// let message = Message::assistant_text("continue")?.with_continuation(7_u32)?;
719 /// assert_eq!(message.continuation(), Some(&7));
720 /// assert!(serde_json::to_value(&message).is_err());
721 /// assert!(Message::user_text("hello")?.with_continuation(7_u32).is_err());
722 ///
723 /// # Ok::<(), providers_kit::KitError>(())
724 /// ```
725 ///
726 #[must_use]
727 pub const fn continuation(&self) -> Option<&C> {
728 self.continuation.as_ref()
729 }
730
731 pub(crate) fn try_map_continuation<D>(
732 self,
733 map: &mut impl FnMut(C) -> Result<D, KitError>,
734 ) -> Result<Message<D>, KitError> {
735 Ok(Message {
736 role: self.role,
737 content: self.content,
738 function_calls: self.function_calls,
739 function_results: self.function_results,
740 continuation: self.continuation.map(map).transpose()?,
741 })
742 }
743
744 pub(crate) fn returned_assistant(
745 content: Vec<ContentPart>,
746 function_calls: Vec<FunctionCall>,
747 continuation: Option<C>,
748 ) -> Self {
749 Self {
750 role: Role::Assistant,
751 content,
752 function_calls,
753 function_results: Vec::new(),
754 continuation,
755 }
756 }
757
758 pub(crate) fn validate_for_request(&self) -> Result<(), KitError> {
759 self.validate_shape()?;
760 if self
761 .content
762 .iter()
763 .any(|part| matches!(part, ContentPart::Thinking { .. }))
764 && self.continuation.is_none()
765 {
766 return Err(KitError::invalid_input(
767 ValidationViolation::MissingField {
768 field: "message.continuation",
769 },
770 "thinking input requires assistant continuation state",
771 ));
772 }
773 Ok(())
774 }
775
776 fn validate_shape(&self) -> Result<(), KitError> {
777 for part in &self.content {
778 part.validate()?;
779 }
780 match self.role {
781 Role::System | Role::Developer => {
782 if self.content.is_empty()
783 || self
784 .content
785 .iter()
786 .any(|part| !matches!(part, ContentPart::Text { .. }))
787 {
788 return Err(KitError::invalid_input(
789 ValidationViolation::InvalidValue {
790 field: "message.content",
791 rule: "instructions require non-empty text-only content",
792 },
793 "instruction messages require non-empty text-only content",
794 ));
795 }
796 if !self.function_calls.is_empty()
797 || !self.function_results.is_empty()
798 || self.continuation.is_some()
799 {
800 return Err(KitError::invalid_input(
801 ValidationViolation::InvalidValue {
802 field: "message.role",
803 rule: "instructions cannot carry functions or continuation",
804 },
805 "instruction messages cannot carry functions or continuation state",
806 ));
807 }
808 }
809 Role::User => {
810 if self.content.is_empty() {
811 return Err(KitError::invalid_input(
812 ValidationViolation::Empty {
813 field: "message.content",
814 },
815 "user message content must not be empty",
816 ));
817 }
818 if self.content.iter().any(|part| {
819 matches!(
820 part,
821 ContentPart::Thinking { .. } | ContentPart::Json { .. }
822 )
823 }) {
824 return Err(KitError::invalid_input(
825 ValidationViolation::InvalidValue {
826 field: "message.content",
827 rule: "user content cannot include thinking or raw JSON",
828 },
829 "user messages cannot contain thinking or raw JSON content",
830 ));
831 }
832 if !self.function_calls.is_empty()
833 || !self.function_results.is_empty()
834 || self.continuation.is_some()
835 {
836 return Err(KitError::invalid_input(
837 ValidationViolation::InvalidValue {
838 field: "message.role",
839 rule: "user messages cannot carry functions or continuation",
840 },
841 "user messages cannot carry functions or continuation state",
842 ));
843 }
844 }
845 Role::Assistant => {
846 if self.content.is_empty()
847 && self.function_calls.is_empty()
848 && self.continuation.is_none()
849 {
850 return Err(KitError::invalid_input(
851 ValidationViolation::InvalidValue {
852 field: "message",
853 rule: "assistant requires content, calls, or continuation",
854 },
855 "assistant message requires content, a function call, or retained continuation",
856 ));
857 }
858 if self.content.iter().any(|part| {
859 !matches!(
860 part,
861 ContentPart::Text { .. } | ContentPart::Thinking { .. }
862 )
863 }) {
864 return Err(KitError::invalid_input(
865 ValidationViolation::InvalidValue {
866 field: "message.content",
867 rule: "assistant input requires text or continued thinking",
868 },
869 "assistant input supports only text and continued thinking content",
870 ));
871 }
872 if !self.function_results.is_empty() {
873 return Err(KitError::invalid_input(
874 ValidationViolation::Incompatible {
875 field: "message.function_results",
876 other: "message.role",
877 },
878 "assistant messages cannot carry function results",
879 ));
880 }
881 }
882 Role::Tool => {
883 if !self.content.is_empty()
884 || !self.function_calls.is_empty()
885 || self.function_results.is_empty()
886 || self.continuation.is_some()
887 {
888 return Err(KitError::invalid_input(
889 ValidationViolation::InvalidValue {
890 field: "message",
891 rule: "tool messages require only function results",
892 },
893 "tool messages require function results and no direct content, calls, or continuation",
894 ));
895 }
896 }
897 }
898 Ok(())
899 }
900}
901
902impl GenerationContinuation {
903 pub(crate) fn open_ai(model_id: ModelId, output_items: Vec<Value>) -> Self {
904 Self {
905 model_id,
906 inner: NativeContinuation::OpenAi { output_items },
907 }
908 }
909
910 pub(crate) fn anthropic(model_id: ModelId, content_blocks: Vec<Value>) -> Self {
911 Self {
912 model_id,
913 inner: NativeContinuation::Anthropic { content_blocks },
914 }
915 }
916
917 pub(crate) fn gemini(model_id: ModelId, signed_parts: Vec<Value>) -> Self {
918 Self {
919 model_id,
920 inner: NativeContinuation::Gemini { signed_parts },
921 }
922 }
923
924 pub(crate) fn ollama(model_id: ModelId, thinking: String) -> Self {
925 Self {
926 model_id,
927 inner: NativeContinuation::Ollama { thinking },
928 }
929 }
930}
931
932impl<C> Serialize for Message<C> {
933 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
934 where
935 S: Serializer,
936 {
937 if self.continuation.is_some() {
938 return Err(S::Error::custom(
939 "provider continuation cannot be serialized; retain the returned message in memory",
940 ));
941 }
942 #[derive(Serialize)]
943 struct Wire<'a> {
944 role: Role,
945 content: &'a [ContentPart],
946 function_calls: &'a [FunctionCall],
947 function_results: &'a [FunctionResult],
948 }
949 Wire {
950 role: self.role,
951 content: &self.content,
952 function_calls: &self.function_calls,
953 function_results: &self.function_results,
954 }
955 .serialize(serializer)
956 }
957}
958
959impl<'de, C> Deserialize<'de> for Message<C> {
960 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
961 where
962 D: Deserializer<'de>,
963 {
964 #[derive(Deserialize)]
965 #[serde(deny_unknown_fields)]
966 struct Wire {
967 role: Role,
968 #[serde(default)]
969 content: Vec<ContentPart>,
970 #[serde(default)]
971 function_calls: Vec<FunctionCall>,
972 #[serde(default)]
973 function_results: Vec<FunctionResult>,
974 }
975
976 let wire = Wire::deserialize(deserializer)?;
977 let message = Self {
978 role: wire.role,
979 content: wire.content,
980 function_calls: wire.function_calls,
981 function_results: wire.function_results,
982 continuation: None,
983 };
984 message
985 .validate_for_request()
986 .map_err(serde::de::Error::custom)?;
987 Ok(message)
988 }
989}
990
991/// Validated strict object JSON schema.
992///
993/// # Examples
994///
995/// ```
996/// use providers_kit::generation::JsonSchema;
997/// let schema = JsonSchema::new(serde_json::json!({"type":"object", "properties":{"answer":{"type":"string"}}}))?;
998/// assert_eq!(schema.schema()["type"], "object");
999///
1000/// # Ok::<(), providers_kit::KitError>(())
1001/// ```
1002///
1003#[derive(Clone, Debug, PartialEq)]
1004pub struct JsonSchema(Value);
1005
1006impl JsonSchema {
1007 /// Validates and owns an object-shaped JSON schema.
1008 ///
1009 /// # Errors
1010 ///
1011 /// Returns an input error unless the schema is a JSON object whose root declares `type: object`. The selected operation performs any stricter schema or provider validation.
1012 ///
1013 /// # Examples
1014 ///
1015 /// ```
1016 /// use providers_kit::generation::JsonSchema;
1017 /// let schema = JsonSchema::new(serde_json::json!({"type":"object", "properties":{"answer":{"type":"string"}}}))?;
1018 /// assert_eq!(schema.schema()["type"], "object");
1019 ///
1020 /// # Ok::<(), providers_kit::KitError>(())
1021 /// ```
1022 ///
1023 pub fn new(schema: Value) -> Result<Self, KitError> {
1024 let object = schema.as_object().ok_or_else(|| {
1025 KitError::invalid_input(
1026 ValidationViolation::InvalidFormat {
1027 field: "json_schema",
1028 rule: "JSON object",
1029 },
1030 "JSON schema must be encoded as an object",
1031 )
1032 })?;
1033 if !matches!(object.get("type"), Some(Value::String(kind)) if kind == "object") {
1034 return Err(KitError::invalid_input(
1035 ValidationViolation::InvalidValue {
1036 field: "json_schema.type",
1037 rule: "must declare object",
1038 },
1039 "JSON schema root must declare `type: object`",
1040 ));
1041 }
1042 Ok(Self(schema))
1043 }
1044
1045 /// Borrows the validated schema document.
1046 ///
1047 /// # Examples
1048 ///
1049 /// ```
1050 /// use providers_kit::generation::JsonSchema;
1051 /// let schema = JsonSchema::new(serde_json::json!({"type":"object", "properties":{"answer":{"type":"string"}}}))?;
1052 /// assert_eq!(schema.schema()["type"], "object");
1053 ///
1054 /// # Ok::<(), providers_kit::KitError>(())
1055 /// ```
1056 ///
1057 #[must_use]
1058 pub const fn schema(&self) -> &Value {
1059 &self.0
1060 }
1061
1062 /// Consumes the wrapper and returns the schema document.
1063 ///
1064 /// # Examples
1065 ///
1066 /// ```
1067 /// use providers_kit::generation::JsonSchema;
1068 /// let schema = JsonSchema::new(serde_json::json!({"type":"object", "properties":{"answer":{"type":"string"}}}))?;
1069 /// assert_eq!(schema.into_value()["type"], "object");
1070 ///
1071 /// # Ok::<(), providers_kit::KitError>(())
1072 /// ```
1073 ///
1074 #[must_use]
1075 pub fn into_value(self) -> Value {
1076 self.0
1077 }
1078}
1079
1080impl Serialize for JsonSchema {
1081 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1082 where
1083 S: Serializer,
1084 {
1085 self.0.serialize(serializer)
1086 }
1087}
1088
1089impl<'de> Deserialize<'de> for JsonSchema {
1090 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1091 where
1092 D: Deserializer<'de>,
1093 {
1094 Self::new(Value::deserialize(deserializer)?).map_err(serde::de::Error::custom)
1095 }
1096}
1097
1098/// Requested output format for a generation request.
1099#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1100#[serde(tag = "type", rename_all = "snake_case", deny_unknown_fields)]
1101pub enum ResponseFormat {
1102 /// Plain text output.
1103 Text,
1104 /// A provider-validated JSON object without a caller schema.
1105 JsonObject,
1106 /// JSON validated against a strict caller schema.
1107 JsonSchema(JsonSchema),
1108}
1109
1110/// Portable reasoning effort hint for providers that support explicit thinking controls.
1111///
1112/// # Examples
1113/// ```rust
1114/// let effort = providers_kit::generation::ReasoningEffort::High;
1115/// assert!(matches!(effort, providers_kit::generation::ReasoningEffort::High));
1116/// ```
1117#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
1118#[serde(rename_all = "snake_case")]
1119pub enum ReasoningEffort {
1120 /// Requests the lightest available reasoning budget.
1121 Minimal,
1122 /// Requests a low reasoning budget when the provider exposes several tiers.
1123 Low,
1124 /// Requests a balanced reasoning budget between latency and depth.
1125 Medium,
1126 /// Requests a high reasoning budget for harder prompts.
1127 High,
1128 /// Requests the deepest reasoning tier the provider allows.
1129 Maximum,
1130}
1131
1132/// Controls how much reasoning summary a provider should return.
1133///
1134/// # Examples
1135/// ```rust
1136/// let mode = providers_kit::generation::ReasoningSummaryMode::Summary;
1137/// assert!(matches!(mode, providers_kit::generation::ReasoningSummaryMode::Summary));
1138/// ```
1139#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
1140#[serde(rename_all = "snake_case")]
1141pub enum ReasoningSummaryMode {
1142 /// Lets the provider decide whether and how to summarize its reasoning.
1143 Auto,
1144 /// Suppresses any reasoning summary in the provider response when supported.
1145 Hidden,
1146 /// Requests a concise reasoning summary.
1147 Summary,
1148 /// Requests the most detailed reasoning summary the provider exposes.
1149 Verbose,
1150}
1151
1152/// Optional reasoning controls attached to a generation request.
1153///
1154/// # Examples
1155/// ```rust
1156/// let config = providers_kit::generation::ReasoningConfig {
1157/// effort: Some(providers_kit::generation::ReasoningEffort::High),
1158/// ..Default::default()
1159/// };
1160/// assert_eq!(config.effort, Some(providers_kit::generation::ReasoningEffort::High));
1161/// ```
1162#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1163#[serde(deny_unknown_fields)]
1164pub struct ReasoningConfig {
1165 /// Requested reasoning effort tier when the backend supports explicit controls.
1166 pub effort: Option<ReasoningEffort>,
1167 /// Upper bound for provider-native reasoning tokens when that concept exists.
1168 pub max_reasoning_tokens: Option<u32>,
1169 /// Requested reasoning-summary presentation mode.
1170 pub summary_mode: ReasoningSummaryMode,
1171}
1172
1173impl Default for ReasoningConfig {
1174 fn default() -> Self {
1175 Self {
1176 effort: None,
1177 max_reasoning_tokens: None,
1178 summary_mode: ReasoningSummaryMode::Auto,
1179 }
1180 }
1181}
1182
1183/// Policy for how strongly the allowed-tool list should be enforced.
1184///
1185/// # Examples
1186/// ```rust
1187/// let mode = providers_kit::generation::AllowedToolMode::Required;
1188/// assert!(matches!(mode, providers_kit::generation::AllowedToolMode::Required));
1189/// ```
1190#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1191pub enum AllowedToolMode {
1192 /// Lets the provider decide how strictly to enforce the allowed-tool list.
1193 Auto,
1194 /// Requires the provider to keep tool calls inside the allowed-tool list.
1195 Required,
1196}
1197
1198impl Serialize for AllowedToolMode {
1199 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1200 where
1201 S: Serializer,
1202 {
1203 self.as_wire().serialize(serializer)
1204 }
1205}
1206
1207impl<'de> Deserialize<'de> for AllowedToolMode {
1208 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1209 where
1210 D: Deserializer<'de>,
1211 {
1212 let value = String::deserialize(deserializer)?;
1213 Self::from_wire(&value)
1214 }
1215}
1216
1217impl AllowedToolMode {
1218 pub(crate) fn as_wire(self) -> &'static str {
1219 match self {
1220 Self::Auto => "auto",
1221 Self::Required => "required",
1222 }
1223 }
1224
1225 fn from_wire<E>(value: &str) -> Result<Self, E>
1226 where
1227 E: serde::de::Error,
1228 {
1229 match value {
1230 "required" => Ok(Self::Required),
1231 "auto" => Ok(Self::Auto),
1232 other => Err(E::custom(format!("unknown allowed tool mode `{other}`"))),
1233 }
1234 }
1235}
1236
1237/// Portable tool-selection policy for a request.
1238///
1239/// Provider renderers map this policy into their native tool-choice shape after
1240/// the selected model has validated whether tools, strict schemas, and forced
1241/// choices are supported together.
1242///
1243/// # Examples
1244/// ```rust
1245/// let choice = providers_kit::generation::ToolChoice::Tool {
1246/// name: "lookup_weather".into(),
1247/// };
1248/// assert!(matches!(choice, providers_kit::generation::ToolChoice::Tool { .. }));
1249/// ```
1250#[derive(Clone, Debug, PartialEq, Eq)]
1251pub enum ToolChoice {
1252 /// Lets the provider choose whether tools should be used.
1253 Auto,
1254 /// Disables tool calling for the request.
1255 None,
1256 /// Requires at least one tool call before completion.
1257 Required,
1258 /// Allows any declared tool to be called.
1259 Any,
1260 /// Forces the provider to call one specific tool.
1261 Tool {
1262 /// Declared tool name that must be used.
1263 name: String,
1264 },
1265 /// Restricts tool calls to an explicit subset of tool names.
1266 Allowed {
1267 /// Enforcement mode for the allowed-tool subset.
1268 mode: AllowedToolMode,
1269 /// Tool names that remain eligible for selection.
1270 names: Vec<String>,
1271 },
1272}
1273
1274impl Serialize for ToolChoice {
1275 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1276 where
1277 S: Serializer,
1278 {
1279 self.as_wire().serialize(serializer)
1280 }
1281}
1282
1283impl<'de> Deserialize<'de> for ToolChoice {
1284 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1285 where
1286 D: Deserializer<'de>,
1287 {
1288 match Value::deserialize(deserializer)? {
1289 Value::String(value) => match value.as_str() {
1290 "auto" => Ok(Self::Auto),
1291 "none" => Ok(Self::None),
1292 "required" => Ok(Self::Required),
1293 "any" => Ok(Self::Any),
1294 other => Err(serde::de::Error::custom(format!(
1295 "unknown tool choice variant `{other}`"
1296 ))),
1297 },
1298 Value::Object(object) => {
1299 serde_json::from_value::<ToolChoiceObjectWire>(Value::Object(object))
1300 .map_err(serde::de::Error::custom)?
1301 .into_tool_choice()
1302 }
1303 _ => Err(serde::de::Error::custom(
1304 "tool choice must be encoded as a string or object",
1305 )),
1306 }
1307 }
1308}
1309
1310impl ToolChoice {
1311 fn as_wire(&self) -> ToolChoiceWireRef<'_> {
1312 match self {
1313 Self::Auto => ToolChoiceWireRef::String("auto"),
1314 Self::None => ToolChoiceWireRef::String("none"),
1315 Self::Required => ToolChoiceWireRef::String("required"),
1316 Self::Any => ToolChoiceWireRef::String("any"),
1317 Self::Tool { name } => ToolChoiceWireRef::Object(ToolChoiceObjectWireRef {
1318 kind: "tool",
1319 mode: None,
1320 name: Some(name),
1321 names: None,
1322 }),
1323 Self::Allowed { mode, names } => ToolChoiceWireRef::Object(ToolChoiceObjectWireRef {
1324 kind: "allowed",
1325 mode: Some(mode.as_wire()),
1326 name: None,
1327 names: Some(names.as_slice()),
1328 }),
1329 }
1330 }
1331}
1332
1333#[derive(Serialize)]
1334#[serde(untagged)]
1335enum ToolChoiceWireRef<'a> {
1336 String(&'static str),
1337 Object(ToolChoiceObjectWireRef<'a>),
1338}
1339
1340#[derive(Serialize)]
1341struct ToolChoiceObjectWireRef<'a> {
1342 #[serde(rename = "type")]
1343 kind: &'static str,
1344 #[serde(skip_serializing_if = "Option::is_none")]
1345 mode: Option<&'static str>,
1346 #[serde(skip_serializing_if = "Option::is_none")]
1347 name: Option<&'a str>,
1348 #[serde(skip_serializing_if = "Option::is_none")]
1349 names: Option<&'a [String]>,
1350}
1351
1352#[derive(Deserialize)]
1353#[serde(deny_unknown_fields)]
1354struct ToolChoiceObjectWire {
1355 #[serde(rename = "type", default)]
1356 kind: Option<String>,
1357 #[serde(default)]
1358 mode: Option<String>,
1359 #[serde(default)]
1360 name: Option<String>,
1361 #[serde(default)]
1362 names: Option<Value>,
1363}
1364
1365impl ToolChoiceObjectWire {
1366 fn into_tool_choice<E>(self) -> Result<ToolChoice, E>
1367 where
1368 E: serde::de::Error,
1369 {
1370 let kind = self
1371 .kind
1372 .ok_or_else(|| E::custom("tool choice objects require a string `type` field"))?;
1373 match kind.as_str() {
1374 "tool" => {
1375 let name = self
1376 .name
1377 .filter(|name| !name.trim().is_empty())
1378 .ok_or_else(|| E::custom("tool choice objects require a non-empty `name`"))?;
1379 Ok(ToolChoice::Tool { name })
1380 }
1381 "allowed" => Ok(ToolChoice::Allowed {
1382 mode: self
1383 .mode
1384 .as_deref()
1385 .map(AllowedToolMode::from_wire)
1386 .transpose()?
1387 .unwrap_or(AllowedToolMode::Auto),
1388 names: parse_allowed_tool_names(self.names)?,
1389 }),
1390 other => Err(E::custom(format!(
1391 "unknown tool choice object type `{other}`"
1392 ))),
1393 }
1394 }
1395}
1396
1397fn parse_allowed_tool_names<E>(value: Option<Value>) -> Result<Vec<String>, E>
1398where
1399 E: serde::de::Error,
1400{
1401 match value {
1402 None => Ok(Vec::new()),
1403 Some(Value::Array(items)) => items
1404 .into_iter()
1405 .map(|item| match item {
1406 Value::String(name) if !name.trim().is_empty() => Ok(name),
1407 Value::String(_) => {
1408 Err(E::custom("allowed tool entries must be non-empty strings"))
1409 }
1410 _ => Err(E::custom("allowed tool entries must be strings")),
1411 })
1412 .collect(),
1413 Some(_) => Err(E::custom("allowed tool names must be encoded as an array")),
1414 }
1415}
1416
1417/// Validated client-executed function declaration shown to a model.
1418///
1419/// This declares a caller-executed function. providers-kit never executes the callback;
1420/// the caller supplies correlated [`FunctionResult`] values after returned calls.
1421///
1422/// # Examples
1423///
1424/// ```
1425/// use providers_kit::generation::{ProviderFunctionTool, JsonSchema};
1426/// let tool = ProviderFunctionTool::new("lookup", Some("Find a record".into()), JsonSchema::new(serde_json::json!({"type":"object"}))?)?;
1427/// assert_eq!(tool.name(), "lookup");
1428/// assert_eq!(tool.description(), Some("Find a record"));
1429/// assert_eq!(tool.input_schema().schema()["type"], "object");
1430///
1431/// # Ok::<(), providers_kit::KitError>(())
1432/// ```
1433///
1434#[derive(Clone, Debug, PartialEq, Serialize)]
1435pub struct ProviderFunctionTool {
1436 name: String,
1437 description: Option<String>,
1438 input_schema: JsonSchema,
1439}
1440
1441impl ProviderFunctionTool {
1442 /// Creates a function declaration with a stable non-empty name.
1443 ///
1444 /// # Errors
1445 ///
1446 /// Returns an input error for an invalid function name or blank description. The input schema has already been validated by [`JsonSchema::new`].
1447 ///
1448 /// # Examples
1449 ///
1450 /// ```
1451 /// use providers_kit::generation::{ProviderFunctionTool, JsonSchema};
1452 /// let tool = ProviderFunctionTool::new("lookup", Some("Find a record".into()), JsonSchema::new(serde_json::json!({"type":"object"}))?)?;
1453 /// assert_eq!(tool.name(), "lookup");
1454 /// assert_eq!(tool.description(), Some("Find a record"));
1455 /// assert_eq!(tool.input_schema().schema()["type"], "object");
1456 ///
1457 /// # Ok::<(), providers_kit::KitError>(())
1458 /// ```
1459 ///
1460 pub fn new(
1461 name: impl Into<String>,
1462 description: Option<String>,
1463 input_schema: JsonSchema,
1464 ) -> Result<Self, KitError> {
1465 let name = name.into();
1466 FunctionCall::validate_name("tools.function.name", &name)?;
1467 if description
1468 .as_ref()
1469 .is_some_and(|value| value.trim().is_empty())
1470 {
1471 return Err(KitError::invalid_input(
1472 ValidationViolation::Empty {
1473 field: "tools.function.description",
1474 },
1475 "function tool description must be omitted or non-empty",
1476 ));
1477 }
1478 Ok(Self {
1479 name,
1480 description,
1481 input_schema,
1482 })
1483 }
1484
1485 /// Returns the provider-visible function name.
1486 ///
1487 /// # Examples
1488 ///
1489 /// ```
1490 /// use providers_kit::generation::{ProviderFunctionTool, JsonSchema};
1491 /// let tool = ProviderFunctionTool::new("lookup", Some("Find a record".into()), JsonSchema::new(serde_json::json!({"type":"object"}))?)?;
1492 /// assert_eq!(tool.name(), "lookup");
1493 /// assert_eq!(tool.description(), Some("Find a record"));
1494 /// assert_eq!(tool.input_schema().schema()["type"], "object");
1495 ///
1496 /// # Ok::<(), providers_kit::KitError>(())
1497 /// ```
1498 ///
1499 #[must_use]
1500 pub fn name(&self) -> &str {
1501 &self.name
1502 }
1503
1504 /// Returns the optional model-visible description.
1505 ///
1506 /// # Examples
1507 ///
1508 /// ```
1509 /// use providers_kit::generation::{ProviderFunctionTool, JsonSchema};
1510 /// let tool = ProviderFunctionTool::new("lookup", Some("Find a record".into()), JsonSchema::new(serde_json::json!({"type":"object"}))?)?;
1511 /// assert_eq!(tool.name(), "lookup");
1512 /// assert_eq!(tool.description(), Some("Find a record"));
1513 /// assert_eq!(tool.input_schema().schema()["type"], "object");
1514 ///
1515 /// # Ok::<(), providers_kit::KitError>(())
1516 /// ```
1517 ///
1518 #[must_use]
1519 pub fn description(&self) -> Option<&str> {
1520 self.description.as_deref()
1521 }
1522
1523 /// Returns the strict argument schema.
1524 ///
1525 /// # Examples
1526 ///
1527 /// ```
1528 /// use providers_kit::generation::{ProviderFunctionTool, JsonSchema};
1529 /// let tool = ProviderFunctionTool::new("lookup", Some("Find a record".into()), JsonSchema::new(serde_json::json!({"type":"object"}))?)?;
1530 /// assert_eq!(tool.name(), "lookup");
1531 /// assert_eq!(tool.description(), Some("Find a record"));
1532 /// assert_eq!(tool.input_schema().schema()["type"], "object");
1533 ///
1534 /// # Ok::<(), providers_kit::KitError>(())
1535 /// ```
1536 ///
1537 #[must_use]
1538 pub const fn input_schema(&self) -> &JsonSchema {
1539 &self.input_schema
1540 }
1541
1542 /// Consumes the declaration into its validated parts.
1543 ///
1544 /// # Examples
1545 ///
1546 /// ```
1547 /// use providers_kit::generation::{ProviderFunctionTool, JsonSchema};
1548 /// let tool = ProviderFunctionTool::new("lookup", Some("Find a record".into()), JsonSchema::new(serde_json::json!({"type":"object"}))?)?;
1549 /// let (name, description, schema) = tool.into_parts();
1550 /// assert_eq!(name, "lookup");
1551 /// assert_eq!(description.as_deref(), Some("Find a record"));
1552 /// assert_eq!(schema.schema()["type"], "object");
1553 ///
1554 /// # Ok::<(), providers_kit::KitError>(())
1555 /// ```
1556 ///
1557 #[must_use]
1558 pub fn into_parts(self) -> (String, Option<String>, JsonSchema) {
1559 (self.name, self.description, self.input_schema)
1560 }
1561}
1562
1563impl<'de> Deserialize<'de> for ProviderFunctionTool {
1564 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1565 where
1566 D: Deserializer<'de>,
1567 {
1568 #[derive(Deserialize)]
1569 #[serde(deny_unknown_fields)]
1570 struct Wire {
1571 name: String,
1572 description: Option<String>,
1573 input_schema: JsonSchema,
1574 }
1575 let wire = Wire::deserialize(deserializer)?;
1576 Self::new(wire.name, wire.description, wire.input_schema).map_err(serde::de::Error::custom)
1577 }
1578}
1579
1580/// Tool declaration supplied to a generation request.
1581#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1582#[serde(tag = "type", content = "declaration", rename_all = "snake_case")]
1583pub enum ProviderToolSpec {
1584 /// Client-executed function.
1585 Function(ProviderFunctionTool),
1586 /// Provider-hosted execution.
1587 Hosted(HostedTool),
1588}
1589
1590impl ProviderToolSpec {
1591 pub(crate) const fn hosted_tool(&self) -> Option<&HostedTool> {
1592 match self {
1593 Self::Function(_) => None,
1594 Self::Hosted(tool) => Some(tool),
1595 }
1596 }
1597}
1598
1599/// Validated client function call requested by a model.
1600///
1601/// # Examples
1602///
1603/// ```
1604/// use providers_kit::generation::FunctionCall;
1605/// let call = FunctionCall::new(Some("call-1".into()), "lookup", serde_json::json!({"key":"a"}))?;
1606/// assert_eq!(call.id(), Some("call-1"));
1607/// assert_eq!(call.name(), "lookup");
1608/// assert_eq!(call.arguments()["key"], "a");
1609///
1610/// # Ok::<(), providers_kit::KitError>(())
1611/// ```
1612///
1613#[derive(Clone, Debug, PartialEq, Serialize)]
1614pub struct FunctionCall {
1615 id: Option<String>,
1616 name: String,
1617 arguments: Value,
1618}
1619
1620impl<'de> Deserialize<'de> for FunctionCall {
1621 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1622 where
1623 D: Deserializer<'de>,
1624 {
1625 #[derive(Deserialize)]
1626 #[serde(deny_unknown_fields)]
1627 struct Wire {
1628 id: Option<String>,
1629 name: String,
1630 arguments: Value,
1631 }
1632
1633 let wire = Wire::deserialize(deserializer)?;
1634 Self::new(wire.id, wire.name, wire.arguments).map_err(serde::de::Error::custom)
1635 }
1636}
1637
1638impl FunctionCall {
1639 /// Creates a validated function call.
1640 ///
1641 /// # Errors
1642 ///
1643 /// Returns an input error for an invalid portable function name, invalid optional call id, or arguments that are not a JSON object. Keep returned call correlation unchanged in its result.
1644 ///
1645 /// # Examples
1646 ///
1647 /// ```
1648 /// use providers_kit::generation::FunctionCall;
1649 /// let call = FunctionCall::new(Some("call-1".into()), "lookup", serde_json::json!({"key":"a"}))?;
1650 /// assert_eq!(call.id(), Some("call-1"));
1651 /// assert_eq!(call.name(), "lookup");
1652 /// assert_eq!(call.arguments()["key"], "a");
1653 ///
1654 /// # Ok::<(), providers_kit::KitError>(())
1655 /// ```
1656 ///
1657 pub fn new(
1658 id: Option<String>,
1659 name: impl Into<String>,
1660 arguments: Value,
1661 ) -> Result<Self, KitError> {
1662 let name = name.into();
1663 Self::validate_name("function_call.name", &name)?;
1664 if let Some(id) = id.as_deref() {
1665 Self::validate_id("function_call.id", id)?;
1666 }
1667 if !arguments.is_object() {
1668 return Err(KitError::invalid_input(
1669 ValidationViolation::InvalidFormat {
1670 field: "function_call.arguments",
1671 rule: "JSON object",
1672 },
1673 "function arguments must be a JSON object",
1674 ));
1675 }
1676 Ok(Self {
1677 id,
1678 name,
1679 arguments,
1680 })
1681 }
1682
1683 /// Returns the provider correlation id, when one was supplied.
1684 ///
1685 /// # Examples
1686 ///
1687 /// ```
1688 /// use providers_kit::generation::FunctionCall;
1689 /// let call = FunctionCall::new(Some("call-1".into()), "lookup", serde_json::json!({"key":"a"}))?;
1690 /// assert_eq!(call.id(), Some("call-1"));
1691 /// assert_eq!(call.name(), "lookup");
1692 /// assert_eq!(call.arguments()["key"], "a");
1693 ///
1694 /// # Ok::<(), providers_kit::KitError>(())
1695 /// ```
1696 ///
1697 #[must_use]
1698 pub fn id(&self) -> Option<&str> {
1699 self.id.as_deref()
1700 }
1701
1702 /// Returns the function name.
1703 ///
1704 /// # Examples
1705 ///
1706 /// ```
1707 /// use providers_kit::generation::FunctionCall;
1708 /// let call = FunctionCall::new(Some("call-1".into()), "lookup", serde_json::json!({"key":"a"}))?;
1709 /// assert_eq!(call.id(), Some("call-1"));
1710 /// assert_eq!(call.name(), "lookup");
1711 /// assert_eq!(call.arguments()["key"], "a");
1712 ///
1713 /// # Ok::<(), providers_kit::KitError>(())
1714 /// ```
1715 ///
1716 #[must_use]
1717 pub fn name(&self) -> &str {
1718 &self.name
1719 }
1720
1721 /// Returns the validated object-shaped arguments.
1722 ///
1723 /// # Examples
1724 ///
1725 /// ```
1726 /// use providers_kit::generation::FunctionCall;
1727 /// let call = FunctionCall::new(Some("call-1".into()), "lookup", serde_json::json!({"key":"a"}))?;
1728 /// assert_eq!(call.id(), Some("call-1"));
1729 /// assert_eq!(call.name(), "lookup");
1730 /// assert_eq!(call.arguments()["key"], "a");
1731 ///
1732 /// # Ok::<(), providers_kit::KitError>(())
1733 /// ```
1734 ///
1735 #[must_use]
1736 pub fn arguments(&self) -> &Value {
1737 &self.arguments
1738 }
1739
1740 pub(crate) fn validate_name(label: &'static str, value: &str) -> Result<(), KitError> {
1741 let mut bytes = value.bytes();
1742 let first = bytes.next();
1743 if value.len() > 64
1744 || !first.is_some_and(|byte| byte.is_ascii_alphabetic() || byte == b'_')
1745 || !bytes.all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'_' | b'-'))
1746 {
1747 Err(KitError::invalid_input(
1748 ValidationViolation::InvalidFormat {
1749 field: label,
1750 rule: "1..=64 ASCII letters, digits, underscores, or hyphens; first character letter or underscore",
1751 },
1752 format!(
1753 "{label} must use 1..=64 portable ASCII letters, digits, underscores, or hyphens and start with a letter or underscore"
1754 ),
1755 ))
1756 } else {
1757 Ok(())
1758 }
1759 }
1760
1761 fn validate_id(label: &'static str, value: &str) -> Result<(), KitError> {
1762 if value.is_empty()
1763 || value.len() > 256
1764 || value.trim() != value
1765 || value.chars().any(char::is_control)
1766 {
1767 Err(KitError::invalid_input(
1768 ValidationViolation::InvalidFormat {
1769 field: label,
1770 rule: "1..=256 unpadded UTF-8 bytes without control characters",
1771 },
1772 format!(
1773 "{label} must contain 1..=256 unpadded UTF-8 bytes without control characters"
1774 ),
1775 ))
1776 } else {
1777 Ok(())
1778 }
1779 }
1780}
1781
1782/// Validated caller-supplied result for a client function call.
1783///
1784/// # Examples
1785///
1786/// ```
1787/// use providers_kit::generation::{FunctionResult, ContentPart};
1788/// let result = FunctionResult::new(Some("call-1".into()), "lookup", vec![ContentPart::Json { value: serde_json::json!({"value":2}) }], false)?;
1789/// assert_eq!(result.call_id(), Some("call-1"));
1790/// assert_eq!(result.name(), "lookup");
1791/// assert_eq!(result.content().len(), 1);
1792/// assert!(!result.is_error());
1793///
1794/// # Ok::<(), providers_kit::KitError>(())
1795/// ```
1796///
1797#[derive(Clone, Debug, PartialEq, Serialize)]
1798pub struct FunctionResult {
1799 call_id: Option<String>,
1800 name: String,
1801 content: Vec<ContentPart>,
1802 is_error: bool,
1803}
1804
1805impl<'de> Deserialize<'de> for FunctionResult {
1806 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1807 where
1808 D: Deserializer<'de>,
1809 {
1810 #[derive(Deserialize)]
1811 #[serde(deny_unknown_fields)]
1812 struct Wire {
1813 call_id: Option<String>,
1814 name: String,
1815 content: Vec<ContentPart>,
1816 is_error: bool,
1817 }
1818
1819 let wire = Wire::deserialize(deserializer)?;
1820 Self::new(wire.call_id, wire.name, wire.content, wire.is_error)
1821 .map_err(serde::de::Error::custom)
1822 }
1823}
1824
1825impl FunctionResult {
1826 /// Creates a validated function result.
1827 ///
1828 /// # Errors
1829 ///
1830 /// Returns an input error for an invalid name/id, empty or invalid content, or provider thinking content. Accepted function failures use `is_error = true` with nonempty model-visible content.
1831 ///
1832 /// # Examples
1833 ///
1834 /// ```
1835 /// use providers_kit::generation::{FunctionResult, ContentPart};
1836 /// let result = FunctionResult::new(Some("call-1".into()), "lookup", vec![ContentPart::Json { value: serde_json::json!({"value":2}) }], false)?;
1837 /// assert_eq!(result.call_id(), Some("call-1"));
1838 /// assert_eq!(result.name(), "lookup");
1839 /// assert_eq!(result.content().len(), 1);
1840 /// assert!(!result.is_error());
1841 ///
1842 /// # Ok::<(), providers_kit::KitError>(())
1843 /// ```
1844 ///
1845 pub fn new(
1846 call_id: Option<String>,
1847 name: impl Into<String>,
1848 content: Vec<ContentPart>,
1849 is_error: bool,
1850 ) -> Result<Self, KitError> {
1851 let name = name.into();
1852 FunctionCall::validate_name("function_result.name", &name)?;
1853 if let Some(call_id) = call_id.as_deref() {
1854 FunctionCall::validate_id("function_result.call_id", call_id)?;
1855 }
1856 if content.is_empty() {
1857 return Err(KitError::invalid_input(
1858 ValidationViolation::Empty {
1859 field: "function_result.content",
1860 },
1861 "function result content must not be empty",
1862 ));
1863 }
1864 for part in &content {
1865 part.validate()?;
1866 }
1867 if content
1868 .iter()
1869 .any(|part| matches!(part, ContentPart::Thinking { .. }))
1870 {
1871 return Err(KitError::invalid_input(
1872 ValidationViolation::Unsupported {
1873 field: "function_result.content",
1874 rule: "thinking content is not permitted",
1875 },
1876 "function results cannot contain provider thinking state",
1877 ));
1878 }
1879 Ok(Self {
1880 call_id,
1881 name,
1882 content,
1883 is_error,
1884 })
1885 }
1886
1887 /// Returns the originating call id, when available.
1888 ///
1889 /// # Examples
1890 ///
1891 /// ```
1892 /// use providers_kit::generation::{FunctionResult, ContentPart};
1893 /// let result = FunctionResult::new(Some("call-1".into()), "lookup", vec![ContentPart::Json { value: serde_json::json!({"value":2}) }], false)?;
1894 /// assert_eq!(result.call_id(), Some("call-1"));
1895 /// assert_eq!(result.name(), "lookup");
1896 /// assert_eq!(result.content().len(), 1);
1897 /// assert!(!result.is_error());
1898 ///
1899 /// # Ok::<(), providers_kit::KitError>(())
1900 /// ```
1901 ///
1902 #[must_use]
1903 pub fn call_id(&self) -> Option<&str> {
1904 self.call_id.as_deref()
1905 }
1906
1907 /// Returns the function name.
1908 ///
1909 /// # Examples
1910 ///
1911 /// ```
1912 /// use providers_kit::generation::{FunctionResult, ContentPart};
1913 /// let result = FunctionResult::new(Some("call-1".into()), "lookup", vec![ContentPart::Json { value: serde_json::json!({"value":2}) }], false)?;
1914 /// assert_eq!(result.call_id(), Some("call-1"));
1915 /// assert_eq!(result.name(), "lookup");
1916 /// assert_eq!(result.content().len(), 1);
1917 /// assert!(!result.is_error());
1918 ///
1919 /// # Ok::<(), providers_kit::KitError>(())
1920 /// ```
1921 ///
1922 #[must_use]
1923 pub fn name(&self) -> &str {
1924 &self.name
1925 }
1926
1927 /// Returns ordered model-visible result content.
1928 ///
1929 /// # Examples
1930 ///
1931 /// ```
1932 /// use providers_kit::generation::{FunctionResult, ContentPart};
1933 /// let result = FunctionResult::new(Some("call-1".into()), "lookup", vec![ContentPart::Json { value: serde_json::json!({"value":2}) }], false)?;
1934 /// assert_eq!(result.call_id(), Some("call-1"));
1935 /// assert_eq!(result.name(), "lookup");
1936 /// assert_eq!(result.content().len(), 1);
1937 /// assert!(!result.is_error());
1938 ///
1939 /// # Ok::<(), providers_kit::KitError>(())
1940 /// ```
1941 ///
1942 #[must_use]
1943 pub fn content(&self) -> &[ContentPart] {
1944 &self.content
1945 }
1946
1947 /// Reports whether the completed function execution failed.
1948 ///
1949 /// # Examples
1950 ///
1951 /// ```
1952 /// use providers_kit::generation::{FunctionResult, ContentPart};
1953 /// let result = FunctionResult::new(Some("call-1".into()), "lookup", vec![ContentPart::Json { value: serde_json::json!({"value":2}) }], false)?;
1954 /// assert_eq!(result.call_id(), Some("call-1"));
1955 /// assert_eq!(result.name(), "lookup");
1956 /// assert_eq!(result.content().len(), 1);
1957 /// assert!(!result.is_error());
1958 ///
1959 /// # Ok::<(), providers_kit::KitError>(())
1960 /// ```
1961 ///
1962 #[must_use]
1963 pub const fn is_error(&self) -> bool {
1964 self.is_error
1965 }
1966}
1967
1968/// Optional sampling and tool-choice overrides.
1969///
1970/// These are portable requested controls. Local validation does not establish support
1971/// for every selected model; unsupported combinations fail at the model operation.
1972///
1973/// # Examples
1974///
1975/// ```
1976/// use providers_kit::generation::GenerationConfig;
1977/// let config = GenerationConfig::default().with_temperature(0.5)?.with_seed(42);
1978/// assert_eq!(config.temperature(), Some(0.5));
1979/// assert_eq!(config.seed(), Some(42));
1980///
1981/// # Ok::<(), providers_kit::KitError>(())
1982/// ```
1983///
1984#[derive(Clone, Debug, PartialEq, Serialize, Default)]
1985#[serde(deny_unknown_fields)]
1986pub struct GenerationConfig {
1987 temperature: Option<f32>,
1988 top_p: Option<f32>,
1989 top_k: Option<NonZeroU32>,
1990 max_output_tokens: Option<NonZeroU32>,
1991 stop_sequences: Vec<String>,
1992 presence_penalty: Option<f32>,
1993 frequency_penalty: Option<f32>,
1994 seed: Option<u64>,
1995 tool_choice: Option<ToolChoice>,
1996}
1997
1998impl GenerationConfig {
1999 /// Sets a finite non-negative sampling temperature.
2000 ///
2001 /// # Errors
2002 ///
2003 /// Returns an input error unless temperature is finite and between 0 and 2 inclusive.
2004 ///
2005 /// # Examples
2006 ///
2007 /// ```
2008 /// use providers_kit::generation::GenerationConfig;
2009 /// let config = GenerationConfig::default().with_temperature(0.5)?;
2010 /// assert_eq!(config.temperature(), Some(0.5));
2011 ///
2012 /// # Ok::<(), providers_kit::KitError>(())
2013 /// ```
2014 ///
2015 pub fn with_temperature(mut self, value: f32) -> Result<Self, KitError> {
2016 if !value.is_finite() || value < 0.0 {
2017 return Err(KitError::invalid_input(
2018 ValidationViolation::OutOfRange {
2019 field: "generation.temperature",
2020 rule: "finite and non-negative",
2021 },
2022 "temperature must be finite and non-negative",
2023 ));
2024 }
2025 self.temperature = Some(value);
2026 Ok(self)
2027 }
2028
2029 /// Sets a finite nucleus-sampling probability in `(0, 1]`.
2030 ///
2031 /// # Errors
2032 ///
2033 /// Returns an input error unless top_p is finite and between 0 and 1 inclusive.
2034 ///
2035 /// # Examples
2036 ///
2037 /// ```
2038 /// use providers_kit::generation::GenerationConfig;
2039 /// let config = GenerationConfig::default().with_top_p(0.8)?;
2040 /// assert_eq!(config.top_p(), Some(0.8));
2041 ///
2042 /// # Ok::<(), providers_kit::KitError>(())
2043 /// ```
2044 ///
2045 pub fn with_top_p(mut self, value: f32) -> Result<Self, KitError> {
2046 if !(value.is_finite() && 0.0 < value && value <= 1.0) {
2047 return Err(KitError::invalid_input(
2048 ValidationViolation::OutOfRange {
2049 field: "generation.top_p",
2050 rule: "finite and in (0, 1]",
2051 },
2052 "top_p must be in (0, 1]",
2053 ));
2054 }
2055 self.top_p = Some(value);
2056 Ok(self)
2057 }
2058
2059 /// Sets a non-zero top-k cutoff.
2060 ///
2061 /// # Examples
2062 ///
2063 /// ```
2064 /// use providers_kit::generation::GenerationConfig;
2065 /// let config = GenerationConfig::default().with_top_k(std::num::NonZeroU32::new(20).unwrap());
2066 /// assert_eq!(config.top_k(), std::num::NonZeroU32::new(20));
2067 ///
2068 /// # Ok::<(), providers_kit::KitError>(())
2069 /// ```
2070 ///
2071 #[must_use]
2072 pub fn with_top_k(mut self, value: NonZeroU32) -> Self {
2073 self.top_k = Some(value);
2074 self
2075 }
2076
2077 /// Sets a non-zero maximum output-token count.
2078 ///
2079 /// # Examples
2080 ///
2081 /// ```
2082 /// use providers_kit::generation::GenerationConfig;
2083 /// let config = GenerationConfig::default().with_max_output_tokens(std::num::NonZeroU32::new(256).unwrap());
2084 /// assert_eq!(config.max_output_tokens(), std::num::NonZeroU32::new(256));
2085 ///
2086 /// # Ok::<(), providers_kit::KitError>(())
2087 /// ```
2088 ///
2089 #[must_use]
2090 pub fn with_max_output_tokens(mut self, value: NonZeroU32) -> Self {
2091 self.max_output_tokens = Some(value);
2092 self
2093 }
2094
2095 /// Replaces validated unique stop sequences.
2096 ///
2097 /// # Errors
2098 ///
2099 /// Returns an input error for a blank or repeated sequence. An empty list clears existing stop sequences.
2100 ///
2101 /// # Examples
2102 ///
2103 /// ```
2104 /// use providers_kit::generation::GenerationConfig;
2105 /// let config = GenerationConfig::default().with_stop_sequences(vec!["END".into()])?;
2106 /// assert_eq!(config.stop_sequences(), &["END"]);
2107 ///
2108 /// # Ok::<(), providers_kit::KitError>(())
2109 /// ```
2110 ///
2111 pub fn with_stop_sequences(mut self, values: Vec<String>) -> Result<Self, KitError> {
2112 let mut seen = BTreeSet::new();
2113 for value in &values {
2114 if value.trim().is_empty() {
2115 return Err(KitError::invalid_input(
2116 ValidationViolation::Empty {
2117 field: "generation.stop_sequences",
2118 },
2119 "stop sequences must not be empty",
2120 ));
2121 }
2122 if !seen.insert(value) {
2123 return Err(KitError::invalid_input(
2124 ValidationViolation::Duplicate {
2125 field: "generation.stop_sequences",
2126 },
2127 "stop sequences must be unique",
2128 ));
2129 }
2130 }
2131 self.stop_sequences = values;
2132 Ok(self)
2133 }
2134
2135 /// Sets a finite presence penalty in `[-2, 2]`.
2136 ///
2137 /// # Errors
2138 ///
2139 /// Returns an input error unless the penalty is finite and between -2 and 2 inclusive.
2140 ///
2141 /// # Examples
2142 ///
2143 /// ```
2144 /// use providers_kit::generation::GenerationConfig;
2145 /// let config = GenerationConfig::default().with_presence_penalty(0.25)?;
2146 /// assert_eq!(config.presence_penalty(), Some(0.25));
2147 ///
2148 /// # Ok::<(), providers_kit::KitError>(())
2149 /// ```
2150 ///
2151 pub fn with_presence_penalty(mut self, value: f32) -> Result<Self, KitError> {
2152 self.presence_penalty = Some(Self::penalty("generation.presence_penalty", value)?);
2153 Ok(self)
2154 }
2155
2156 /// Sets a finite frequency penalty in `[-2, 2]`.
2157 ///
2158 /// # Errors
2159 ///
2160 /// Returns an input error unless the penalty is finite and between -2 and 2 inclusive.
2161 ///
2162 /// # Examples
2163 ///
2164 /// ```
2165 /// use providers_kit::generation::GenerationConfig;
2166 /// let config = GenerationConfig::default().with_frequency_penalty(0.25)?;
2167 /// assert_eq!(config.frequency_penalty(), Some(0.25));
2168 ///
2169 /// # Ok::<(), providers_kit::KitError>(())
2170 /// ```
2171 ///
2172 pub fn with_frequency_penalty(mut self, value: f32) -> Result<Self, KitError> {
2173 self.frequency_penalty = Some(Self::penalty("generation.frequency_penalty", value)?);
2174 Ok(self)
2175 }
2176
2177 fn penalty(name: &'static str, value: f32) -> Result<f32, KitError> {
2178 if value.is_finite() && (-2.0..=2.0).contains(&value) {
2179 Ok(value)
2180 } else {
2181 Err(KitError::invalid_input(
2182 ValidationViolation::OutOfRange {
2183 field: name,
2184 rule: "finite and in [-2, 2]",
2185 },
2186 format!("{name} must be finite and in [-2, 2]"),
2187 ))
2188 }
2189 }
2190
2191 /// Sets a deterministic sampling seed.
2192 ///
2193 /// # Examples
2194 ///
2195 /// ```
2196 /// use providers_kit::generation::GenerationConfig;
2197 /// let config = GenerationConfig::default().with_seed(42);
2198 /// assert_eq!(config.seed(), Some(42));
2199 ///
2200 /// # Ok::<(), providers_kit::KitError>(())
2201 /// ```
2202 ///
2203 #[must_use]
2204 pub fn with_seed(mut self, value: u64) -> Self {
2205 self.seed = Some(value);
2206 self
2207 }
2208
2209 /// Sets a validated tool-selection policy.
2210 ///
2211 /// # Examples
2212 ///
2213 /// ```
2214 /// use providers_kit::generation::GenerationConfig;
2215 /// let config = GenerationConfig::default().with_tool_choice(providers_kit::generation::ToolChoice::Auto);
2216 /// assert_eq!(config.tool_choice(), Some(&providers_kit::generation::ToolChoice::Auto));
2217 ///
2218 /// # Ok::<(), providers_kit::KitError>(())
2219 /// ```
2220 ///
2221 #[must_use]
2222 pub fn with_tool_choice(mut self, value: ToolChoice) -> Self {
2223 self.tool_choice = Some(value);
2224 self
2225 }
2226
2227 #[must_use]
2228 /// Returns the requested sampling temperature.
2229 ///
2230 /// # Examples
2231 ///
2232 /// ```
2233 /// use providers_kit::generation::GenerationConfig;
2234 /// let config = GenerationConfig::default().with_temperature(0.5)?;
2235 /// assert_eq!(config.temperature(), Some(0.5));
2236 ///
2237 /// # Ok::<(), providers_kit::KitError>(())
2238 /// ```
2239 ///
2240 pub const fn temperature(&self) -> Option<f32> {
2241 self.temperature
2242 }
2243
2244 #[must_use]
2245 /// Returns the requested nucleus-sampling probability.
2246 ///
2247 /// # Examples
2248 ///
2249 /// ```
2250 /// use providers_kit::generation::GenerationConfig;
2251 /// let config = GenerationConfig::default().with_top_p(0.8)?;
2252 /// assert_eq!(config.top_p(), Some(0.8));
2253 ///
2254 /// # Ok::<(), providers_kit::KitError>(())
2255 /// ```
2256 ///
2257 pub const fn top_p(&self) -> Option<f32> {
2258 self.top_p
2259 }
2260
2261 #[must_use]
2262 /// Returns the requested top-k cutoff.
2263 ///
2264 /// # Examples
2265 ///
2266 /// ```
2267 /// use providers_kit::generation::GenerationConfig;
2268 /// let config = GenerationConfig::default().with_top_k(std::num::NonZeroU32::new(20).unwrap());
2269 /// assert_eq!(config.top_k(), std::num::NonZeroU32::new(20));
2270 ///
2271 /// # Ok::<(), providers_kit::KitError>(())
2272 /// ```
2273 ///
2274 pub const fn top_k(&self) -> Option<NonZeroU32> {
2275 self.top_k
2276 }
2277
2278 #[must_use]
2279 /// Returns the requested maximum output-token count.
2280 ///
2281 /// # Examples
2282 ///
2283 /// ```
2284 /// use providers_kit::generation::GenerationConfig;
2285 /// let config = GenerationConfig::default().with_max_output_tokens(std::num::NonZeroU32::new(256).unwrap());
2286 /// assert_eq!(config.max_output_tokens(), std::num::NonZeroU32::new(256));
2287 ///
2288 /// # Ok::<(), providers_kit::KitError>(())
2289 /// ```
2290 ///
2291 pub const fn max_output_tokens(&self) -> Option<NonZeroU32> {
2292 self.max_output_tokens
2293 }
2294
2295 #[must_use]
2296 /// Returns validated stop sequences in caller order.
2297 ///
2298 /// # Examples
2299 ///
2300 /// ```
2301 /// use providers_kit::generation::GenerationConfig;
2302 /// let config = GenerationConfig::default().with_stop_sequences(vec!["END".into()])?;
2303 /// assert_eq!(config.stop_sequences(), &["END"]);
2304 ///
2305 /// # Ok::<(), providers_kit::KitError>(())
2306 /// ```
2307 ///
2308 pub fn stop_sequences(&self) -> &[String] {
2309 &self.stop_sequences
2310 }
2311
2312 #[must_use]
2313 /// Returns the requested presence penalty.
2314 ///
2315 /// # Examples
2316 ///
2317 /// ```
2318 /// use providers_kit::generation::GenerationConfig;
2319 /// let config = GenerationConfig::default().with_presence_penalty(0.25)?;
2320 /// assert_eq!(config.presence_penalty(), Some(0.25));
2321 ///
2322 /// # Ok::<(), providers_kit::KitError>(())
2323 /// ```
2324 ///
2325 pub const fn presence_penalty(&self) -> Option<f32> {
2326 self.presence_penalty
2327 }
2328
2329 #[must_use]
2330 /// Returns the requested frequency penalty.
2331 ///
2332 /// # Examples
2333 ///
2334 /// ```
2335 /// use providers_kit::generation::GenerationConfig;
2336 /// let config = GenerationConfig::default().with_frequency_penalty(0.25)?;
2337 /// assert_eq!(config.frequency_penalty(), Some(0.25));
2338 ///
2339 /// # Ok::<(), providers_kit::KitError>(())
2340 /// ```
2341 ///
2342 pub const fn frequency_penalty(&self) -> Option<f32> {
2343 self.frequency_penalty
2344 }
2345
2346 #[must_use]
2347 /// Returns the requested sampling seed.
2348 ///
2349 /// # Examples
2350 ///
2351 /// ```
2352 /// use providers_kit::generation::GenerationConfig;
2353 /// let config = GenerationConfig::default().with_seed(42);
2354 /// assert_eq!(config.seed(), Some(42));
2355 ///
2356 /// # Ok::<(), providers_kit::KitError>(())
2357 /// ```
2358 ///
2359 pub const fn seed(&self) -> Option<u64> {
2360 self.seed
2361 }
2362
2363 #[must_use]
2364 /// Returns the requested tool-selection policy.
2365 ///
2366 /// # Examples
2367 ///
2368 /// ```
2369 /// use providers_kit::generation::GenerationConfig;
2370 /// let config = GenerationConfig::default().with_tool_choice(providers_kit::generation::ToolChoice::Auto);
2371 /// assert_eq!(config.tool_choice(), Some(&providers_kit::generation::ToolChoice::Auto));
2372 ///
2373 /// # Ok::<(), providers_kit::KitError>(())
2374 /// ```
2375 ///
2376 pub const fn tool_choice(&self) -> Option<&ToolChoice> {
2377 self.tool_choice.as_ref()
2378 }
2379}
2380
2381impl<'de> Deserialize<'de> for GenerationConfig {
2382 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2383 where
2384 D: Deserializer<'de>,
2385 {
2386 #[derive(Deserialize, Default)]
2387 #[serde(deny_unknown_fields)]
2388 struct Wire {
2389 temperature: Option<f32>,
2390 top_p: Option<f32>,
2391 top_k: Option<NonZeroU32>,
2392 max_output_tokens: Option<NonZeroU32>,
2393 #[serde(default)]
2394 stop_sequences: Vec<String>,
2395 presence_penalty: Option<f32>,
2396 frequency_penalty: Option<f32>,
2397 seed: Option<u64>,
2398 tool_choice: Option<ToolChoice>,
2399 }
2400 let wire = Wire::deserialize(deserializer)?;
2401 let mut config = Self::default();
2402 if let Some(value) = wire.temperature {
2403 config = config
2404 .with_temperature(value)
2405 .map_err(serde::de::Error::custom)?;
2406 }
2407 if let Some(value) = wire.top_p {
2408 config = config.with_top_p(value).map_err(serde::de::Error::custom)?;
2409 }
2410 config.top_k = wire.top_k;
2411 config.max_output_tokens = wire.max_output_tokens;
2412 config = config
2413 .with_stop_sequences(wire.stop_sequences)
2414 .map_err(serde::de::Error::custom)?;
2415 if let Some(value) = wire.presence_penalty {
2416 config = config
2417 .with_presence_penalty(value)
2418 .map_err(serde::de::Error::custom)?;
2419 }
2420 if let Some(value) = wire.frequency_penalty {
2421 config = config
2422 .with_frequency_penalty(value)
2423 .map_err(serde::de::Error::custom)?;
2424 }
2425 config.seed = wire.seed;
2426 config.tool_choice = wire.tool_choice;
2427 Ok(config)
2428 }
2429}
2430
2431/// Validated model-bound generation request.
2432///
2433/// Requests borrow no provider resources and carry no model selector. The selected
2434/// Model owns exact identity and continuation type; use the same type across all
2435/// messages in this request.
2436///
2437/// # Examples
2438///
2439/// ```
2440/// use providers_kit::generation::{GenerateRequest, Message};
2441/// let request = GenerateRequest::<()>::new(vec![Message::user_text("Hello")?])?;
2442/// assert_eq!(request.messages().len(), 1);
2443///
2444/// # Ok::<(), providers_kit::KitError>(())
2445/// ```
2446///
2447#[derive(Clone, Debug, PartialEq, Serialize)]
2448#[serde(bound(serialize = ""), deny_unknown_fields)]
2449pub struct GenerateRequest<C = GenerationContinuation> {
2450 messages: Vec<Message<C>>,
2451 tools: Vec<ProviderToolSpec>,
2452 response_format: Option<ResponseFormat>,
2453 reasoning: Option<ReasoningConfig>,
2454 generation: GenerationConfig,
2455}
2456
2457impl<C> GenerateRequest<C> {
2458 /// Creates and validates a request containing at least one ordinary message.
2459 ///
2460 /// # Errors
2461 ///
2462 /// Returns an input error when messages are empty, contain only instructions, place instructions after ordinary messages, or violate a message role/content invariant.
2463 ///
2464 /// # Examples
2465 ///
2466 /// ```
2467 /// use providers_kit::generation::{GenerateRequest, Message};
2468 /// let request = GenerateRequest::<()>::new(vec![Message::user_text("Hello")?])?;
2469 /// assert_eq!(request.messages().len(), 1);
2470 ///
2471 /// # Ok::<(), providers_kit::KitError>(())
2472 /// ```
2473 ///
2474 pub fn new(messages: Vec<Message<C>>) -> Result<Self, KitError> {
2475 let request = Self {
2476 messages,
2477 tools: Vec::new(),
2478 response_format: None,
2479 reasoning: None,
2480 generation: GenerationConfig::default(),
2481 };
2482 request.validate()?;
2483 Ok(request)
2484 }
2485
2486 /// Replaces model-visible tool declarations.
2487 ///
2488 /// # Errors
2489 ///
2490 /// Returns an input error for duplicate function declarations or a tool selection inconsistent with the inventory. Hosted-tool and model compatibility are checked by the selected model.
2491 ///
2492 /// # Examples
2493 ///
2494 /// ```
2495 /// use providers_kit::generation::{ProviderFunctionTool, JsonSchema};
2496 /// let tool = ProviderFunctionTool::new("lookup", Some("Find a record".into()), JsonSchema::new(serde_json::json!({"type":"object"}))?)?;
2497 /// use providers_kit::generation::{GenerateRequest, Message, ProviderToolSpec};
2498 /// let request = GenerateRequest::<()>::new(vec![Message::user_text("Look up a record")?])?
2499 /// .with_tools(vec![ProviderToolSpec::Function(tool)])?;
2500 /// assert_eq!(request.tools().len(), 1);
2501 ///
2502 /// # Ok::<(), providers_kit::KitError>(())
2503 /// ```
2504 ///
2505 pub fn with_tools(mut self, tools: Vec<ProviderToolSpec>) -> Result<Self, KitError> {
2506 self.tools = tools;
2507 self.validate_tools()?;
2508 Ok(self)
2509 }
2510
2511 /// Replaces structured-output configuration.
2512 ///
2513 /// # Examples
2514 ///
2515 /// ```
2516 /// use providers_kit::generation::{GenerateRequest, Message};
2517 /// let request = GenerateRequest::<()>::new(vec![Message::user_text("Hello")?])?;
2518 /// use providers_kit::generation::ResponseFormat;
2519 /// let request = request.with_response_format(ResponseFormat::JsonObject);
2520 /// assert!(matches!(request.response_format(), Some(ResponseFormat::JsonObject)));
2521 ///
2522 /// # Ok::<(), providers_kit::KitError>(())
2523 /// ```
2524 ///
2525 #[must_use]
2526 pub fn with_response_format(mut self, value: ResponseFormat) -> Self {
2527 self.response_format = Some(value);
2528 self
2529 }
2530
2531 /// Replaces reasoning configuration.
2532 ///
2533 /// # Examples
2534 ///
2535 /// ```
2536 /// use providers_kit::generation::{GenerateRequest, Message};
2537 /// let request = GenerateRequest::<()>::new(vec![Message::user_text("Hello")?])?;
2538 /// use providers_kit::generation::{ReasoningConfig, ReasoningEffort};
2539 /// let request = request.with_reasoning(ReasoningConfig { effort: Some(ReasoningEffort::Low), ..Default::default() });
2540 /// assert_eq!(request.reasoning().unwrap().effort, Some(ReasoningEffort::Low));
2541 ///
2542 /// # Ok::<(), providers_kit::KitError>(())
2543 /// ```
2544 ///
2545 #[must_use]
2546 pub fn with_reasoning(mut self, value: ReasoningConfig) -> Self {
2547 self.reasoning = Some(value);
2548 self
2549 }
2550
2551 /// Replaces generation controls after validating tool references.
2552 ///
2553 /// # Errors
2554 ///
2555 /// Returns an input error when the generation/tool-choice settings do not match the declared tools, including an undeclared selected function or required choice without tools.
2556 ///
2557 /// # Examples
2558 ///
2559 /// ```
2560 /// use providers_kit::generation::{GenerateRequest, Message};
2561 /// let request = GenerateRequest::<()>::new(vec![Message::user_text("Hello")?])?;
2562 /// use providers_kit::generation::GenerationConfig;
2563 /// let request = request.with_generation(GenerationConfig::default().with_seed(42))?;
2564 /// assert_eq!(request.generation().seed(), Some(42));
2565 ///
2566 /// # Ok::<(), providers_kit::KitError>(())
2567 /// ```
2568 ///
2569 pub fn with_generation(mut self, value: GenerationConfig) -> Result<Self, KitError> {
2570 self.generation = value;
2571 self.validate_tools()?;
2572 Ok(self)
2573 }
2574
2575 #[must_use]
2576 /// Returns validated messages in request order.
2577 ///
2578 /// # Examples
2579 ///
2580 /// ```
2581 /// use providers_kit::generation::{GenerateRequest, Message};
2582 /// let request = GenerateRequest::<()>::new(vec![Message::user_text("Hello")?])?;
2583 /// assert_eq!(request.messages().len(), 1);
2584 ///
2585 /// # Ok::<(), providers_kit::KitError>(())
2586 /// ```
2587 ///
2588 pub fn messages(&self) -> &[Message<C>] {
2589 &self.messages
2590 }
2591
2592 #[must_use]
2593 /// Returns declared client and hosted tools.
2594 ///
2595 /// # Examples
2596 ///
2597 /// ```
2598 /// use providers_kit::generation::{GenerateRequest, Message};
2599 /// let request = GenerateRequest::<()>::new(vec![Message::user_text("Hello")?])?;
2600 /// assert!(request.tools().is_empty());
2601 ///
2602 /// # Ok::<(), providers_kit::KitError>(())
2603 /// ```
2604 ///
2605 pub fn tools(&self) -> &[ProviderToolSpec] {
2606 &self.tools
2607 }
2608
2609 #[must_use]
2610 /// Returns the requested structured-output format.
2611 ///
2612 /// # Examples
2613 ///
2614 /// ```
2615 /// use providers_kit::generation::{GenerateRequest, Message};
2616 /// let request = GenerateRequest::<()>::new(vec![Message::user_text("Hello")?])?;
2617 /// assert!(request.response_format().is_none());
2618 ///
2619 /// # Ok::<(), providers_kit::KitError>(())
2620 /// ```
2621 ///
2622 pub const fn response_format(&self) -> Option<&ResponseFormat> {
2623 self.response_format.as_ref()
2624 }
2625
2626 #[must_use]
2627 /// Returns the requested reasoning controls.
2628 ///
2629 /// # Examples
2630 ///
2631 /// ```
2632 /// use providers_kit::generation::{GenerateRequest, Message};
2633 /// let request = GenerateRequest::<()>::new(vec![Message::user_text("Hello")?])?;
2634 /// assert!(request.reasoning().is_none());
2635 ///
2636 /// # Ok::<(), providers_kit::KitError>(())
2637 /// ```
2638 ///
2639 pub const fn reasoning(&self) -> Option<&ReasoningConfig> {
2640 self.reasoning.as_ref()
2641 }
2642
2643 #[must_use]
2644 /// Returns sampling and tool-choice controls.
2645 ///
2646 /// # Examples
2647 ///
2648 /// ```
2649 /// use providers_kit::generation::{GenerateRequest, Message};
2650 /// let request = GenerateRequest::<()>::new(vec![Message::user_text("Hello")?])?;
2651 /// assert_eq!(request.generation().seed(), None);
2652 ///
2653 /// # Ok::<(), providers_kit::KitError>(())
2654 /// ```
2655 ///
2656 pub const fn generation(&self) -> &GenerationConfig {
2657 &self.generation
2658 }
2659
2660 /// Maps only opaque continuation values while preserving validated request data.
2661 ///
2662 /// This is value conversion for caller-owned concrete composition. It does not
2663 /// inspect a supplied continuation or authorize dropping its replay information.
2664 ///
2665 /// # Errors
2666 ///
2667 /// Returns the first callback error unchanged. Messages without continuation do not invoke the callback.
2668 ///
2669 /// # Examples
2670 ///
2671 /// ```
2672 /// use providers_kit::generation::{GenerateRequest, Message};
2673 /// let assistant = Message::assistant_text("continue")?.with_continuation(7_u32)?;
2674 /// let request = GenerateRequest::new(vec![assistant])?;
2675 /// let mapped = request.try_map_continuation(|value| Ok(value.to_string()))?;
2676 /// assert_eq!(mapped.messages()[0].continuation().map(String::as_str), Some("7"));
2677 ///
2678 /// # Ok::<(), providers_kit::KitError>(())
2679 /// ```
2680 ///
2681 pub fn try_map_continuation<D>(
2682 self,
2683 mut map: impl FnMut(C) -> Result<D, KitError>,
2684 ) -> Result<GenerateRequest<D>, KitError> {
2685 Ok(GenerateRequest {
2686 messages: self
2687 .messages
2688 .into_iter()
2689 .map(|message| message.try_map_continuation(&mut map))
2690 .collect::<Result<_, _>>()?,
2691 tools: self.tools,
2692 response_format: self.response_format,
2693 reasoning: self.reasoning,
2694 generation: self.generation,
2695 })
2696 }
2697
2698 fn validate(&self) -> Result<(), KitError> {
2699 if self.messages.is_empty() {
2700 return Err(KitError::invalid_input(
2701 ValidationViolation::Empty {
2702 field: "generation.messages",
2703 },
2704 "generation request messages must not be empty",
2705 ));
2706 }
2707 let mut saw_ordinary = false;
2708 for message in &self.messages {
2709 match message.role() {
2710 crate::generation::Role::System | crate::generation::Role::Developer
2711 if saw_ordinary =>
2712 {
2713 return Err(KitError::invalid_input(
2714 ValidationViolation::InvalidValue {
2715 field: "generation.messages",
2716 rule: "instructions must form a leading prefix",
2717 },
2718 "system and developer instructions must form one leading prefix",
2719 ));
2720 }
2721 crate::generation::Role::System | crate::generation::Role::Developer => {}
2722 _ => saw_ordinary = true,
2723 }
2724 message.validate_for_request()?;
2725 }
2726 if !saw_ordinary {
2727 return Err(KitError::invalid_input(
2728 ValidationViolation::InvalidValue {
2729 field: "generation.messages",
2730 rule: "requires a non-instruction message",
2731 },
2732 "generation request requires at least one non-instruction message",
2733 ));
2734 }
2735 self.validate_tools()
2736 }
2737
2738 fn validate_tools(&self) -> Result<(), KitError> {
2739 let mut names = BTreeSet::new();
2740 for tool in &self.tools {
2741 if let ProviderToolSpec::Function(function) = tool
2742 && !names.insert(function.name())
2743 {
2744 return Err(KitError::invalid_input(
2745 ValidationViolation::Duplicate {
2746 field: "tools.function.name",
2747 },
2748 format!("duplicate function tool name `{}`", function.name()),
2749 ));
2750 }
2751 }
2752 selected_tools_for_choice(&self.tools, self.generation.tool_choice())?;
2753 Ok(())
2754 }
2755
2756 #[must_use]
2757 pub(crate) fn as_view(&self) -> GenerateRequestView<'_, C> {
2758 GenerateRequestView {
2759 messages: &self.messages,
2760 tools: &self.tools,
2761 response_format: self.response_format.as_ref(),
2762 reasoning: self.reasoning.as_ref(),
2763 generation: &self.generation,
2764 }
2765 }
2766}
2767
2768impl<'de, C> Deserialize<'de> for GenerateRequest<C> {
2769 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2770 where
2771 D: Deserializer<'de>,
2772 {
2773 #[derive(Deserialize)]
2774 #[serde(bound(deserialize = ""), deny_unknown_fields)]
2775 struct Wire<C> {
2776 messages: Vec<Message<C>>,
2777 #[serde(default)]
2778 tools: Vec<ProviderToolSpec>,
2779 response_format: Option<ResponseFormat>,
2780 reasoning: Option<ReasoningConfig>,
2781 #[serde(default)]
2782 generation: GenerationConfig,
2783 }
2784 let wire = Wire::deserialize(deserializer)?;
2785 let mut request = Self::new(wire.messages).map_err(serde::de::Error::custom)?;
2786 request.tools = wire.tools;
2787 request.response_format = wire.response_format;
2788 request.reasoning = wire.reasoning;
2789 request.generation = wire.generation;
2790 request.validate().map_err(serde::de::Error::custom)?;
2791 Ok(request)
2792 }
2793}
2794
2795/// Portable completion reason.
2796#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
2797#[serde(rename_all = "snake_case")]
2798pub enum FinishReason {
2799 /// The model reached a natural stop.
2800 Stop,
2801 /// The configured or provider output limit was reached.
2802 Length,
2803 /// The model stopped to request a function call.
2804 ToolCall,
2805 /// Provider safety filtering stopped output.
2806 ContentFilter,
2807 /// A provider-authored terminal error stopped output.
2808 Error,
2809 /// Generation paused for provider-managed continuation.
2810 Paused,
2811}
2812
2813/// Citation attached to generated output.
2814#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
2815#[serde(tag = "type", rename_all = "snake_case", deny_unknown_fields)]
2816pub enum Citation {
2817 /// Citation of an external URL.
2818 Url {
2819 /// Cited URL.
2820 url: String,
2821 /// Provider-reported title.
2822 title: Option<String>,
2823 /// Provider-reported excerpt.
2824 snippet: Option<String>,
2825 /// Inclusive output start index, when reported.
2826 start_index: Option<usize>,
2827 /// Exclusive output end index, when reported.
2828 end_index: Option<usize>,
2829 },
2830 /// Citation of a provider-managed or remote file.
2831 File {
2832 /// Exact cited file reference.
2833 reference: FileReference,
2834 /// Provider-reported title.
2835 title: Option<String>,
2836 /// Provider-reported excerpt.
2837 snippet: Option<String>,
2838 /// Inclusive output start index, when reported.
2839 start_index: Option<usize>,
2840 /// Exclusive output end index, when reported.
2841 end_index: Option<usize>,
2842 },
2843}
2844
2845/// Final normalized generation result.
2846///
2847/// A returned assistant message is the replay owner. Preserve it unchanged through
2848/// function continuation; projections of text, hosted results, or artifacts cannot
2849/// reconstruct native state. Serialization fails while continuation is present.
2850///
2851/// # Examples
2852///
2853/// ```
2854/// use providers_kit::{ModelId, generation::{GenerateResponse, Message, FinishReason, Usage}};
2855/// let response = GenerateResponse { model: ModelId::new("exact-model")?, message: Message::assistant_text("done")?.with_continuation(7_u32)?, finish_reason: Some(FinishReason::Stop), citations: Vec::new(), artifacts: Vec::new(), hosted_tool_outputs: Vec::new(), usage: Usage::default() };
2856/// let mapped = response.try_map_continuation(|value| Ok(value.to_string()))?;
2857/// assert_eq!(mapped.message.continuation().map(String::as_str), Some("7"));
2858/// assert_eq!(mapped.model.as_str(), "exact-model");
2859///
2860/// # Ok::<(), providers_kit::KitError>(())
2861/// ```
2862///
2863#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
2864#[serde(bound(serialize = "", deserialize = ""))]
2865pub struct GenerateResponse<C = GenerationContinuation> {
2866 /// Exact model that produced the response.
2867 pub model: ModelId,
2868 /// Returned assistant message and opaque continuation state.
2869 pub message: Message<C>,
2870 /// Provider-reported completion reason.
2871 pub finish_reason: Option<FinishReason>,
2872 /// Citations attached to generated output.
2873 pub citations: Vec<Citation>,
2874 /// Generated artifacts.
2875 pub artifacts: Vec<Artifact>,
2876 /// Normalized provider-hosted tool outputs.
2877 pub hosted_tool_outputs: Vec<HostedToolOutput>,
2878 /// Provider-reported token accounting.
2879 pub usage: Usage,
2880}
2881
2882impl<C> GenerateResponse<C> {
2883 /// Maps only opaque continuation values.
2884 ///
2885 /// # Errors
2886 ///
2887 /// Returns the callback error unchanged if converting the response continuation fails; other response fields retain their exact values.
2888 ///
2889 /// # Examples
2890 ///
2891 /// ```
2892 /// use providers_kit::{ModelId, generation::{GenerateResponse, Message, FinishReason, Usage}};
2893 /// let response = GenerateResponse { model: ModelId::new("exact-model")?, message: Message::assistant_text("done")?.with_continuation(7_u32)?, finish_reason: Some(FinishReason::Stop), citations: Vec::new(), artifacts: Vec::new(), hosted_tool_outputs: Vec::new(), usage: Usage::default() };
2894 /// let mapped = response.try_map_continuation(|value| Ok(value.to_string()))?;
2895 /// assert_eq!(mapped.message.continuation().map(String::as_str), Some("7"));
2896 /// assert_eq!(mapped.model.as_str(), "exact-model");
2897 ///
2898 /// # Ok::<(), providers_kit::KitError>(())
2899 /// ```
2900 ///
2901 pub fn try_map_continuation<D>(
2902 self,
2903 mut map: impl FnMut(C) -> Result<D, KitError>,
2904 ) -> Result<GenerateResponse<D>, KitError> {
2905 Ok(GenerateResponse {
2906 model: self.model,
2907 message: self.message.try_map_continuation(&mut map)?,
2908 finish_reason: self.finish_reason,
2909 citations: self.citations,
2910 artifacts: self.artifacts,
2911 hosted_tool_outputs: self.hosted_tool_outputs,
2912 usage: self.usage,
2913 })
2914 }
2915}
2916
2917/// Incremental generation event.
2918#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
2919#[serde(
2920 bound(serialize = "", deserialize = ""),
2921 tag = "event",
2922 rename_all = "snake_case"
2923)]
2924pub enum GenerateEvent<C = GenerationContinuation> {
2925 /// Stream metadata is ready.
2926 Started {
2927 /// Exact model producing the stream.
2928 model: ModelId,
2929 },
2930 /// Incremental visible text.
2931 ContentDelta {
2932 /// Text appended by this event.
2933 delta: String,
2934 },
2935 /// Incremental provider-authored thinking summary.
2936 ThinkingDelta {
2937 /// Thinking text appended by this event.
2938 delta: String,
2939 },
2940 /// Incremental provider-hosted tool activity.
2941 HostedTool(Box<HostedToolOutput>),
2942 /// Updated provider usage accounting.
2943 Usage(Usage),
2944 /// Terminal normalized response.
2945 Completed(Box<GenerateResponse<C>>),
2946}
2947
2948impl<C> GenerateEvent<C> {
2949 /// Maps continuation state carried by a completion event.
2950 ///
2951 /// # Errors
2952 ///
2953 /// Returns the callback error for a completion continuation. Other events retain their data without invoking the callback.
2954 ///
2955 /// # Examples
2956 ///
2957 /// ```
2958 /// use providers_kit::{ModelId, generation::{GenerateResponse, Message, FinishReason, Usage}};
2959 /// let response = GenerateResponse { model: ModelId::new("exact-model")?, message: Message::assistant_text("done")?.with_continuation(7_u32)?, finish_reason: Some(FinishReason::Stop), citations: Vec::new(), artifacts: Vec::new(), hosted_tool_outputs: Vec::new(), usage: Usage::default() };
2960 /// use providers_kit::generation::GenerateEvent;
2961 /// let event = GenerateEvent::Completed(Box::new(response));
2962 /// let mapped = event.try_map_continuation(|value| Ok(value.to_string()))?;
2963 /// assert!(matches!(mapped, GenerateEvent::Completed(response) if response.message.continuation().map(String::as_str) == Some("7")));
2964 ///
2965 /// # Ok::<(), providers_kit::KitError>(())
2966 /// ```
2967 ///
2968 pub fn try_map_continuation<D>(
2969 self,
2970 map: impl FnMut(C) -> Result<D, KitError>,
2971 ) -> Result<GenerateEvent<D>, KitError> {
2972 Ok(match self {
2973 Self::Started { model } => GenerateEvent::Started { model },
2974 Self::ContentDelta { delta } => GenerateEvent::ContentDelta { delta },
2975 Self::ThinkingDelta { delta } => GenerateEvent::ThinkingDelta { delta },
2976 Self::HostedTool(output) => GenerateEvent::HostedTool(output),
2977 Self::Usage(usage) => GenerateEvent::Usage(usage),
2978 Self::Completed(response) => {
2979 GenerateEvent::Completed(Box::new(response.try_map_continuation(map)?))
2980 }
2981 })
2982 }
2983}
2984
2985/// Value-erased stream of normalized generation events.
2986///
2987/// This value adapter does not enforce a backend protocol. Caller Models own
2988/// bounded production, terminal ordering, admission release and cancellation.
2989/// Supplied model streams produce no events after Completed and own no forwarding task.
2990///
2991/// # Examples
2992///
2993/// ```
2994///
2995/// # #[tokio::main(flavor = "current_thread")]
2996/// # async fn main() -> Result<(), providers_kit::KitError> {
2997/// use providers_kit::generation::{GenerateEvent, GenerateEventStream};
2998/// use futures_util::{stream, StreamExt};
2999/// let source = stream::iter([Ok(GenerateEvent::<()>::ContentDelta { delta: "hello".into() })]);
3000/// let mut stream = GenerateEventStream::new(source);
3001/// assert!(matches!(stream.next().await.transpose()?, Some(GenerateEvent::ContentDelta { delta }) if delta == "hello"));
3002/// assert!(stream.next().await.is_none());
3003///
3004/// # Ok(())
3005/// # }
3006/// ```
3007///
3008pub struct GenerateEventStream<C = GenerationContinuation> {
3009 inner: Pin<Box<dyn Stream<Item = Result<GenerateEvent<C>, KitError>> + Send + 'static>>,
3010}
3011
3012impl<C: 'static> GenerateEventStream<C> {
3013 /// Wraps a concrete event stream without erasing model behavior.
3014 ///
3015 /// This value adapter does not enforce a backend protocol. Caller Models own
3016 /// bounded production, terminal ordering, admission release and cancellation.
3017 /// Supplied model streams produce no events after Completed and own no forwarding task.
3018 ///
3019 /// # Examples
3020 ///
3021 /// ```
3022 ///
3023 /// # #[tokio::main(flavor = "current_thread")]
3024 /// # async fn main() -> Result<(), providers_kit::KitError> {
3025 /// use providers_kit::generation::{GenerateEvent, GenerateEventStream};
3026 /// use futures_util::{stream, StreamExt};
3027 /// let source = stream::iter([Ok(GenerateEvent::<()>::ContentDelta { delta: "hello".into() })]);
3028 /// let mut stream = GenerateEventStream::new(source);
3029 /// assert!(matches!(stream.next().await.transpose()?, Some(GenerateEvent::ContentDelta { delta }) if delta == "hello"));
3030 /// assert!(stream.next().await.is_none());
3031 ///
3032 /// # Ok(())
3033 /// # }
3034 /// ```
3035 ///
3036 pub fn new<S>(stream: S) -> Self
3037 where
3038 S: Stream<Item = Result<GenerateEvent<C>, KitError>> + Send + 'static,
3039 {
3040 Self {
3041 inner: Box::pin(stream),
3042 }
3043 }
3044}
3045
3046impl<C> Stream for GenerateEventStream<C> {
3047 type Item = Result<GenerateEvent<C>, KitError>;
3048
3049 fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
3050 self.inner.as_mut().poll_next(cx)
3051 }
3052}
3053
3054/// Token accounting reported by a provider response.
3055///
3056/// Fields stay `None` when the provider did not report that exact value.
3057/// The Kit does not derive aggregate totals or request counts from other
3058/// fields.
3059///
3060/// # Examples
3061/// ```rust
3062/// let usage = providers_kit::generation::Usage {
3063/// input_tokens: Some(12),
3064/// output_tokens: Some(34),
3065/// reasoning_tokens: None,
3066/// cached_input_tokens: Some(5),
3067/// total_tokens: Some(46),
3068/// };
3069/// assert_eq!(usage.cached_input_tokens, Some(5));
3070/// assert_eq!(usage.total_tokens, Some(46));
3071/// ```
3072#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
3073pub struct Usage {
3074 /// Input tokens billed or counted for the request.
3075 pub input_tokens: Option<u64>,
3076 /// Output tokens generated by the provider.
3077 pub output_tokens: Option<u64>,
3078 /// Reasoning or thinking tokens reported separately by the provider.
3079 pub reasoning_tokens: Option<u64>,
3080 /// Input tokens served from prompt cache rather than recomputed.
3081 pub cached_input_tokens: Option<u64>,
3082 /// Total token count when the provider reports an aggregate value.
3083 pub total_tokens: Option<u64>,
3084}
3085
3086impl Usage {
3087 pub(crate) fn reported_tokens(input_tokens: Option<u64>, output_tokens: Option<u64>) -> Self {
3088 Self {
3089 input_tokens,
3090 output_tokens,
3091 ..Self::default()
3092 }
3093 }
3094
3095 pub(crate) fn with_reported_total(mut self, total_tokens: Option<u64>) -> Self {
3096 self.total_tokens = total_tokens;
3097 self
3098 }
3099
3100 pub(crate) fn with_cached_input(mut self, cached_input_tokens: Option<u64>) -> Self {
3101 self.cached_input_tokens = cached_input_tokens;
3102 self
3103 }
3104
3105 pub(crate) fn with_reasoning(mut self, reasoning_tokens: Option<u64>) -> Self {
3106 self.reasoning_tokens = reasoning_tokens;
3107 self
3108 }
3109}