#[ allow( clippy::missing_inline_in_public_items ) ]
mod private
{
use crate::{
error::{ AnthropicError, AnthropicResult },
client::{ CreateMessageRequest, Client },
messages::Message,
};
use serde::{ Serialize, Deserialize };
use core::time::Duration;
#[ derive( Debug, Clone, Serialize ) ]
pub struct ContentGenerationRequest
{
pub model : String,
pub max_tokens : u32,
pub messages : Vec< Message >,
#[ serde( skip_serializing_if = "Option::is_none" ) ]
pub system : Option< String >,
#[ serde( skip_serializing_if = "Option::is_none" ) ]
pub temperature : Option< f32 >,
#[ serde( skip_serializing_if = "Option::is_none" ) ]
pub settings : Option< GenerationSettings >,
}
#[ derive( Debug, Clone, Serialize, Deserialize, Default ) ]
pub struct GenerationSettings
{
#[ serde( skip_serializing_if = "Option::is_none" ) ]
pub stop_sequences : Option< Vec< String > >,
#[ serde( skip_serializing_if = "Option::is_none" ) ]
pub top_p : Option< f32 >,
#[ serde( skip_serializing_if = "Option::is_none" ) ]
pub top_k : Option< u32 >,
#[ serde( skip_serializing_if = "Option::is_none" ) ]
pub presence_penalty : Option< f32 >,
#[ serde( skip_serializing_if = "Option::is_none" ) ]
pub frequency_penalty : Option< f32 >,
}
#[ derive( Debug, Default ) ]
pub struct ContentGenerationRequestBuilder
{
model : Option< String >,
max_tokens : Option< u32 >,
messages : Vec< Message >,
system : Option< String >,
temperature : Option< f32 >,
settings : Option< GenerationSettings >,
}
impl ContentGenerationRequestBuilder
{
#[ must_use ]
pub fn new() -> Self
{
Self::default()
}
#[ must_use ]
pub fn model< S : Into< String > >( mut self, model : S ) -> Self
{
self.model = Some( model.into() );
self
}
#[ must_use ]
pub fn max_tokens( mut self, max_tokens : u32 ) -> Self
{
self.max_tokens = Some( max_tokens );
self
}
#[ must_use ]
pub fn message( mut self, message : Message ) -> Self
{
self.messages.push( message );
self
}
#[ must_use ]
pub fn messages( mut self, messages : Vec< Message > ) -> Self
{
self.messages.extend( messages );
self
}
#[ must_use ]
pub fn system< S : Into< String > >( mut self, system : S ) -> Self
{
self.system = Some( system.into() );
self
}
#[ must_use ]
pub fn temperature( mut self, temperature : f32 ) -> Self
{
self.temperature = Some( temperature );
self
}
#[ must_use ]
pub fn settings( mut self, settings : GenerationSettings ) -> Self
{
self.settings = Some( settings );
self
}
pub fn build( self ) -> AnthropicResult< ContentGenerationRequest >
{
let model = self.model.ok_or_else( ||
AnthropicError::InvalidArgument( "Model is required".to_string() )
)?;
if model.trim().is_empty()
{
return Err( AnthropicError::InvalidArgument( "Model cannot be empty".to_string() ) );
}
let max_tokens = self.max_tokens.ok_or_else( ||
AnthropicError::InvalidArgument( "Max tokens is required".to_string() )
)?;
if self.messages.is_empty()
{
return Err( AnthropicError::InvalidArgument( "At least one message is required".to_string() ) );
}
Ok( ContentGenerationRequest
{
model,
max_tokens,
messages : self.messages,
system : self.system,
temperature : self.temperature,
settings : self.settings,
})
}
}
impl ContentGenerationRequest
{
#[ must_use ]
pub fn builder() -> ContentGenerationRequestBuilder
{
ContentGenerationRequestBuilder::new()
}
#[ must_use ]
pub fn to_message_request( &self ) -> CreateMessageRequest
{
CreateMessageRequest
{
model : self.model.clone(),
max_tokens : self.max_tokens,
messages : self.messages.clone(),
system : self.system.as_ref().map( | s | vec![ crate::SystemContent::text( s.as_str() ) ] ),
temperature : self.temperature,
stream : None,
tools : None,
tool_choice : None,
}
}
pub fn validate( &self ) -> AnthropicResult< () >
{
if self.model.trim().is_empty()
{
return Err( AnthropicError::InvalidArgument( "Model cannot be empty".to_string() ) );
}
if self.max_tokens == 0 || self.max_tokens > 200_000
{
return Err( AnthropicError::InvalidArgument(
"Max tokens must be between 1 and 200,000".to_string()
));
}
if let Some( temp ) = self.temperature
{
if !(0.0..=1.0).contains( &temp )
{
return Err( AnthropicError::InvalidArgument(
"Temperature must be between 0.0 and 1.0".to_string()
));
}
}
if self.messages.is_empty()
{
return Err( AnthropicError::InvalidArgument( "At least one message is required".to_string() ) );
}
Ok( () )
}
}
#[ derive( Debug, Clone, Deserialize ) ]
pub struct ContentGenerationResponse
{
pub content : String,
pub model : String,
pub usage : crate::client::Usage,
pub stop_reason : Option< String >,
pub metadata : GenerationMetadata,
}
#[ derive( Debug, Clone, Deserialize, Default ) ]
pub struct GenerationMetadata
{
pub generation_time_ms : Option< u64 >,
pub model_version : Option< String >,
pub safety_assessment : Option< String >,
}
#[ derive( Debug, Clone ) ]
pub struct ContentGenerator
{
client : Client,
default_model : String,
default_max_tokens : u32,
timeout : Duration,
}
impl ContentGenerator
{
#[ must_use ]
pub fn new( client : Client ) -> Self
{
Self
{
client,
default_model : "claude-sonnet-4-5-20250929".to_string(),
default_max_tokens : 1000,
timeout : Duration::from_secs( 60 ),
}
}
#[ must_use ]
pub fn with_default_model< S : Into< String > >( mut self, model : S ) -> Self
{
self.default_model = model.into();
self
}
#[ must_use ]
pub fn with_default_max_tokens( mut self, max_tokens : u32 ) -> Self
{
self.default_max_tokens = max_tokens;
self
}
#[ must_use ]
pub fn with_timeout( mut self, timeout : Duration ) -> Self
{
self.timeout = timeout;
self
}
pub async fn generate( &self, request : ContentGenerationRequest ) -> AnthropicResult< ContentGenerationResponse >
{
request.validate()?;
let message_request = request.to_message_request();
let start_time = std::time::Instant::now();
let response = self.client.create_message( message_request ).await?;
let generation_time = start_time.elapsed();
let content = response.text().unwrap_or( "" ).to_string();
Ok( ContentGenerationResponse
{
content,
model : response.model,
usage : response.usage,
stop_reason : response.stop_reason,
metadata : GenerationMetadata
{
generation_time_ms : Some( generation_time.as_millis().try_into().unwrap_or( u64::MAX ) ),
model_version : None,
safety_assessment : None,
},
})
}
pub async fn generate_text( &self, prompt : &str ) -> AnthropicResult< String >
{
let request = ContentGenerationRequest::builder()
.model( &self.default_model )
.max_tokens( self.default_max_tokens )
.message( Message::user( prompt.to_string() ) )
.build()?;
let response = self.generate( request ).await?;
Ok( response.content )
}
pub async fn generate_with_system( &self, system : &str, prompt : &str ) -> AnthropicResult< String >
{
let request = ContentGenerationRequest::builder()
.model( &self.default_model )
.max_tokens( self.default_max_tokens )
.system( system )
.message( Message::user( prompt.to_string() ) )
.build()?;
let response = self.generate( request ).await?;
Ok( response.content )
}
}
impl Client
{
#[ must_use ]
pub fn content_generator( &self ) -> ContentGenerator
{
ContentGenerator::new( self.clone() )
}
pub async fn generate_content( &self, request : ContentGenerationRequest ) -> AnthropicResult< ContentGenerationResponse >
{
let generator = self.content_generator();
generator.generate( request ).await
}
}
}
#[ cfg( feature = "content-generation" ) ]
crate::mod_interface!
{
exposed use
{
ContentGenerationRequest,
ContentGenerationRequestBuilder,
ContentGenerationResponse,
GenerationSettings,
GenerationMetadata,
ContentGenerator,
};
}
#[ cfg( not( feature = "content-generation" ) ) ]
crate::mod_interface!
{
}