use eventsource_client::{Client as EsClient, ClientBuilder, ReconnectOptions, SSE};
use futures::stream::{Stream, TryStreamExt};
use serde::{Deserialize, Serialize};
use std::time::Duration;
use crate::error::Error;
const STREAM_GENERATE_CONTENT_TEMPLATE: &str =
"/models/{{model}}:streamGenerateContent?alt=sse&key={{key}}";
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
#[serde(rename_all = "lowercase")]
pub enum Role {
Model,
#[default]
User,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Message {
pub role: Role,
pub content: String,
}
#[derive(Debug, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct GenerationConfig {
#[serde(skip_serializing_if = "Option::is_none")]
pub stop_sequences: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub response_mime_type: Option<String>,
pub candidate_count: Option<u32>,
pub max_output_tokens: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub temperature: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub top_p: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub top_k: Option<u32>,
}
#[derive(Debug, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct Part {
pub text: String,
}
#[derive(Debug, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct Content {
pub parts: Vec<Part>,
pub role: Role,
}
#[derive(Debug, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct MessageBody {
#[serde(skip_serializing)]
pub model: String,
pub contents: Vec<Content>,
pub generation_config: Option<GenerationConfig>,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct Candidate {
pub content: Content,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct Root {
pub candidates: Vec<Candidate>,
}
impl MessageBody {
#[must_use]
pub fn new(model: &str, contents: Vec<Content>) -> Self {
Self {
model: model.into(),
contents,
..Default::default()
}
}
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Auth {
pub api_key: String,
}
impl Auth {
#[must_use]
pub fn new(api_key: String) -> Self {
Self { api_key }
}
pub fn from_env() -> Result<Self, Error> {
let api_key = match std::env::var("OPENAI_API_KEY") {
Ok(key) => key,
Err(_) => return Err(Error::AuthError("OPENAI_API_KEY not found".to_string())),
};
Ok(Self { api_key })
}
}
#[derive(Debug, Clone)]
pub struct Client {
pub auth: Auth,
pub api_url: String,
}
impl Client {
pub fn new(auth: Auth, api_url: impl Into<String>) -> Self {
Self {
auth,
api_url: api_url.into(),
}
}
}
impl Client {
pub fn delta<'a>(
&'a self,
message_body: &'a MessageBody,
) -> Result<impl Stream<Item = Result<String, Error>> + 'a, Error> {
log::debug!("message_body: {:#?}", message_body);
let request_body = match serde_json::to_value(message_body) {
Ok(body) => body,
Err(e) => return Err(Error::Serde(e)),
};
log::debug!("request_body: {:#?}", request_body);
let sub_url =
STREAM_GENERATE_CONTENT_TEMPLATE.replace("{{model}}", message_body.model.as_str());
let url = &(self.api_url.clone() + &sub_url);
let url = url.replace("{{key}}", &self.auth.api_key);
let client = ClientBuilder::for_url(&url)?
.header("content-type", "application/json")?
.method("POST".into())
.body(request_body.to_string())
.reconnect(
ReconnectOptions::reconnect(true)
.retry_initial(false)
.delay(Duration::from_secs(1))
.backoff_factor(2)
.delay_max(Duration::from_secs(60))
.build(),
)
.build();
let stream = Box::pin(client.stream())
.map_err(Error::from)
.map_ok(|event| match event {
SSE::Connected(_) => String::default(),
SSE::Event(ev) => match serde_json::from_str::<Root>(&ev.data) {
Ok(root) => {
if root.candidates[0].content.parts.is_empty() {
String::default()
} else {
root.candidates[0].content.parts[0].text.clone()
}
}
Err(_) => String::default(),
},
SSE::Comment(comment) => {
log::debug!("Comment: {:#?}", comment);
String::default()
}
});
Ok(stream)
}
}