diff --git a/Cargo.lock b/Cargo.lock index 87e1539..ec3bc44 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -329,6 +329,14 @@ dependencies = [ "tracing", ] +[[package]] +name = "cydonia-service" +version = "0.0.0" +dependencies = [ + "serde", + "serde_json", +] + [[package]] name = "darling" version = "0.20.10" @@ -900,10 +908,6 @@ version = "1.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" -[[package]] -name = "hub" -version = "0.1.0" - [[package]] name = "hyper" version = "1.5.2" @@ -1597,9 +1601,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.92" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" dependencies = [ "unicode-ident", ] @@ -1988,6 +1992,16 @@ dependencies = [ "serde", ] +[[package]] +name = "service-codegen" +version = "0.0.0" +dependencies = [ + "paste", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "shlex" version = "1.3.0" @@ -2068,9 +2082,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.93" +version = "2.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c786062daee0d6db1132800e623df74274a0a87322d8e183338e01b3d98d058" +checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 5f1146c..666ccec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [workspace] resolver = "2" -members = ["crates/*"] +members = ["crates/*", "crates/service/codegen"] [workspace.dependencies] ccore = { path = "crates/core", package = "cydonia-core" } @@ -16,9 +16,13 @@ dirs = "5.0.1" hf-hub = "0.4.1" llamac-sys = { version = "0.1.86", package = "llama-cpp-sys-2" } once_cell = "1.20.2" +paste = "1.0.15" +proc-macro2 = "1.0.93" +quote = "1.0.38" rand = "0.8.5" serde = "1.0.217" serde_json = "1.0.134" +syn = "2.0.96" tokenizers = "0.21.0" toml = "0.8.19" tracing = "0.1.41" diff --git a/crates/registry/Cargo.toml b/crates/registry/Cargo.toml deleted file mode 100644 index 0f84d6f..0000000 --- a/crates/registry/Cargo.toml +++ /dev/null @@ -1,6 +0,0 @@ -[package] -name = "hub" -version = "0.1.0" -edition = "2021" - -[dependencies] diff --git a/crates/registry/models.toml b/crates/registry/models.toml deleted file mode 100644 index 4fe22db..0000000 --- a/crates/registry/models.toml +++ /dev/null @@ -1,29 +0,0 @@ -# Supported Models by Default -# -# Since we currently don't have a way to download models from the internet, -# we only support predefined models in the following. - -family = ["gemma", "llama"] - -[gemma.gemma] -description = "Gemma from Google DeepMind" -family = "gemma" - -[gemma.gemma2] -description = "Gemma2 from Google DeepMind" -tags = ["latest", "2b-instruct-q4_K_S", "2b-instruct-q4_K_S_v2"] - -[llama] -description = "Llama family developed by Meta" - -[llama.llama3] -description = "Llama3 from Llama" - -[llama.llama3.1] -description = "Llama3.1 from Llama" - -[llama.llama3.2] -description = "Llama3.2 from Llama" - -[llama.llama3.3] -description = "Llama3.3 from Llama" diff --git a/crates/registry/src/main.rs b/crates/registry/src/main.rs deleted file mode 100644 index e7a11a9..0000000 --- a/crates/registry/src/main.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - println!("Hello, world!"); -} diff --git a/crates/service/Cargo.toml b/crates/service/Cargo.toml new file mode 100644 index 0000000..2d10b50 --- /dev/null +++ b/crates/service/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "cydonia-service" +version = "0.0.0" +edition = "2021" +description = "The service crate for the cydonia network." + +[dependencies] +serde = { workspace = true, features = ["derive"] } +serde_json.workspace = true diff --git a/crates/service/codegen/Cargo.toml b/crates/service/codegen/Cargo.toml new file mode 100644 index 0000000..f2dc4dd --- /dev/null +++ b/crates/service/codegen/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "service-codegen" +version = "0.0.0" +edition = "2021" + +[lib] +proc-macro = true + +[dependencies] +syn.workspace = true +proc-macro2.workspace = true +quote.workspace = true +paste.workspace = true diff --git a/crates/service/codegen/src/lib.rs b/crates/service/codegen/src/lib.rs new file mode 100644 index 0000000..36ae60a --- /dev/null +++ b/crates/service/codegen/src/lib.rs @@ -0,0 +1,64 @@ +//! Derive macros for the service crate + +use proc_macro::TokenStream; +use syn::parse_macro_input; +use tool::ServiceImpl; + +mod tool; + +/// Generate tools for the service crate, for example +/// +/// ```rust +/// #[cydonia_service::service] +/// impl MyService { +/// /// My function +/// fn my_function( +/// // The first number +/// a: u64, +/// // The second number +/// b: u64, +/// ) -> u64 { +/// // ... +/// } +/// } +/// ``` +/// +/// generates +/// +/// ```rust +/// impl MyService { +/// // My function +/// fn my_function( +/// // The first number +/// a: u64, +/// // The second number +/// b: u64, +/// ) -> u64 { +/// // ... +/// } +/// +/// fn tools() -> Vec { +/// vec![Function { +/// name: "my_function".to_string(), +/// description: "My function".to_string(), +/// arguments: vec![ +/// Argument { +/// name: "a".to_string(), +/// description: "The first number".to_string(), +/// type: "uint64".to_string(), +/// }, +/// Argument { +/// name: "b".to_string(), +/// description: "The second number".to_string(), +/// type: "uint64".to_string(), +/// }, +/// ], +/// }] +/// } +/// } +/// ``` +#[proc_macro_attribute] +pub fn service(_: TokenStream, item: TokenStream) -> TokenStream { + let service_impl = parse_macro_input!(item as ServiceImpl); + service_impl.into_token_stream() +} diff --git a/crates/service/codegen/src/tool.rs b/crates/service/codegen/src/tool.rs new file mode 100644 index 0000000..117fae1 --- /dev/null +++ b/crates/service/codegen/src/tool.rs @@ -0,0 +1,200 @@ +//! Tool generation for the services + +use quote::{quote, ToTokens}; +use syn::{ + parse::{Parse, ParseStream}, + GenericArgument, ImplItem, ItemImpl, PathArguments, Result, Type, +}; + +/// Structure to hold the parsed service implementation +pub struct ServiceImpl { + impl_block: ItemImpl, + functions: Vec, +} + +impl Parse for ServiceImpl { + fn parse(input: ParseStream) -> Result { + let impl_block: ItemImpl = input.parse()?; + let mut functions = Vec::new(); + + // Collect all documented methods + for item in &impl_block.items { + // Skip non-function items + let ImplItem::Fn(method) = item else { + continue; + }; + + // Description of the method is required. + let Some(doc) = method.attrs.iter().find(|attr| attr.path().is_ident("doc")) else { + panic!("No doc found for method {}", method.sig.ident); + }; + + let description = doc.to_token_stream().to_string(); + let name = method.sig.ident.to_string(); + let mut arguments = Vec::new(); + for param in &method.sig.inputs { + if let syn::FnArg::Typed(pat_type) = param { + if let syn::Pat::Ident(pat_ident) = &*pat_type.pat { + let param_name = pat_ident.ident.to_string(); + let param_type = validate_param(param)?; + + // Get parameter description from doc comment if available + let param_desc = if let Some(doc) = pat_type + .attrs + .iter() + .find(|attr| attr.path().is_ident("doc")) + { + doc.to_token_stream().to_string() + } else { + format!("Parameter {}", param_name) + }; + + arguments.push(quote! { + Argument { + name: #param_name.to_string(), + description: #param_desc.to_string(), + ty: #param_type.to_string(), + } + }); + } + } + } + + functions.push(quote! { + Function { + name: #name.to_string(), + description: #description.to_string(), + arguments: vec![ + #(#arguments),* + ], + } + }); + } + + Ok(ServiceImpl { + impl_block, + functions, + }) + } +} + +impl ServiceImpl { + /// Convert the parsed service implementation into a TokenStream + pub fn into_token_stream(self) -> proc_macro::TokenStream { + let ty = &self.impl_block.self_ty; + let functions = self.functions; + let impl_block = self.impl_block.clone(); + + quote! { + #impl_block + + impl #ty { + /// Generate tools for the service implementation + pub fn tools() -> Vec { + vec![ + #(#functions),* + ] + } + } + } + .into_token_stream() + .into() + } +} + +// 1. If it is and option, support the inner type of the option +// 2. tuple is not supported +// 3. `Vec` or `slice` (e.g. `[u8]`) should be regarded as `Vec` +// 4. other generic types are not supported, panic when unsupported +// function argument is found. +fn validate_param(param: &syn::FnArg) -> Result { + let syn::FnArg::Typed(pat_type) = param else { + return Err(syn::Error::new_spanned( + param, + "Self parameters are not supported", + )); + }; + + match &*pat_type.ty { + Type::Path(type_path) => { + let segment = type_path + .path + .segments + .last() + .ok_or_else(|| syn::Error::new_spanned(type_path, "Empty type path"))?; + + // Handle Option + if segment.ident == "Option" { + if let PathArguments::AngleBracketed(args) = &segment.arguments { + if let Some(GenericArgument::Type(inner_type)) = args.args.first() { + return validate_inner_type(inner_type); + } + } + return Err(syn::Error::new_spanned(segment, "Invalid Option type")); + } + + // Handle Vec or similar collection types + if segment.ident == "Vec" { + if let PathArguments::AngleBracketed(args) = &segment.arguments { + if let Some(GenericArgument::Type(inner_type)) = args.args.first() { + return validate_inner_type(inner_type); + } + } + return Err(syn::Error::new_spanned(segment, "Invalid Vec type")); + } + + // Handle basic types + match segment.ident.to_string().as_str() { + "String" | "str" | "bool" | "i8" | "i16" | "i32" | "i64" | "i128" | "isize" + | "u8" | "u16" | "u32" | "u64" | "u128" | "usize" | "f32" | "f64" => { + Ok(segment.ident.to_string()) + } + _ => Err(syn::Error::new_spanned(segment, "Unsupported type")), + } + } + Type::Reference(type_ref) => { + // Handle slices like &[T] + if let Type::Slice(slice) = &*type_ref.elem { + return validate_inner_type(&slice.elem).map(|t| format!("Vec<{}>", t)); + } + + // Handle string slices + if let Type::Path(type_path) = &*type_ref.elem { + if let Some(segment) = type_path.path.segments.last() { + if segment.ident == "str" { + return Ok("String".to_string()); + } + } + } + + validate_inner_type(&type_ref.elem) + } + Type::Slice(slice) => { + // Handle direct slice types [T] + validate_inner_type(&slice.elem).map(|t| format!("Vec<{}>", t)) + } + Type::Tuple(_) => Err(syn::Error::new_spanned( + pat_type, + "Tuple types are not supported", + )), + _ => Err(syn::Error::new_spanned(pat_type, "Unsupported type")), + } +} + +fn validate_inner_type(ty: &Type) -> Result { + let Type::Path(type_path) = ty else { + return Err(syn::Error::new_spanned(ty, "Unsupported inner type")); + }; + + let segment = type_path + .path + .segments + .last() + .ok_or_else(|| syn::Error::new_spanned(type_path, "Empty type path"))?; + + match segment.ident.to_string().as_str() { + "String" | "str" | "bool" | "i8" | "i16" | "i32" | "i64" | "i128" | "isize" | "u8" + | "u16" | "u32" | "u64" | "u128" | "usize" | "f32" | "f64" => Ok(segment.ident.to_string()), + _ => Err(syn::Error::new_spanned(segment, "Unsupported inner type")), + } +} diff --git a/crates/service/src/fun.rs b/crates/service/src/fun.rs new file mode 100644 index 0000000..d5b0771 --- /dev/null +++ b/crates/service/src/fun.rs @@ -0,0 +1,66 @@ +//! Llama3 function + +use serde::{Deserialize, Serialize}; + +/// The function description +#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Default)] +pub struct Function { + /// The name of the function + pub name: String, + /// The description of the function + pub description: String, + /// The arguments of the function + pub arguments: Vec, +} + +/// The argument of the function +#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Default)] +pub struct Argument { + /// The name of the argument + pub name: String, + /// The description of the argument + pub description: String, + /// The type of the argument + #[serde(rename = "type")] + pub ty: String, +} + +#[test] +fn test_serde() { + const JSON: &str = r#"{ + "name": "my_function", + "description": "My function", + "arguments": [ + { + "name": "a", + "description": "The first number", + "type": "uint64" + }, + { + "name": "b", + "description": "The second number", + "type": "uint64" + } + ] +}"#; + + let expected = Function { + name: "my_function".to_string(), + description: "My function".to_string(), + arguments: vec![ + Argument { + name: "a".to_string(), + description: "The first number".to_string(), + ty: "uint64".to_string(), + }, + Argument { + name: "b".to_string(), + description: "The second number".to_string(), + ty: "uint64".to_string(), + }, + ], + }; + + let actual: Function = serde_json::from_str(JSON).unwrap(); + assert_eq!(expected, actual); +} diff --git a/crates/service/src/lib.rs b/crates/service/src/lib.rs new file mode 100644 index 0000000..3c2c96e --- /dev/null +++ b/crates/service/src/lib.rs @@ -0,0 +1,3 @@ +//! The service crate for the cydonia network. + +mod fun; diff --git a/docs/.gitignore b/docs/.gitignore index 7585238..e9c0728 100644 --- a/docs/.gitignore +++ b/docs/.gitignore @@ -1 +1 @@ -book +book \ No newline at end of file diff --git a/docs/src/README.md b/docs/src/README.md index 428b894..9396db1 100644 --- a/docs/src/README.md +++ b/docs/src/README.md @@ -1,22 +1 @@ # Cydonia - -Cydonia is a library based on [candle][candle] for developing modern AI applications in rust. - -```rust -use cydonia::Model; - -fn main() { - let model = Model::new("llama3.2-1b"); - let response = model.invoke("Hello, world!"); - println!("{}", response); -} -``` - -## LICENSE - -[GPL-3.0](LICENSE) - - - -[candle]: https://github.com/huggingface/candle -[ollama]: https://github.com/ollama/ollama diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index ce4eed8..a934802 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -1,3 +1,9 @@ # Summary [Cydonia](./README.md) + +- [Oracle Inscription](./oracle-inscription/README.md) + - [Account](./oracle-inscription/account.md) + - [Transaction](./oracle-inscription/transaction.md) + - [State](./oracle-inscription/state.md) + - [Service](./oracle-inscription/service.md) diff --git a/docs/src/get-started.md b/docs/src/get-started.md new file mode 100644 index 0000000..7e0bdca --- /dev/null +++ b/docs/src/get-started.md @@ -0,0 +1 @@ +# Get Started diff --git a/docs/src/oracle-inscription/README.md b/docs/src/oracle-inscription/README.md new file mode 100644 index 0000000..da40692 --- /dev/null +++ b/docs/src/oracle-inscription/README.md @@ -0,0 +1,5 @@ +# Oracle Inscription + +[Oracle Inscription][oi] is the protocol used in the cydonia network. + +[oi]: https://en.wikipedia.org/wiki/Oracle_inscription diff --git a/docs/src/oracle-inscription/account.md b/docs/src/oracle-inscription/account.md new file mode 100644 index 0000000..24869c6 --- /dev/null +++ b/docs/src/oracle-inscription/account.md @@ -0,0 +1,22 @@ +## Account + +There are three types of accounts in the cydonia: [User](#user), [Agent](#agent), [Service](#service). + +Each account represents as an ed25519 public key in the network, used for the [Transaction](#transaction) and [State](#state). + +### 1. User + +Users are the ones that create the agents and use the services provided by the agents. + +#### 1.1. Validator + +Validators are the ones host the artificial intelligences and the related services, however, the hosted services are not required to be the same in each node, for more details, please refer to the [Oracle Inscriptions](/oracle-inscriptions/index.html) section. + +### 2. Agent + +Agents could be created by users after the validation of the validators, each validator could have different requirements for creating agents. + +### 3. Service + +As known as `Tools` for the agents, services are the ones that provide the services to the agents, they could also be used by the validator nodes to +handle their custom logic. diff --git a/docs/src/oracle-inscription/service.md b/docs/src/oracle-inscription/service.md new file mode 100644 index 0000000..42321b7 --- /dev/null +++ b/docs/src/oracle-inscription/service.md @@ -0,0 +1 @@ +# Service diff --git a/docs/src/oracle-inscription/state.md b/docs/src/oracle-inscription/state.md new file mode 100644 index 0000000..da704cc --- /dev/null +++ b/docs/src/oracle-inscription/state.md @@ -0,0 +1 @@ +# State diff --git a/docs/src/oracle-inscription/transaction.md b/docs/src/oracle-inscription/transaction.md new file mode 100644 index 0000000..77ca59b --- /dev/null +++ b/docs/src/oracle-inscription/transaction.md @@ -0,0 +1,3 @@ +## 2. Transaction + +### 2.1. OrderBook