diff --git a/Cargo.lock b/Cargo.lock index 5338028..f88644c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -46,6 +46,21 @@ dependencies = [ "memchr", ] +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anstream" version = "0.3.2" @@ -285,6 +300,21 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "time 0.1.45", + "wasm-bindgen", + "winapi", +] + [[package]] name = "cipher" version = "0.4.4" @@ -845,7 +875,7 @@ checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" dependencies = [ "cfg-if", "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", ] [[package]] @@ -998,6 +1028,29 @@ dependencies = [ "tokio-native-tls", ] +[[package]] +name = "iana-time-zone" +version = "0.1.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0722cd7114b7de04316e7ea5456a0bbb20e4adb46fd27a3697adb812cff0f37c" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "ident_case" version = "1.0.1" @@ -1281,7 +1334,7 @@ checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" dependencies = [ "libc", "log", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", "windows-sys 0.45.0", ] @@ -1334,6 +1387,15 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + [[package]] name = "num_cpus" version = "1.15.0" @@ -1750,6 +1812,28 @@ dependencies = [ "winreg", ] +[[package]] +name = "rmp" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44519172358fd6d58656c86ab8e7fbc9e1490c3e8f14d35ed78ca0dd07403c9f" +dependencies = [ + "byteorder", + "num-traits", + "paste", +] + +[[package]] +name = "rmp-serde" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5b13be192e0220b8afb7222aa5813cb62cc269ebb5cac346ca6487681d2913e" +dependencies = [ + "byteorder", + "rmp", + "serde", +] + [[package]] name = "rust-cxx-cmake-bridge" version = "0.1.0" @@ -1914,6 +1998,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_spanned" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93107647184f6027e3b7dcb2e11034cf95ffa1e3a682c67951963ac69c1c007d" +dependencies = [ + "serde", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -1926,6 +2019,19 @@ dependencies = [ "serde", ] +[[package]] +name = "serdeconv" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8897696def1d25e554294b168e0e8e77c860483666eeb8d3d33ae58b06f47221" +dependencies = [ + "rmp-serde", + "serde", + "serde_json", + "toml", + "trackable", +] + [[package]] name = "sha1" version = "0.10.5" @@ -2085,6 +2191,7 @@ dependencies = [ "rust-embed", "serde", "serde_json", + "serdeconv", "strum", "tabby-common", "tokio", @@ -2098,6 +2205,12 @@ dependencies = [ [[package]] name = "tabby-common" version = "0.1.0" +dependencies = [ + "chrono", + "lazy_static", + "serde", + "serdeconv", +] [[package]] name = "tar" @@ -2152,6 +2265,17 @@ dependencies = [ "syn 2.0.18", ] +[[package]] +name = "time" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" +dependencies = [ + "libc", + "wasi 0.10.0+wasi-snapshot-preview1", + "winapi", +] + [[package]] name = "time" version = "0.3.21" @@ -2273,6 +2397,40 @@ dependencies = [ "tracing", ] +[[package]] +name = "toml" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6135d499e69981f9ff0ef2167955a5333c35e36f6937d382974566b3d5b94ec" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a76a9312f5ba4c2dec6b9161fdf25d87ad8a09256ccea5a556fef03c706a10f" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.19.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2380d56e8670370eee6566b0bfd4265f65b3f432e8c6d85623f728d4fa31f739" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + [[package]] name = "tower" version = "0.4.13" @@ -2340,6 +2498,25 @@ dependencies = [ "once_cell", ] +[[package]] +name = "trackable" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15bd114abb99ef8cee977e517c8f37aee63f184f2d08e3e6ceca092373369ae" +dependencies = [ + "trackable_derive", +] + +[[package]] +name = "trackable_derive" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebeb235c5847e2f82cfe0f07eb971d1e5f6804b18dac2ae16349cc604380f82f" +dependencies = [ + "quote", + "syn 1.0.109", +] + [[package]] name = "try-lock" version = "0.2.4" @@ -2520,6 +2697,12 @@ dependencies = [ "try-lock", ] +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -2646,6 +2829,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +dependencies = [ + "windows-targets 0.48.0", +] + [[package]] name = "windows-sys" version = "0.42.0" @@ -2793,6 +2985,15 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" +[[package]] +name = "winnow" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61de7bac303dc551fe038e2b3cef0f571087a47571ea6e79a87692ac99b99699" +dependencies = [ + "memchr", +] + [[package]] name = "winreg" version = "0.10.1" @@ -2827,7 +3028,7 @@ dependencies = [ "hmac", "pbkdf2", "sha1", - "time", + "time 0.3.21", "zstd", ] diff --git a/Cargo.toml b/Cargo.toml index 22fae40..ac23343 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,3 +11,8 @@ version = "0.1.0" edition = "2021" authors = ["Meng Zhang"] homepage = "https://github.com/TabbyML/tabby" + +[workspace.dependencies] +lazy_static = "1.4.0" +serde = { version = "1.0", features = ["derive"] } +serdeconv = "0.4.1" diff --git a/crates/tabby-common/Cargo.toml b/crates/tabby-common/Cargo.toml index e457ec7..0cf0b63 100644 --- a/crates/tabby-common/Cargo.toml +++ b/crates/tabby-common/Cargo.toml @@ -6,3 +6,7 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +chrono = "0.4.26" +lazy_static = { workspace = true } +serde = { workspace = true } +serdeconv = { workspace = true } diff --git a/crates/tabby-common/src/events.rs b/crates/tabby-common/src/events.rs new file mode 100644 index 0000000..2c9e4a6 --- /dev/null +++ b/crates/tabby-common/src/events.rs @@ -0,0 +1,82 @@ +use chrono::Utc; +use lazy_static::lazy_static; +use serde::Serialize; +use std::fs; +use std::io::{BufWriter, Write}; +use std::sync::Mutex; + +lazy_static! { + static ref WRITER: Mutex> = { + let events_dir = &crate::path::EVENTS_DIR; + std::fs::create_dir_all(events_dir.as_path()).ok(); + + let now = Utc::now(); + let fname = now.format("%Y-%m-%d.json"); + let file = fs::OpenOptions::new() + .create(true) + .append(true) + .write(true) + .open(events_dir.join(fname.to_string())) + .ok() + .unwrap(); + + Mutex::new(BufWriter::new(file)) + }; +} + +#[derive(Serialize)] +pub struct Choice<'a> { + pub index: u32, + pub text: &'a str, +} + +#[derive(Serialize)] +#[serde(rename_all = "snake_case")] +pub enum Event<'a> { + View { + completion_id: &'a str, + choice_index: u32, + }, + Selected { + completion_id: &'a str, + choice_index: u32, + }, + Completion { + completion_id: &'a str, + language: &'a str, + prompt: &'a str, + choices: Vec>, + }, +} + +#[derive(Serialize)] +struct Log<'a> { + ts: u128, + event: &'a Event<'a>, +} + +impl Event<'_> { + pub fn log(&self) { + let mut writer = WRITER.lock().unwrap(); + + serdeconv::to_json_writer( + &Log { + ts: timestamp(), + event: self, + }, + writer.by_ref(), + ) + .unwrap(); + write!(writer, "\n").unwrap(); + writer.flush().unwrap(); + } +} + +fn timestamp() -> u128 { + use std::time::{SystemTime, UNIX_EPOCH}; + let start = SystemTime::now(); + start + .duration_since(UNIX_EPOCH) + .expect("Time went backwards") + .as_millis() +} diff --git a/crates/tabby-common/src/lib.rs b/crates/tabby-common/src/lib.rs index 4da9789..0db41fb 100644 --- a/crates/tabby-common/src/lib.rs +++ b/crates/tabby-common/src/lib.rs @@ -1 +1,2 @@ +pub mod events; pub mod path; diff --git a/crates/tabby-common/src/path.rs b/crates/tabby-common/src/path.rs index 333b182..a837d75 100644 --- a/crates/tabby-common/src/path.rs +++ b/crates/tabby-common/src/path.rs @@ -1,18 +1,23 @@ use std::env; use std::path::PathBuf; -fn get_root_dir() -> PathBuf { - match env::var("TABBY_ROOT") { - Ok(x) => PathBuf::from(x), - Err(_) => PathBuf::from(env::var("HOME").unwrap()).join(".tabby"), - } +use lazy_static::lazy_static; + +lazy_static! { + pub static ref TABBY_ROOT: PathBuf = { + match env::var("TABBY_ROOT") { + Ok(x) => PathBuf::from(x), + Err(_) => PathBuf::from(env::var("HOME").unwrap()).join(".tabby"), + } + }; + pub static ref EVENTS_DIR: PathBuf = TABBY_ROOT.join("events"); } pub struct ModelDir(PathBuf); impl ModelDir { pub fn new(model: &str) -> Self { - Self(get_root_dir().join("models").join(model)) + Self(TABBY_ROOT.join("models").join(model)) } pub fn from(path: &str) -> Self { diff --git a/crates/tabby/Cargo.toml b/crates/tabby/Cargo.toml index 97f6ec7..3d2dd74 100644 --- a/crates/tabby/Cargo.toml +++ b/crates/tabby/Cargo.toml @@ -10,7 +10,8 @@ tokio = { version = "1.17", features = ["full"] } tower = "0.4" utoipa = { version = "3.3", features = ["axum_extras", "preserve_order"] } utoipa-swagger-ui = { version = "3.1", features = ["axum"] } -serde = { version = "1.0", features = ["derive"] } +serde = { workspace = true } +serdeconv = { workspace = true } serde_json = "1.0" env_logger = "0.10.0" log = "0.4" @@ -18,7 +19,7 @@ ctranslate2-bindings = { path = "../ctranslate2-bindings" } tower-http = { version = "0.4.0", features = ["cors"] } clap = { version = "4.3.0", features = ["derive"] } regex = "1.8.3" -lazy_static = "1.4.0" +lazy_static = { workspace = true } rust-embed = "6.6.1" mime_guess = "2.0.4" strum = { version = "0.24", features = ["derive"] } diff --git a/crates/tabby/src/serve/completions.rs b/crates/tabby/src/serve/completions.rs index 746e12e..6e1f9dc 100644 --- a/crates/tabby/src/serve/completions.rs +++ b/crates/tabby/src/serve/completions.rs @@ -5,7 +5,7 @@ use ctranslate2_bindings::{ use serde::{Deserialize, Serialize}; use std::path::Path; use std::sync::Arc; -use tabby_common::path::ModelDir; +use tabby_common::{events, path::ModelDir}; use utoipa::ToSchema; mod languages; @@ -29,7 +29,6 @@ pub struct Choice { #[derive(Serialize, Deserialize, ToSchema, Clone, Debug)] pub struct CompletionResponse { id: String, - created: u64, choices: Vec, } @@ -51,15 +50,26 @@ pub async fn completion( let language = request.language.unwrap_or("unknown".into()); let filtered_text = languages::remove_stop_words(&language, &text); - Json(CompletionResponse { + let response = CompletionResponse { id: format!("cmpl-{}", uuid::Uuid::new_v4()), - created: timestamp(), - choices: [Choice { + choices: vec![Choice { index: 0, text: filtered_text.to_string(), - }] - .to_vec(), - }) + }], + }; + + events::Event::Completion { + completion_id: &response.id, + language: &language, + prompt: &request.prompt, + choices: vec![events::Choice { + index: 0, + text: filtered_text, + }], + } + .log(); + + Json(response) } pub struct CompletionState { @@ -94,15 +104,6 @@ fn get_model_dir(model: &str) -> ModelDir { } } -fn timestamp() -> u64 { - use std::time::{SystemTime, UNIX_EPOCH}; - let start = SystemTime::now(); - start - .duration_since(UNIX_EPOCH) - .expect("Time went backwards") - .as_secs() -} - #[derive(Deserialize)] #[serde(rename_all = "camelCase")] struct Metadata { @@ -115,7 +116,5 @@ struct TransformersInfo { } fn read_metadata(model_dir: &ModelDir) -> Metadata { - let file = std::fs::File::open(model_dir.metadata_file()).unwrap(); - let reader = std::io::BufReader::new(file); - serde_json::from_reader(reader).unwrap() + serdeconv::from_json_file(model_dir.metadata_file()).unwrap() } diff --git a/crates/tabby/src/serve/events.rs b/crates/tabby/src/serve/events.rs index eb0b605..e36b89b 100644 --- a/crates/tabby/src/serve/events.rs +++ b/crates/tabby/src/serve/events.rs @@ -1,10 +1,12 @@ use axum::Json; use hyper::StatusCode; use serde::{Deserialize, Serialize}; +use tabby_common::events; use utoipa::ToSchema; #[derive(Serialize, Deserialize, ToSchema, Clone, Debug)] pub struct LogEventRequest { + #[schema(example = "view")] #[serde(rename = "type")] event_type: String, completion_id: String, @@ -17,6 +19,21 @@ pub struct LogEventRequest { request_body = LogEventRequest, )] pub async fn log_event(Json(request): Json) -> StatusCode { - println!("log_event: {:?}", request); - StatusCode::OK + if request.event_type == "view" { + events::Event::View { + completion_id: &request.completion_id, + choice_index: request.choice_index, + } + .log(); + StatusCode::OK + } else if request.event_type == "selected" { + events::Event::Selected { + completion_id: &request.completion_id, + choice_index: request.choice_index, + } + .log(); + StatusCode::OK + } else { + StatusCode::BAD_REQUEST + } }