804 lines · 25.2 KB · Rust
RawDownload
use std::mem; use anyhow::{Result, anyhow, bail};use futures::{AsyncBufReadExt, AsyncReadExt, StreamExt, io::BufReader, stream::BoxStream};use http_client::{    AsyncBody, CustomHeaders, HttpClient, Method, Request as HttpRequest, RequestBuilderExt,};pub use language_model_core::ModelMode as GoogleModelMode;use serde::{Deserialize, Deserializer, Serialize, Serializer};pub mod completion; pub const API_URL: &str = "https://generativelanguage.googleapis.com"; pub async fn stream_generate_content(    client: &dyn HttpClient,    api_url: &str,    api_key: &str,    mut request: GenerateContentRequest,    extra_headers: &CustomHeaders,) -> Result<BoxStream<'static, Result<GenerateContentResponse>>> {    let api_key = api_key.trim();    validate_generate_content_request(&request)?;     // The `model` field is emptied as it is provided as a path parameter.    let model_id = mem::take(&mut request.model.model_id);     let uri =        format!("{api_url}/v1beta/models/{model_id}:streamGenerateContent?alt=sse&key={api_key}",);     let request = HttpRequest::builder()        .method(Method::POST)        .uri(uri)        .header("Content-Type", "application/json")        .extra_headers(extra_headers)        .body(AsyncBody::from(serde_json::to_string(&request)?))?;    let mut response = client.send(request).await?;    if response.status().is_success() {        let reader = BufReader::new(response.into_body());        Ok(reader            .lines()            .filter_map(|line| async move {                match line {                    Ok(line) => {                        if let Some(line) = line.strip_prefix("data: ") {                            // Some proxies and gateways append `data: [DONE]` as a                            // stream-termination sentinel (an OpenAI convention).                            // Google's streaming spec does not include this sentinel,                            // but we tolerate it here for the same reason as the                            // anthropic crate: proxies that inject it would otherwise                            // cause a spurious JSON deserialization error.                            if line.trim() == "[DONE]" {                                return None;                            }                            match serde_json::from_str(line) {                                Ok(response) => Some(Ok(response)),                                Err(error) => Some(Err(anyhow!(format!(                                    "Error parsing JSON: {error:?}\n{line:?}"                                )))),                            }                        } else {                            None                        }                    }                    Err(error) => Some(Err(anyhow!(error))),                }            })            .boxed())    } else {        let mut text = String::new();        response.body_mut().read_to_string(&mut text).await?;        Err(anyhow!(            "error during streamGenerateContent, status code: {:?}, body: {}",            response.status(),            text        ))    }} pub fn validate_generate_content_request(request: &GenerateContentRequest) -> Result<()> {    if request.model.is_empty() {        bail!("Model must be specified");    }     if request.contents.is_empty() {        bail!("Request must contain at least one content item");    }     if let Some(user_content) = request        .contents        .iter()        .find(|content| content.role == Role::User)        && user_content.parts.is_empty()    {        bail!("User content must contain at least one part");    }     Ok(())} #[derive(Debug, Serialize, Deserialize)]pub enum Task {    #[serde(rename = "generateContent")]    GenerateContent,    #[serde(rename = "streamGenerateContent")]    StreamGenerateContent,    #[serde(rename = "embedContent")]    EmbedContent,    #[serde(rename = "batchEmbedContents")]    BatchEmbedContents,} #[derive(Debug, Serialize, Deserialize)]#[serde(rename_all = "camelCase")]pub struct GenerateContentRequest {    #[serde(default, skip_serializing_if = "ModelName::is_empty")]    pub model: ModelName,    pub contents: Vec<Content>,    #[serde(skip_serializing_if = "Option::is_none")]    pub system_instruction: Option<SystemInstruction>,    #[serde(skip_serializing_if = "Option::is_none")]    pub generation_config: Option<GenerationConfig>,    #[serde(skip_serializing_if = "Option::is_none")]    pub safety_settings: Option<Vec<SafetySetting>>,    #[serde(skip_serializing_if = "Option::is_none")]    pub tools: Option<Vec<Tool>>,    #[serde(skip_serializing_if = "Option::is_none")]    pub tool_config: Option<ToolConfig>,} #[derive(Debug, Serialize, Deserialize)]#[serde(rename_all = "camelCase")]pub struct GenerateContentResponse {    #[serde(skip_serializing_if = "Option::is_none")]    pub candidates: Option<Vec<GenerateContentCandidate>>,    #[serde(skip_serializing_if = "Option::is_none")]    pub prompt_feedback: Option<PromptFeedback>,    #[serde(skip_serializing_if = "Option::is_none")]    pub usage_metadata: Option<UsageMetadata>,} #[derive(Debug, Serialize, Deserialize)]#[serde(rename_all = "camelCase")]pub struct GenerateContentCandidate {    #[serde(skip_serializing_if = "Option::is_none")]    pub index: Option<usize>,    pub content: Content,    #[serde(skip_serializing_if = "Option::is_none")]    pub finish_reason: Option<String>,    #[serde(skip_serializing_if = "Option::is_none")]    pub finish_message: Option<String>,    #[serde(skip_serializing_if = "Option::is_none")]    pub safety_ratings: Option<Vec<SafetyRating>>,    #[serde(skip_serializing_if = "Option::is_none")]    pub citation_metadata: Option<CitationMetadata>,} #[derive(Debug, Serialize, Deserialize)]#[serde(rename_all = "camelCase")]pub struct Content {    #[serde(default)]    pub parts: Vec<Part>,    pub role: Role,} #[derive(Debug, Serialize, Deserialize)]#[serde(rename_all = "camelCase")]pub struct SystemInstruction {    pub parts: Vec<Part>,} #[derive(Debug, PartialEq, Deserialize, Serialize)]#[serde(rename_all = "camelCase")]pub enum Role {    User,    Model,} #[derive(Debug, Serialize, Deserialize)]#[serde(untagged)]pub enum Part {    FunctionCallPart(FunctionCallPart),    FunctionResponsePart(FunctionResponsePart),    InlineDataPart(InlineDataPart),    TextPart(TextPart),} fn is_false(value: &bool) -> bool {    !*value} #[derive(Debug, Serialize, Deserialize)]#[serde(rename_all = "camelCase")]pub struct TextPart {    pub text: String,    #[serde(default, skip_serializing_if = "is_false")]    pub thought: bool,    #[serde(skip_serializing_if = "Option::is_none")]    pub thought_signature: Option<String>,} #[derive(Debug, Serialize, Deserialize)]#[serde(rename_all = "camelCase")]pub struct InlineDataPart {    pub inline_data: GenerativeContentBlob,} #[derive(Debug, Serialize, Deserialize)]#[serde(rename_all = "camelCase")]pub struct GenerativeContentBlob {    pub mime_type: String,    pub data: String,} #[derive(Debug, Serialize, Deserialize)]#[serde(rename_all = "camelCase")]pub struct FunctionCallPart {    pub function_call: FunctionCall,    /// Thought signature returned by the model for function calls.    /// Only present on the first function call in parallel call scenarios.    #[serde(skip_serializing_if = "Option::is_none")]    pub thought_signature: Option<String>,} #[derive(Debug, Serialize, Deserialize)]#[serde(rename_all = "camelCase")]pub struct FunctionResponsePart {    pub function_response: FunctionResponse,} #[derive(Debug, Serialize, Deserialize)]#[serde(rename_all = "camelCase")]pub struct CitationSource {    #[serde(skip_serializing_if = "Option::is_none")]    pub start_index: Option<usize>,    #[serde(skip_serializing_if = "Option::is_none")]    pub end_index: Option<usize>,    #[serde(skip_serializing_if = "Option::is_none")]    pub uri: Option<String>,    #[serde(skip_serializing_if = "Option::is_none")]    pub license: Option<String>,} #[derive(Debug, Serialize, Deserialize)]#[serde(rename_all = "camelCase")]pub struct CitationMetadata {    pub citation_sources: Vec<CitationSource>,} #[derive(Debug, Serialize, Deserialize)]#[serde(rename_all = "camelCase")]pub struct PromptFeedback {    #[serde(skip_serializing_if = "Option::is_none")]    pub block_reason: Option<String>,    pub safety_ratings: Option<Vec<SafetyRating>>,    #[serde(skip_serializing_if = "Option::is_none")]    pub block_reason_message: Option<String>,} #[derive(Debug, Serialize, Deserialize, Default)]#[serde(rename_all = "camelCase")]pub struct UsageMetadata {    #[serde(skip_serializing_if = "Option::is_none")]    pub prompt_token_count: Option<u64>,    #[serde(skip_serializing_if = "Option::is_none")]    pub cached_content_token_count: Option<u64>,    #[serde(skip_serializing_if = "Option::is_none")]    pub candidates_token_count: Option<u64>,    #[serde(skip_serializing_if = "Option::is_none")]    pub tool_use_prompt_token_count: Option<u64>,    #[serde(skip_serializing_if = "Option::is_none")]    pub thoughts_token_count: Option<u64>,    #[serde(skip_serializing_if = "Option::is_none")]    pub total_token_count: Option<u64>,} #[derive(Debug, Serialize, Deserialize, Default)]#[serde(rename_all = "camelCase")]pub struct ThinkingConfig {    #[serde(skip_serializing_if = "Option::is_none")]    pub thinking_budget: Option<u32>,    #[serde(skip_serializing_if = "Option::is_none")]    pub thinking_level: Option<ThinkingLevel>,    #[serde(skip_serializing_if = "Option::is_none")]    pub include_thoughts: Option<bool>,} #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]#[serde(rename_all = "UPPERCASE")]pub enum ThinkingLevel {    Minimal,    Low,    Medium,    High,} impl ThinkingLevel {    pub fn from_effort(effort: &str) -> Option<Self> {        match effort.to_lowercase().as_str() {            "minimal" => Some(Self::Minimal),            "low" => Some(Self::Low),            "medium" => Some(Self::Medium),            "high" => Some(Self::High),            _ => None,        }    }     pub fn name(self) -> &'static str {        match self {            Self::Minimal => "Minimal",            Self::Low => "Low",            Self::Medium => "Medium",            Self::High => "High",        }    }     pub fn value(self) -> &'static str {        match self {            Self::Minimal => "minimal",            Self::Low => "low",            Self::Medium => "medium",            Self::High => "high",        }    }} #[derive(Debug, Deserialize, Serialize)]#[serde(rename_all = "camelCase")]pub struct GenerationConfig {    #[serde(skip_serializing_if = "Option::is_none")]    pub candidate_count: Option<usize>,    #[serde(skip_serializing_if = "Option::is_none")]    pub stop_sequences: Option<Vec<String>>,    #[serde(skip_serializing_if = "Option::is_none")]    pub max_output_tokens: Option<usize>,    #[serde(skip_serializing_if = "Option::is_none")]    pub temperature: Option<f64>,    #[serde(skip_serializing_if = "Option::is_none")]    pub top_p: Option<f64>,    #[serde(skip_serializing_if = "Option::is_none")]    pub top_k: Option<usize>,    #[serde(skip_serializing_if = "Option::is_none")]    pub thinking_config: Option<ThinkingConfig>,} #[derive(Debug, Serialize, Deserialize)]#[serde(rename_all = "camelCase")]pub struct SafetySetting {    pub category: HarmCategory,    pub threshold: HarmBlockThreshold,} #[derive(Debug, Serialize, Deserialize)]pub enum HarmCategory {    #[serde(rename = "HARM_CATEGORY_UNSPECIFIED")]    Unspecified,    #[serde(rename = "HARM_CATEGORY_DEROGATORY")]    Derogatory,    #[serde(rename = "HARM_CATEGORY_TOXICITY")]    Toxicity,    #[serde(rename = "HARM_CATEGORY_VIOLENCE")]    Violence,    #[serde(rename = "HARM_CATEGORY_SEXUAL")]    Sexual,    #[serde(rename = "HARM_CATEGORY_MEDICAL")]    Medical,    #[serde(rename = "HARM_CATEGORY_DANGEROUS")]    Dangerous,    #[serde(rename = "HARM_CATEGORY_HARASSMENT")]    Harassment,    #[serde(rename = "HARM_CATEGORY_HATE_SPEECH")]    HateSpeech,    #[serde(rename = "HARM_CATEGORY_SEXUALLY_EXPLICIT")]    SexuallyExplicit,    #[serde(rename = "HARM_CATEGORY_DANGEROUS_CONTENT")]    DangerousContent,} #[derive(Debug, Serialize, Deserialize)]#[serde(rename_all = "SCREAMING_SNAKE_CASE")]pub enum HarmBlockThreshold {    #[serde(rename = "HARM_BLOCK_THRESHOLD_UNSPECIFIED")]    Unspecified,    BlockLowAndAbove,    BlockMediumAndAbove,    BlockOnlyHigh,    BlockNone,} #[derive(Debug, Serialize, Deserialize)]#[serde(rename_all = "SCREAMING_SNAKE_CASE")]pub enum HarmProbability {    #[serde(rename = "HARM_PROBABILITY_UNSPECIFIED")]    Unspecified,    Negligible,    Low,    Medium,    High,} #[derive(Debug, Serialize, Deserialize)]#[serde(rename_all = "camelCase")]pub struct SafetyRating {    pub category: HarmCategory,    pub probability: HarmProbability,} #[derive(Debug, Serialize, Deserialize)]pub struct FunctionCall {    pub name: String,    pub args: serde_json::Value,    #[serde(skip_serializing_if = "Option::is_none")]    pub id: Option<String>,} #[derive(Debug, Serialize, Deserialize)]pub struct FunctionResponse {    pub name: String,    pub response: serde_json::Value,    #[serde(skip_serializing_if = "Option::is_none")]    pub id: Option<String>,} #[derive(Debug, Serialize, Deserialize)]#[serde(rename_all = "camelCase")]pub struct Tool {    pub function_declarations: Vec<FunctionDeclaration>,} #[derive(Debug, Serialize, Deserialize)]#[serde(rename_all = "camelCase")]pub struct ToolConfig {    pub function_calling_config: FunctionCallingConfig,} #[derive(Debug, Serialize, Deserialize)]#[serde(rename_all = "camelCase")]pub struct FunctionCallingConfig {    pub mode: FunctionCallingMode,    #[serde(skip_serializing_if = "Option::is_none")]    pub allowed_function_names: Option<Vec<String>>,} #[derive(Debug, Serialize, Deserialize)]#[serde(rename_all = "lowercase")]pub enum FunctionCallingMode {    Auto,    Any,    None,} #[derive(Debug, Serialize, Deserialize)]pub struct FunctionDeclaration {    pub name: String,    pub description: String,    pub parameters: serde_json::Value,} #[derive(Debug, Default)]pub struct ModelName {    pub model_id: String,} impl ModelName {    pub fn is_empty(&self) -> bool {        self.model_id.is_empty()    }} const MODEL_NAME_PREFIX: &str = "models/"; impl Serialize for ModelName {    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>    where        S: Serializer,    {        serializer.serialize_str(&format!("{MODEL_NAME_PREFIX}{}", &self.model_id))    }} impl<'de> Deserialize<'de> for ModelName {    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>    where        D: Deserializer<'de>,    {        let string = String::deserialize(deserializer)?;        if let Some(id) = string.strip_prefix(MODEL_NAME_PREFIX) {            Ok(Self {                model_id: id.to_string(),            })        } else {            Err(serde::de::Error::custom(format!(                "Expected model name to begin with {}, got: {}",                MODEL_NAME_PREFIX, string            )))        }    }} #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]#[derive(Clone, Default, Debug, Deserialize, Serialize, PartialEq, Eq, strum::EnumIter)]

Lines 1–500 of 804.