feat: server outline
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -360,6 +360,7 @@ dependencies = [
|
|||||||
"axum",
|
"axum",
|
||||||
"axum-extra",
|
"axum-extra",
|
||||||
"clap",
|
"clap",
|
||||||
|
"headers",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"schemars",
|
"schemars",
|
||||||
"secrecy",
|
"secrecy",
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ edition = "2024"
|
|||||||
axum = { version = "0.8.9", features = ["http2", "multipart", "ws"] }
|
axum = { version = "0.8.9", features = ["http2", "multipart", "ws"] }
|
||||||
axum-extra = { version = "0.12.6", features = ["typed-header", "typed-routing"] }
|
axum-extra = { version = "0.12.6", features = ["typed-header", "typed-routing"] }
|
||||||
clap = { version = "4.6.7", features = ["derive", "env"] }
|
clap = { version = "4.6.7", features = ["derive", "env"] }
|
||||||
|
headers = "0.4.2"
|
||||||
reqwest = { version = "0.13.5", features = ["brotli", "cookies", "deflate", "form", "gzip", "json", "multipart", "query", "stream", "zstd"] }
|
reqwest = { version = "0.13.5", features = ["brotli", "cookies", "deflate", "form", "gzip", "json", "multipart", "query", "stream", "zstd"] }
|
||||||
schemars = "1.2.2"
|
schemars = "1.2.2"
|
||||||
secrecy = { version = "0.10.3", features = ["serde"] }
|
secrecy = { version = "0.10.3", features = ["serde"] }
|
||||||
|
|||||||
29
src/main.rs
29
src/main.rs
@@ -1,11 +1,20 @@
|
|||||||
|
use std::net::{IpAddr, SocketAddr};
|
||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use secrecy::SecretString;
|
use secrecy::SecretString;
|
||||||
use tracing_subscriber::{EnvFilter, fmt::format::FmtSpan};
|
use tracing_subscriber::{EnvFilter, fmt::format::FmtSpan};
|
||||||
|
|
||||||
mod openai;
|
mod openai;
|
||||||
|
mod server;
|
||||||
|
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
struct AppArgs {
|
struct AppArgs {
|
||||||
|
#[arg(long, env)]
|
||||||
|
ip: IpAddr,
|
||||||
|
|
||||||
|
#[arg(long, env)]
|
||||||
|
port: u16,
|
||||||
|
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
cloudflare_workers_api_key: Vec<SecretString>,
|
cloudflare_workers_api_key: Vec<SecretString>,
|
||||||
|
|
||||||
@@ -48,12 +57,16 @@ async fn main() {
|
|||||||
.with_env_filter(env_filter)
|
.with_env_filter(env_filter)
|
||||||
.init();
|
.init();
|
||||||
tracing::debug!(?app_args, "using");
|
tracing::debug!(?app_args, "using");
|
||||||
let AppArgs {
|
|
||||||
cloudflare_workers_api_key,
|
let addr = SocketAddr::new(app_args.ip, app_args.port);
|
||||||
google_ai_studio_api_key,
|
|
||||||
groq_api_key,
|
let app = server::create_app(
|
||||||
hugging_face_api_key,
|
server::AppState::builder()
|
||||||
mistral_api_key,
|
.app_args(app_args.into())
|
||||||
openrouter_api_key,
|
.build(),
|
||||||
} = app_args;
|
);
|
||||||
|
|
||||||
|
let listener = tokio::net::TcpListener::bind(addr).await.expect("TODO");
|
||||||
|
|
||||||
|
axum::serve(listener, app).await.expect("TODO");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,81 +1,44 @@
|
|||||||
use std::marker::PhantomData;
|
use schemars::Schema;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use schemars::{JsonSchema, Schema, SchemaGenerator};
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
use serde::{Deserialize, Serialize, ser::SerializeMap as _};
|
pub struct Message {
|
||||||
use typed_builder::TypedBuilder;
|
|
||||||
use url::Url;
|
|
||||||
|
|
||||||
#[derive(Debug, TypedBuilder, Serialize, Deserialize)]
|
|
||||||
struct Message {
|
|
||||||
role: String,
|
role: String,
|
||||||
content: String,
|
content: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct StructuredOutputJson<S>(PhantomData<S>);
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
|
pub struct ResponseFormatJsonSchema {
|
||||||
impl<S> Serialize for StructuredOutputJson<S>
|
pub strict: bool,
|
||||||
where
|
pub name: String,
|
||||||
S: JsonSchema,
|
pub schema: Schema,
|
||||||
{
|
|
||||||
fn serialize<Serializer>(
|
|
||||||
&self,
|
|
||||||
serializer: Serializer,
|
|
||||||
) -> Result<Serializer::Ok, Serializer::Error>
|
|
||||||
where
|
|
||||||
Serializer: serde::Serializer,
|
|
||||||
{
|
|
||||||
let mut map = serializer.serialize_map(Some(3))?;
|
|
||||||
map.serialize_entry("strict", &true)?;
|
|
||||||
map.serialize_entry("name", &S::schema_name())?;
|
|
||||||
map.serialize_entry(
|
|
||||||
"schema",
|
|
||||||
&SchemaGenerator::default().into_root_schema_for::<S>(),
|
|
||||||
)?;
|
|
||||||
map.end()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
enum ResponseFormat<OutputFormat> {
|
#[serde(tag = "type")]
|
||||||
Json { schema: PhantomData<OutputFormat> },
|
pub enum ResponseFormat {
|
||||||
|
JsonSchema {
|
||||||
|
json_schema: ResponseFormatJsonSchema,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<OutputFormat> Serialize for ResponseFormat<OutputFormat>
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
where
|
pub struct ChatCompletionsRequest<Model> {
|
||||||
OutputFormat: JsonSchema,
|
pub model: Model,
|
||||||
{
|
|
||||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
pub messages: Vec<Message>,
|
||||||
where
|
|
||||||
S: serde::Serializer,
|
pub response_format: ResponseFormat,
|
||||||
{
|
|
||||||
let mut map = serializer.serialize_map(Some(2))?;
|
|
||||||
map.serialize_entry("type", "json_schema")?;
|
|
||||||
map.serialize_entry(
|
|
||||||
"json_schema",
|
|
||||||
&StructuredOutputJson::<OutputFormat>(Default::default()),
|
|
||||||
)?;
|
|
||||||
map.end()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, TypedBuilder, Serialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
#[serde(bound(serialize = "ResponseFormat<OutputSchema>: Serialize"))]
|
pub struct Choice {
|
||||||
struct ChatCompletionsRequest<OutputSchema> {
|
pub finish_reason: String, // I don't care
|
||||||
model: String,
|
|
||||||
|
|
||||||
messages: Vec<Message>,
|
pub message: Message,
|
||||||
|
|
||||||
response_format: ResponseFormat<OutputSchema>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
struct Choice {
|
pub struct ChatCompletionsResponse {
|
||||||
finish_reason: String, // I don't care
|
pub choices: Vec<Choice>,
|
||||||
|
|
||||||
message: Message,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
struct ChatCompletionsResponse {
|
|
||||||
choices: Vec<Choice>,
|
|
||||||
}
|
}
|
||||||
|
|||||||
46
src/server.rs
Normal file
46
src/server.rs
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use crate::AppArgs;
|
||||||
|
use crate::openai;
|
||||||
|
use axum::Json;
|
||||||
|
use axum::Router;
|
||||||
|
use axum::extract::State;
|
||||||
|
use axum_extra::routing::RouterExt;
|
||||||
|
use axum_extra::routing::TypedPath;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use serde::Serialize;
|
||||||
|
use typed_builder::TypedBuilder;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, TypedBuilder)]
|
||||||
|
pub struct AppState {
|
||||||
|
app_args: Arc<AppArgs>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, TypedPath)]
|
||||||
|
#[typed_path("/v1/chat/completions")]
|
||||||
|
struct V1ChatCompletions;
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
enum Shortcut {
|
||||||
|
Xs,
|
||||||
|
S,
|
||||||
|
M,
|
||||||
|
L,
|
||||||
|
XL
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn post_v1_chat_completions(
|
||||||
|
_: V1ChatCompletions,
|
||||||
|
State(state): State<AppState>,
|
||||||
|
Json(request): Json<openai::ChatCompletionsRequest<Shortcut>>,
|
||||||
|
) -> Result<Json<openai::ChatCompletionsResponse>, ()> {
|
||||||
|
todo!();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_router() -> Router<AppState> {
|
||||||
|
Router::new().typed_post(post_v1_chat_completions)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_app(state: AppState) -> Router<()> {
|
||||||
|
create_router().with_state(state)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user