feat: LiquidAI and decisions and system one implementations
This commit is contained in:
@@ -316,6 +316,7 @@ pub async fn create_chat_completion(
|
||||
.build()
|
||||
})
|
||||
.collect(),
|
||||
Provider::LiquidAi => unreachable!(),
|
||||
Provider::Mistral => state
|
||||
.app_args
|
||||
.mistral_api_key
|
||||
|
||||
160
src/server/decisions.rs
Normal file
160
src/server/decisions.rs
Normal file
@@ -0,0 +1,160 @@
|
||||
use crate::CloudflareCredentials;
|
||||
use crate::openai;
|
||||
use crate::server::AppState;
|
||||
use crate::server::Provider;
|
||||
use crate::server::Upstream;
|
||||
use crate::server::take_random_by_swapping;
|
||||
use axum::Json;
|
||||
use axum::extract::State;
|
||||
use axum::http::StatusCode;
|
||||
use axum::response::IntoResponse;
|
||||
use axum::response::Response;
|
||||
use axum_extra::routing::TypedPath;
|
||||
use ecow::EcoString;
|
||||
use headers::Header;
|
||||
use headers::authorization::Bearer;
|
||||
use mitsein::vec1::Vec1;
|
||||
use reqwest::Certificate;
|
||||
use secrecy::ExposeSecret;
|
||||
use serde::Deserialize;
|
||||
use serde_with::DeserializeFromStr;
|
||||
use serde_with::SerializeDisplay;
|
||||
use std::collections::VecDeque;
|
||||
use strum::EnumString;
|
||||
use strum::VariantArray;
|
||||
|
||||
#[derive(Deserialize, TypedPath)]
|
||||
#[typed_path("/v1/decisions")]
|
||||
pub struct V1DecisionsPath;
|
||||
|
||||
#[derive(
|
||||
Clone, Debug, EnumString, DeserializeFromStr, strum::Display, SerializeDisplay, VariantArray,
|
||||
)]
|
||||
#[strum(serialize_all = "lowercase")]
|
||||
pub enum Shortcut {}
|
||||
|
||||
impl Shortcut {
|
||||
fn suggestions(&self) -> Vec<(Provider, EcoString)> {
|
||||
use Provider as P;
|
||||
use Shortcut as S;
|
||||
|
||||
match self {
|
||||
_ => unreachable!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DecisionsResponse(openai::embeddings::CreateEmbeddingResponse<String>);
|
||||
|
||||
impl IntoResponse for DecisionsResponse {
|
||||
fn into_response(self) -> Response {
|
||||
let Self(embeddings) = self;
|
||||
|
||||
Json(embeddings).into_response()
|
||||
}
|
||||
}
|
||||
|
||||
pub enum DecisionsError {
|
||||
NoneSucceeded,
|
||||
}
|
||||
|
||||
impl IntoResponse for DecisionsError {
|
||||
fn into_response(self) -> Response {
|
||||
match self {
|
||||
DecisionsError::NoneSucceeded => StatusCode::SERVICE_UNAVAILABLE.into_response(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(state, request))]
|
||||
pub async fn create_decision(
|
||||
_: V1DecisionsPath,
|
||||
State(state): State<AppState>,
|
||||
Json(request): Json<openai::decisions::DecisionRequest<Shortcut>>,
|
||||
) -> Result<DecisionsResponse, DecisionsError> {
|
||||
let shortcut = request.model.clone();
|
||||
let mut model_upstreams = VecDeque::new();
|
||||
|
||||
for (provider, model) in shortcut.suggestions() {
|
||||
let upstreams: Vec<Upstream> = match provider {
|
||||
Provider::CloudflareWorkersAi => unreachable!(),
|
||||
Provider::GoogleAiStudio => unreachable!(),
|
||||
Provider::Groq => unreachable!(),
|
||||
Provider::HuggingFace => unreachable!(),
|
||||
Provider::LiquidAi => unreachable!(),
|
||||
Provider::Mistral => unreachable!(),
|
||||
Provider::OpenRouter => unreachable!(),
|
||||
Provider::ZAi => unreachable!(),
|
||||
};
|
||||
|
||||
if let Ok(upstreams) = Vec1::try_from(upstreams) {
|
||||
model_upstreams.push_back((model, upstreams));
|
||||
}
|
||||
}
|
||||
|
||||
while let Some((model, upstreams)) = model_upstreams.pop_front() {
|
||||
let (upstream, upstreams) = take_random_by_swapping(upstreams, &mut rand::rng());
|
||||
if let Some(upstreams) = upstreams {
|
||||
model_upstreams.push_back((model.clone(), upstreams));
|
||||
}
|
||||
|
||||
tracing::info!(?model, ?upstream);
|
||||
|
||||
let openai::decisions::DecisionRequest { model: _shortcut, input, questions } = request.clone();
|
||||
|
||||
let request = openai::decisions::DecisionRequest { model, input, questions };
|
||||
|
||||
let client = reqwest::Client::builder()
|
||||
.tls_certs_only(
|
||||
webpki_root_certs::TLS_SERVER_ROOT_CERTS
|
||||
.iter()
|
||||
.filter_map(|cert| Certificate::from_der(cert).ok()),
|
||||
)
|
||||
.build()
|
||||
.expect("TODO");
|
||||
|
||||
let mut upstream_request = client.post(upstream.url).json(&request);
|
||||
|
||||
if let Some(api_key) = upstream.api_key {
|
||||
let authorization =
|
||||
headers::Authorization::bearer(api_key.expose_secret()).expect("TODO");
|
||||
upstream_request = upstream_request.header(headers::Authorization::<Bearer>::name(), {
|
||||
let mut vec = vec![];
|
||||
|
||||
authorization.encode(&mut vec);
|
||||
|
||||
let [one] = vec.try_into().expect("TODO");
|
||||
|
||||
one
|
||||
});
|
||||
}
|
||||
|
||||
let upstream_response = match upstream_request.send().await {
|
||||
Ok(upstream_response) => upstream_response,
|
||||
Err(upstream_error) => {
|
||||
tracing::error!(?upstream_error);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(upstream_error) = upstream_response.error_for_status_ref() {
|
||||
tracing::error!(?upstream_error);
|
||||
match upstream_response.text().await {
|
||||
Ok(body) => tracing::error!(?body),
|
||||
Err(body_error) => tracing::error!(?body_error),
|
||||
};
|
||||
continue;
|
||||
};
|
||||
|
||||
let response = match upstream_response.json().await {
|
||||
Ok(response) => response,
|
||||
Err(json_error) => {
|
||||
tracing::error!(?json_error);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
return Ok(DecisionsResponse(response));
|
||||
}
|
||||
Err(DecisionsError::NoneSucceeded)
|
||||
}
|
||||
@@ -144,6 +144,7 @@ pub async fn create_embeddings(
|
||||
.build()
|
||||
})
|
||||
.collect(),
|
||||
Provider::LiquidAi => unreachable!(),
|
||||
Provider::Mistral => state
|
||||
.app_args
|
||||
.mistral_api_key
|
||||
@@ -168,19 +169,7 @@ pub async fn create_embeddings(
|
||||
.build()
|
||||
})
|
||||
.collect(),
|
||||
Provider::ZAi => state
|
||||
.app_args
|
||||
.z_ai_api_key
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|api_key| {
|
||||
Upstream::builder()
|
||||
// TODO: doesn't exist
|
||||
.url("https://api.z.ai/api/paas/v4/embeddings".parse().expect("TODO"))
|
||||
.api_key(api_key)
|
||||
.build()
|
||||
})
|
||||
.collect(),
|
||||
Provider::ZAi => unreachable!(),
|
||||
};
|
||||
|
||||
if let Ok(upstreams) = Vec1::try_from(upstreams) {
|
||||
|
||||
@@ -10,8 +10,10 @@ use typed_builder::TypedBuilder;
|
||||
use url::Url;
|
||||
|
||||
mod chat;
|
||||
mod decisions;
|
||||
mod embeddings;
|
||||
mod models;
|
||||
mod system_one;
|
||||
|
||||
#[derive(Debug, Clone, TypedBuilder)]
|
||||
pub struct AppState {
|
||||
@@ -24,6 +26,7 @@ enum Provider {
|
||||
GoogleAiStudio,
|
||||
Groq,
|
||||
HuggingFace,
|
||||
LiquidAi,
|
||||
Mistral,
|
||||
OpenRouter,
|
||||
ZAi,
|
||||
@@ -52,7 +55,9 @@ struct Upstream {
|
||||
pub fn create_router() -> Router<AppState> {
|
||||
Router::new()
|
||||
.typed_post(chat::completions::create_chat_completion)
|
||||
.typed_post(decisions::create_decision)
|
||||
.typed_post(embeddings::create_embeddings)
|
||||
.typed_post(system_one::create_system_one)
|
||||
.typed_get(models::list_models)
|
||||
}
|
||||
|
||||
|
||||
215
src/server/system_one.rs
Normal file
215
src/server/system_one.rs
Normal file
@@ -0,0 +1,215 @@
|
||||
use crate::CloudflareCredentials;
|
||||
use crate::openai;
|
||||
use crate::server::AppState;
|
||||
use crate::server::Provider;
|
||||
use crate::server::Upstream;
|
||||
use crate::server::take_random_by_swapping;
|
||||
use axum::Json;
|
||||
use axum::extract::State;
|
||||
use axum::http::StatusCode;
|
||||
use axum::response::IntoResponse;
|
||||
use axum::response::Response;
|
||||
use axum_extra::routing::TypedPath;
|
||||
use ecow::EcoString;
|
||||
use headers::Header;
|
||||
use headers::authorization::Bearer;
|
||||
use mitsein::vec1::Vec1;
|
||||
use reqwest::Certificate;
|
||||
use secrecy::ExposeSecret;
|
||||
use serde::Deserialize;
|
||||
use serde_with::DeserializeFromStr;
|
||||
use serde_with::SerializeDisplay;
|
||||
use std::collections::VecDeque;
|
||||
use strum::EnumString;
|
||||
use strum::VariantArray;
|
||||
|
||||
#[derive(Deserialize, TypedPath)]
|
||||
#[typed_path("/v1/systemone")]
|
||||
pub struct V1SystemOnePath;
|
||||
|
||||
#[derive(
|
||||
Clone, Debug, EnumString, DeserializeFromStr, strum::Display, SerializeDisplay, VariantArray,
|
||||
)]
|
||||
#[strum(serialize_all = "lowercase")]
|
||||
pub enum Shortcut {
|
||||
M,
|
||||
}
|
||||
|
||||
impl Shortcut {
|
||||
fn suggestions(&self) -> Vec<(Provider, EcoString)> {
|
||||
use Provider as P;
|
||||
use Shortcut as S;
|
||||
|
||||
match self {
|
||||
S::M => {
|
||||
vec![(P::LiquidAi, "d1:free".into())]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SystemOneResponse(openai::system_one::SystemOneResponse<String>);
|
||||
|
||||
impl IntoResponse for SystemOneResponse {
|
||||
fn into_response(self) -> Response {
|
||||
let Self(system_one) = self;
|
||||
|
||||
Json(system_one).into_response()
|
||||
}
|
||||
}
|
||||
|
||||
pub enum SystemOneError {
|
||||
NoneSucceeded,
|
||||
}
|
||||
|
||||
impl IntoResponse for SystemOneError {
|
||||
fn into_response(self) -> Response {
|
||||
match self {
|
||||
SystemOneError::NoneSucceeded => StatusCode::SERVICE_UNAVAILABLE.into_response(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(state, request))]
|
||||
pub async fn create_system_one(
|
||||
_: V1SystemOnePath,
|
||||
State(state): State<AppState>,
|
||||
Json(request): Json<openai::system_one::SystemOneRequest<Shortcut>>,
|
||||
) -> Result<SystemOneResponse, SystemOneError> {
|
||||
let shortcut = request.model.clone();
|
||||
let mut model_upstreams = VecDeque::new();
|
||||
|
||||
for (provider, model) in shortcut.suggestions() {
|
||||
let upstreams: Vec<Upstream> =
|
||||
match provider {
|
||||
Provider::CloudflareWorkersAi => state
|
||||
.app_args
|
||||
.cloudflare_workers_ai_credentials
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(
|
||||
|CloudflareCredentials {
|
||||
account_id,
|
||||
api_key,
|
||||
}| {
|
||||
Upstream::builder()
|
||||
.url(
|
||||
format!("https://api.cloudflare.com/client/v4/accounts/{account_id}/ai/v1/systemone")
|
||||
.parse()
|
||||
.expect("TODO"),
|
||||
)
|
||||
.api_key(api_key)
|
||||
.build()
|
||||
},
|
||||
)
|
||||
.collect(),
|
||||
Provider::GoogleAiStudio => unreachable!(),
|
||||
Provider::Groq => unreachable!(),
|
||||
Provider::HuggingFace => unreachable!(),
|
||||
Provider::LiquidAi => state
|
||||
.app_args
|
||||
.liquid_ai_api_key
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|api_key| {
|
||||
Upstream::builder()
|
||||
.url("https://api.liquid.ai/decisions/v1/systemone".parse().expect("TODO"))
|
||||
.api_key(api_key)
|
||||
.build()
|
||||
})
|
||||
.collect(),
|
||||
Provider::Mistral => unreachable!(),
|
||||
Provider::OpenRouter => state
|
||||
.app_args
|
||||
.openrouter_api_key
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|api_key| {
|
||||
Upstream::builder()
|
||||
.url("https://openrouter.ai/api/v1/systemone".parse().expect("TODO"))
|
||||
.api_key(api_key)
|
||||
.build()
|
||||
})
|
||||
.collect(),
|
||||
Provider::ZAi => unreachable!(),
|
||||
};
|
||||
|
||||
if let Ok(upstreams) = Vec1::try_from(upstreams) {
|
||||
model_upstreams.push_back((model, upstreams));
|
||||
}
|
||||
}
|
||||
|
||||
while let Some((model, upstreams)) = model_upstreams.pop_front() {
|
||||
let (upstream, upstreams) = take_random_by_swapping(upstreams, &mut rand::rng());
|
||||
if let Some(upstreams) = upstreams {
|
||||
model_upstreams.push_back((model.clone(), upstreams));
|
||||
}
|
||||
|
||||
tracing::info!(?model, ?upstream);
|
||||
|
||||
let openai::system_one::SystemOneRequest { model: _shortcut, state, questions } = request.clone();
|
||||
|
||||
let request = openai::system_one::SystemOneRequest {
|
||||
model,
|
||||
state,
|
||||
questions,
|
||||
};
|
||||
|
||||
|
||||
let client = reqwest::Client::builder()
|
||||
.tls_certs_only(
|
||||
webpki_root_certs::TLS_SERVER_ROOT_CERTS
|
||||
.iter()
|
||||
.filter_map(|cert| Certificate::from_der(cert).ok()),
|
||||
)
|
||||
.build()
|
||||
.expect("TODO");
|
||||
|
||||
let mut upstream_request = client
|
||||
.post(upstream.url)
|
||||
.json(&request);
|
||||
|
||||
if let Some(api_key) = upstream.api_key {
|
||||
let authorization =
|
||||
headers::Authorization::bearer(api_key.expose_secret()).expect("TODO");
|
||||
upstream_request = upstream_request.header(headers::Authorization::<Bearer>::name(), {
|
||||
let mut vec = vec![];
|
||||
|
||||
authorization.encode(&mut vec);
|
||||
|
||||
let [one] = vec.try_into().expect("TODO");
|
||||
|
||||
one
|
||||
});
|
||||
}
|
||||
|
||||
let upstream_response = match upstream_request.send().await {
|
||||
Ok(upstream_response) => upstream_response,
|
||||
Err(upstream_error) => {
|
||||
tracing::error!(?upstream_error);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(upstream_error) = upstream_response.error_for_status_ref() {
|
||||
tracing::error!(?upstream_error);
|
||||
match upstream_response.text().await {
|
||||
Ok(body) => tracing::error!(?body),
|
||||
Err(body_error) => tracing::error!(?body_error),
|
||||
};
|
||||
continue;
|
||||
};
|
||||
|
||||
|
||||
let response = match upstream_response.json().await {
|
||||
Ok(response) => response,
|
||||
Err(json_error) => {
|
||||
tracing::error!(?json_error);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
return Ok(SystemOneResponse(response));
|
||||
}
|
||||
Err(SystemOneError::NoneSucceeded)
|
||||
}
|
||||
Reference in New Issue
Block a user