use std::sync::Arc;
use async_stream::try_stream;
use futures::stream::{BoxStream, StreamExt};
use crate::error::Error;
use crate::provider::Provider;
use crate::schema::ObjectSchema;
use crate::value_objects::{Message, UserMessage};
use super::repair::RepairStrategy;
use super::response::{StructuredResponse, StructuredStreamEvent};
#[derive(Clone)]
pub struct StructuredRequest {
pub model: String,
pub system_prompts: Vec<String>,
pub messages: Vec<Message>,
pub max_tokens: Option<u32>,
pub temperature: Option<f32>,
pub top_p: Option<f32>,
pub schema: ObjectSchema,
pub cache_system_prompt: bool,
pub reasoning_effort: Option<String>,
pub seed: Option<u64>,
pub repair: Option<Arc<dyn RepairStrategy>>,
pub provider_options: serde_json::Value,
}
impl StructuredRequest {
pub fn new(model: impl Into<String>, schema: ObjectSchema) -> Self {
Self {
model: model.into(),
system_prompts: Vec::new(),
messages: Vec::new(),
max_tokens: None,
temperature: None,
top_p: None,
schema,
cache_system_prompt: false,
reasoning_effort: None,
seed: None,
repair: None,
provider_options: serde_json::Value::Null,
}
}
}
pub struct PendingStructuredRequest {
provider: Arc<dyn Provider>,
request: StructuredRequest,
}
impl PendingStructuredRequest {
pub fn new(
provider: Arc<dyn Provider>,
model: impl Into<String>,
schema: ObjectSchema,
) -> Self {
Self {
provider,
request: StructuredRequest::new(model, schema),
}
}
pub fn with_system_prompt(mut self, prompt: impl Into<String>) -> Self {
self.request.system_prompts.push(prompt.into());
self
}
pub fn with_prompt(mut self, prompt: impl Into<String>) -> Self {
self.request
.messages
.push(Message::User(UserMessage::text(prompt)));
self
}
pub fn with_message(mut self, message: Message) -> Self {
self.request.messages.push(message);
self
}
pub fn with_messages(mut self, messages: impl IntoIterator<Item = Message>) -> Self {
self.request.messages.extend(messages);
self
}
pub fn with_max_tokens(mut self, max_tokens: u32) -> Self {
self.request.max_tokens = Some(max_tokens);
self
}
pub fn with_temperature(mut self, temperature: f32) -> Self {
self.request.temperature = Some(temperature);
self
}
pub fn with_top_p(mut self, top_p: f32) -> Self {
self.request.top_p = Some(top_p);
self
}
pub fn with_prompt_caching(mut self) -> Self {
self.request.cache_system_prompt = true;
self
}
pub fn with_reasoning_effort(mut self, effort: impl Into<String>) -> Self {
self.request.reasoning_effort = Some(effort.into());
self
}
pub fn with_seed(mut self, seed: u64) -> Self {
self.request.seed = Some(seed);
self
}
pub fn with_repair(mut self, repair: impl RepairStrategy + 'static) -> Self {
self.request.repair = Some(Arc::new(repair));
self
}
pub fn to_request(&self) -> StructuredRequest {
self.request.clone()
}
pub async fn generate(self) -> Result<StructuredResponse, Error> {
let repair = self.request.repair.clone();
let error = match self.provider.structured(self.request).await {
Ok(response) => return Ok(response),
Err(error) => error,
};
let Some(repair) = repair else {
return Err(error);
};
let Error::StructuredDecode { context, .. } = &error else {
return Err(error);
};
match repair.repair(&context.raw, &error).await {
Some(data) => Ok(StructuredResponse {
data,
finish_reason: context.finish_reason,
usage: context.usage,
meta: context.meta.clone(),
}),
None => Err(error),
}
}
pub fn stream(self) -> BoxStream<'static, Result<StructuredStreamEvent, Error>> {
let PendingStructuredRequest { provider, request } = self;
let stream = try_stream! {
let mut inner = provider.stream_structured_once(&request).await?;
while let Some(event) = inner.next().await {
yield event?;
}
};
stream.boxed()
}
}