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