From e29c2bb27d51540d196ab0563b9aa30943b20fee Mon Sep 17 00:00:00 2001 From: Nick Mitchell Date: Tue, 8 Apr 2025 10:13:05 -0400 Subject: [PATCH 01/39] feat: update rust python support to pull in python stdlib This also improves error handling to avoid the unwrap/panic paths. Signed-off-by: Nick Mitchell --- pdl-live-react/src-tauri/Cargo.lock | 376 ++++++++++++++++++ pdl-live-react/src-tauri/Cargo.toml | 1 + .../src-tauri/src/pdl/interpreter.rs | 46 ++- 3 files changed, 402 insertions(+), 21 deletions(-) diff --git a/pdl-live-react/src-tauri/Cargo.lock b/pdl-live-react/src-tauri/Cargo.lock index 7b9b57c05..691a91727 100644 --- a/pdl-live-react/src-tauri/Cargo.lock +++ b/pdl-live-react/src-tauri/Cargo.lock @@ -17,6 +17,12 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +[[package]] +name = "adler32" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" + [[package]] name = "ahash" version = "0.8.11" @@ -325,6 +331,15 @@ dependencies = [ "system-deps", ] +[[package]] +name = "atomic" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d818003e740b63afc82337e3160717f4f63078720a810b7b903e70a5d1d2994" +dependencies = [ + "bytemuck", +] + [[package]] name = "atomic-waker" version = "1.1.2" @@ -363,6 +378,12 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + [[package]] name = "base64" version = "0.21.7" @@ -396,6 +417,15 @@ dependencies = [ "serde", ] +[[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -862,6 +892,15 @@ dependencies = [ "syn 2.0.100", ] +[[package]] +name = "csv-core" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d02f3b0da4c6504f86e9cd789d8dbafab48c2321be74e9987593de5a894d93d" +dependencies = [ + "memchr", +] + [[package]] name = "ctor" version = "0.2.9" @@ -959,6 +998,7 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "crypto-common", + "subtle", ] [[package]] @@ -1022,6 +1062,18 @@ dependencies = [ "syn 2.0.100", ] +[[package]] +name = "dns-lookup" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5766087c2235fec47fafa4cfecc81e494ee679d0fd4a59887ea0919bfb0e4fc" +dependencies = [ + "cfg-if", + "libc", + "socket2", + "windows-sys 0.48.0", +] + [[package]] name = "downcast-rs" version = "1.2.1" @@ -1257,6 +1309,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece" dependencies = [ "crc32fast", + "libz-sys", "miniz_oxide", ] @@ -1553,6 +1606,16 @@ dependencies = [ "version_check", ] +[[package]] +name = "gethostname" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1ebd34e35c46e00bb73e81363248d627782724609fe1b6396f553f68fe3862e" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "getopts" version = "0.2.21" @@ -2315,6 +2378,15 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "keccak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +dependencies = [ + "cpufeatures", +] + [[package]] name = "keyboard-types" version = "0.7.0" @@ -2437,6 +2509,28 @@ dependencies = [ "libc", ] +[[package]] +name = "libsqlite3-sys" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c10584274047cb335c23d3e61bcef8e323adae7c5c8c760540f73610177fc3f" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "libz-sys" +version = "1.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b70e7a7df205e92a1a4cd9aaae7898dac0aa555503cc0a649494d0d60e7651d" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + [[package]] name = "linux-raw-sys" version = "0.4.15" @@ -2486,6 +2580,16 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" +[[package]] +name = "mac_address" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0aeb26bf5e836cc1c341c8106051b573f1766dfa05aa87f0b98be5e51b02303" +dependencies = [ + "nix 0.29.0", + "winapi", +] + [[package]] name = "malachite" version = "0.4.22" @@ -2570,12 +2674,31 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + [[package]] name = "memchr" version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +[[package]] +name = "memmap2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327" +dependencies = [ + "libc", +] + [[package]] name = "memoffset" version = "0.9.1" @@ -2622,6 +2745,15 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "mt19937" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12ca7f22ed370d5991a9caec16a83187e865bc8a532f889670337d5a5689e3a1" +dependencies = [ + "rand_core 0.6.4", +] + [[package]] name = "muda" version = "0.16.1" @@ -3152,6 +3284,16 @@ version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1036865bb9422d3300cf723f657c2851d0e9ab12567854b1f4eba3d77decf564" +[[package]] +name = "page_size" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eebde548fbbf1ea81a99b128872779c437752fb99f217c45245e1a61dcd9edcd" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "pango" version = "0.18.3" @@ -3232,6 +3374,7 @@ dependencies = [ "ollama-rs", "owo-colors", "rayon", + "rustpython-stdlib", "rustpython-vm", "serde", "serde_json", @@ -3592,6 +3735,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "puruspe" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3804877ffeba468c806c2ad9057bbbae92e4b2c410c2f108baaa0042f241fa4c" + [[package]] name = "quick-xml" version = "0.32.0" @@ -3657,6 +3806,17 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "rand" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.3", + "zerocopy 0.8.24", +] + [[package]] name = "rand_chacha" version = "0.2.2" @@ -3677,6 +3837,16 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.3", +] + [[package]] name = "rand_core" version = "0.5.1" @@ -3695,6 +3865,15 @@ dependencies = [ "getrandom 0.2.15", ] +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.2", +] + [[package]] name = "rand_hc" version = "0.2.0" @@ -4125,6 +4304,79 @@ dependencies = [ "optional", ] +[[package]] +name = "rustpython-stdlib" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "014cb19d897ca26566dd91104f446c6c396091d99561aa32fcc1e461000192b4" +dependencies = [ + "adler32", + "ahash", + "ascii", + "base64 0.13.1", + "blake2", + "cfg-if", + "crc32fast", + "crossbeam-utils", + "csv-core", + "digest", + "dns-lookup", + "dyn-clone", + "flate2", + "gethostname", + "hex", + "indexmap 2.9.0", + "itertools", + "junction", + "libc", + "libsqlite3-sys", + "libz-sys", + "mac_address", + "malachite-bigint", + "md-5", + "memchr", + "memmap2", + "mt19937", + "nix 0.27.1", + "num-complex", + "num-integer", + "num-traits", + "num_enum", + "once_cell", + "openssl", + "page_size", + "parking_lot", + "paste", + "puruspe", + "rand 0.8.5", + "rand_core 0.6.4", + "rustpython-common", + "rustpython-derive", + "rustpython-vm", + "schannel", + "sha-1", + "sha2", + "sha3", + "socket2", + "system-configuration", + "termios", + "thread_local", + "ucd", + "unic-char-property", + "unic-normal", + "unic-ucd-age", + "unic-ucd-bidi", + "unic-ucd-category", + "unic-ucd-ident", + "unicode-casing", + "unicode_names2", + "uuid", + "widestring", + "winapi", + "windows-sys 0.52.0", + "xml-rs", +] + [[package]] name = "rustpython-vm" version = "0.4.0" @@ -4509,6 +4761,17 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "sha-1" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5058ada175748e33390e40e872bd0fe59a19f265d0158daa551c5a88a76009c" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "sha2" version = "0.10.8" @@ -4520,6 +4783,16 @@ dependencies = [ "digest", ] +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest", + "keccak", +] + [[package]] name = "shared_child" version = "1.0.1" @@ -4714,6 +4987,12 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "swift-rs" version = "1.0.7" @@ -4776,6 +5055,27 @@ dependencies = [ "syn 2.0.100", ] +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.9.4", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "system-deps" version = "6.2.2" @@ -5159,6 +5459,15 @@ dependencies = [ "utf-8", ] +[[package]] +name = "termios" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "411c5bf740737c7918b8b1fe232dca4dc9f8e754b8ad5e20966814001ed0ac6b" +dependencies = [ + "libc", +] + [[package]] name = "textwrap" version = "0.15.2" @@ -5519,6 +5828,12 @@ version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" +[[package]] +name = "ucd" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe4fa6e588762366f1eb4991ce59ad1b93651d0b769dfb4e4d1c5c4b943d1159" + [[package]] name = "uds_windows" version = "1.1.0" @@ -5571,6 +5886,26 @@ dependencies = [ "unic-ucd-version", ] +[[package]] +name = "unic-normal" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f09d64d33589a94628bc2aeb037f35c2e25f3f049c7348b5aa5580b48e6bba62" +dependencies = [ + "unic-ucd-normal", +] + +[[package]] +name = "unic-ucd-age" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8cfdfe71af46b871dc6af2c24fcd360e2f3392ee4c5111877f2947f311671c" +dependencies = [ + "unic-char-property", + "unic-char-range", + "unic-ucd-version", +] + [[package]] name = "unic-ucd-bidi" version = "0.9.0" @@ -5594,6 +5929,15 @@ dependencies = [ "unic-ucd-version", ] +[[package]] +name = "unic-ucd-hangul" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb1dc690e19010e1523edb9713224cba5ef55b54894fe33424439ec9a40c0054" +dependencies = [ + "unic-ucd-version", +] + [[package]] name = "unic-ucd-ident" version = "0.9.0" @@ -5605,6 +5949,18 @@ dependencies = [ "unic-ucd-version", ] +[[package]] +name = "unic-ucd-normal" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86aed873b8202d22b13859dda5fe7c001d271412c31d411fd9b827e030569410" +dependencies = [ + "unic-char-property", + "unic-char-range", + "unic-ucd-hangul", + "unic-ucd-version", +] + [[package]] name = "unic-ucd-version" version = "0.9.0" @@ -5741,8 +6097,22 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9" dependencies = [ + "atomic", "getrandom 0.3.2", + "rand 0.9.0", "serde", + "uuid-macro-internal", +] + +[[package]] +name = "uuid-macro-internal" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72dcd78c4f979627a754f5522cea6e6a25e55139056535fe6e69c506cd64a862" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", ] [[package]] @@ -6651,6 +7021,12 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "xml-rs" +version = "0.8.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5b940ebc25896e71dd073bad2dbaa2abfe97b0a391415e22ad1326d9c54e3c4" + [[package]] name = "yaml-rust2" version = "0.10.1" diff --git a/pdl-live-react/src-tauri/Cargo.toml b/pdl-live-react/src-tauri/Cargo.toml index 40155af98..e94a7f254 100644 --- a/pdl-live-react/src-tauri/Cargo.toml +++ b/pdl-live-react/src-tauri/Cargo.toml @@ -42,6 +42,7 @@ async-recursion = "1.1.1" tokio-stream = "0.1.17" tokio = { version = "1.44.1", features = ["io-std"] } indexmap = { version = "2.9.0", features = ["serde"] } +rustpython-stdlib = { version = "0.4.0", features = ["openssl", "zlib"] } [target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies] tauri-plugin-cli = "2" diff --git a/pdl-live-react/src-tauri/src/pdl/interpreter.rs b/pdl-live-react/src-tauri/src/pdl/interpreter.rs index e695f87fd..df6da9b27 100644 --- a/pdl-live-react/src-tauri/src/pdl/interpreter.rs +++ b/pdl-live-react/src-tauri/src/pdl/interpreter.rs @@ -459,37 +459,41 @@ impl<'a> Interpreter<'a> { _context: Context, ) -> Interpretation { use rustpython_vm as vm; - vm::Interpreter::without_stdlib(Default::default()).enter(|vm| -> Interpretation { + let interp = vm::Interpreter::with_init(vm::Settings::default(), |vm| { + vm.add_native_modules(rustpython_stdlib::get_module_inits()); + }); + interp.enter(|vm| -> Interpretation { let scope = vm.new_scope_with_builtins(); // TODO vm.new_syntax_error(&err, Some(block.code.as_str())) - let code_obj = vm + let code_obj = match vm .compile( block.code.as_str(), vm::compiler::Mode::Exec, "".to_owned(), - ) - .map_err(|_err| { - panic!("Syntax error in Python code"); - }) - .unwrap(); - - let _output = vm - .run_code_obj(code_obj, scope.clone()) - .map_err(|_err| { - // TODO vm.print_exception(exc); - println!("Error executing Python code"); - }) - .unwrap(); + ) { + Ok(x) => Ok(x), + Err(exc) => Err(Box::::from(format!("Syntax error in Python code {:?}", exc))), + }?; + + // TODO vm.print_exception(exc); + match vm.run_code_obj(code_obj, scope.clone()) { + Ok(_) => Ok(()), + Err(exc) => { + vm.print_exception(exc); + Err(Box::::from("Error executing Python code")) + }, + }?; match scope.globals.get_item("result", vm) { Ok(result) => { - let result_string = result - .str(vm) - .map_err(|e| { - panic!("Unable to stringify Python 'result' value {:?}", e); - }) - .unwrap(); + let result_string = match result.str(vm) { + Ok(x) => Ok(x), + Err(exc) => { + vm.print_exception(exc); + Err(Box::::from("Unable to stringify Python 'result' value")) + }, + }?; let messages = vec![ChatMessage::user(result_string.as_str().to_string())]; let trace = PdlBlock::PythonCode(block.clone()); Ok((messages[0].content.clone().into(), messages, trace)) From 3e3e31c9b4718add0ba4523904757bb4fbc8a1bc Mon Sep 17 00:00:00 2001 From: Nick Mitchell Date: Tue, 8 Apr 2025 09:29:40 -0400 Subject: [PATCH 02/39] feat: port rust model pull logic to use rust AST Signed-off-by: Nick Mitchell --- pdl-live-react/src-tauri/src/pdl/extract.rs | 69 +++++++++---------- .../src-tauri/src/pdl/interpreter.rs | 35 ++++++---- pdl-live-react/src-tauri/src/pdl/pull.rs | 20 +++--- pdl-live-react/src-tauri/src/pdl/run.rs | 8 ++- 4 files changed, 71 insertions(+), 61 deletions(-) diff --git a/pdl-live-react/src-tauri/src/pdl/extract.rs b/pdl-live-react/src-tauri/src/pdl/extract.rs index 640915e8e..a33416288 100644 --- a/pdl-live-react/src-tauri/src/pdl/extract.rs +++ b/pdl-live-react/src-tauri/src/pdl/extract.rs @@ -1,16 +1,14 @@ -use yaml_rust2::Yaml; +use crate::pdl::ast::PdlBlock; /// Extract models referenced by the programs -pub fn extract_models(programs: Vec) -> Vec { - extract_values(programs, "model") +pub fn extract_models(program: &PdlBlock) -> Vec { + extract_values(program, "model") } /// Take a list of Yaml fragments and produce a vector of the string-valued entries of the given field -pub fn extract_values(programs: Vec, field: &str) -> Vec { - let mut values = programs - .into_iter() - .flat_map(|p| extract_one_values(p, field)) - .collect::>(); +pub fn extract_values(program: &PdlBlock, field: &str) -> Vec { + let mut values = vec![]; + extract_values_iter(program, field, &mut values); // A single program may specify the same model more than once. Dedup! values.sort(); @@ -20,38 +18,37 @@ pub fn extract_values(programs: Vec, field: &str) -> Vec { } /// Take one Yaml fragment and produce a vector of the string-valued entries of the given field -fn extract_one_values(program: Yaml, field: &str) -> Vec { - let mut values: Vec = Vec::new(); - +fn extract_values_iter(program: &PdlBlock, field: &str, values: &mut Vec) { match program { - Yaml::Hash(h) => { - for (key, val) in h { - match key { - Yaml::String(f) if f == field => match &val { - Yaml::String(m) => { - values.push(m.to_string()); - } - _ => {} - }, - _ => {} - } - - for m in extract_one_values(val, field) { - values.push(m) - } - } + PdlBlock::Model(b) => values.push(b.model.clone()), + PdlBlock::Repeat(b) => { + extract_values_iter(&b.repeat, field, values); } - - Yaml::Array(a) => { - for val in a { - for m in extract_one_values(val, field) { - values.push(m) - } + PdlBlock::Message(b) => { + extract_values_iter(&b.content, field, values); + } + PdlBlock::Array(b) => b + .array + .iter() + .for_each(|p| extract_values_iter(p, field, values)), + PdlBlock::Text(b) => b + .text + .iter() + .for_each(|p| extract_values_iter(p, field, values)), + PdlBlock::LastOf(b) => b + .last_of + .iter() + .for_each(|p| extract_values_iter(p, field, values)), + PdlBlock::If(b) => { + extract_values_iter(&b.then, field, values); + if let Some(else_) = &b.else_ { + extract_values_iter(else_, field, values); } } - + PdlBlock::Object(b) => b + .object + .values() + .for_each(|p| extract_values_iter(p, field, values)), _ => {} } - - values } diff --git a/pdl-live-react/src-tauri/src/pdl/interpreter.rs b/pdl-live-react/src-tauri/src/pdl/interpreter.rs index df6da9b27..112cccf6b 100644 --- a/pdl-live-react/src-tauri/src/pdl/interpreter.rs +++ b/pdl-live-react/src-tauri/src/pdl/interpreter.rs @@ -466,23 +466,27 @@ impl<'a> Interpreter<'a> { let scope = vm.new_scope_with_builtins(); // TODO vm.new_syntax_error(&err, Some(block.code.as_str())) - let code_obj = match vm - .compile( - block.code.as_str(), - vm::compiler::Mode::Exec, - "".to_owned(), - ) { - Ok(x) => Ok(x), - Err(exc) => Err(Box::::from(format!("Syntax error in Python code {:?}", exc))), - }?; + let code_obj = match vm.compile( + block.code.as_str(), + vm::compiler::Mode::Exec, + "".to_owned(), + ) { + Ok(x) => Ok(x), + Err(exc) => Err(Box::::from(format!( + "Syntax error in Python code {:?}", + exc + ))), + }?; // TODO vm.print_exception(exc); match vm.run_code_obj(code_obj, scope.clone()) { Ok(_) => Ok(()), Err(exc) => { vm.print_exception(exc); - Err(Box::::from("Error executing Python code")) - }, + Err(Box::::from( + "Error executing Python code", + )) + } }?; match scope.globals.get_item("result", vm) { @@ -491,8 +495,10 @@ impl<'a> Interpreter<'a> { Ok(x) => Ok(x), Err(exc) => { vm.print_exception(exc); - Err(Box::::from("Unable to stringify Python 'result' value")) - }, + Err(Box::::from( + "Unable to stringify Python 'result' value", + )) + } }?; let messages = vec![ChatMessage::user(result_string.as_str().to_string())]; let trace = PdlBlock::PythonCode(block.clone()); @@ -927,7 +933,7 @@ pub fn run_sync(program: &PdlBlock, cwd: Option, debug: bool) -> Interp } /// Read in a file from disk and parse it as a PDL program -fn parse_file(path: &PathBuf) -> Result { +pub fn parse_file(path: &PathBuf) -> Result { from_reader(File::open(path)?) .map_err(|err| Box::::from(err.to_string())) } @@ -937,6 +943,7 @@ pub async fn run_file(source_file_path: &str, debug: bool) -> Interpretation { let cwd = path.parent().and_then(|cwd| Some(cwd.to_path_buf())); let program = parse_file(&path)?; + crate::pdl::pull::pull_if_needed(&program).await?; run(&program, cwd, debug).await } diff --git a/pdl-live-react/src-tauri/src/pdl/pull.rs b/pdl-live-react/src-tauri/src/pdl/pull.rs index 6f4302b8d..cdb42a04a 100644 --- a/pdl-live-react/src-tauri/src/pdl/pull.rs +++ b/pdl-live-react/src-tauri/src/pdl/pull.rs @@ -1,20 +1,24 @@ -use ::std::io::{Error, ErrorKind}; +use ::std::io::Error; use duct::cmd; use rayon::prelude::*; -use yaml_rust2::{Yaml, YamlLoader}; +use crate::pdl::ast::PdlBlock; use crate::pdl::extract; +use crate::pdl::interpreter::parse_file; -/// Read the given filesystem path and produce a potentially multi-document Yaml -fn from_path(path: &str) -> Result, Error> { - let content = std::fs::read_to_string(path)?; - YamlLoader::load_from_str(&content).map_err(|e| Error::new(ErrorKind::Other, e.to_string())) +pub async fn pull_if_needed_from_path( + source_file_path: &str, +) -> Result<(), Box> { + let program = parse_file(&::std::path::PathBuf::from(source_file_path))?; + pull_if_needed(&program) + .await + .map_err(|e| Box::from(e.to_string())) } /// Pull models (in parallel) from the PDL program in the given filepath. -pub async fn pull_if_needed(path: &str) -> Result<(), Error> { - extract::extract_models(from_path(path)?) +pub async fn pull_if_needed(program: &PdlBlock) -> Result<(), Error> { + extract::extract_models(program) .into_par_iter() .try_for_each(|model| match model { m if model.starts_with("ollama/") => ollama_pull_if_needed(&m[7..]), diff --git a/pdl-live-react/src-tauri/src/pdl/run.rs b/pdl-live-react/src-tauri/src/pdl/run.rs index 2bc6c18dd..ee7281f93 100644 --- a/pdl-live-react/src-tauri/src/pdl/run.rs +++ b/pdl-live-react/src-tauri/src/pdl/run.rs @@ -3,7 +3,7 @@ use duct::cmd; use futures::executor::block_on; use crate::pdl::pip::pip_install_if_needed; -use crate::pdl::pull::pull_if_needed; +use crate::pdl::pull::pull_if_needed_from_path; use crate::pdl::requirements::PDL_INTERPRETER; #[cfg(desktop)] @@ -19,11 +19,13 @@ pub fn run_pdl_program( ); // async the model pull and pip installs - let pull_future = pull_if_needed(&source_file_path); + let pull_future = pull_if_needed_from_path(&source_file_path); let bin_path_future = pip_install_if_needed(&PDL_INTERPRETER); // wait for any model pulls to finish - block_on(pull_future)?; + if let Err(e) = block_on(pull_future) { + return Err(e); + } // wait for any pip installs to finish let bin_path = block_on(bin_path_future)?; From 91f2784e6d848f8172c1e57115d2a1b22cddc19b Mon Sep 17 00:00:00 2001 From: Nick Mitchell Date: Tue, 8 Apr 2025 14:44:00 -0400 Subject: [PATCH 03/39] fix: avoid openssl in rust app for now Signed-off-by: Nick Mitchell --- pdl-live-react/src-tauri/Cargo.lock | 1 - pdl-live-react/src-tauri/Cargo.toml | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/pdl-live-react/src-tauri/Cargo.lock b/pdl-live-react/src-tauri/Cargo.lock index 691a91727..1779e2668 100644 --- a/pdl-live-react/src-tauri/Cargo.lock +++ b/pdl-live-react/src-tauri/Cargo.lock @@ -4343,7 +4343,6 @@ dependencies = [ "num-traits", "num_enum", "once_cell", - "openssl", "page_size", "parking_lot", "paste", diff --git a/pdl-live-react/src-tauri/Cargo.toml b/pdl-live-react/src-tauri/Cargo.toml index e94a7f254..62534f2e5 100644 --- a/pdl-live-react/src-tauri/Cargo.toml +++ b/pdl-live-react/src-tauri/Cargo.toml @@ -42,7 +42,7 @@ async-recursion = "1.1.1" tokio-stream = "0.1.17" tokio = { version = "1.44.1", features = ["io-std"] } indexmap = { version = "2.9.0", features = ["serde"] } -rustpython-stdlib = { version = "0.4.0", features = ["openssl", "zlib"] } +rustpython-stdlib = { version = "0.4.0", features = ["zlib"] } [target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies] tauri-plugin-cli = "2" From 618b83853a9d5cb08baf002c7df3f4f83e5f29b3 Mon Sep 17 00:00:00 2001 From: Mandana Vaziri Date: Tue, 8 Apr 2025 16:14:18 -0400 Subject: [PATCH 04/39] gsm8k multi-plan, tree-of-thought, tree-of-thought with few shots (#881) Signed-off-by: Mandana Vaziri --- examples/gsm8k/gsm8k-tot-few-shot.pdl | 221 +++++++++++++++++++++++++ examples/gsm8k/gsm8k-tot-multiplan.pdl | 167 +++++++++++++++++++ examples/gsm8k/gsm8k-tot.pdl | 208 +++++++++++++++++++++++ 3 files changed, 596 insertions(+) create mode 100644 examples/gsm8k/gsm8k-tot-few-shot.pdl create mode 100644 examples/gsm8k/gsm8k-tot-multiplan.pdl create mode 100644 examples/gsm8k/gsm8k-tot.pdl diff --git a/examples/gsm8k/gsm8k-tot-few-shot.pdl b/examples/gsm8k/gsm8k-tot-few-shot.pdl new file mode 100644 index 000000000..37a2e7f4c --- /dev/null +++ b/examples/gsm8k/gsm8k-tot-few-shot.pdl @@ -0,0 +1,221 @@ +description: Grade School Math -- for every problem we generate a plan, then exectute and evaluate it. +defs: + problems: + read: ./test.jsonl + parser: jsonl + + MAX_ITERATIONS: 10 + N: 3 + + + majority_vote: + function: + numbers: [float] + return: + lang: python + code: | + from collections import Counter + frequency = Counter( ${ numbers }) + most_frequent = max(frequency, key=frequency.get) + result = most_frequent + + majority_vote_json: + function: + results: [{ "result": float }] + return: + lastOf: + - lang: python + def: numbers + code: | + result = [o["result"] for o in ${ results }] + - call: ${ majority_vote } + args: + numbers: ${ numbers } + + planning: + function: + problem: str + demos: [str] + return: + text: + - | + Please generate a high-level plan for solving the following question. + As the first step, just say what method and idea you will use to solve the question. + You can reorganize the information in the question. Do not do the actual calculation. + Keep your response concise and within 80 words. + + - for: + demo: ${ demos } + repeat: + ${ demo } + join: + with: "\n" + - text: + - "\nProblem:\n" + - ${ problem } + - "\n" + - model: ollama/granite3.2:8b + parameters: + temperature: 0.7 + top_p: 0.85 + + solve: + function: + plan: str + return: + text: + - ${ plan } + - | + + The plan looks good! Now, use real numbers and do the calculation. Please solve the question + step-by-step according to the high-level plan. Give me the final answer. Make your response short. + - "\nThe answer is:\n" + - model: ollama/granite3.2:8b + parameters: + temperature: 0.7 + top_p: 0.85 + + extract_final_answer: + function: + solution: str + return: + lastOf: + - ${ solution } + - Extract the result from the above solution into a JSON object with field "result" and a float as value. Remove any dollar signs or other symbols. + - model: ollama/granite3.2:8b + parser: json + def: result + spec: { "result": float } + fallback: + data: + result: 0 + + compare_to_ground_truth: + function: + result: float + truth: str + return: + lastOf: + - data: ${ truth } + parser: + regex: "(.|\n)*#### (?P([0-9])*)\n*" + spec: + answer: str + def: ground_truth + - if: ${ result|float == ground_truth.answer|float} + then: + 1 + else: + 0 + +text: +- defs: + demos: + read: demos.yaml + parser: yaml + for: + problem: ${ problems } + repeat: + repeat: + call: ${ planning } + args: + pdl_context: [] + problem: ${ problem.question } + demos: ${ demos } + max_iterations: ${ N } + join: + as: array + max_iterations: ${ MAX_ITERATIONS } + def: plans + join: + as: array + +- for: + plans_for_problem: ${ plans } + repeat: + for: + plan: ${ plans_for_problem } + repeat: + repeat: + call: ${ solve } + args: + pdl_context: [] + plan: ${ plan } + max_iterations: ${ N } + join: + as: array + join: + as: array + max_iterations: ${ MAX_ITERATIONS } + def: solutions + join: + as: array + +- for: + solution: ${ solutions } + repeat: + for: + solutions_for_problem: ${ solution } + repeat: + for: + solution_for_problem: ${ solutions_for_problem } + repeat: + call: ${ extract_final_answer } + args: + pdl_context: [] + solution: ${ solution_for_problem } + max_iterations: ${ N } + join: + as: array + join: + as: array + max_iterations: ${ MAX_ITERATIONS } + def: results + join: + as: array + +- for: + all_results_for_problem: ${ results } + repeat: + for: + results_for_problem: ${ all_results_for_problem } + repeat: + call: ${ majority_vote_json } + args: + pdl_context: [] + results: ${ results_for_problem } + max_iterations: ${ N } + join: + as: array + max_iterations: ${ MAX_ITERATIONS } + def: per_plan_votes + join: + as: array + +- for: + votes: ${ per_plan_votes } + repeat: + call: ${ majority_vote } + args: + pdl_context: [] + numbers: ${ votes } + max_iterations: ${ MAX_ITERATIONS } + join: + as: array + def: results + +- for: + result: ${ results } + problem: ${ problems[:MAX_ITERATIONS] } + repeat: + call: ${ compare_to_ground_truth } + args: + pdl_context: [] + result: ${ result } + truth: ${ problem.answer } + max_iterations: ${ MAX_ITERATIONS } + def: stats + join: + as: array + +- "\nAccuracy: ${ stats|sum / MAX_ITERATIONS * 100}% " \ No newline at end of file diff --git a/examples/gsm8k/gsm8k-tot-multiplan.pdl b/examples/gsm8k/gsm8k-tot-multiplan.pdl new file mode 100644 index 000000000..321aea6d5 --- /dev/null +++ b/examples/gsm8k/gsm8k-tot-multiplan.pdl @@ -0,0 +1,167 @@ +description: Grade School Math -- for every problem we generate a plan, then exectute and evaluate it. +defs: + problems: + read: ./test.jsonl + parser: jsonl + + MAX_ITERATIONS: 2 + N: 3 + + + majority_vote: + function: + results: [{ "result": float }] + return: + lang: python + code: | + from collections import Counter + numbers = [o["result"] for o in ${ results }] + frequency = Counter(numbers) + most_frequent = max(frequency, key=frequency.get) + result = most_frequent + planning: + function: + problem: str + return: + text: + - | + Please generate a high-level plan for solving the following question. + As the first step, just say what method and idea you will use to solve the question. + You can reorganize the information in the question. Do not do the actual calculation. + Keep your response concise and within 80 words. + + - "\nProblem:\n" + - ${ problem } + - "\n" + - model: ollama/granite3.2:8b + parameters: + temperature: 0.7 + top_p: 0.85 + + solve: + function: + plan: str + return: + text: + - ${ plan } + - | + + The plan looks good! Now, use real numbers and do the calculation. Please solve the question + step-by-step according to the high-level plan. Give me the final answer. Make your response short. + - "\nThe answer is:\n" + - model: ollama/granite3.2:8b + parameters: + temperature: 0.7 + top_p: 0.85 + + extract_final_answer: + function: + solution: str + return: + lastOf: + - ${ solution } + - Extract the result from the above solution into a JSON object with field "result" and a float as value. Remove any dollar signs or other symbols. + - model: ollama/granite3.2:8b + parser: json + def: result + spec: { "result": float } + fallback: + data: + result: 0 + + compare_to_ground_truth: + function: + result: float + truth: str + return: + lastOf: + - data: ${ truth } + parser: + regex: "(.|\n)*#### (?P([0-9])*)\n*" + spec: + answer: str + def: ground_truth + - if: ${ result|float == ground_truth.answer|float} + then: + 1 + else: + 0 + +text: +- for: + problem: ${ problems } + repeat: + repeat: + call: ${ planning } + args: + pdl_context: [] + problem: ${ problem.question } + max_iterations: ${ N } + join: + as: array + max_iterations: ${ MAX_ITERATIONS } + def: plans + join: + as: array + +- for: + plans_for_problem: ${ plans } + repeat: + for: + plan: ${ plans_for_problem } + repeat: + call: ${ solve } + args: + pdl_context: [] + plan: ${ plan } + join: + as: array + max_iterations: ${ MAX_ITERATIONS } + def: solutions + join: + as: array + +- for: + solution: ${ solutions } + repeat: + for: + solution_for_problem: ${ solution } + repeat: + call: ${ extract_final_answer } + args: + pdl_context: [] + solution: ${ solution_for_problem } + join: + as: array + max_iterations: ${ MAX_ITERATIONS } + def: results + join: + as: array + +- for: + results_for_problem: ${ results } + repeat: + call: ${ majority_vote } + args: + pdl_context: [] + results: ${ results_for_problem } + max_iterations: ${ MAX_ITERATIONS } + def: votes + join: + as: array + +- for: + result: ${ votes } + problem: ${ problems[:MAX_ITERATIONS] } + repeat: + call: ${ compare_to_ground_truth } + args: + pdl_context: [] + result: ${ result } + truth: ${ problem.answer } + max_iterations: ${ MAX_ITERATIONS } + def: stats + join: + as: array + +- "\nAccuracy: ${ stats|sum / MAX_ITERATIONS * 100}% " \ No newline at end of file diff --git a/examples/gsm8k/gsm8k-tot.pdl b/examples/gsm8k/gsm8k-tot.pdl new file mode 100644 index 000000000..d9df68868 --- /dev/null +++ b/examples/gsm8k/gsm8k-tot.pdl @@ -0,0 +1,208 @@ +description: Grade School Math -- for every problem we generate a plan, then exectute and evaluate it. +defs: + problems: + read: ./test.jsonl + parser: jsonl + + MAX_ITERATIONS: 10 + N: 3 + + + majority_vote: + function: + numbers: [float] + return: + lang: python + code: | + from collections import Counter + frequency = Counter( ${ numbers }) + most_frequent = max(frequency, key=frequency.get) + result = most_frequent + + majority_vote_json: + function: + results: [{ "result": float }] + return: + lastOf: + - lang: python + def: numbers + code: | + result = [o["result"] for o in ${ results }] + - call: ${ majority_vote } + args: + numbers: ${ numbers } + + planning: + function: + problem: str + return: + text: + - | + Please generate a high-level plan for solving the following question. + As the first step, just say what method and idea you will use to solve the question. + You can reorganize the information in the question. Do not do the actual calculation. + Keep your response concise and within 80 words. + + - "\nProblem:\n" + - ${ problem } + - "\n" + - model: ollama/granite3.2:8b + parameters: + temperature: 0.7 + top_p: 0.85 + + solve: + function: + plan: str + return: + text: + - ${ plan } + - | + + The plan looks good! Now, use real numbers and do the calculation. Please solve the question + step-by-step according to the high-level plan. Give me the final answer. Make your response short. + - "\nThe answer is:\n" + - model: ollama/granite3.2:8b + parameters: + temperature: 0.7 + top_p: 0.85 + + extract_final_answer: + function: + solution: str + return: + lastOf: + - ${ solution } + - Extract the result from the above solution into a JSON object with field "result" and a float as value. Remove any dollar signs or other symbols. + - model: ollama/granite3.2:8b + parser: json + def: result + spec: { "result": float } + fallback: + data: + result: 0 + + compare_to_ground_truth: + function: + result: float + truth: str + return: + lastOf: + - data: ${ truth } + parser: + regex: "(.|\n)*#### (?P([0-9])*)\n*" + spec: + answer: str + def: ground_truth + - if: ${ result|float == ground_truth.answer|float} + then: + 1 + else: + 0 + +text: +- for: + problem: ${ problems } + repeat: + repeat: + call: ${ planning } + args: + pdl_context: [] + problem: ${ problem.question } + max_iterations: ${ N } + join: + as: array + max_iterations: ${ MAX_ITERATIONS } + def: plans + join: + as: array + +- for: + plans_for_problem: ${ plans } + repeat: + for: + plan: ${ plans_for_problem } + repeat: + repeat: + call: ${ solve } + args: + pdl_context: [] + plan: ${ plan } + max_iterations: ${ N } + join: + as: array + join: + as: array + max_iterations: ${ MAX_ITERATIONS } + def: solutions + join: + as: array + +- for: + solution: ${ solutions } + repeat: + for: + solutions_for_problem: ${ solution } + repeat: + for: + solution_for_problem: ${ solutions_for_problem } + repeat: + call: ${ extract_final_answer } + args: + pdl_context: [] + solution: ${ solution_for_problem } + max_iterations: ${ N } + join: + as: array + join: + as: array + max_iterations: ${ MAX_ITERATIONS } + def: results + join: + as: array + +- for: + all_results_for_problem: ${ results } + repeat: + for: + results_for_problem: ${ all_results_for_problem } + repeat: + call: ${ majority_vote_json } + args: + pdl_context: [] + results: ${ results_for_problem } + max_iterations: ${ N } + join: + as: array + max_iterations: ${ MAX_ITERATIONS } + def: per_plan_votes + join: + as: array + +- for: + votes: ${ per_plan_votes } + repeat: + call: ${ majority_vote } + args: + pdl_context: [] + numbers: ${ votes } + max_iterations: ${ MAX_ITERATIONS } + join: + as: array + def: results + +- for: + result: ${ results } + problem: ${ problems[:MAX_ITERATIONS] } + repeat: + call: ${ compare_to_ground_truth } + args: + pdl_context: [] + result: ${ result } + truth: ${ problem.answer } + max_iterations: ${ MAX_ITERATIONS } + def: stats + join: + as: array + +- "\nAccuracy: ${ stats|sum / MAX_ITERATIONS * 100}% " \ No newline at end of file From dcf4e9b3eb5c5d85bb9bda376114d881971155fa Mon Sep 17 00:00:00 2001 From: Nick Mitchell Date: Wed, 9 Apr 2025 10:05:41 -0400 Subject: [PATCH 05/39] feat: rust interpreter support for modelResponse, ollama-rs tooling calling, and no-stream This adds a test for the bee compiler, and that test also runs the compiled code. Signed-off-by: Nick Mitchell --- pdl-live-react/src-tauri/Cargo.lock | 4 +- pdl-live-react/src-tauri/Cargo.toml | 4 +- pdl-live-react/src-tauri/src/cli.rs | 7 +- pdl-live-react/src-tauri/src/compile/beeai.rs | 24 ++- .../src-tauri/src/pdl/interpreter.rs | 202 ++++++++++++------ .../src-tauri/src/pdl/interpreter_tests.rs | 28 ++- pdl-live-react/src-tauri/tauri.conf.json | 3 + pdl-live-react/src-tauri/tests/data/bee_1.py | 64 ++++++ 8 files changed, 254 insertions(+), 82 deletions(-) create mode 100644 pdl-live-react/src-tauri/tests/data/bee_1.py diff --git a/pdl-live-react/src-tauri/Cargo.lock b/pdl-live-react/src-tauri/Cargo.lock index 1779e2668..f3f6acca7 100644 --- a/pdl-live-react/src-tauri/Cargo.lock +++ b/pdl-live-react/src-tauri/Cargo.lock @@ -3168,8 +3168,7 @@ dependencies = [ [[package]] name = "ollama-rs" version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a4b4750770584c8b4a643d0329e7bedacc4ecf68b7c7ac3e1fec2bafd6312f7" +source = "git+https://github.com/starpit/ollama-rs.git?branch=tools-pub-7#90820473b4b6e037a82f4c5bd2e254935bae8da7" dependencies = [ "async-stream", "log", @@ -3376,6 +3375,7 @@ dependencies = [ "rayon", "rustpython-stdlib", "rustpython-vm", + "schemars", "serde", "serde_json", "serde_norway", diff --git a/pdl-live-react/src-tauri/Cargo.toml b/pdl-live-react/src-tauri/Cargo.toml index 62534f2e5..a26aa414a 100644 --- a/pdl-live-react/src-tauri/Cargo.toml +++ b/pdl-live-react/src-tauri/Cargo.toml @@ -35,7 +35,8 @@ base64ct = { version = "1.7.1", features = ["alloc"] } dirs = "6.0.0" serde_norway = "0.9.42" minijinja = { version = "2.9.0", features = ["custom_syntax"] } -ollama-rs = { version = "0.3.0", features = ["stream"] } +#ollama-rs = { version = "0.3.0", features = ["stream"] } +ollama-rs = { git = "https://github.com/starpit/ollama-rs.git", branch = "tools-pub-7", features = ["stream"] } owo-colors = "4.2.0" rustpython-vm = "0.4.0" async-recursion = "1.1.1" @@ -43,6 +44,7 @@ tokio-stream = "0.1.17" tokio = { version = "1.44.1", features = ["io-std"] } indexmap = { version = "2.9.0", features = ["serde"] } rustpython-stdlib = { version = "0.4.0", features = ["zlib"] } +schemars = "0.8.22" [target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies] tauri-plugin-cli = "2" diff --git a/pdl-live-react/src-tauri/src/cli.rs b/pdl-live-react/src-tauri/src/cli.rs index 06e15541b..2e2909031 100644 --- a/pdl-live-react/src-tauri/src/cli.rs +++ b/pdl-live-react/src-tauri/src/cli.rs @@ -34,7 +34,7 @@ pub fn setup(app: &mut tauri::App) -> Result> let args = compile_subcommand_matches.matches.args; match compile_subcommand_matches.name.as_str() { - "beeai" => compile::beeai::compile( + "beeai" => compile::beeai::compile_to_file( args.get("source") .and_then(|a| a.value.as_str()) .expect("valid positional source arg"), @@ -60,6 +60,11 @@ pub fn setup(app: &mut tauri::App) -> Result> .and_then(|a| a.value.as_bool()) .or(Some(false)) == Some(true), + subcommand_args + .get("no-stream") + .and_then(|a| a.value.as_bool()) + .or(Some(false)) + == Some(false), ) .and_then(|_trace| Ok(true)), "run" => run_pdl_program( diff --git a/pdl-live-react/src-tauri/src/compile/beeai.rs b/pdl-live-react/src-tauri/src/compile/beeai.rs index c686f2119..bd589fc94 100644 --- a/pdl-live-react/src-tauri/src/compile/beeai.rs +++ b/pdl-live-react/src-tauri/src/compile/beeai.rs @@ -348,15 +348,7 @@ fn python_source_to_json(source_file_path: &str, debug: bool) -> Result Result<(), Box> { - if debug { - eprintln!("Compiling beeai {} to {}", source_file_path, output_path); - } - +pub fn compile(source_file_path: &str, debug: bool) -> Result> { let file = match Path::new(source_file_path) .extension() .and_then(OsStr::to_str) @@ -559,6 +551,20 @@ asyncio.run(invoke()) text: body, }); + Ok(pdl) +} + +pub fn compile_to_file( + source_file_path: &str, + output_path: &str, + debug: bool, +) -> Result<(), Box> { + if debug { + eprintln!("Compiling beeai {} to {}", source_file_path, output_path); + } + + let pdl = compile(source_file_path, debug)?; + match output_path { "-" => println!("{}", to_string(&pdl)?), _ => { diff --git a/pdl-live-react/src-tauri/src/pdl/interpreter.rs b/pdl-live-react/src-tauri/src/pdl/interpreter.rs index 112cccf6b..a0e9aac93 100644 --- a/pdl-live-react/src-tauri/src/pdl/interpreter.rs +++ b/pdl-live-react/src-tauri/src/pdl/interpreter.rs @@ -15,13 +15,13 @@ use tokio_stream::StreamExt; use ollama_rs::{ generation::{ chat::{request::ChatMessageRequest, ChatMessage, ChatMessageResponse, MessageRole}, - tools::ToolInfo, + tools::{ToolFunctionInfo, ToolInfo, ToolType}, }, models::ModelOptions, Ollama, }; -use serde_json::{from_str, to_string, Value}; +use serde_json::{from_str, json, to_string, Value}; use serde_norway::{from_reader, from_str as from_yaml_str}; use crate::pdl::ast::{ @@ -45,6 +45,7 @@ struct Interpreter<'a> { scope: Vec, debug: bool, emit: bool, + stream: bool, } impl<'a> Interpreter<'a> { @@ -67,6 +68,7 @@ impl<'a> Interpreter<'a> { scope: vec![Scope::new()], debug: false, emit: true, + stream: true, } } @@ -76,14 +78,13 @@ impl<'a> Interpreter<'a> { context: Context, emit: bool, ) -> Interpretation { - if self.debug { + /* if self.debug { if let Some(scope) = self.scope.last() { if scope.len() > 0 { eprintln!("Run with Scope {:?}", scope); } } - } - + } */ let prior_emit = self.emit; self.emit = emit; @@ -118,7 +119,9 @@ impl<'a> Interpreter<'a> { }?; if match program { - PdlBlock::Call(_) | PdlBlock::Model(_) => false, + PdlBlock::Text(_) | PdlBlock::LastOf(_) | PdlBlock::Call(_) | PdlBlock::Model(_) => { + false + } _ => self.emit, } { println!("{}", pretty_print(&messages)); @@ -150,7 +153,7 @@ impl<'a> Interpreter<'a> { let backup = result.clone(); Ok(from_str(&result).unwrap_or_else(|err| { if self.debug { - eprintln!("Treating as plain string {}", &result); + eprintln!("Treating as plain string {}", result); eprintln!("... due to {}", err); } backup.into() @@ -332,7 +335,10 @@ impl<'a> Interpreter<'a> { self.run(&c.function.return_, context.clone()).await } - _ => Err(Box::from(format!("call of non-function {:?}", &block.call))), + x => Err(Box::from(format!( + "call of non-function {:?}->{:?}", + block.call, x + ))), }; if let Some(_) = block.args { @@ -438,10 +444,36 @@ impl<'a> Interpreter<'a> { 0.0 }; - let tools = if let Some(Value::Array(_tools)) = parameters.get(&"tools".to_string()) { - // TODO - //tools.into_iter().map(|tool| function!()).collect() - vec![] + let tools = if let Some(Value::Array(tools)) = parameters.get("tools") { + tools + .into_iter() + .filter_map(|tool| tool.get("function")) + .filter_map(|tool| { + //from_str(&to_string(tool)?) + match ( + tool.get("name"), + tool.get("description"), + tool.get("parameters"), + ) { + ( + Some(Value::String(name)), + Some(Value::String(description)), + Some(Value::Object(parameters)), + ) => Some(ToolInfo { + tool_type: ToolType::Function, + function: ToolFunctionInfo { + name: name.to_string(), + description: description.to_string(), + parameters: schemars::schema_for_value!(parameters), + }, + }), + _ => { + eprintln!("Error: tools do not satisfy schema {:?}", tool); + None + } + } + }) + .collect() } else { vec![] }; @@ -517,7 +549,7 @@ impl<'a> Interpreter<'a> { pdl_model if pdl_model.starts_with("ollama/") || pdl_model.starts_with("ollama_chat/") => { - let ollama = Ollama::default(); + let mut ollama = Ollama::default(); let model = if pdl_model.starts_with("ollama/") { &pdl_model[7..] } else { @@ -526,7 +558,8 @@ impl<'a> Interpreter<'a> { let (options, tools) = self.to_ollama_model_options(&block.parameters); if self.debug { - println!("Model options {:?}", options); + eprintln!("Model options {:?} {:?}", block.description, options); + eprintln!("Model tools {:?} {:?}", block.description, tools); } let input_messages = match &block.input { @@ -542,7 +575,7 @@ impl<'a> Interpreter<'a> { Some(x) => x, None => (&ChatMessage::user("".into()), &[]), }; - let history = Vec::from(history_slice); + let mut history = Vec::from(history_slice); if self.debug { eprintln!( "Ollama {:?} model={:?} prompt={:?} history={:?}", @@ -560,52 +593,62 @@ impl<'a> Interpreter<'a> { let req = ChatMessageRequest::new(model.into(), vec![prompt.clone()]) .options(options) .tools(tools); - /* if we ever want non-streaming: - let res = ollama - .send_chat_messages_with_history( - &mut history, - req, - //ollama.generate(GenerationRequest::new(model.into(), prompt), - ) - .await?; - // dbg!("Model result {:?}", &res); - let mut trace = block.clone(); - trace.pdl_result = Some(res.message.content.clone()); - - if let Some(usage) = res.final_data { - trace.pdl_usage = Some(PdlUsage { - prompt_tokens: usage.prompt_eval_count, - prompt_nanos: usage.prompt_eval_duration, - completion_tokens: usage.eval_count, - completion_nanos: usage.eval_duration, - }); - } - // dbg!(history); - Ok((vec![res.message], PdlBlock::Model(trace))) - */ - let mut stream = ollama - .send_chat_messages_with_history_stream( - Arc::new(Mutex::new(history)), - req, - //ollama.generate(GenerationRequest::new(model.into(), prompt), - ) - .await?; - // dbg!("Model result {:?}", &res); - - let mut last_res: Option = None; - let mut response_string = String::new(); - let mut stdout = stdout(); - stdout.write_all(b"\x1b[1mAssistant: \x1b[0m").await?; - while let Some(Ok(res)) = stream.next().await { - stdout.write_all(b"\x1b[32m").await?; // green - stdout.write_all(res.message.content.as_bytes()).await?; - stdout.flush().await?; - stdout.write_all(b"\x1b[0m").await?; // reset color - response_string += res.message.content.as_str(); - last_res = Some(res); + let (last_res, response_string) = if !self.stream { + let res = ollama + .send_chat_messages_with_history(&mut history, req) + .await?; + let response_string = res.message.content.clone(); + print!("{}", response_string); + (Some(res), response_string) + } else { + let mut stream = ollama + .send_chat_messages_with_history_stream( + Arc::new(Mutex::new(history)), + req, + //ollama.generate(GenerationRequest::new(model.into(), prompt), + ) + .await?; + // dbg!("Model result {:?}", &res); + + let emit = if let Some(_) = &block.model_response { + false + } else { + true + }; + + let mut last_res: Option = None; + let mut response_string = String::new(); + let mut stdout = stdout(); + if emit { + stdout.write_all(b"\x1b[1mAssistant: \x1b[0m").await?; + } + while let Some(Ok(res)) = stream.next().await { + if emit { + stdout.write_all(b"\x1b[32m").await?; // green + stdout.write_all(res.message.content.as_bytes()).await?; + stdout.flush().await?; + stdout.write_all(b"\x1b[0m").await?; // reset color + } + response_string += res.message.content.as_str(); + last_res = Some(res); + } + if emit { + stdout.write_all(b"\n").await?; + } + + (last_res, response_string) + }; + + if let Some(_) = &block.model_response { + if let Some(ref res) = last_res { + self.def( + &block.model_response, + &self.resultify_as_litellm(&from_str(&to_string(&res)?)?), + &None, + )?; + } } - stdout.write_all(b"\n").await?; let mut trace = block.clone(); trace.pdl_result = Some(response_string.clone()); @@ -653,6 +696,15 @@ impl<'a> Interpreter<'a> { } } + /// Transform a JSON Value into a PdlResult object that is compatible with litellm's model response schema + fn resultify_as_litellm(&self, value: &Value) -> PdlResult { + self.resultify(&json!({ + "choices": [ + value + ] + })) + } + /// Run a PdlBlock::Data async fn run_data(&mut self, block: &DataBlock, _context: Context) -> Interpretation { if self.debug { @@ -821,7 +873,7 @@ impl<'a> Interpreter<'a> { while let Some(block) = iter.next() { // run each element of the Text block let (this_result, this_messages, trace) = - self.run_quiet(&block, input_messages.clone()).await?; + self.run(&block, input_messages.clone()).await?; input_messages.extend(this_messages.clone()); output_results.push(this_result); @@ -918,17 +970,29 @@ impl<'a> Interpreter<'a> { } } -pub async fn run(program: &PdlBlock, cwd: Option, debug: bool) -> Interpretation { +pub async fn run( + program: &PdlBlock, + cwd: Option, + debug: bool, + stream: bool, +) -> Interpretation { let mut interpreter = Interpreter::new(); interpreter.debug = debug; + interpreter.stream = stream; if let Some(cwd) = cwd { interpreter.cwd = cwd }; interpreter.run(&program, vec![]).await } -pub fn run_sync(program: &PdlBlock, cwd: Option, debug: bool) -> InterpretationSync { - tauri::async_runtime::block_on(run(program, cwd, debug)) +#[allow(dead_code)] +pub fn run_sync( + program: &PdlBlock, + cwd: Option, + debug: bool, + stream: bool, +) -> InterpretationSync { + tauri::async_runtime::block_on(run(program, cwd, debug, stream)) .map_err(|err| Box::::from(err.to_string())) } @@ -938,28 +1002,30 @@ pub fn parse_file(path: &PathBuf) -> Result { .map_err(|err| Box::::from(err.to_string())) } -pub async fn run_file(source_file_path: &str, debug: bool) -> Interpretation { +pub async fn run_file(source_file_path: &str, debug: bool, stream: bool) -> Interpretation { let path = PathBuf::from(source_file_path); let cwd = path.parent().and_then(|cwd| Some(cwd.to_path_buf())); let program = parse_file(&path)?; crate::pdl::pull::pull_if_needed(&program).await?; - run(&program, cwd, debug).await + run(&program, cwd, debug, stream).await } -pub fn run_file_sync(source_file_path: &str, debug: bool) -> InterpretationSync { - tauri::async_runtime::block_on(run_file(source_file_path, debug)) +pub fn run_file_sync(source_file_path: &str, debug: bool, stream: bool) -> InterpretationSync { + tauri::async_runtime::block_on(run_file(source_file_path, debug, stream)) .map_err(|err| Box::::from(err.to_string())) } pub async fn run_string(source: &str, debug: bool) -> Interpretation { - run(&from_yaml_str(source)?, None, debug).await + run(&from_yaml_str(source)?, None, debug, true).await } +#[allow(dead_code)] pub async fn run_json(source: Value, debug: bool) -> Interpretation { run_string(&to_string(&source)?, debug).await } +#[allow(dead_code)] pub fn run_json_sync(source: Value, debug: bool) -> InterpretationSync { tauri::async_runtime::block_on(run_json(source, debug)) .map_err(|err| Box::::from(err.to_string())) diff --git a/pdl-live-react/src-tauri/src/pdl/interpreter_tests.rs b/pdl-live-react/src-tauri/src/pdl/interpreter_tests.rs index 7d9f3c278..436aef3cc 100644 --- a/pdl-live-react/src-tauri/src/pdl/interpreter_tests.rs +++ b/pdl-live-react/src-tauri/src/pdl/interpreter_tests.rs @@ -15,7 +15,7 @@ mod tests { #[test] fn string() -> Result<(), Box> { - let (_, messages, _) = run(&"hello".into(), None, false)?; + let (_, messages, _) = run(&"hello".into(), None, false, true)?; assert_eq!(messages.len(), 1); assert_eq!(messages[0].role, MessageRole::User); assert_eq!(messages[0].content, "hello"); @@ -28,6 +28,7 @@ mod tests { &PdlBlock::Model(ModelBlock::new(DEFAULT_MODEL).input_str("hello").build()), None, false, + true, )?; assert_eq!(messages.len(), 1); assert_eq!(messages[0].role, MessageRole::Assistant); @@ -534,4 +535,29 @@ mod tests { assert_eq!(messages[0].content, "Bye!"); Ok(()) } + + #[test] + fn bee_1() -> Result<(), Box> { + let program = crate::compile::beeai::compile("./tests/data/bee_1.py", false)?; + let (_, messages, _) = run(&program, None, false, false)?; + assert_eq!(messages.len(), 9); + assert!( + messages.iter().any(|m| m.role == MessageRole::User + && m.content == "Provide a short history of Saint-Tropez."), + "Could not find message user:Provide a short history of Saint-Tropez. in {:?}", + messages + ); + assert!( + messages.iter().any(|m| m.role == MessageRole::System + && m.content + == "You can combine disparate information into a final coherent summary."), + "Could not find message system:Provide a short history of Saint-Tropez. in {:?}", + messages + ); + // assert!(messages.iter().any(|m| m.role == MessageRole::Assistant && m.content.contains("a renowned French Riviera town")), "Could not find message assistant:a renowned French Riviera town in {:?}", messages); + //assert_eq!(true, messages.iter().any(|m| m.role == MessageRole::Assistant && m.content.contains("I'll use the OpenMeteoTool"))); + //assert_eq!(true, messages.iter().any(|m| m.role == MessageRole::Assistant && m.content.contains("The current temperature in Saint-Tropez"))); + + Ok(()) + } } diff --git a/pdl-live-react/src-tauri/tauri.conf.json b/pdl-live-react/src-tauri/tauri.conf.json index e35d1fd32..108356584 100644 --- a/pdl-live-react/src-tauri/tauri.conf.json +++ b/pdl-live-react/src-tauri/tauri.conf.json @@ -55,6 +55,9 @@ "required": true, "takesValue": true }, + { + "name": "no-stream" + }, { "name": "debug", "short": "g" diff --git a/pdl-live-react/src-tauri/tests/data/bee_1.py b/pdl-live-react/src-tauri/tests/data/bee_1.py new file mode 100644 index 000000000..85fabf131 --- /dev/null +++ b/pdl-live-react/src-tauri/tests/data/bee_1.py @@ -0,0 +1,64 @@ +import asyncio + +from beeai_framework.backend.chat import ChatModel +from beeai_framework.tools.search.wikipedia import WikipediaTool +from beeai_framework.tools.weather.openmeteo import OpenMeteoTool +from beeai_framework.workflows.agent import AgentWorkflow, AgentWorkflowInput + + +async def main() -> None: + llm = ChatModel.from_name("ollama:granite3.2:2b") + workflow = AgentWorkflow(name="Smart assistant") + + workflow.add_agent( + name="Researcher", + role="A diligent researcher.", + instructions="You look up and provide information about a specific topic.", + tools=[WikipediaTool()], + llm=llm, + ) + + workflow.add_agent( + name="WeatherForecaster", + role="A weather reporter.", + instructions="You provide detailed weather reports.", + tools=[OpenMeteoTool()], + llm=llm, + ) + + workflow.add_agent( + name="DataSynthesizer", + role="A meticulous and creative data synthesizer", + instructions="You can combine disparate information into a final coherent summary.", + llm=llm, + ) + + location = "Saint-Tropez" + + response = await workflow.run( + inputs=[ + AgentWorkflowInput( + prompt=f"Provide a short history of {location}.", + ), + AgentWorkflowInput( + prompt=f"Provide a comprehensive weather summary for {location} today.", + expected_output="Essential weather details such as chance of rain, temperature and wind. Only report information that is available.", + ), + AgentWorkflowInput( + prompt=f"Summarize the historical and weather data for {location}.", + expected_output=f"A paragraph that describes the history of {location}, followed by the current weather conditions.", + ), + ] + ).on( + "success", + lambda data, event: print( + f"\n-> Step '{data.step}' has been completed with the following outcome.\n\n{data.state.final_answer}" + ), + ) + + print("==== Final Answer ====") + print(response.result.final_answer) + + +if __name__ == "__main__": + asyncio.run(main()) From 257f0a61c77694c053f72a002dcc8e98ef76dea5 Mon Sep 17 00:00:00 2001 From: Nick Mitchell Date: Wed, 9 Apr 2025 11:42:08 -0400 Subject: [PATCH 06/39] chore: minor code cleanups to rust interpreter Signed-off-by: Nick Mitchell --- .../src-tauri/src/pdl/interpreter.rs | 24 +++++++++---------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/pdl-live-react/src-tauri/src/pdl/interpreter.rs b/pdl-live-react/src-tauri/src/pdl/interpreter.rs index a0e9aac93..3cd1d53cd 100644 --- a/pdl-live-react/src-tauri/src/pdl/interpreter.rs +++ b/pdl-live-react/src-tauri/src/pdl/interpreter.rs @@ -1,10 +1,7 @@ // use ::std::cell::LazyCell; use ::std::collections::HashMap; -use ::std::env::current_dir; use ::std::error::Error; -use ::std::fs::{read_to_string as read_file_to_string, File}; use ::std::path::PathBuf; -use std::sync::{Arc, Mutex}; use async_recursion::async_recursion; use minijinja::{syntax::SyntaxConfig, Environment}; @@ -32,7 +29,8 @@ use crate::pdl::ast::{ }; type Context = Vec; -type PdlError = Box; +type ThreadSafeError = dyn Error + Send + Sync; +type PdlError = Box; type Interpretation = Result<(PdlResult, Context, PdlBlock), PdlError>; type InterpretationSync = Result<(PdlResult, Context, PdlBlock), Box>; @@ -62,7 +60,7 @@ impl<'a> Interpreter<'a> { Self { // batch: 0, // role: Role::User, - cwd: current_dir().unwrap_or(PathBuf::from("/")), + cwd: ::std::env::current_dir().unwrap_or(PathBuf::from("/")), // id_stack: vec![], jinja_env: jinja_env, scope: vec![Scope::new()], @@ -289,7 +287,7 @@ impl<'a> Interpreter<'a> { ); let buffer = match &block.read { - StringOrNull::String(file_path) => read_file_to_string(self.path_to(file_path))?, + StringOrNull::String(file_path) => ::std::fs::read_to_string(self.path_to(file_path))?, StringOrNull::Null => { let mut buffer = String::new(); let mut bytes_read = ::std::io::stdin().read_line(&mut buffer)?; @@ -326,7 +324,7 @@ impl<'a> Interpreter<'a> { self.push_and_extend_scope_with(m, c.scope); Ok(()) } - x => Err(Box::::from(format!( + x => Err(PdlError::from(format!( "Call arguments not a map: {:?}", x ))), @@ -504,7 +502,7 @@ impl<'a> Interpreter<'a> { "".to_owned(), ) { Ok(x) => Ok(x), - Err(exc) => Err(Box::::from(format!( + Err(exc) => Err(PdlError::from(format!( "Syntax error in Python code {:?}", exc ))), @@ -515,7 +513,7 @@ impl<'a> Interpreter<'a> { Ok(_) => Ok(()), Err(exc) => { vm.print_exception(exc); - Err(Box::::from( + Err(PdlError::from( "Error executing Python code", )) } @@ -527,7 +525,7 @@ impl<'a> Interpreter<'a> { Ok(x) => Ok(x), Err(exc) => { vm.print_exception(exc); - Err(Box::::from( + Err(PdlError::from( "Unable to stringify Python 'result' value", )) } @@ -604,7 +602,7 @@ impl<'a> Interpreter<'a> { } else { let mut stream = ollama .send_chat_messages_with_history_stream( - Arc::new(Mutex::new(history)), + ::std::sync::Arc::new(::std::sync::Mutex::new(history)), req, //ollama.generate(GenerationRequest::new(model.into(), prompt), ) @@ -998,8 +996,8 @@ pub fn run_sync( /// Read in a file from disk and parse it as a PDL program pub fn parse_file(path: &PathBuf) -> Result { - from_reader(File::open(path)?) - .map_err(|err| Box::::from(err.to_string())) + from_reader(::std::fs::File::open(path)?) + .map_err(|err| PdlError::from(err.to_string())) } pub async fn run_file(source_file_path: &str, debug: bool, stream: bool) -> Interpretation { From 828cefac64cfbec53bf73c975c1fdc0f44e1b851 Mon Sep 17 00:00:00 2001 From: Nick Mitchell Date: Wed, 9 Apr 2025 11:44:22 -0400 Subject: [PATCH 07/39] test: update github action rust interpreter test to remove ollama pull This will force a test of the built-in rust-based pull logic. Signed-off-by: Nick Mitchell --- .github/workflows/rust-interpreter.yml | 2 -- pdl-live-react/src-tauri/src/pdl/interpreter.rs | 3 ++- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/rust-interpreter.yml b/.github/workflows/rust-interpreter.yml index f9827e813..18c0de249 100644 --- a/.github/workflows/rust-interpreter.yml +++ b/.github/workflows/rust-interpreter.yml @@ -30,7 +30,5 @@ jobs: npm ci & sudo apt update && sudo apt install -y libgtk-3-dev libwebkit2gtk-4.1-dev librsvg2-dev patchelf at-spi2-core & (curl -fsSL https://ollama.com/install.sh | sudo -E sh && sleep 2) wait - # todo: do this in rust - ollama pull granite3.2:2b - name: Run interpreter tests run: npm run test:interpreter diff --git a/pdl-live-react/src-tauri/src/pdl/interpreter.rs b/pdl-live-react/src-tauri/src/pdl/interpreter.rs index 3cd1d53cd..21dbb96ea 100644 --- a/pdl-live-react/src-tauri/src/pdl/interpreter.rs +++ b/pdl-live-react/src-tauri/src/pdl/interpreter.rs @@ -974,6 +974,8 @@ pub async fn run( debug: bool, stream: bool, ) -> Interpretation { + crate::pdl::pull::pull_if_needed(&program).await?; + let mut interpreter = Interpreter::new(); interpreter.debug = debug; interpreter.stream = stream; @@ -1005,7 +1007,6 @@ pub async fn run_file(source_file_path: &str, debug: bool, stream: bool) -> Inte let cwd = path.parent().and_then(|cwd| Some(cwd.to_path_buf())); let program = parse_file(&path)?; - crate::pdl::pull::pull_if_needed(&program).await?; run(&program, cwd, debug, stream).await } From a8cac9be5b102a05c09ea2aa250ca95c45e87de1 Mon Sep 17 00:00:00 2001 From: Nick Mitchell Date: Wed, 9 Apr 2025 12:54:32 -0400 Subject: [PATCH 08/39] fix: add kind tags to rust ast blocks This also updates the interpreter so that we get a compile time error if the match-over-block type is not exhaustive. Signed-off-by: Nick Mitchell --- pdl-live-react/src-tauri/src/pdl/ast.rs | 23 +++++++++++++++++++ .../src-tauri/src/pdl/interpreter.rs | 22 ++++++++---------- 2 files changed, 32 insertions(+), 13 deletions(-) diff --git a/pdl-live-react/src-tauri/src/pdl/ast.rs b/pdl-live-react/src-tauri/src/pdl/ast.rs index 5fd8d24ad..aad959cb2 100644 --- a/pdl-live-react/src-tauri/src/pdl/ast.rs +++ b/pdl-live-react/src-tauri/src/pdl/ast.rs @@ -53,6 +53,7 @@ pub enum PdlType { /// Call a function #[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(tag = "kind", rename = "call")] pub struct CallBlock { /// Function to call pub call: String, @@ -91,6 +92,7 @@ pub trait SequencingBlock { /// Return the value of the last block if the list of blocks #[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(tag = "kind", rename = "lastOf")] pub struct LastOfBlock { /// Sequence of blocks to execute #[serde(rename = "lastOf")] @@ -158,6 +160,7 @@ impl SequencingBlock for LastOfBlock { /// Create the concatenation of the stringify version of the result of /// each block of the list of blocks. #[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(tag = "kind", rename = "text")] pub struct TextBlock { /// Body of the text pub text: Vec, @@ -260,6 +263,7 @@ impl From> for TextBlock { } #[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(tag = "kind", rename = "function")] pub struct FunctionBlock { pub function: HashMap, #[serde(rename = "return")] @@ -279,6 +283,7 @@ pub struct PdlUsage { } #[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(tag = "kind", rename = "model")] pub struct ModelBlock { #[serde(skip_serializing_if = "Option::is_none")] pub description: Option, @@ -352,6 +357,7 @@ pub enum ListOrString { /// "${ name }'s number is ${ number }\\n" /// ``` #[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(tag = "kind", rename = "repeat")] pub struct RepeatBlock { /// Arrays to iterate over #[serde(rename = "for")] @@ -363,6 +369,7 @@ pub struct RepeatBlock { /// Create a message #[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(tag = "kind", rename = "message")] pub struct MessageBlock { /// Role of associated to the message, e.g. User or Assistant pub role: Role, @@ -386,6 +393,7 @@ pub struct MessageBlock { /// block. If the body of the object is an array, the resulting object /// is the union of the objects computed by each element of the array. #[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(tag = "kind", rename = "object")] pub struct ObjectBlock { pub object: HashMap, } @@ -413,6 +421,7 @@ pub struct ObjectBlock { /// def: EXTRACTED_GROUND_TRUTH /// ``` #[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(tag = "kind")] pub struct DataBlock { pub data: Value, @@ -438,6 +447,7 @@ pub struct DataBlock { /// result = random.randint(1, 20) /// ``` #[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(tag = "kind", rename = "code")] pub struct PythonCodeBlock { pub lang: String, pub code: String, @@ -464,6 +474,7 @@ pub enum StringOrNull { /// parser: yaml /// ``` #[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(tag = "kind", rename = "read")] pub struct ReadBlock { /// Name of the file to read. If `None`, read the standard input. pub read: StringOrNull, @@ -500,6 +511,7 @@ pub enum StringOrBoolean { /// then: You won! /// ``` #[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(tag = "kind", rename = "if")] pub struct IfBlock { /// The condition to check #[serde(rename = "if")] @@ -519,6 +531,7 @@ pub struct IfBlock { /// Return the array of values computed by each block of the list of blocks #[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(tag = "kind", rename = "array")] pub struct ArrayBlock { /// Elements of the array pub array: Vec, @@ -526,6 +539,7 @@ pub struct ArrayBlock { /// Include a PDL file #[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(tag = "kind", rename = "include")] pub struct IncludeBlock { /// Name of the file to include. pub include: String, @@ -533,6 +547,7 @@ pub struct IncludeBlock { /// Import a PDL file #[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(tag = "kind", rename = "import")] pub struct ImportBlock { /// Name of the file to include. pub import: String, @@ -540,10 +555,12 @@ pub struct ImportBlock { /// Block containing only defs #[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(tag = "kind", rename = "empty")] pub struct EmptyBlock { pub defs: IndexMap, } +/// A PDL program/sub-program consists of either a literal (string, number, boolean) or some kind of structured block #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(untagged)] pub enum PdlBlock { @@ -570,6 +587,12 @@ pub enum PdlBlock { Empty(EmptyBlock), } +impl From for PdlBlock { + fn from(b: bool) -> Self { + PdlBlock::Bool(b) + } +} + impl From<&str> for PdlBlock { fn from(s: &str) -> Self { PdlBlock::String(s.into()) diff --git a/pdl-live-react/src-tauri/src/pdl/interpreter.rs b/pdl-live-react/src-tauri/src/pdl/interpreter.rs index 21dbb96ea..72c9af73f 100644 --- a/pdl-live-react/src-tauri/src/pdl/interpreter.rs +++ b/pdl-live-react/src-tauri/src/pdl/interpreter.rs @@ -87,6 +87,11 @@ impl<'a> Interpreter<'a> { self.emit = emit; let (result, messages, trace) = match program { + PdlBlock::Bool(b) => Ok(( + b.into(), + vec![ChatMessage::user(format!("{b}"))], + PdlBlock::Bool(b.clone()), + )), PdlBlock::Number(n) => Ok(( n.clone().into(), vec![ChatMessage::user(format!("{n}"))], @@ -113,7 +118,6 @@ impl<'a> Interpreter<'a> { PdlBlock::Text(block) => self.run_sequence(block, context).await, PdlBlock::Array(block) => self.run_array(block, context).await, PdlBlock::Message(block) => self.run_message(block, context).await, - _ => Err(Box::from(format!("Unsupported block {:?}", program))), }?; if match program { @@ -324,10 +328,7 @@ impl<'a> Interpreter<'a> { self.push_and_extend_scope_with(m, c.scope); Ok(()) } - x => Err(PdlError::from(format!( - "Call arguments not a map: {:?}", - x - ))), + x => Err(PdlError::from(format!("Call arguments not a map: {:?}", x))), }?; } @@ -513,9 +514,7 @@ impl<'a> Interpreter<'a> { Ok(_) => Ok(()), Err(exc) => { vm.print_exception(exc); - Err(PdlError::from( - "Error executing Python code", - )) + Err(PdlError::from("Error executing Python code")) } }?; @@ -525,9 +524,7 @@ impl<'a> Interpreter<'a> { Ok(x) => Ok(x), Err(exc) => { vm.print_exception(exc); - Err(PdlError::from( - "Unable to stringify Python 'result' value", - )) + Err(PdlError::from("Unable to stringify Python 'result' value")) } }?; let messages = vec![ChatMessage::user(result_string.as_str().to_string())]; @@ -998,8 +995,7 @@ pub fn run_sync( /// Read in a file from disk and parse it as a PDL program pub fn parse_file(path: &PathBuf) -> Result { - from_reader(::std::fs::File::open(path)?) - .map_err(|err| PdlError::from(err.to_string())) + from_reader(::std::fs::File::open(path)?).map_err(|err| PdlError::from(err.to_string())) } pub async fn run_file(source_file_path: &str, debug: bool, stream: bool) -> Interpretation { From 206cf63f3ff96b32f4d209b0a849fd635170d0f4 Mon Sep 17 00:00:00 2001 From: Nick Mitchell Date: Wed, 9 Apr 2025 18:52:56 -0400 Subject: [PATCH 09/39] fix: update rust interpreter to avoid global emit/scope state Also this fixes lexical scoping. Signed-off-by: Nick Mitchell --- pdl-live-react/src-tauri/src/pdl/ast.rs | 8 +- .../src-tauri/src/pdl/interpreter.rs | 446 ++++++++++-------- .../src-tauri/src/pdl/interpreter_tests.rs | 15 + .../src-tauri/tests/cli/model-input-array.pdl | 2 +- .../tests/cli/model-input-nested.pdl | 7 + .../src-tauri/tests/cli/scoping_1.pdl | 6 + .../src-tauri/tests/cli/scoping_1_wrapper.pdl | 1 + 7 files changed, 281 insertions(+), 204 deletions(-) create mode 100644 pdl-live-react/src-tauri/tests/cli/model-input-nested.pdl create mode 100644 pdl-live-react/src-tauri/tests/cli/scoping_1.pdl create mode 100644 pdl-live-react/src-tauri/tests/cli/scoping_1_wrapper.pdl diff --git a/pdl-live-react/src-tauri/src/pdl/ast.rs b/pdl-live-react/src-tauri/src/pdl/ast.rs index aad959cb2..4a6610725 100644 --- a/pdl-live-react/src-tauri/src/pdl/ast.rs +++ b/pdl-live-react/src-tauri/src/pdl/ast.rs @@ -87,7 +87,7 @@ pub trait SequencingBlock { fn parser(&self) -> &Option; fn to_block(&self) -> PdlBlock; fn result_for(&self, output_results: Vec) -> PdlResult; - fn messages_for(&self, output_messages: Vec) -> Vec; + fn messages_for(&self, output_messages: &Vec) -> Vec; } /// Return the value of the last block if the list of blocks @@ -149,7 +149,7 @@ impl SequencingBlock for LastOfBlock { None => "".into(), } } - fn messages_for(&self, output_messages: Vec) -> Vec { + fn messages_for(&self, output_messages: &Vec) -> Vec { match output_messages.last() { Some(m) => vec![m.clone()], None => vec![], @@ -219,8 +219,8 @@ impl SequencingBlock for TextBlock { .join("\n"), ) } - fn messages_for(&self, output_messages: Vec) -> Vec { - output_messages + fn messages_for(&self, output_messages: &Vec) -> Vec { + output_messages.clone() } } diff --git a/pdl-live-react/src-tauri/src/pdl/interpreter.rs b/pdl-live-react/src-tauri/src/pdl/interpreter.rs index 72c9af73f..b92c164cb 100644 --- a/pdl-live-react/src-tauri/src/pdl/interpreter.rs +++ b/pdl-live-react/src-tauri/src/pdl/interpreter.rs @@ -28,21 +28,57 @@ use crate::pdl::ast::{ StringOrBoolean, StringOrNull, }; -type Context = Vec; +type Messages = Vec; type ThreadSafeError = dyn Error + Send + Sync; type PdlError = Box; -type Interpretation = Result<(PdlResult, Context, PdlBlock), PdlError>; -type InterpretationSync = Result<(PdlResult, Context, PdlBlock), Box>; +type Interpretation = Result<(PdlResult, Messages, PdlBlock), PdlError>; +type InterpretationSync = Result<(PdlResult, Messages, PdlBlock), Box>; + +#[derive(Clone)] +struct State { + emit: bool, + cwd: PathBuf, + scope: Scope, + escaped_variables: Vec, + messages: Messages, +} + +impl State { + fn new() -> Self { + Self { + emit: true, + cwd: ::std::env::current_dir().unwrap_or(PathBuf::from("/")), + scope: Scope::new(), + escaped_variables: vec![], + messages: vec![], + } + } + + fn with_cwd(&self, cwd: PathBuf) -> Self { + let mut s = self.clone(); + s.cwd = cwd; + s + } + + fn with_emit(&self, emit: bool) -> Self { + let mut s = self.clone(); + s.emit = emit; + s + } + + fn extend_scope(&self, scopes: Vec) -> Self { + let mut s = self.clone(); + scopes.into_iter().for_each(|m| s.scope.extend(m)); + s + } +} struct Interpreter<'a> { // batch: u32, // role: Role, - cwd: PathBuf, // id_stack: Vec, jinja_env: Environment<'a>, - scope: Vec, debug: bool, - emit: bool, stream: bool, } @@ -60,32 +96,19 @@ impl<'a> Interpreter<'a> { Self { // batch: 0, // role: Role::User, - cwd: ::std::env::current_dir().unwrap_or(PathBuf::from("/")), // id_stack: vec![], jinja_env: jinja_env, - scope: vec![Scope::new()], debug: false, - emit: true, stream: true, } } - async fn run_with_emit( + async fn _run_with_state( &mut self, program: &PdlBlock, - context: Context, - emit: bool, + state: &mut State, + parent_scope: &mut Scope, ) -> Interpretation { - /* if self.debug { - if let Some(scope) = self.scope.last() { - if scope.len() > 0 { - eprintln!("Run with Scope {:?}", scope); - } - } - } */ - let prior_emit = self.emit; - self.emit = emit; - let (result, messages, trace) = match program { PdlBlock::Bool(b) => Ok(( b.into(), @@ -98,58 +121,69 @@ impl<'a> Interpreter<'a> { PdlBlock::Number(n.clone()), )), PdlBlock::Function(f) => Ok(( - PdlResult::Closure(self.closure(&f)), + PdlResult::Closure(self.closure(&f, state)), vec![], PdlBlock::Function(f.clone()), )), - PdlBlock::String(s) => self.run_string(s, context).await, - PdlBlock::Call(block) => self.run_call(block, context).await, - PdlBlock::Empty(block) => self.run_empty(block, context).await, - PdlBlock::If(block) => self.run_if(block, context).await, - PdlBlock::Import(block) => self.run_import(block, context).await, - PdlBlock::Include(block) => self.run_include(block, context).await, - PdlBlock::Model(block) => self.run_model(block, context).await, - PdlBlock::Data(block) => self.run_data(block, context).await, - PdlBlock::Object(block) => self.run_object(block, context).await, - PdlBlock::PythonCode(block) => self.run_python_code(block, context).await, - PdlBlock::Read(block) => self.run_read(block, context).await, - PdlBlock::Repeat(block) => self.run_repeat(block, context).await, - PdlBlock::LastOf(block) => self.run_sequence(block, context).await, - PdlBlock::Text(block) => self.run_sequence(block, context).await, - PdlBlock::Array(block) => self.run_array(block, context).await, - PdlBlock::Message(block) => self.run_message(block, context).await, + PdlBlock::String(s) => self.run_string(s, state).await, + PdlBlock::Call(block) => self.run_call(block, state).await, + PdlBlock::Empty(block) => self.run_empty(block, state).await, + PdlBlock::If(block) => self.run_if(block, state).await, + PdlBlock::Import(block) => self.run_import(block, state).await, + PdlBlock::Include(block) => self.run_include(block, state).await, + PdlBlock::Model(block) => self.run_model(block, state).await, + PdlBlock::Data(block) => self.run_data(block, state).await, + PdlBlock::Object(block) => self.run_object(block, state).await, + PdlBlock::PythonCode(block) => self.run_python_code(block, state).await, + PdlBlock::Read(block) => self.run_read(block, state).await, + PdlBlock::Repeat(block) => self.run_repeat(block, state).await, + PdlBlock::LastOf(block) => self.run_sequence(block, state).await, + PdlBlock::Text(block) => self.run_sequence(block, state).await, + PdlBlock::Array(block) => self.run_array(block, state).await, + PdlBlock::Message(block) => self.run_message(block, state).await, }?; if match program { - PdlBlock::Text(_) | PdlBlock::LastOf(_) | PdlBlock::Call(_) | PdlBlock::Model(_) => { - false - } - _ => self.emit, + PdlBlock::Message(_) + | PdlBlock::Text(_) + | PdlBlock::Import(_) + | PdlBlock::Include(_) + | PdlBlock::LastOf(_) + | PdlBlock::Call(_) + | PdlBlock::Model(_) => false, + _ => state.emit, } { println!("{}", pretty_print(&messages)); } - self.emit = prior_emit; + + // copy any escaped variable bindings to the parent scope + parent_scope.extend( + state + .escaped_variables + .iter() + .filter_map(|variable| state.scope.remove_entry(&variable.clone())), + ); Ok((result, messages, trace)) } #[async_recursion] - async fn run_quiet(&mut self, program: &PdlBlock, context: Context) -> Interpretation { - self.run_with_emit(program, context, false).await + async fn run_quiet(&mut self, program: &PdlBlock, state: &mut State) -> Interpretation { + self._run_with_state(program, &mut state.with_emit(false), &mut state.scope) + .await } #[async_recursion] - async fn run(&mut self, program: &PdlBlock, context: Context) -> Interpretation { - self.run_with_emit(program, context, self.emit).await + async fn run(&mut self, program: &PdlBlock, state: &mut State) -> Interpretation { + self._run_with_state(program, &mut state.with_emit(true), &mut state.scope) + .await } /// Evaluate String as a Jinja2 expression - fn eval(&self, expr: &String) -> Result { - let result = self - .jinja_env - .render_str(expr.as_str(), self.scope.last().unwrap_or(&HashMap::new()))?; + fn eval(&self, expr: &String, state: &State) -> Result { + let result = self.jinja_env.render_str(expr.as_str(), &state.scope)?; if self.debug { - eprintln!("Eval {} -> {}", expr, result); + eprintln!("Eval {} -> {} with scope {:?}", expr, result, state.scope); } let backup = result.clone(); @@ -163,8 +197,8 @@ impl<'a> Interpreter<'a> { } /// Evaluate String as a Jinja2 expression, expecting a string in response - fn eval_to_string(&self, expr: &String) -> Result { - match self.eval(expr)? { + fn eval_to_string(&self, expr: &String, state: &State) -> Result { + match self.eval(expr, state)? { PdlResult::String(s) => Ok(s), x => Err(Box::from(format!( "Expression {expr} evaluated to non-string {:?}", @@ -174,20 +208,20 @@ impl<'a> Interpreter<'a> { } /// Traverse the given JSON Value, applying `self.eval()` to the value elements within. - fn eval_json(&self, expr: &Value) -> Result { + fn eval_json(&self, expr: &Value, state: &State) -> Result { match expr { Value::Null => Ok("".into()), Value::Bool(b) => Ok(PdlResult::Bool(*b)), Value::Number(n) => Ok(PdlResult::Number(n.clone())), - Value::String(s) => self.eval(s), + Value::String(s) => self.eval(s, state), Value::Array(a) => Ok(PdlResult::List( a.iter() - .map(|v| self.eval_json(v)) + .map(|v| self.eval_json(v, state)) .collect::>()?, )), Value::Object(o) => Ok(PdlResult::Dict( o.iter() - .map(|(k, v)| match self.eval_json(v) { + .map(|(k, v)| match self.eval_json(v, state) { Ok(v) => Ok((k.clone(), v)), Err(e) => Err(e), }) @@ -197,30 +231,34 @@ impl<'a> Interpreter<'a> { } /// Evaluate an string or list of Values into a list of Values - fn eval_list_or_string(&self, expr: &ListOrString) -> Result, PdlError> { + fn eval_list_or_string( + &self, + expr: &ListOrString, + state: &State, + ) -> Result, PdlError> { match expr { - ListOrString::String(s) => match self.eval(s)? { + ListOrString::String(s) => match self.eval(s, state)? { PdlResult::List(a) => Ok(a), x => Err(Box::from(format!( "Jinja string expanded to non-list. {} -> {:?}", s, x ))), }, - ListOrString::List(l) => l.iter().map(|v| self.eval_json(v)).collect(), + ListOrString::List(l) => l.iter().map(|v| self.eval_json(v, state)).collect(), } } /// Create a closure for the given function `f` - fn closure(&self, f: &FunctionBlock) -> Closure { + fn closure(&self, f: &FunctionBlock, state: &State) -> Closure { Closure { function: f.clone(), - scope: self.scope.last().unwrap_or(&HashMap::new()).clone(), + scope: state.scope.clone(), } } /// Run a PdlBlock::String - async fn run_string(&self, msg: &String, _context: Context) -> Interpretation { - let trace = self.eval(msg)?; + async fn run_string(&self, msg: &String, state: &State) -> Interpretation { + let trace = self.eval(msg, state)?; if self.debug { eprintln!("String {} -> {:?}", msg, trace); } @@ -234,9 +272,9 @@ impl<'a> Interpreter<'a> { Ok((trace, messages, PdlBlock::String(msg.clone()))) } - /// If `file_path` is not absolute, join it with self.cwd - fn path_to(&self, file_path: &String) -> PathBuf { - let mut path = self.cwd.clone(); + /// If `file_path` is not absolute, join it with state.cwd + fn path_to(&self, file_path: &String, state: &State) -> PathBuf { + let mut path = state.cwd.clone(); path.push(file_path); if path.extension().is_none() { path.with_extension("pdl") @@ -250,6 +288,8 @@ impl<'a> Interpreter<'a> { variable: &Option, value: &PdlResult, parser: &Option, + state: &mut State, + escape: bool, ) -> Result { let result = if let Some(parser) = parser { if let PdlResult::String(s) = value { @@ -266,11 +306,14 @@ impl<'a> Interpreter<'a> { }?; if let Some(def) = &variable { - if let Some(scope) = self.scope.last_mut() { - if self.debug { - eprintln!("Def {} -> {}", def, result); - } - scope.insert(def.clone(), result.clone()); + if self.debug { + eprintln!("Def {} -> {}", def, result); + } + state.scope.insert(def.clone(), result.clone()); + + // then we want this binding to escape to the parent scope + if escape { + state.escaped_variables.push(def.clone()); } } @@ -278,20 +321,27 @@ impl<'a> Interpreter<'a> { } /// Run a PdlBlock::Read - async fn run_read(&mut self, block: &ReadBlock, _context: Context) -> Interpretation { + async fn run_read(&mut self, block: &ReadBlock, state: &mut State) -> Interpretation { let trace = block.clone(); - println!( - "{}", - match (&block.message, block.multiline) { - (Some(message), _) => message.as_str(), - (None, Some(true)) => "Enter/Paste your content. Ctrl-D to save it.", - _ => "How can i help you?", + match (&block.read, &block.message) { + (StringOrNull::String(_), None) => {} // read from file and no explicit message... then don't print a message + _ => { + println!( + "{}", + match (&block.message, block.multiline) { + (Some(message), _) => message.as_str(), + (None, Some(true)) => "Enter/Paste your content. Ctrl-D to save it.", + _ => "How can i help you?", + } + ); } - ); + } let buffer = match &block.read { - StringOrNull::String(file_path) => ::std::fs::read_to_string(self.path_to(file_path))?, + StringOrNull::String(file_path) => { + ::std::fs::read_to_string(self.path_to(file_path, state))? + } StringOrNull::Null => { let mut buffer = String::new(); let mut bytes_read = ::std::io::stdin().read_line(&mut buffer)?; @@ -304,7 +354,13 @@ impl<'a> Interpreter<'a> { } }; - let result = self.def(&block.def, &buffer.clone().into(), &block.parser)?; + let result = self.def( + &block.def, + &buffer.clone().into(), + &block.parser, + state, + true, + )?; Ok(( result, @@ -314,112 +370,102 @@ impl<'a> Interpreter<'a> { } /// Run a PdlBlock::Call - async fn run_call(&mut self, block: &CallBlock, context: Context) -> Interpretation { + async fn run_call(&mut self, block: &CallBlock, state: &mut State) -> Interpretation { if self.debug { eprintln!("Call {:?}({:?})", block.call, block.args); - eprintln!("Call scope {:?}", self.scope.last()); + eprintln!("Call scope {:?}", state.scope); } - let res = match self.eval(&block.call)? { + match self.eval(&block.call, state)? { PdlResult::Closure(c) => { - if let Some(args) = &block.args { - match self.eval_json(args)? { - PdlResult::Dict(m) => { - self.push_and_extend_scope_with(m, c.scope); - Ok(()) - } + let mut new_state = match &block.args { + None => Ok(state.clone()), + Some(args) => match self.eval_json(args, state)? { + PdlResult::Dict(m) => Ok(state.extend_scope(vec![m, c.scope])), x => Err(PdlError::from(format!("Call arguments not a map: {:?}", x))), - }?; - } + }, + }?; - self.run(&c.function.return_, context.clone()).await + self.run(&c.function.return_, &mut new_state).await } x => Err(Box::from(format!( "call of non-function {:?}->{:?}", block.call, x ))), - }; - - if let Some(_) = block.args { - self.scope.pop(); } - - res } /// Run a PdlBlock::Empty - async fn run_empty(&mut self, block: &EmptyBlock, _context: Context) -> Interpretation { + async fn run_empty(&mut self, block: &EmptyBlock, state: &mut State) -> Interpretation { if self.debug { eprintln!("Empty"); } let trace = block.clone(); - self.process_defs(&Some(block.defs.clone())).await?; + self.process_defs(&Some(block.defs.clone()), state).await?; Ok(( - PdlResult::Dict(self.scope.last().unwrap_or(&HashMap::new()).clone()), + PdlResult::Dict(state.scope.clone()), vec![], PdlBlock::Empty(trace), )) } /// Run a PdlBlock::Call - async fn run_if(&mut self, block: &IfBlock, context: Context) -> Interpretation { + async fn run_if(&mut self, block: &IfBlock, state: &mut State) -> Interpretation { if self.debug { eprintln!("If {:?}({:?})", block.condition, block.then); } - self.process_defs(&block.defs).await?; + self.process_defs(&block.defs, state).await?; let cond = match &block.condition { StringOrBoolean::Boolean(b) => PdlResult::Bool(*b), - StringOrBoolean::String(s) => self.eval(s)?, + StringOrBoolean::String(s) => self.eval(s, state)?, }; - let res = match cond { - PdlResult::Bool(true) => self.run_quiet(&block.then, context).await, + + match cond { + PdlResult::Bool(true) => self.run_quiet(&block.then, state).await, PdlResult::Bool(false) => match &block.else_ { - Some(else_block) => self.run_quiet(&else_block, context).await, + Some(else_block) => self.run_quiet(&else_block, state).await, None => Ok(("".into(), vec![], PdlBlock::If(block.clone()))), }, x => Err(Box::from(format!( "if block condition evaluated to non-boolean value: {:?}", x ))), - }; - - self.scope.pop(); - res + } } /// Run a PdlBlock::Include - async fn run_include(&mut self, block: &IncludeBlock, context: Context) -> Interpretation { + async fn run_include(&mut self, block: &IncludeBlock, state: &mut State) -> Interpretation { if self.debug { eprintln!("Include {:?}", block.include); } - let path = self.path_to(&block.include); - let old_cwd = self.cwd.clone(); - if let Some(cwd) = path.parent() { - self.cwd = cwd.to_path_buf() - } - let res = self.run_quiet(&parse_file(&path)?, context.clone()).await; - self.cwd = old_cwd; - res + let path = self.path_to(&block.include, state); + let mut new_state = if let Some(cwd) = path.parent() { + state.with_cwd(cwd.to_path_buf()) + } else { + state.clone() + }; + + self.run(&parse_file(&path)?, &mut new_state).await } /// Run a PdlBlock::Import - async fn run_import(&mut self, block: &ImportBlock, context: Context) -> Interpretation { + async fn run_import(&mut self, block: &ImportBlock, state: &mut State) -> Interpretation { if self.debug { eprintln!("Import {:?}", block.import); } - let path = self.path_to(&block.import); - let old_cwd = self.cwd.clone(); - if let Some(cwd) = path.parent() { - self.cwd = cwd.to_path_buf() - } - let res = self.run_quiet(&parse_file(&path)?, context.clone()).await; - self.cwd = old_cwd; - res + let path = self.path_to(&block.import, state); + let mut new_state = if let Some(cwd) = path.parent() { + state.with_cwd(cwd.to_path_buf()) + } else { + state.clone() + }; + + self.run(&parse_file(&path)?, &mut new_state).await } fn to_ollama_model_options( @@ -487,7 +533,7 @@ impl<'a> Interpreter<'a> { async fn run_python_code( &mut self, block: &PythonCodeBlock, - _context: Context, + _state: &mut State, ) -> Interpretation { use rustpython_vm as vm; let interp = vm::Interpreter::with_init(vm::Settings::default(), |vm| { @@ -539,7 +585,7 @@ impl<'a> Interpreter<'a> { } /// Run a PdlBlock::Model - async fn run_model(&mut self, block: &ModelBlock, context: Context) -> Interpretation { + async fn run_model(&mut self, block: &ModelBlock, state: &mut State) -> Interpretation { match &block.model { pdl_model if pdl_model.starts_with("ollama/") || pdl_model.starts_with("ollama_chat/") => @@ -557,13 +603,16 @@ impl<'a> Interpreter<'a> { eprintln!("Model tools {:?} {:?}", block.description, tools); } + // The input messages to the model is either: + // a) block.input, if given + // b) the current state's accumulated messages let input_messages = match &block.input { Some(input) => { // TODO ignoring result, trace - let (_result, messages, _trace) = self.run_quiet(&*input, context).await?; + let (_result, messages, _trace) = self.run_quiet(&*input, state).await?; messages } - None => context, + None => state.messages.clone(), }; let (prompt, history_slice): (&ChatMessage, &[ChatMessage]) = match input_messages.split_last() { @@ -581,9 +630,9 @@ impl<'a> Interpreter<'a> { ); } - if self.emit { - println!("{}", pretty_print(&input_messages)); - } + //if state.emit { + //println!("{}", pretty_print(&input_messages)); + //} let req = ChatMessageRequest::new(model.into(), vec![prompt.clone()]) .options(options) @@ -641,6 +690,8 @@ impl<'a> Interpreter<'a> { &block.model_response, &self.resultify_as_litellm(&from_str(&to_string(&res)?)?), &None, + state, + true, )?; } } @@ -701,23 +752,35 @@ impl<'a> Interpreter<'a> { } /// Run a PdlBlock::Data - async fn run_data(&mut self, block: &DataBlock, _context: Context) -> Interpretation { + async fn run_data(&mut self, block: &DataBlock, state: &mut State) -> Interpretation { if self.debug { eprintln!("Data raw={:?} {:?}", block.raw, block.data); } let mut trace = block.clone(); if let Some(true) = block.raw { - let result = self.def(&block.def, &self.resultify(&block.data), &block.parser)?; + let result = self.def( + &block.def, + &self.resultify(&block.data), + &block.parser, + state, + true, + )?; Ok((result, vec![], PdlBlock::Data(trace))) } else { - let result = self.def(&block.def, &self.eval_json(&block.data)?, &block.parser)?; + let result = self.def( + &block.def, + &self.eval_json(&block.data, state)?, + &block.parser, + state, + true, + )?; trace.data = from_str(to_string(&result)?.as_str())?; Ok((result, vec![], PdlBlock::Data(trace))) } } - async fn run_object(&mut self, block: &ObjectBlock, context: Context) -> Interpretation { + async fn run_object(&mut self, block: &ObjectBlock, state: &mut State) -> Interpretation { if self.debug { eprintln!("Object {:?}", block.object); } @@ -728,8 +791,7 @@ impl<'a> Interpreter<'a> { let mut iter = block.object.iter(); while let Some((k, v)) = iter.next() { - let (this_result, this_messages, this_trace) = - self.run_quiet(v, context.clone()).await?; + let (this_result, this_messages, this_trace) = self.run_quiet(v, state).await?; messages.extend(this_messages); result_map.insert(k.clone(), this_result); trace_map.insert(k.clone(), this_trace); @@ -743,7 +805,7 @@ impl<'a> Interpreter<'a> { } /// Run a PdlBlock::Repeat - async fn run_repeat(&mut self, block: &RepeatBlock, context: Context) -> Interpretation { + async fn run_repeat(&mut self, block: &RepeatBlock, state: &mut State) -> Interpretation { // { i:[1,2,3], j: [4,5,6]} -> ([i,j], [[1,2,3],[4,5,6]]) // let (variables, values): (Vec<_>, Vec>) = block // .into_iter() @@ -751,10 +813,12 @@ impl<'a> Interpreter<'a> { let iter_scopes = block .for_ .iter() - .map(|(var, values)| match self.eval_list_or_string(values) { - Ok(value) => Ok((var.clone(), value)), - Err(e) => Err(e), - }) + .map( + |(var, values)| match self.eval_list_or_string(values, state) { + Ok(value) => Ok((var.clone(), value)), + Err(e) => Err(e), + }, + ) .collect::, _>>()?; if self.debug { @@ -764,18 +828,19 @@ impl<'a> Interpreter<'a> { let mut results = vec![]; let mut messages = vec![]; let mut trace = vec![]; + let mut iter_state = state.clone(); if let Some(n) = iter_scopes.iter().map(|(_, v)| v.len()).min() { for iter in 0..n { let this_iter_scope = iter_scopes .iter() .map(|(k, v)| (k.clone(), v[iter].clone())) .collect(); - self.push_and_extend_scope(this_iter_scope); - let (result, ms, t) = self.run_quiet(&block.repeat, context.clone()).await?; + iter_state = iter_state.extend_scope(vec![this_iter_scope]); + let (result, ms, t) = self.run_quiet(&block.repeat, &mut iter_state).await?; results.push(result); messages.extend(ms); trace.push(t); - self.pop_scope(); + //self.pop_scope(); } } @@ -802,40 +867,20 @@ impl<'a> Interpreter<'a> { } } - fn push_and_extend_scope(&mut self, scope: HashMap) { - let mut new_scope = self.scope.last().unwrap_or(&HashMap::new()).clone(); - new_scope.extend(scope); - self.scope.push(new_scope); - } - - fn push_and_extend_scope_with( - &mut self, - mut scope: HashMap, - other_scope: HashMap, - ) { - scope.extend(other_scope); - self.push_and_extend_scope(scope); - } - - fn pop_scope(&mut self) { - self.scope.pop(); - } - async fn process_defs( &mut self, defs: &Option>, + state: &mut State, ) -> Result<(), PdlError> { - let mut new_scope: Scope = HashMap::new(); - if let Some(cur_scope) = self.scope.last() { - new_scope.extend(cur_scope.clone()); - } - self.scope.push(new_scope); + // in pdl, blocks are not a lexical scope. strange, but true + //let mut new_state = state.clone(); if let Some(defs) = defs { let mut iter = defs.iter(); while let Some((var, def)) = iter.next() { - let (result, _, _) = self.run_quiet(def, vec![]).await?; - let _ = self.def(&Some(var.clone()), &result, &None); + let (result, _, _) = self.run_quiet(def, state).await?; + let escape = false; // ?? do we want pdl defs to escape to the parent scope? lexical scoping would say no + let _ = self.def(&Some(var.clone()), &result, &None, state, escape); } } @@ -846,7 +891,7 @@ impl<'a> Interpreter<'a> { async fn run_sequence( &mut self, block: &impl SequencingBlock, - context: Context, + state: &mut State, ) -> Interpretation { if self.debug { let description = if let Some(d) = block.description() { @@ -857,34 +902,36 @@ impl<'a> Interpreter<'a> { eprintln!("{} {description}", block.kind()); } - let mut input_messages = context.clone(); let mut output_results = vec![]; let mut output_messages = vec![]; let mut output_blocks = vec![]; - self.process_defs(block.defs()).await?; + self.process_defs(block.defs(), state).await?; + // here is where we iterate over the sequence items let mut iter = block.items().iter(); while let Some(block) = iter.next() { // run each element of the Text block - let (this_result, this_messages, trace) = - self.run(&block, input_messages.clone()).await?; - input_messages.extend(this_messages.clone()); - output_results.push(this_result); + let (this_result, this_messages, trace) = self.run(&block, state).await?; + + state.messages.extend(this_messages.iter().cloned()); - output_messages.extend(this_messages); + output_results.push(this_result); + output_messages.extend(this_messages.iter().cloned()); output_blocks.push(trace); } - self.scope.pop(); + // self.scope.pop(); let trace = block.with_items(output_blocks); let result = self.def( trace.def(), &trace.result_for(output_results), trace.parser(), + state, + true, )?; - let result_messages = trace.messages_for::(output_messages); + let result_messages = trace.messages_for::(&output_messages); Ok(( result, match block.role() { @@ -899,7 +946,7 @@ impl<'a> Interpreter<'a> { } /// Run a PdlBlock::Array - async fn run_array(&mut self, block: &ArrayBlock, context: Context) -> Interpretation { + async fn run_array(&mut self, block: &ArrayBlock, state: &mut State) -> Interpretation { let mut result_items = vec![]; let mut all_messages = vec![]; let mut trace_items = vec![]; @@ -907,7 +954,7 @@ impl<'a> Interpreter<'a> { let mut iter = block.array.iter(); while let Some(item) = iter.next() { // TODO accumulate messages - let (result, messages, trace) = self.run_quiet(item, context.clone()).await?; + let (result, messages, trace) = self.run_quiet(item, state).await?; result_items.push(result); all_messages.extend(messages); trace_items.push(trace); @@ -921,16 +968,16 @@ impl<'a> Interpreter<'a> { } /// Run a PdlBlock::Message - async fn run_message(&mut self, block: &MessageBlock, context: Context) -> Interpretation { + async fn run_message(&mut self, block: &MessageBlock, state: &mut State) -> Interpretation { let (content_result, content_messages, content_trace) = - self.run(&block.content, context).await?; + self.run(&block.content, state).await?; let name = if let Some(name) = &block.name { - Some(self.eval_to_string(&name)?) + Some(self.eval_to_string(&name, state)?) } else { None }; let tool_call_id = if let Some(tool_call_id) = &block.tool_call_id { - Some(self.eval_to_string(&tool_call_id)?) + Some(self.eval_to_string(&tool_call_id, state)?) } else { None }; @@ -976,10 +1023,11 @@ pub async fn run( let mut interpreter = Interpreter::new(); interpreter.debug = debug; interpreter.stream = stream; + let mut state = State::new(); if let Some(cwd) = cwd { - interpreter.cwd = cwd - }; - interpreter.run(&program, vec![]).await + state.cwd = cwd + } + interpreter.run(&program, &mut state).await } #[allow(dead_code)] diff --git a/pdl-live-react/src-tauri/src/pdl/interpreter_tests.rs b/pdl-live-react/src-tauri/src/pdl/interpreter_tests.rs index 436aef3cc..4e9741118 100644 --- a/pdl-live-react/src-tauri/src/pdl/interpreter_tests.rs +++ b/pdl-live-react/src-tauri/src/pdl/interpreter_tests.rs @@ -536,6 +536,21 @@ mod tests { Ok(()) } + #[test] + fn scoping_1() -> Result<(), Box> { + let program = json!({ + "include": "./tests/cli/scoping_1.pdl" + }); + + let (_, messages, _) = run_json(program, false)?; + assert_eq!(messages.len(), 2); + assert_eq!(messages[0].role, MessageRole::User); + assert_eq!(messages[0].content, "3yo"); + assert_eq!(messages[1].role, MessageRole::User); + assert_eq!(messages[1].content, "mo"); + Ok(()) + } + #[test] fn bee_1() -> Result<(), Box> { let program = crate::compile::beeai::compile("./tests/data/bee_1.py", false)?; diff --git a/pdl-live-react/src-tauri/tests/cli/model-input-array.pdl b/pdl-live-react/src-tauri/tests/cli/model-input-array.pdl index 7fbce7342..18c18124a 100644 --- a/pdl-live-react/src-tauri/tests/cli/model-input-array.pdl +++ b/pdl-live-react/src-tauri/tests/cli/model-input-array.pdl @@ -1,7 +1,7 @@ model: ollama/granite3.2:2b input: array: - - role: system + - role: user content: answer as if you live in europe - role: user content: what is the fastest animal where i live? diff --git a/pdl-live-react/src-tauri/tests/cli/model-input-nested.pdl b/pdl-live-react/src-tauri/tests/cli/model-input-nested.pdl new file mode 100644 index 000000000..096b698d4 --- /dev/null +++ b/pdl-live-react/src-tauri/tests/cli/model-input-nested.pdl @@ -0,0 +1,7 @@ +text: + - role: user + content: answer as if you live in europe + - text: + - role: user + content: what is the fastest animal where i live? + - model: ollama/granite3.2:2b diff --git a/pdl-live-react/src-tauri/tests/cli/scoping_1.pdl b/pdl-live-react/src-tauri/tests/cli/scoping_1.pdl new file mode 100644 index 000000000..12c773d90 --- /dev/null +++ b/pdl-live-react/src-tauri/tests/cli/scoping_1.pdl @@ -0,0 +1,6 @@ +text: + - defs: + x: 3 + text: + - ${x~"yo"} + - ${x~"mo"} diff --git a/pdl-live-react/src-tauri/tests/cli/scoping_1_wrapper.pdl b/pdl-live-react/src-tauri/tests/cli/scoping_1_wrapper.pdl new file mode 100644 index 000000000..7adaa0590 --- /dev/null +++ b/pdl-live-react/src-tauri/tests/cli/scoping_1_wrapper.pdl @@ -0,0 +1 @@ +include: ./scoping_1.pdl From 5de9648d10a92b644693eefeed0c88c6ec59c3ba Mon Sep 17 00:00:00 2001 From: Nick Mitchell Date: Thu, 10 Apr 2025 10:03:39 -0400 Subject: [PATCH 10/39] fix: rust pull logic may not always pull 1) extract was not traversing completely, thus missing some model references 2) ollama pull itself (the CLI) may double/triple fetch if concurrent ollama pulls are launched. this PR introduces a file lock to avoid this. in github actions, we were running out of disk space due to this issue. Signed-off-by: Nick Mitchell --- pdl-live-react/src-tauri/Cargo.lock | 11 +++++++ pdl-live-react/src-tauri/Cargo.toml | 1 + pdl-live-react/src-tauri/src/pdl/extract.rs | 35 ++++++++++++++++----- pdl-live-react/src-tauri/src/pdl/pull.rs | 21 ++++++++++--- 4 files changed, 56 insertions(+), 12 deletions(-) diff --git a/pdl-live-react/src-tauri/Cargo.lock b/pdl-live-react/src-tauri/Cargo.lock index f3f6acca7..82bc1528e 100644 --- a/pdl-live-react/src-tauri/Cargo.lock +++ b/pdl-live-react/src-tauri/Cargo.lock @@ -1376,6 +1376,16 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fs4" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8640e34b88f7652208ce9e88b1a37a2ae95227d84abec377ccd3c5cfeb141ed4" +dependencies = [ + "rustix 1.0.5", + "windows-sys 0.59.0", +] + [[package]] name = "futf" version = "0.1.5" @@ -3367,6 +3377,7 @@ dependencies = [ "base64ct", "dirs", "duct", + "fs4", "futures", "indexmap 2.9.0", "minijinja", diff --git a/pdl-live-react/src-tauri/Cargo.toml b/pdl-live-react/src-tauri/Cargo.toml index a26aa414a..0bf71e50c 100644 --- a/pdl-live-react/src-tauri/Cargo.toml +++ b/pdl-live-react/src-tauri/Cargo.toml @@ -45,6 +45,7 @@ tokio = { version = "1.44.1", features = ["io-std"] } indexmap = { version = "2.9.0", features = ["serde"] } rustpython-stdlib = { version = "0.4.0", features = ["zlib"] } schemars = "0.8.22" +fs4 = "0.13.1" [target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies] tauri-plugin-cli = "2" diff --git a/pdl-live-react/src-tauri/src/pdl/extract.rs b/pdl-live-react/src-tauri/src/pdl/extract.rs index a33416288..2489b7b07 100644 --- a/pdl-live-react/src-tauri/src/pdl/extract.rs +++ b/pdl-live-react/src-tauri/src/pdl/extract.rs @@ -31,24 +31,43 @@ fn extract_values_iter(program: &PdlBlock, field: &str, values: &mut Vec .array .iter() .for_each(|p| extract_values_iter(p, field, values)), - PdlBlock::Text(b) => b - .text - .iter() - .for_each(|p| extract_values_iter(p, field, values)), - PdlBlock::LastOf(b) => b - .last_of - .iter() - .for_each(|p| extract_values_iter(p, field, values)), + PdlBlock::Text(b) => { + b.text + .iter() + .for_each(|p| extract_values_iter(p, field, values)); + if let Some(defs) = &b.defs { + defs.values() + .for_each(|p| extract_values_iter(p, field, values)); + } + } + PdlBlock::LastOf(b) => { + b.last_of + .iter() + .for_each(|p| extract_values_iter(p, field, values)); + if let Some(defs) = &b.defs { + defs.values() + .for_each(|p| extract_values_iter(p, field, values)); + } + } PdlBlock::If(b) => { extract_values_iter(&b.then, field, values); if let Some(else_) = &b.else_ { extract_values_iter(else_, field, values); } + if let Some(defs) = &b.defs { + defs.values() + .for_each(|p| extract_values_iter(p, field, values)); + } } PdlBlock::Object(b) => b .object .values() .for_each(|p| extract_values_iter(p, field, values)), + + PdlBlock::Function(b) => { + extract_values_iter(&b.return_, field, values); + } + _ => {} } } diff --git a/pdl-live-react/src-tauri/src/pdl/pull.rs b/pdl-live-react/src-tauri/src/pdl/pull.rs index cdb42a04a..cb7763c61 100644 --- a/pdl-live-react/src-tauri/src/pdl/pull.rs +++ b/pdl-live-react/src-tauri/src/pdl/pull.rs @@ -1,6 +1,7 @@ use ::std::io::Error; use duct::cmd; +use fs4::fs_std::FileExt; use rayon::prelude::*; use crate::pdl::ast::PdlBlock; @@ -46,8 +47,20 @@ fn ollama_exists(model: &str) -> bool { /// The Ollama implementation of a single model pull fn ollama_pull_if_needed(model: &str) -> Result<(), Error> { - if !ollama_exists(model) { - cmd!("ollama", "pull", model).stdout_to_stderr().run()?; - } - Ok(()) + let path = ::std::env::temp_dir().join(format!("pdl-ollama-pull-{model}")); + let f = ::std::fs::File::create(path)?; + f.lock_exclusive()?; + + // don't ? the cmd! so that we can "finally" unlock the file + let res = if !ollama_exists(model) { + cmd!("ollama", "pull", model) + .stdout_to_stderr() + .run() + .and_then(|_| Ok(())) + } else { + Ok(()) + }; + + FileExt::unlock(&f)?; + res } From 98182a661050ebbe438333582e727258b2650da6 Mon Sep 17 00:00:00 2001 From: Nick Mitchell Date: Thu, 10 Apr 2025 10:27:26 -0400 Subject: [PATCH 11/39] feat: remove tauri cli support for running python interpreter This also updates the tauri cli to rename runr -> run (run previously invoked the python interpreter). And adds stub support for --data, --data-file, --trace command line options (not implemented yet). Signed-off-by: Nick Mitchell --- .github/workflows/tauri-cli.yml | 6 +-- pdl-live-react/src-tauri/src/cli.rs | 20 +++---- .../src-tauri/src/pdl/interpreter.rs | 9 +++- pdl-live-react/src-tauri/src/pdl/mod.rs | 1 - pdl-live-react/src-tauri/src/pdl/pull.rs | 5 +- .../src-tauri/src/pdl/requirements.rs | 3 -- pdl-live-react/src-tauri/src/pdl/run.rs | 52 ------------------- pdl-live-react/src-tauri/tauri.conf.json | 24 +++------ 8 files changed, 27 insertions(+), 93 deletions(-) delete mode 100644 pdl-live-react/src-tauri/src/pdl/run.rs diff --git a/.github/workflows/tauri-cli.yml b/.github/workflows/tauri-cli.yml index 30a9c25ea..2d9e7fe25 100644 --- a/.github/workflows/tauri-cli.yml +++ b/.github/workflows/tauri-cli.yml @@ -69,9 +69,9 @@ jobs: # demo5,demo6 each depend on an external file, and the interpreter does not currently capture this in the trace # demo8 currently requires building a model which the interpreter does not directly support # demo9 takes forever, so... for now skip it - for i in ./src/demos/*.json - do if [[ $(basename $i) != "demo4.json" ]] && [[ $(basename $i) != "demo5.json" ]] && [[ $(basename $i) != "demo6.json" ]] && [[ $(basename $i) != "demo8.json" ]] && [[ $(basename $i) != "demo9.json" ]]; then pdl run $i; fi - done + #for i in ./src/demos/*.json + #do if [[ $(basename $i) != "demo4.json" ]] && [[ $(basename $i) != "demo5.json" ]] && [[ $(basename $i) != "demo6.json" ]] && [[ $(basename $i) != "demo8.json" ]] && [[ $(basename $i) != "demo9.json" ]]; then pdl run $i; fi + #done - name: Tear down xvfb run: killall Xvfb || true diff --git a/pdl-live-react/src-tauri/src/cli.rs b/pdl-live-react/src-tauri/src/cli.rs index 2e2909031..edbe62c86 100644 --- a/pdl-live-react/src-tauri/src/cli.rs +++ b/pdl-live-react/src-tauri/src/cli.rs @@ -5,8 +5,7 @@ use urlencoding::encode; use crate::compile; use crate::gui::new_window; -use crate::pdl::interpreter::run_file_sync as runr; -use crate::pdl::run::run_pdl_program; +use crate::pdl::interpreter::run_file_sync as run; #[cfg(desktop)] pub fn setup(app: &mut tauri::App) -> Result> { @@ -50,11 +49,16 @@ pub fn setup(app: &mut tauri::App) -> Result> _ => Err(Box::from("Unsupported compile command")), } } - "runr" => runr( + "run" => run( subcommand_args .get("source") .and_then(|a| a.value.as_str()) .expect("valid positional source arg"), + subcommand_args.get("trace").and_then(|a| a.value.as_str()), + subcommand_args.get("data").and_then(|a| a.value.as_str()), + subcommand_args + .get("data-file") + .and_then(|a| a.value.as_str()), subcommand_args .get("debug") .and_then(|a| a.value.as_bool()) @@ -67,16 +71,6 @@ pub fn setup(app: &mut tauri::App) -> Result> == Some(false), ) .and_then(|_trace| Ok(true)), - "run" => run_pdl_program( - subcommand_args - .get("source") - .and_then(|a| a.value.as_str()) - .expect("valid positional source arg"), - subcommand_args.get("trace").and_then(|a| a.value.as_str()), - subcommand_args.get("data").and_then(|a| a.value.as_str()), - subcommand_args.get("stream").and_then(|a| a.value.as_str()), - ) - .and_then(|()| Ok(true)), "view" => new_window( app.handle().clone(), subcommand_args.get("trace").and_then(|a| { diff --git a/pdl-live-react/src-tauri/src/pdl/interpreter.rs b/pdl-live-react/src-tauri/src/pdl/interpreter.rs index b92c164cb..7ace47581 100644 --- a/pdl-live-react/src-tauri/src/pdl/interpreter.rs +++ b/pdl-live-react/src-tauri/src/pdl/interpreter.rs @@ -1054,7 +1054,14 @@ pub async fn run_file(source_file_path: &str, debug: bool, stream: bool) -> Inte run(&program, cwd, debug, stream).await } -pub fn run_file_sync(source_file_path: &str, debug: bool, stream: bool) -> InterpretationSync { +pub fn run_file_sync( + source_file_path: &str, + _trace: Option<&str>, + _data: Option<&str>, + _data_file: Option<&str>, + debug: bool, + stream: bool, +) -> InterpretationSync { tauri::async_runtime::block_on(run_file(source_file_path, debug, stream)) .map_err(|err| Box::::from(err.to_string())) } diff --git a/pdl-live-react/src-tauri/src/pdl/mod.rs b/pdl-live-react/src-tauri/src/pdl/mod.rs index fcdcab28b..179cb9e10 100644 --- a/pdl-live-react/src-tauri/src/pdl/mod.rs +++ b/pdl-live-react/src-tauri/src/pdl/mod.rs @@ -5,4 +5,3 @@ mod interpreter_tests; pub mod pip; pub mod pull; pub mod requirements; -pub mod run; diff --git a/pdl-live-react/src-tauri/src/pdl/pull.rs b/pdl-live-react/src-tauri/src/pdl/pull.rs index cb7763c61..7b11e3113 100644 --- a/pdl-live-react/src-tauri/src/pdl/pull.rs +++ b/pdl-live-react/src-tauri/src/pdl/pull.rs @@ -6,16 +6,15 @@ use rayon::prelude::*; use crate::pdl::ast::PdlBlock; use crate::pdl::extract; -use crate::pdl::interpreter::parse_file; -pub async fn pull_if_needed_from_path( +/* pub async fn pull_if_needed_from_path( source_file_path: &str, ) -> Result<(), Box> { let program = parse_file(&::std::path::PathBuf::from(source_file_path))?; pull_if_needed(&program) .await .map_err(|e| Box::from(e.to_string())) -} +} */ /// Pull models (in parallel) from the PDL program in the given filepath. pub async fn pull_if_needed(program: &PdlBlock) -> Result<(), Error> { diff --git a/pdl-live-react/src-tauri/src/pdl/requirements.rs b/pdl-live-react/src-tauri/src/pdl/requirements.rs index 2b08c9f16..6e6bb0501 100644 --- a/pdl-live-react/src-tauri/src/pdl/requirements.rs +++ b/pdl-live-react/src-tauri/src/pdl/requirements.rs @@ -1,5 +1,2 @@ -//pub const PDL_INTERPRETER: &str = "-e ../"; -pub const PDL_INTERPRETER: &str = "prompt-declaration-language==0.6.0"; - pub const BEEAI_FRAMEWORK: &str = "-e git+https://github.com/starpit/bee-agent-framework.git@nick-meta-combo#egg=beeai_framework&subdirectory=python"; //pub const BEEAI_FRAMEWORK: &str = "beeai_framework==0.1"; diff --git a/pdl-live-react/src-tauri/src/pdl/run.rs b/pdl-live-react/src-tauri/src/pdl/run.rs deleted file mode 100644 index ee7281f93..000000000 --- a/pdl-live-react/src-tauri/src/pdl/run.rs +++ /dev/null @@ -1,52 +0,0 @@ -use ::std::path::Path; -use duct::cmd; -use futures::executor::block_on; - -use crate::pdl::pip::pip_install_if_needed; -use crate::pdl::pull::pull_if_needed_from_path; -use crate::pdl::requirements::PDL_INTERPRETER; - -#[cfg(desktop)] -pub fn run_pdl_program( - source_file_path: &str, - trace_file: Option<&str>, - data: Option<&str>, - stream: Option<&str>, -) -> Result<(), Box> { - println!( - "Running {:#?}", - Path::new(&source_file_path).file_name().unwrap() - ); - - // async the model pull and pip installs - let pull_future = pull_if_needed_from_path(&source_file_path); - let bin_path_future = pip_install_if_needed(&PDL_INTERPRETER); - - // wait for any model pulls to finish - if let Err(e) = block_on(pull_future) { - return Err(e); - } - - // wait for any pip installs to finish - let bin_path = block_on(bin_path_future)?; - - let mut args = vec![ - source_file_path.to_string(), - dashdash("--trace", trace_file), - dashdash("--data", data), - dashdash("--stream", stream), - ]; - args.retain(|x| x.chars().count() > 0); - cmd(bin_path.join("pdl"), &args).run()?; - - Ok(()) -} - -/// Format `--{opt}={a}` based on whether `a` is given or not -fn dashdash(opt: &str, a: Option<&str>) -> String { - if let Some(s) = a { - format!("{}={}", opt, s) - } else { - "".to_owned() - } -} diff --git a/pdl-live-react/src-tauri/tauri.conf.json b/pdl-live-react/src-tauri/tauri.conf.json index 108356584..f37c55cd1 100644 --- a/pdl-live-react/src-tauri/tauri.conf.json +++ b/pdl-live-react/src-tauri/tauri.conf.json @@ -47,7 +47,8 @@ } } }, - "runr": { + "run": { + "description": "Run a PDL program", "args": [ { "name": "source", @@ -59,31 +60,20 @@ "name": "no-stream" }, { - "name": "debug", - "short": "g" - } - ] - }, - "run": { - "description": "Run a PDL program", - "args": [ - { - "name": "source", - "index": 1, - "required": true, + "name": "data", "takesValue": true }, { - "name": "stream", + "name": "data-file", "takesValue": true }, { - "name": "data", + "name": "trace", "takesValue": true }, { - "name": "trace", - "takesValue": true + "name": "debug", + "short": "g" } ] }, From d1c407ee1b63e7c4769244919b271e1a7d57d3ef Mon Sep 17 00:00:00 2001 From: Nick Mitchell Date: Thu, 10 Apr 2025 10:45:08 -0400 Subject: [PATCH 12/39] chore: bump rust dependencies to resolve alert Signed-off-by: Nick Mitchell --- pdl-live-react/package-lock.json | 18 +++++++++--------- pdl-live-react/src-tauri/Cargo.lock | 22 +++++++++++----------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/pdl-live-react/package-lock.json b/pdl-live-react/package-lock.json index 3b97b3d0e..c3cb2ffaa 100644 --- a/pdl-live-react/package-lock.json +++ b/pdl-live-react/package-lock.json @@ -2463,9 +2463,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001712", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001712.tgz", - "integrity": "sha512-MBqPpGYYdQ7/hfKiet9SCI+nmN5/hp4ZzveOJubl5DTAMa5oggjAuoi0Z4onBpKPFI2ePGnQuQIzF3VxDjDJig==", + "version": "1.0.30001713", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001713.tgz", + "integrity": "sha512-wCIWIg+A4Xr7NfhTuHdX+/FKh3+Op3LBbSp2N5Pfx6T/LhdQy3GTyoTg48BReaW/MyMNZAkTadsBtai3ldWK0Q==", "dev": true, "funding": [ { @@ -2924,9 +2924,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.134", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.134.tgz", - "integrity": "sha512-zSwzrLg3jNP3bwsLqWHmS5z2nIOQ5ngMnfMZOWWtXnqqQkPVyOipxK98w+1beLw1TB+EImPNcG8wVP/cLVs2Og==", + "version": "1.5.135", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.135.tgz", + "integrity": "sha512-8gXUdEmvb+WCaYUhA0Svr08uSeRjM2w3x5uHOc1QbaEVzJXB8rgm5eptieXzyKoVEtinLvW6MtTcurA65PeS1Q==", "dev": true, "license": "ISC" }, @@ -6206,9 +6206,9 @@ } }, "node_modules/vite": { - "version": "6.2.5", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.2.5.tgz", - "integrity": "sha512-j023J/hCAa4pRIUH6J9HemwYfjB5llR2Ps0CWeikOtdR8+pAURAk0DoJC5/mm9kd+UgdnIy7d6HE4EAvlYhPhA==", + "version": "6.2.6", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.2.6.tgz", + "integrity": "sha512-9xpjNl3kR4rVDZgPNdTL0/c6ao4km69a/2ihNQbcANz8RuCOK3hQBmLSJf3bRKVQjVMda+YvizNE8AwvogcPbw==", "dev": true, "license": "MIT", "dependencies": { diff --git a/pdl-live-react/src-tauri/Cargo.lock b/pdl-live-react/src-tauri/Cargo.lock index 82bc1528e..74def7f13 100644 --- a/pdl-live-react/src-tauri/Cargo.lock +++ b/pdl-live-react/src-tauri/Cargo.lock @@ -817,9 +817,9 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.14" +version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ba6d68e24814cb8de6bb986db8222d3a027d15872cabc0d18817bc3c0e4471" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" dependencies = [ "crossbeam-utils", ] @@ -2549,9 +2549,9 @@ checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "linux-raw-sys" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe7db12097d22ec582439daf8618b8fdd1a7bef6270e9af3b1ebcd30893cf413" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" [[package]] name = "litemap" @@ -2736,9 +2736,9 @@ dependencies = [ [[package]] name = "miniz_oxide" -version = "0.8.7" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff70ce3e48ae43fa075863cef62e8b43b71a4f2382229920e0df362592919430" +checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" dependencies = [ "adler2", "simd-adler32", @@ -4091,7 +4091,7 @@ dependencies = [ "bitflags 2.9.0", "errno", "libc", - "linux-raw-sys 0.9.3", + "linux-raw-sys 0.9.4", "windows-sys 0.59.0", ] @@ -4730,9 +4730,9 @@ dependencies = [ [[package]] name = "serial2" -version = "0.2.28" +version = "0.2.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cd0c773455b60177d1abe4c739cbfa316c4f2f0ef37465befcb72e8a15cdd02" +checksum = "c7d1d08630509d69f90eff4afcd02c3bd974d979225cbd815ff5942351b14375" dependencies = [ "cfg-if", "libc", @@ -7033,9 +7033,9 @@ dependencies = [ [[package]] name = "xml-rs" -version = "0.8.25" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5b940ebc25896e71dd073bad2dbaa2abfe97b0a391415e22ad1326d9c54e3c4" +checksum = "a62ce76d9b56901b19a74f19431b0d8b3bc7ca4ad685a746dfd78ca8f4fc6bda" [[package]] name = "yaml-rust2" From 61ef43629e083ca57c05e51af3deaf9bdc598cb7 Mon Sep 17 00:00:00 2001 From: Nick Mitchell Date: Thu, 10 Apr 2025 10:42:04 -0400 Subject: [PATCH 13/39] test: tauri github actions test should apt install the deb This also removes the beeai compiler test in that workflow, as we now cover that in the core rust interpreter tests. Signed-off-by: Nick Mitchell --- .github/workflows/tauri-cli.yml | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/.github/workflows/tauri-cli.yml b/.github/workflows/tauri-cli.yml index 2d9e7fe25..47886a006 100644 --- a/.github/workflows/tauri-cli.yml +++ b/.github/workflows/tauri-cli.yml @@ -31,27 +31,20 @@ jobs: sudo apt install -y libgtk-3-dev libwebkit2gtk-4.1-dev librsvg2-dev patchelf at-spi2-core && \ (curl -fsSL https://ollama.com/install.sh | sudo -E sh && sleep 2) wait - - name: Test production build - run: npm run tauri build -- --bundles deb # Skip testing appimage, is this dangerous? It's slow... + - name: Test production build # Skip testing appimage, is this dangerous? It's slow... + run: | + npm run tauri build -- --ci --bundles deb + - name: Install production build + run: | + ls ./src-tauri/target/release/bundle/deb && sudo apt install -y ./src-tauri/target/release/bundle/deb/*.deb - name: Setup xvfb for screen 0 - run: Xvfb :1 -screen 0 1600x1200x24 & - - - name: Test beeai compiler - env: - DISPLAY: :1 run: | - PATH=./src-tauri/target/release/:$PATH - - for i in ./demos/beeai/*.py - do pdl compile beeai $i -g -o /tmp/z.json && jq .description /tmp/z.json - done + Xvfb :1 -screen 0 1600x1200x24 & - - name: Test pdl run against production build + - name: Test 'pdl run' against production build env: DISPLAY: :1 run: | - PATH=./src-tauri/target/release/:$PATH - # 1a. `run` subcommand errors due to missing required positional parameter pdl run && (echo "This should have failed" && exit 1) || (echo "Great, expected failure received" && exit 0) From a8029eb20e250ddcf71aa16770c07ca14625990c Mon Sep 17 00:00:00 2001 From: Nick Mitchell Date: Thu, 10 Apr 2025 12:40:35 -0400 Subject: [PATCH 14/39] feat: support for --data and --data-file in rust interpreter Signed-off-by: Nick Mitchell --- pdl-live-react/src-tauri/src/cli.rs | 39 ++-- .../src-tauri/src/commands/interpreter.rs | 4 +- .../src-tauri/src/pdl/interpreter.rs | 215 +++++++++++------- .../src-tauri/src/pdl/interpreter_tests.rs | 105 ++++++--- pdl-live-react/src-tauri/tauri.conf.json | 2 + 5 files changed, 235 insertions(+), 130 deletions(-) diff --git a/pdl-live-react/src-tauri/src/cli.rs b/pdl-live-react/src-tauri/src/cli.rs index edbe62c86..65102b1f4 100644 --- a/pdl-live-react/src-tauri/src/cli.rs +++ b/pdl-live-react/src-tauri/src/cli.rs @@ -5,7 +5,7 @@ use urlencoding::encode; use crate::compile; use crate::gui::new_window; -use crate::pdl::interpreter::run_file_sync as run; +use crate::pdl::interpreter::{load_scope, run_file_sync, RunOptions}; #[cfg(desktop)] pub fn setup(app: &mut tauri::App) -> Result> { @@ -49,26 +49,31 @@ pub fn setup(app: &mut tauri::App) -> Result> _ => Err(Box::from("Unsupported compile command")), } } - "run" => run( + "run" => run_file_sync( subcommand_args .get("source") .and_then(|a| a.value.as_str()) .expect("valid positional source arg"), - subcommand_args.get("trace").and_then(|a| a.value.as_str()), - subcommand_args.get("data").and_then(|a| a.value.as_str()), - subcommand_args - .get("data-file") - .and_then(|a| a.value.as_str()), - subcommand_args - .get("debug") - .and_then(|a| a.value.as_bool()) - .or(Some(false)) - == Some(true), - subcommand_args - .get("no-stream") - .and_then(|a| a.value.as_bool()) - .or(Some(false)) - == Some(false), + RunOptions { + trace: subcommand_args.get("trace").and_then(|a| a.value.as_str()), + debug: subcommand_args + .get("debug") + .and_then(|a| a.value.as_bool()) + .or(Some(false)) + == Some(true), + stream: subcommand_args + .get("no-stream") + .and_then(|a| a.value.as_bool()) + .or(Some(false)) + == Some(false), + }, + load_scope( + subcommand_args.get("data").and_then(|a| a.value.as_str()), + subcommand_args + .get("data-file") + .and_then(|a| a.value.as_str()), + None, + )?, ) .and_then(|_trace| Ok(true)), "view" => new_window( diff --git a/pdl-live-react/src-tauri/src/commands/interpreter.rs b/pdl-live-react/src-tauri/src/commands/interpreter.rs index ff59f47e2..46bf804a9 100644 --- a/pdl-live-react/src-tauri/src/commands/interpreter.rs +++ b/pdl-live-react/src-tauri/src/commands/interpreter.rs @@ -1,8 +1,8 @@ use crate::pdl::interpreter::{pretty_print, run_string}; #[tauri::command] -pub async fn run_pdl_program(program: String, debug: bool) -> Result { - let (_, messages, _) = run_string(&program, debug) +pub async fn run_pdl_program(program: String) -> Result { + let (_, messages, _) = run_string(&program, Default::default(), Default::default()) .await .map_err(|err| err.to_string())?; diff --git a/pdl-live-react/src-tauri/src/pdl/interpreter.rs b/pdl-live-react/src-tauri/src/pdl/interpreter.rs index 7ace47581..c0659f1ce 100644 --- a/pdl-live-react/src-tauri/src/pdl/interpreter.rs +++ b/pdl-live-react/src-tauri/src/pdl/interpreter.rs @@ -34,6 +34,22 @@ type PdlError = Box; type Interpretation = Result<(PdlResult, Messages, PdlBlock), PdlError>; type InterpretationSync = Result<(PdlResult, Messages, PdlBlock), Box>; +pub struct RunOptions<'a> { + pub stream: bool, + pub debug: bool, + pub trace: Option<&'a str>, +} + +impl<'a> Default for RunOptions<'a> { + fn default() -> Self { + Self { + stream: true, + debug: false, + trace: None, + } + } +} + #[derive(Clone)] struct State { emit: bool, @@ -44,11 +60,11 @@ struct State { } impl State { - fn new() -> Self { + fn new(initial_scope: Scope) -> Self { Self { emit: true, cwd: ::std::env::current_dir().unwrap_or(PathBuf::from("/")), - scope: Scope::new(), + scope: initial_scope, escaped_variables: vec![], messages: vec![], } @@ -77,13 +93,12 @@ struct Interpreter<'a> { // batch: u32, // role: Role, // id_stack: Vec, + options: RunOptions<'a>, jinja_env: Environment<'a>, - debug: bool, - stream: bool, } impl<'a> Interpreter<'a> { - fn new() -> Self { + fn new(options: RunOptions<'a>) -> Self { let mut jinja_env = Environment::new(); // PDL uses custom variable delimeters, because {{ }} have pre-defined meaning in yaml jinja_env.set_syntax( @@ -98,8 +113,7 @@ impl<'a> Interpreter<'a> { // role: Role::User, // id_stack: vec![], jinja_env: jinja_env, - debug: false, - stream: true, + options: options, } } @@ -182,13 +196,13 @@ impl<'a> Interpreter<'a> { /// Evaluate String as a Jinja2 expression fn eval(&self, expr: &String, state: &State) -> Result { let result = self.jinja_env.render_str(expr.as_str(), &state.scope)?; - if self.debug { + if self.options.debug { eprintln!("Eval {} -> {} with scope {:?}", expr, result, state.scope); } let backup = result.clone(); Ok(from_str(&result).unwrap_or_else(|err| { - if self.debug { + if self.options.debug { eprintln!("Treating as plain string {}", result); eprintln!("... due to {}", err); } @@ -259,7 +273,7 @@ impl<'a> Interpreter<'a> { /// Run a PdlBlock::String async fn run_string(&self, msg: &String, state: &State) -> Interpretation { let trace = self.eval(msg, state)?; - if self.debug { + if self.options.debug { eprintln!("String {} -> {:?}", msg, trace); } @@ -306,7 +320,7 @@ impl<'a> Interpreter<'a> { }?; if let Some(def) = &variable { - if self.debug { + if self.options.debug { eprintln!("Def {} -> {}", def, result); } state.scope.insert(def.clone(), result.clone()); @@ -371,7 +385,7 @@ impl<'a> Interpreter<'a> { /// Run a PdlBlock::Call async fn run_call(&mut self, block: &CallBlock, state: &mut State) -> Interpretation { - if self.debug { + if self.options.debug { eprintln!("Call {:?}({:?})", block.call, block.args); eprintln!("Call scope {:?}", state.scope); } @@ -397,7 +411,7 @@ impl<'a> Interpreter<'a> { /// Run a PdlBlock::Empty async fn run_empty(&mut self, block: &EmptyBlock, state: &mut State) -> Interpretation { - if self.debug { + if self.options.debug { eprintln!("Empty"); } @@ -412,7 +426,7 @@ impl<'a> Interpreter<'a> { /// Run a PdlBlock::Call async fn run_if(&mut self, block: &IfBlock, state: &mut State) -> Interpretation { - if self.debug { + if self.options.debug { eprintln!("If {:?}({:?})", block.condition, block.then); } @@ -438,7 +452,7 @@ impl<'a> Interpreter<'a> { /// Run a PdlBlock::Include async fn run_include(&mut self, block: &IncludeBlock, state: &mut State) -> Interpretation { - if self.debug { + if self.options.debug { eprintln!("Include {:?}", block.include); } @@ -454,7 +468,7 @@ impl<'a> Interpreter<'a> { /// Run a PdlBlock::Import async fn run_import(&mut self, block: &ImportBlock, state: &mut State) -> Interpretation { - if self.debug { + if self.options.debug { eprintln!("Import {:?}", block.import); } @@ -598,7 +612,7 @@ impl<'a> Interpreter<'a> { }; let (options, tools) = self.to_ollama_model_options(&block.parameters); - if self.debug { + if self.options.debug { eprintln!("Model options {:?} {:?}", block.description, options); eprintln!("Model tools {:?} {:?}", block.description, tools); } @@ -620,7 +634,7 @@ impl<'a> Interpreter<'a> { None => (&ChatMessage::user("".into()), &[]), }; let mut history = Vec::from(history_slice); - if self.debug { + if self.options.debug { eprintln!( "Ollama {:?} model={:?} prompt={:?} history={:?}", block.description.clone().unwrap_or("".into()), @@ -638,7 +652,7 @@ impl<'a> Interpreter<'a> { .options(options) .tools(tools); - let (last_res, response_string) = if !self.stream { + let (last_res, response_string) = if !self.options.stream { let res = ollama .send_chat_messages_with_history(&mut history, req) .await?; @@ -688,7 +702,7 @@ impl<'a> Interpreter<'a> { if let Some(ref res) = last_res { self.def( &block.model_response, - &self.resultify_as_litellm(&from_str(&to_string(&res)?)?), + &resultify_as_litellm(&from_str(&to_string(&res)?)?), &None, state, true, @@ -724,36 +738,9 @@ impl<'a> Interpreter<'a> { } } - /// Transform a JSON Value into a PdlResult object - fn resultify(&self, value: &Value) -> PdlResult { - match value { - Value::Null => "".into(), - Value::Bool(b) => b.into(), - Value::Number(n) => n.clone().into(), - Value::String(s) => s.clone().into(), - Value::Array(a) => { - PdlResult::List(a.iter().map(|v| self.resultify(v)).collect::>()) - } - Value::Object(m) => PdlResult::Dict( - m.iter() - .map(|(k, v)| (k.clone(), self.resultify(v))) - .collect::>(), - ), - } - } - - /// Transform a JSON Value into a PdlResult object that is compatible with litellm's model response schema - fn resultify_as_litellm(&self, value: &Value) -> PdlResult { - self.resultify(&json!({ - "choices": [ - value - ] - })) - } - /// Run a PdlBlock::Data async fn run_data(&mut self, block: &DataBlock, state: &mut State) -> Interpretation { - if self.debug { + if self.options.debug { eprintln!("Data raw={:?} {:?}", block.raw, block.data); } @@ -761,7 +748,7 @@ impl<'a> Interpreter<'a> { if let Some(true) = block.raw { let result = self.def( &block.def, - &self.resultify(&block.data), + &resultify(&block.data), &block.parser, state, true, @@ -781,7 +768,7 @@ impl<'a> Interpreter<'a> { } async fn run_object(&mut self, block: &ObjectBlock, state: &mut State) -> Interpretation { - if self.debug { + if self.options.debug { eprintln!("Object {:?}", block.object); } @@ -821,7 +808,7 @@ impl<'a> Interpreter<'a> { ) .collect::, _>>()?; - if self.debug { + if self.options.debug { eprintln!("Repeat {:?}", iter_scopes); } @@ -893,7 +880,7 @@ impl<'a> Interpreter<'a> { block: &impl SequencingBlock, state: &mut State, ) -> Interpretation { - if self.debug { + if self.options.debug { let description = if let Some(d) = block.description() { d } else { @@ -1012,18 +999,16 @@ impl<'a> Interpreter<'a> { } } -pub async fn run( +pub async fn run<'a>( program: &PdlBlock, cwd: Option, - debug: bool, - stream: bool, + options: RunOptions<'a>, + initial_scope: Scope, ) -> Interpretation { crate::pdl::pull::pull_if_needed(&program).await?; - let mut interpreter = Interpreter::new(); - interpreter.debug = debug; - interpreter.stream = stream; - let mut state = State::new(); + let mut interpreter = Interpreter::new(options); + let mut state = State::new(initial_scope); if let Some(cwd) = cwd { state.cwd = cwd } @@ -1031,13 +1016,13 @@ pub async fn run( } #[allow(dead_code)] -pub fn run_sync( +pub fn run_sync<'a>( program: &PdlBlock, cwd: Option, - debug: bool, - stream: bool, + options: RunOptions<'a>, + initial_scope: Scope, ) -> InterpretationSync { - tauri::async_runtime::block_on(run(program, cwd, debug, stream)) + tauri::async_runtime::block_on(run(program, cwd, options, initial_scope)) .map_err(|err| Box::::from(err.to_string())) } @@ -1046,38 +1031,51 @@ pub fn parse_file(path: &PathBuf) -> Result { from_reader(::std::fs::File::open(path)?).map_err(|err| PdlError::from(err.to_string())) } -pub async fn run_file(source_file_path: &str, debug: bool, stream: bool) -> Interpretation { +pub async fn run_file<'a>( + source_file_path: &str, + options: RunOptions<'a>, + initial_scope: Scope, +) -> Interpretation { let path = PathBuf::from(source_file_path); let cwd = path.parent().and_then(|cwd| Some(cwd.to_path_buf())); let program = parse_file(&path)?; - run(&program, cwd, debug, stream).await + run(&program, cwd, options, initial_scope).await } -pub fn run_file_sync( +pub fn run_file_sync<'a>( source_file_path: &str, - _trace: Option<&str>, - _data: Option<&str>, - _data_file: Option<&str>, - debug: bool, - stream: bool, + options: RunOptions<'a>, + initial_scope: Scope, ) -> InterpretationSync { - tauri::async_runtime::block_on(run_file(source_file_path, debug, stream)) + tauri::async_runtime::block_on(run_file(source_file_path, options, initial_scope)) .map_err(|err| Box::::from(err.to_string())) } -pub async fn run_string(source: &str, debug: bool) -> Interpretation { - run(&from_yaml_str(source)?, None, debug, true).await +pub async fn run_string<'a>( + source: &str, + options: RunOptions<'a>, + initial_scope: Scope, +) -> Interpretation { + run(&from_yaml_str(source)?, None, options, initial_scope).await } #[allow(dead_code)] -pub async fn run_json(source: Value, debug: bool) -> Interpretation { - run_string(&to_string(&source)?, debug).await +pub async fn run_json<'a>( + source: Value, + options: RunOptions<'a>, + initial_scope: Scope, +) -> Interpretation { + run_string(&to_string(&source)?, options, initial_scope).await } #[allow(dead_code)] -pub fn run_json_sync(source: Value, debug: bool) -> InterpretationSync { - tauri::async_runtime::block_on(run_json(source, debug)) +pub fn run_json_sync<'a>( + source: Value, + options: RunOptions<'a>, + initial_scope: Scope, +) -> InterpretationSync { + tauri::async_runtime::block_on(run_json(source, options, initial_scope)) .map_err(|err| Box::::from(err.to_string())) } @@ -1105,3 +1103,64 @@ pub fn pretty_print(messages: &Vec) -> String { .collect::>() .join("\n") } + +/// Transform a JSON Value into a PdlResult object +fn resultify(value: &Value) -> PdlResult { + match value { + Value::Null => "".into(), + Value::Bool(b) => b.into(), + Value::Number(n) => n.clone().into(), + Value::String(s) => s.clone().into(), + Value::Array(a) => PdlResult::List(a.iter().map(|v| resultify(v)).collect::>()), + Value::Object(m) => PdlResult::Dict( + m.iter() + .map(|(k, v)| (k.clone(), resultify(v))) + .collect::>(), + ), + } +} + +/// Transform a JSON Value into a PdlResult object that is compatible with litellm's model response schema +fn resultify_as_litellm(value: &Value) -> PdlResult { + resultify(&json!({ + "choices": [ + value + ] + })) +} + +pub fn load_scope( + data: Option<&str>, + data_file: Option<&str>, + value: Option, +) -> Result> { + let mut scope = HashMap::new(); + + if let Some(data_file) = data_file { + if let PdlResult::Dict(d) = resultify(&from_reader(::std::fs::File::open(data_file)?)?) { + scope.extend(d); + } else { + return Err(Box::from(format!( + "Data file {data_file} does not contain a dictionary" + ))); + } + } + + if let Some(data) = data { + if let PdlResult::Dict(d) = resultify(&from_yaml_str(data)?) { + scope.extend(d); + } else { + return Err(Box::from(format!("Data is not a dictionary"))); + } + } + + if let Some(value) = value { + if let PdlResult::Dict(d) = resultify(&value) { + scope.extend(d); + } else { + return Err(Box::from(format!("Data is not a dictionary"))); + } + } + + Ok(scope) +} diff --git a/pdl-live-react/src-tauri/src/pdl/interpreter_tests.rs b/pdl-live-react/src-tauri/src/pdl/interpreter_tests.rs index 4e9741118..991a18d93 100644 --- a/pdl-live-react/src-tauri/src/pdl/interpreter_tests.rs +++ b/pdl-live-react/src-tauri/src/pdl/interpreter_tests.rs @@ -5,30 +5,66 @@ mod tests { use serde_json::json; use crate::pdl::{ - ast::{ModelBlock, PdlBlock}, - interpreter::{run_json_sync as run_json, run_sync as run}, + ast::{ModelBlock, PdlBlock, Scope}, + interpreter::{load_scope, run_json_sync as run_json, run_sync as run, RunOptions}, }; use ollama_rs::generation::chat::MessageRole; const DEFAULT_MODEL: &'static str = "ollama/granite3.2:2b"; + fn streaming<'a>() -> RunOptions<'a> { + let mut o: RunOptions = Default::default(); + o.stream = true; + o + } + + fn non_streaming<'a>() -> RunOptions<'a> { + let mut o: RunOptions = Default::default(); + o.stream = false; + o + } + + fn initial_scope() -> Scope { + Default::default() + } + #[test] fn string() -> Result<(), Box> { - let (_, messages, _) = run(&"hello".into(), None, false, true)?; + let (_, messages, _) = run(&"hello".into(), None, streaming(), initial_scope())?; assert_eq!(messages.len(), 1); assert_eq!(messages[0].role, MessageRole::User); assert_eq!(messages[0].content, "hello"); Ok(()) } + #[test] + fn string_via_initial_scope() -> Result<(), Box> { + let (_, messages, _) = run( + &"${x}".into(), + None, + streaming(), + load_scope( + None, + None, + Some(json!({ + "x": 333 + })), + )?, + )?; + assert_eq!(messages.len(), 1); + assert_eq!(messages[0].role, MessageRole::User); + assert_eq!(messages[0].content, "333"); + Ok(()) + } + #[test] fn single_model_via_input_string() -> Result<(), Box> { let (_, messages, _) = run( &PdlBlock::Model(ModelBlock::new(DEFAULT_MODEL).input_str("hello").build()), None, - false, - true, + streaming(), + initial_scope(), )?; assert_eq!(messages.len(), 1); assert_eq!(messages[0].role, MessageRole::Assistant); @@ -45,7 +81,8 @@ mod tests { { "model": DEFAULT_MODEL } ] }), - false, + streaming(), + initial_scope(), )?; assert_eq!(messages.len(), 2); assert_eq!(messages[0].role, MessageRole::User); @@ -67,7 +104,8 @@ mod tests { ] } }), - false, + streaming(), + initial_scope(), )?; assert_eq!(messages.len(), 1); assert_eq!(messages[0].role, MessageRole::Assistant); @@ -92,7 +130,8 @@ mod tests { { "model": DEFAULT_MODEL }, ] }), - false, + streaming(), + initial_scope(), )?; assert_eq!(messages.len(), 4); assert_eq!(messages[0].role, MessageRole::User); @@ -130,7 +169,7 @@ mod tests { ] }); - let (_, messages, _) = run_json(program, false)?; + let (_, messages, _) = run_json(program, streaming(), initial_scope())?; assert_eq!(messages.len(), 2); assert_eq!(messages[0].role, MessageRole::User); assert_eq!(messages[0].content, json); @@ -149,7 +188,7 @@ mod tests { ] }); - let (_, messages, _) = run_json(program, false)?; + let (_, messages, _) = run_json(program, streaming(), initial_scope())?; assert_eq!(messages.len(), 1); assert_eq!(messages[0].role, MessageRole::User); assert_eq!(messages[0].content, "value"); @@ -175,7 +214,7 @@ mod tests { ] }); - let (_, messages, _) = run_json(program, false)?; + let (_, messages, _) = run_json(program, streaming(), initial_scope())?; assert_eq!(messages.len(), 1); assert_eq!(messages[0].role, MessageRole::User); assert_eq!(messages[0].content, "hello world"); @@ -203,7 +242,7 @@ mod tests { ] }); - let (_, messages, _) = run_json(program, false)?; + let (_, messages, _) = run_json(program, streaming(), initial_scope())?; assert_eq!(messages.len(), 1); assert_eq!(messages[0].role, MessageRole::User); assert_eq!(messages[0].content, "hello world 4"); @@ -217,7 +256,7 @@ mod tests { "code":"print('hi ho'); result = 33" }); - let (_, messages, _) = run_json(program, false)?; + let (_, messages, _) = run_json(program, streaming(), initial_scope())?; assert_eq!(messages.len(), 1); assert_eq!(messages[0].role, MessageRole::User); assert_eq!(messages[0].content, "33"); @@ -231,7 +270,7 @@ mod tests { "code":"print('hi ho'); result = 'foo'" }); - let (_, messages, _) = run_json(program, false)?; + let (_, messages, _) = run_json(program, streaming(), initial_scope())?; assert_eq!(messages.len(), 1); assert_eq!(messages[0].role, MessageRole::User); assert_eq!(messages[0].content, "foo"); @@ -245,7 +284,7 @@ mod tests { "code":"print('hi ho'); result = {\"foo\": 3}" }); - let (_, messages, _) = run_json(program, false)?; + let (_, messages, _) = run_json(program, streaming(), initial_scope())?; assert_eq!(messages.len(), 1); assert_eq!(messages[0].role, MessageRole::User); assert_eq!(messages[0].content, "{'foo': 3}"); @@ -259,7 +298,7 @@ mod tests { "read":"./tests/data/foo.txt" }); - let (_, messages, _) = run_json(program, false)?; + let (_, messages, _) = run_json(program, streaming(), initial_scope())?; assert_eq!(messages.len(), 1); assert_eq!(messages[0].role, MessageRole::User); assert_eq!(messages[0].content, "this should be foo\n"); @@ -275,7 +314,7 @@ mod tests { ] }); - let (_, messages, _) = run_json(program, false)?; + let (_, messages, _) = run_json(program, streaming(), initial_scope())?; assert_eq!(messages.len(), 2); assert_eq!(messages[0].role, MessageRole::User); assert_eq!( @@ -302,7 +341,7 @@ mod tests { } }); - let (_, messages, _) = run_json(program, false)?; + let (_, messages, _) = run_json(program, streaming(), initial_scope())?; assert_eq!(messages.len(), 3); assert_eq!(messages[0].role, MessageRole::User); assert_eq!(messages[0].content, "2"); @@ -327,7 +366,7 @@ mod tests { } }); - let (_, messages, _) = run_json(program, false)?; + let (_, messages, _) = run_json(program, streaming(), initial_scope())?; assert_eq!(messages.len(), 3); assert_eq!(messages[0].role, MessageRole::User); assert_eq!(messages[0].content, "5"); @@ -352,7 +391,7 @@ mod tests { } }); - let (_, messages, _) = run_json(program, false)?; + let (_, messages, _) = run_json(program, streaming(), initial_scope())?; assert_eq!(messages.len(), 3); assert_eq!(messages[0].role, MessageRole::User); assert_eq!(messages[0].content, "4a"); @@ -370,7 +409,7 @@ mod tests { "then": "good" }); - let (_, messages, _) = run_json(program, false)?; + let (_, messages, _) = run_json(program, streaming(), initial_scope())?; assert_eq!(messages.len(), 1); assert_eq!(messages[0].role, MessageRole::User); assert_eq!(messages[0].content, "good"); @@ -385,7 +424,7 @@ mod tests { "else": "good" }); - let (_, messages, _) = run_json(program, false)?; + let (_, messages, _) = run_json(program, streaming(), initial_scope())?; assert_eq!(messages.len(), 1); assert_eq!(messages[0].role, MessageRole::User); assert_eq!(messages[0].content, "good"); @@ -403,7 +442,7 @@ mod tests { "else": "good" }); - let (_, messages, _) = run_json(program, false)?; + let (_, messages, _) = run_json(program, streaming(), initial_scope())?; assert_eq!(messages.len(), 1); assert_eq!(messages[0].role, MessageRole::User); assert_eq!(messages[0].content, "good"); @@ -425,7 +464,7 @@ mod tests { "text": [ "${ obj.a }" ] }); - let (_, messages, _) = run_json(program, false)?; + let (_, messages, _) = run_json(program, streaming(), initial_scope())?; assert_eq!(messages.len(), 1); assert_eq!(messages[0].role, MessageRole::User); assert_eq!(messages[0].content, "good on object"); @@ -451,7 +490,7 @@ mod tests { "text": [ "${ obj.a.b }" ] }); - let (_, messages, _) = run_json(program, false)?; + let (_, messages, _) = run_json(program, streaming(), initial_scope())?; assert_eq!(messages.len(), 1); assert_eq!(messages[0].role, MessageRole::User); assert_eq!(messages[0].content, "good on object"); @@ -464,7 +503,7 @@ mod tests { "include": "./tests/cli/call-with-args.pdl" }); - let (_, messages, _) = run_json(program, false)?; + let (_, messages, _) = run_json(program, streaming(), initial_scope())?; assert_eq!(messages.len(), 1); assert_eq!(messages[0].role, MessageRole::User); assert_eq!(messages[0].content, "hello world 4 bye"); @@ -477,7 +516,7 @@ mod tests { "include": "./tests/cli/data1.pdl" }); - let (_, messages, _) = run_json(program, false)?; + let (_, messages, _) = run_json(program, streaming(), initial_scope())?; assert_eq!(messages.len(), 1); assert_eq!(messages[0].role, MessageRole::User); assert_eq!(messages[0].content, "xxxx3true"); @@ -490,7 +529,7 @@ mod tests { "include": "./tests/cli/data2.pdl" }); - let (_, messages, _) = run_json(program, false)?; + let (_, messages, _) = run_json(program, streaming(), initial_scope())?; assert_eq!(messages.len(), 1); assert_eq!(messages[0].role, MessageRole::User); assert_eq!(messages[0].content, "xxxx3true"); @@ -503,7 +542,7 @@ mod tests { "include": "./tests/cli/data3.pdl" }); - let (_, messages, _) = run_json(program, false)?; + let (_, messages, _) = run_json(program, streaming(), initial_scope())?; assert_eq!(messages.len(), 1); assert_eq!(messages[0].role, MessageRole::User); assert_eq!(messages[0].content, "${x}3true"); @@ -516,7 +555,7 @@ mod tests { "include": "./tests/cli/data4.pdl" }); - let (_, messages, _) = run_json(program, false)?; + let (_, messages, _) = run_json(program, streaming(), initial_scope())?; assert_eq!(messages.len(), 1); assert_eq!(messages[0].role, MessageRole::User); assert_eq!(messages[0].content, "yyyyxxxx3true"); @@ -529,7 +568,7 @@ mod tests { "include": "../../examples/tutorial/import.pdl" }); - let (_, messages, _) = run_json(program, false)?; + let (_, messages, _) = run_json(program, streaming(), initial_scope())?; assert_eq!(messages.len(), 1); assert_eq!(messages[0].role, MessageRole::User); assert_eq!(messages[0].content, "Bye!"); @@ -542,7 +581,7 @@ mod tests { "include": "./tests/cli/scoping_1.pdl" }); - let (_, messages, _) = run_json(program, false)?; + let (_, messages, _) = run_json(program, streaming(), initial_scope())?; assert_eq!(messages.len(), 2); assert_eq!(messages[0].role, MessageRole::User); assert_eq!(messages[0].content, "3yo"); @@ -554,7 +593,7 @@ mod tests { #[test] fn bee_1() -> Result<(), Box> { let program = crate::compile::beeai::compile("./tests/data/bee_1.py", false)?; - let (_, messages, _) = run(&program, None, false, false)?; + let (_, messages, _) = run(&program, None, non_streaming(), initial_scope())?; assert_eq!(messages.len(), 9); assert!( messages.iter().any(|m| m.role == MessageRole::User diff --git a/pdl-live-react/src-tauri/tauri.conf.json b/pdl-live-react/src-tauri/tauri.conf.json index f37c55cd1..7f8f9a864 100644 --- a/pdl-live-react/src-tauri/tauri.conf.json +++ b/pdl-live-react/src-tauri/tauri.conf.json @@ -61,10 +61,12 @@ }, { "name": "data", + "short": "d", "takesValue": true }, { "name": "data-file", + "short": "f", "takesValue": true }, { From 2d2862658e0cbf1cd2819752070de3878c885cb7 Mon Sep 17 00:00:00 2001 From: Nick Mitchell Date: Thu, 10 Apr 2025 13:39:25 -0400 Subject: [PATCH 15/39] fix: begin phasing in Metadata (common defs, etc. attrs) into rust AST Rather than duplicating this "metadata" logic -- the stuff common to all non-literal blocks. This just starts the migration. The main trick is to use the serde `flatten` capability, so that we can maintain a separate Metadata struct in Rust, but have it flattened into the enclosing object for serde. Signed-off-by: Nick Mitchell --- pdl-live-react/src-tauri/src/compile/beeai.rs | 30 +++++++++-------- pdl-live-react/src-tauri/src/pdl/ast.rs | 33 ++++++++++++------- pdl-live-react/src-tauri/src/pdl/extract.rs | 22 ++++++++++--- .../src-tauri/src/pdl/interpreter.rs | 14 ++++---- 4 files changed, 64 insertions(+), 35 deletions(-) diff --git a/pdl-live-react/src-tauri/src/compile/beeai.rs b/pdl-live-react/src-tauri/src/compile/beeai.rs index bd589fc94..a13fa0e0c 100644 --- a/pdl-live-react/src-tauri/src/compile/beeai.rs +++ b/pdl-live-react/src-tauri/src/compile/beeai.rs @@ -12,9 +12,9 @@ use serde_json::{from_reader, json, to_string, Map, Value}; use tempfile::Builder; use crate::pdl::ast::{ - ArrayBlock, CallBlock, FunctionBlock, ListOrString, MessageBlock, ModelBlock, ObjectBlock, - PdlBaseType, PdlBlock, PdlOptionalType, PdlParser, PdlType, PythonCodeBlock, RepeatBlock, Role, - TextBlock, + ArrayBlock, CallBlock, FunctionBlock, ListOrString, MessageBlock, Metadata, ModelBlock, + ObjectBlock, PdlBaseType, PdlBlock, PdlOptionalType, PdlParser, PdlType, PythonCodeBlock, + RepeatBlock, Role, TextBlock, }; use crate::pdl::pip::pip_install_if_needed; use crate::pdl::requirements::BEEAI_FRAMEWORK; @@ -192,7 +192,7 @@ fn with_tools( fn call_tools(model: &String, parameters: &HashMap) -> PdlBlock { let repeat = PdlBlock::Text(TextBlock { def: None, - defs: None, + metadata: None, role: None, parser: None, description: Some("Calling tool ${ tool.function.name }".to_string()), @@ -206,11 +206,13 @@ fn call_tools(model: &String, parameters: &HashMap) -> PdlBlock { name: Some("${ tool.function.name }".to_string()), tool_call_id: Some("${ tool.id }".to_string()), content: Box::new(PdlBlock::Call(CallBlock { - defs: json_loads( - &"args", - &"pdl__args", - &"${ tool.function.arguments }", - ), + metadata: Some(Metadata { + defs: json_loads( + &"args", + &"pdl__args", + &"${ tool.function.arguments }", + ), + }), call: "${ pdl__tools[tool.function.name] }".to_string(), // look up tool in tool_declarations def (see below) args: Some("${ args }".into()), // invoke with arguments as specified by the model })), @@ -464,7 +466,7 @@ asyncio.run(invoke()) role: Some(Role::System), text: vec![PdlBlock::String(instructions)], def: None, - defs: None, + metadata: None, parser: None, description: Some("Model instructions".into()), })); @@ -504,7 +506,7 @@ asyncio.run(invoke()) function: HashMap::new(), return_: Box::new(PdlBlock::Text(TextBlock { def: None, - defs: None, + metadata: None, role: None, parser: None, description: Some(format!("Model call {}", &model)), @@ -514,7 +516,7 @@ asyncio.run(invoke()) ); PdlBlock::Text(TextBlock { def: None, - defs: Some(defs), + metadata: Some(Metadata { defs: Some(defs) }), role: None, parser: None, description: Some("Model call wrapper".to_string()), @@ -533,7 +535,7 @@ asyncio.run(invoke()) let pdl: PdlBlock = PdlBlock::Text(TextBlock { def: None, - defs: if tool_declarations.len() == 0 { + metadata: if tool_declarations.len() == 0 { None } else { let mut m = indexmap::IndexMap::new(); @@ -543,7 +545,7 @@ asyncio.run(invoke()) object: tool_declarations, }), ); - Some(m) + Some(Metadata { defs: Some(m) }) }, description: Some(bee.workflow.workflow.name), role: None, diff --git a/pdl-live-react/src-tauri/src/pdl/ast.rs b/pdl-live-react/src-tauri/src/pdl/ast.rs index 4a6610725..8e5398f0b 100644 --- a/pdl-live-react/src-tauri/src/pdl/ast.rs +++ b/pdl-live-react/src-tauri/src/pdl/ast.rs @@ -51,6 +51,13 @@ pub enum PdlType { Object(HashMap), } +/// Common metadata of blocks +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct Metadata { + #[serde(skip_serializing_if = "Option::is_none")] + pub defs: Option>, +} + /// Call a function #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(tag = "kind", rename = "call")] @@ -62,8 +69,9 @@ pub struct CallBlock { #[serde(skip_serializing_if = "Option::is_none")] pub args: Option, + #[serde(flatten)] #[serde(skip_serializing_if = "Option::is_none")] - pub defs: Option>, + pub metadata: Option, } impl CallBlock { @@ -71,7 +79,7 @@ impl CallBlock { CallBlock { call: call, args: None, - defs: None, + metadata: None, } } } @@ -81,7 +89,7 @@ pub trait SequencingBlock { fn description(&self) -> &Option; fn role(&self) -> &Option; fn def(&self) -> &Option; - fn defs(&self) -> &Option>; + fn metadata(&self) -> &Option; fn items(&self) -> &Vec; fn with_items(&self, items: Vec) -> Self; fn parser(&self) -> &Option; @@ -104,8 +112,9 @@ pub struct LastOfBlock { #[serde(skip_serializing_if = "Option::is_none")] pub role: Option, + #[serde(flatten)] #[serde(skip_serializing_if = "Option::is_none")] - pub defs: Option>, + pub metadata: Option, #[serde(skip_serializing_if = "Option::is_none")] pub parser: Option, @@ -126,8 +135,8 @@ impl SequencingBlock for LastOfBlock { fn def(&self) -> &Option { return &self.def; } - fn defs(&self) -> &Option> { - &self.defs + fn metadata(&self) -> &Option { + &self.metadata } fn items(&self) -> &Vec { &self.last_of @@ -171,8 +180,9 @@ pub struct TextBlock { #[serde(skip_serializing_if = "Option::is_none")] pub role: Option, + #[serde(flatten)] #[serde(skip_serializing_if = "Option::is_none")] - pub defs: Option>, + pub metadata: Option, #[serde(skip_serializing_if = "Option::is_none")] pub parser: Option, @@ -193,8 +203,8 @@ impl SequencingBlock for TextBlock { fn def(&self) -> &Option { return &self.def; } - fn defs(&self) -> &Option> { - &self.defs + fn metadata(&self) -> &Option { + &self.metadata } fn items(&self) -> &Vec { &self.text @@ -228,7 +238,7 @@ impl TextBlock { pub fn new(text: Vec) -> Self { TextBlock { def: None, - defs: None, + metadata: None, description: None, role: None, parser: None, @@ -525,8 +535,9 @@ pub struct IfBlock { #[serde(skip_serializing_if = "Option::is_none")] pub else_: Option>, + #[serde(flatten)] #[serde(skip_serializing_if = "Option::is_none")] - pub defs: Option>, + pub metadata: Option, } /// Return the array of values computed by each block of the list of blocks diff --git a/pdl-live-react/src-tauri/src/pdl/extract.rs b/pdl-live-react/src-tauri/src/pdl/extract.rs index 2489b7b07..61af006e1 100644 --- a/pdl-live-react/src-tauri/src/pdl/extract.rs +++ b/pdl-live-react/src-tauri/src/pdl/extract.rs @@ -1,4 +1,4 @@ -use crate::pdl::ast::PdlBlock; +use crate::pdl::ast::{Metadata, PdlBlock}; /// Extract models referenced by the programs pub fn extract_models(program: &PdlBlock) -> Vec { @@ -35,7 +35,10 @@ fn extract_values_iter(program: &PdlBlock, field: &str, values: &mut Vec b.text .iter() .for_each(|p| extract_values_iter(p, field, values)); - if let Some(defs) = &b.defs { + if let Some(Metadata { + defs: Some(defs), .. + }) = &b.metadata + { defs.values() .for_each(|p| extract_values_iter(p, field, values)); } @@ -44,7 +47,10 @@ fn extract_values_iter(program: &PdlBlock, field: &str, values: &mut Vec b.last_of .iter() .for_each(|p| extract_values_iter(p, field, values)); - if let Some(defs) = &b.defs { + if let Some(Metadata { + defs: Some(defs), .. + }) = &b.metadata + { defs.values() .for_each(|p| extract_values_iter(p, field, values)); } @@ -54,11 +60,19 @@ fn extract_values_iter(program: &PdlBlock, field: &str, values: &mut Vec if let Some(else_) = &b.else_ { extract_values_iter(else_, field, values); } - if let Some(defs) = &b.defs { + if let Some(Metadata { + defs: Some(defs), .. + }) = &b.metadata + { defs.values() .for_each(|p| extract_values_iter(p, field, values)); } } + PdlBlock::Empty(b) => { + b.defs + .values() + .for_each(|p| extract_values_iter(p, field, values)); + } PdlBlock::Object(b) => b .object .values() diff --git a/pdl-live-react/src-tauri/src/pdl/interpreter.rs b/pdl-live-react/src-tauri/src/pdl/interpreter.rs index c0659f1ce..171a5d175 100644 --- a/pdl-live-react/src-tauri/src/pdl/interpreter.rs +++ b/pdl-live-react/src-tauri/src/pdl/interpreter.rs @@ -1,4 +1,3 @@ -// use ::std::cell::LazyCell; use ::std::collections::HashMap; use ::std::error::Error; use ::std::path::PathBuf; @@ -91,7 +90,6 @@ impl State { struct Interpreter<'a> { // batch: u32, - // role: Role, // id_stack: Vec, options: RunOptions<'a>, jinja_env: Environment<'a>, @@ -110,7 +108,6 @@ impl<'a> Interpreter<'a> { Self { // batch: 0, - // role: Role::User, // id_stack: vec![], jinja_env: jinja_env, options: options, @@ -415,8 +412,9 @@ impl<'a> Interpreter<'a> { eprintln!("Empty"); } - let trace = block.clone(); self.process_defs(&Some(block.defs.clone()), state).await?; + + let trace = block.clone(); Ok(( PdlResult::Dict(state.scope.clone()), vec![], @@ -430,7 +428,9 @@ impl<'a> Interpreter<'a> { eprintln!("If {:?}({:?})", block.condition, block.then); } - self.process_defs(&block.defs, state).await?; + if let Some(meta) = &block.metadata { + self.process_defs(&meta.defs, state).await?; + } let cond = match &block.condition { StringOrBoolean::Boolean(b) => PdlResult::Bool(*b), @@ -893,7 +893,9 @@ impl<'a> Interpreter<'a> { let mut output_messages = vec![]; let mut output_blocks = vec![]; - self.process_defs(block.defs(), state).await?; + if let Some(meta) = block.metadata() { + self.process_defs(&meta.defs, state).await?; + } // here is where we iterate over the sequence items let mut iter = block.items().iter(); From c5b1817d88e5451f514bf7e4adba8f35a4f326ff Mon Sep 17 00:00:00 2001 From: Louis Mandel Date: Thu, 10 Apr 2025 15:41:56 -0400 Subject: [PATCH 16/39] chore: update granite-io dependency (#896) Signed-off-by: Louis Mandel --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 5d834f936..bad003d1f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,7 +17,7 @@ dependencies = [ "termcolor~=2.0", "ipython>=8,<10", "json-repair~=0.35", - "granite-io~=0.2", + "granite-io>=0.2,<0.4", ] authors = [ { name="Mandana Vaziri", email="mvaziri@us.ibm.com" }, From c9c8fc5ec9b648701d6c222147cf808d9e91b8c7 Mon Sep 17 00:00:00 2001 From: Nick Mitchell Date: Fri, 11 Apr 2025 05:32:19 -0400 Subject: [PATCH 17/39] refactor: move def attr into Metadata (rust interpreter) Signed-off-by: Nick Mitchell --- pdl-live-react/src-tauri/src/compile/beeai.rs | 18 +++--- pdl-live-react/src-tauri/src/pdl/ast.rs | 58 +++++++++++-------- .../src-tauri/src/pdl/interpreter.rs | 8 +-- 3 files changed, 48 insertions(+), 36 deletions(-) diff --git a/pdl-live-react/src-tauri/src/compile/beeai.rs b/pdl-live-react/src-tauri/src/compile/beeai.rs index a13fa0e0c..76d2d3c7c 100644 --- a/pdl-live-react/src-tauri/src/compile/beeai.rs +++ b/pdl-live-react/src-tauri/src/compile/beeai.rs @@ -191,7 +191,6 @@ fn with_tools( fn call_tools(model: &String, parameters: &HashMap) -> PdlBlock { let repeat = PdlBlock::Text(TextBlock { - def: None, metadata: None, role: None, parser: None, @@ -207,6 +206,7 @@ fn call_tools(model: &String, parameters: &HashMap) -> PdlBlock { tool_call_id: Some("${ tool.id }".to_string()), content: Box::new(PdlBlock::Call(CallBlock { metadata: Some(Metadata { + def: None, defs: json_loads( &"args", &"pdl__args", @@ -465,7 +465,6 @@ asyncio.run(invoke()) model_call.push(PdlBlock::Text(TextBlock { role: Some(Role::System), text: vec![PdlBlock::String(instructions)], - def: None, metadata: None, parser: None, description: Some("Model instructions".into()), @@ -482,9 +481,9 @@ asyncio.run(invoke()) }; model_call.push(PdlBlock::Model(ModelBlock { + metadata: None, input: None, description: Some(description), - def: None, model: model.clone(), model_response: model_response, pdl_result: None, @@ -505,7 +504,6 @@ asyncio.run(invoke()) PdlBlock::Function(FunctionBlock { function: HashMap::new(), return_: Box::new(PdlBlock::Text(TextBlock { - def: None, metadata: None, role: None, parser: None, @@ -515,8 +513,10 @@ asyncio.run(invoke()) }), ); PdlBlock::Text(TextBlock { - def: None, - metadata: Some(Metadata { defs: Some(defs) }), + metadata: Some(Metadata { + def: None, + defs: Some(defs), + }), role: None, parser: None, description: Some("Model call wrapper".to_string()), @@ -534,7 +534,6 @@ asyncio.run(invoke()) .collect::>(); let pdl: PdlBlock = PdlBlock::Text(TextBlock { - def: None, metadata: if tool_declarations.len() == 0 { None } else { @@ -545,7 +544,10 @@ asyncio.run(invoke()) object: tool_declarations, }), ); - Some(Metadata { defs: Some(m) }) + Some(Metadata { + def: None, + defs: Some(m), + }) }, description: Some(bee.workflow.workflow.name), role: None, diff --git a/pdl-live-react/src-tauri/src/pdl/ast.rs b/pdl-live-react/src-tauri/src/pdl/ast.rs index 8e5398f0b..599304208 100644 --- a/pdl-live-react/src-tauri/src/pdl/ast.rs +++ b/pdl-live-react/src-tauri/src/pdl/ast.rs @@ -56,6 +56,17 @@ pub enum PdlType { pub struct Metadata { #[serde(skip_serializing_if = "Option::is_none")] pub defs: Option>, + + #[serde(skip_serializing_if = "Option::is_none")] + pub def: Option, +} +impl Default for Metadata { + fn default() -> Self { + Self { + defs: None, + def: None, + } + } } /// Call a function @@ -88,7 +99,6 @@ pub trait SequencingBlock { fn kind(&self) -> &str; fn description(&self) -> &Option; fn role(&self) -> &Option; - fn def(&self) -> &Option; fn metadata(&self) -> &Option; fn items(&self) -> &Vec; fn with_items(&self, items: Vec) -> Self; @@ -118,9 +128,6 @@ pub struct LastOfBlock { #[serde(skip_serializing_if = "Option::is_none")] pub parser: Option, - - #[serde(skip_serializing_if = "Option::is_none")] - pub def: Option, } impl SequencingBlock for LastOfBlock { fn kind(&self) -> &str { @@ -132,9 +139,6 @@ impl SequencingBlock for LastOfBlock { fn role(&self) -> &Option { &self.role } - fn def(&self) -> &Option { - return &self.def; - } fn metadata(&self) -> &Option { &self.metadata } @@ -186,9 +190,6 @@ pub struct TextBlock { #[serde(skip_serializing_if = "Option::is_none")] pub parser: Option, - - #[serde(skip_serializing_if = "Option::is_none")] - pub def: Option, } impl SequencingBlock for TextBlock { fn kind(&self) -> &str { @@ -200,9 +201,6 @@ impl SequencingBlock for TextBlock { fn role(&self) -> &Option { &self.role } - fn def(&self) -> &Option { - return &self.def; - } fn metadata(&self) -> &Option { &self.metadata } @@ -237,7 +235,6 @@ impl SequencingBlock for TextBlock { impl TextBlock { pub fn new(text: Vec) -> Self { TextBlock { - def: None, metadata: None, description: None, role: None, @@ -247,7 +244,16 @@ impl TextBlock { } pub fn def(&mut self, def: &str) -> &mut Self { - self.def = Some(def.into()); + match &mut self.metadata { + Some(metadata) => { + metadata.def = Some(def.into()); + } + None => { + let mut metadata: Metadata = Default::default(); + metadata.def = Some(def.into()); + self.metadata = Some(metadata); + } + } self } @@ -295,12 +301,14 @@ pub struct PdlUsage { #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(tag = "kind", rename = "model")] pub struct ModelBlock { + #[serde(flatten)] + #[serde(skip_serializing_if = "Option::is_none")] + pub metadata: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub description: Option, pub model: String, #[serde(skip_serializing_if = "Option::is_none")] - pub def: Option, - #[serde(skip_serializing_if = "Option::is_none")] pub parameters: Option>, #[serde(skip_serializing_if = "Option::is_none")] pub input: Option>, @@ -318,7 +326,7 @@ pub struct ModelBlock { impl ModelBlock { pub fn new(model: &str) -> Self { ModelBlock { - def: None, + metadata: None, description: None, model_response: None, parameters: None, @@ -433,15 +441,16 @@ pub struct ObjectBlock { #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(tag = "kind")] pub struct DataBlock { + #[serde(flatten)] + #[serde(skip_serializing_if = "Option::is_none")] + pub metadata: Option, + pub data: Value, /// Do not evaluate expressions inside strings. #[serde(skip_serializing_if = "Option::is_none")] pub raw: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub def: Option, - #[serde(skip_serializing_if = "Option::is_none")] pub parser: Option, } @@ -486,6 +495,10 @@ pub enum StringOrNull { #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(tag = "kind", rename = "read")] pub struct ReadBlock { + #[serde(flatten)] + #[serde(skip_serializing_if = "Option::is_none")] + pub metadata: Option, + /// Name of the file to read. If `None`, read the standard input. pub read: StringOrNull, @@ -495,9 +508,6 @@ pub struct ReadBlock { /// Indicate if one or multiple lines should be read. pub multiline: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub def: Option, - #[serde(skip_serializing_if = "Option::is_none")] pub parser: Option, } diff --git a/pdl-live-react/src-tauri/src/pdl/interpreter.rs b/pdl-live-react/src-tauri/src/pdl/interpreter.rs index 171a5d175..ba834127e 100644 --- a/pdl-live-react/src-tauri/src/pdl/interpreter.rs +++ b/pdl-live-react/src-tauri/src/pdl/interpreter.rs @@ -366,7 +366,7 @@ impl<'a> Interpreter<'a> { }; let result = self.def( - &block.def, + &block.metadata.as_ref().and_then(|m| m.def.clone()), &buffer.clone().into(), &block.parser, state, @@ -747,7 +747,7 @@ impl<'a> Interpreter<'a> { let mut trace = block.clone(); if let Some(true) = block.raw { let result = self.def( - &block.def, + &block.metadata.as_ref().and_then(|m| m.def.clone()), &resultify(&block.data), &block.parser, state, @@ -756,7 +756,7 @@ impl<'a> Interpreter<'a> { Ok((result, vec![], PdlBlock::Data(trace))) } else { let result = self.def( - &block.def, + &block.metadata.as_ref().and_then(|m| m.def.clone()), &self.eval_json(&block.data, state)?, &block.parser, state, @@ -914,7 +914,7 @@ impl<'a> Interpreter<'a> { let trace = block.with_items(output_blocks); let result = self.def( - trace.def(), + &block.metadata().as_ref().and_then(|m| m.def.clone()), &trace.result_for(output_results), trace.parser(), state, From 82ae5425ad84040565e9dc38187596609dfce1b0 Mon Sep 17 00:00:00 2001 From: Nick Mitchell Date: Fri, 11 Apr 2025 11:07:49 -0400 Subject: [PATCH 18/39] feat: introduce Expr typing and apply it to IfBlock.condition Signed-off-by: Nick Mitchell --- pdl-live-react/src-tauri/src/pdl/ast.rs | 19 ++++++- .../src-tauri/src/pdl/interpreter.rs | 53 +++++++++++++------ 2 files changed, 54 insertions(+), 18 deletions(-) diff --git a/pdl-live-react/src-tauri/src/pdl/ast.rs b/pdl-live-react/src-tauri/src/pdl/ast.rs index 599304208..32666ddee 100644 --- a/pdl-live-react/src-tauri/src/pdl/ast.rs +++ b/pdl-live-react/src-tauri/src/pdl/ast.rs @@ -519,6 +519,23 @@ pub enum StringOrBoolean { Boolean(bool), } +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct Expr { + #[serde(rename = "pdl__expr")] + pub pdl_expr: S, + + #[serde(rename = "pdl__result", skip_serializing_if = "Option::is_none")] + pub pdl_result: Option, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(untagged)] +pub enum EvalsTo { + Const(T), + Jinja(String), + Expr(Expr), +} + /// Conditional control structure. /// /// Example: @@ -535,7 +552,7 @@ pub enum StringOrBoolean { pub struct IfBlock { /// The condition to check #[serde(rename = "if")] - pub condition: StringOrBoolean, + pub condition: EvalsTo, /// Branch to execute if the condition is true pub then: Box, diff --git a/pdl-live-react/src-tauri/src/pdl/interpreter.rs b/pdl-live-react/src-tauri/src/pdl/interpreter.rs index ba834127e..5d7a5120d 100644 --- a/pdl-live-react/src-tauri/src/pdl/interpreter.rs +++ b/pdl-live-react/src-tauri/src/pdl/interpreter.rs @@ -21,10 +21,10 @@ use serde_json::{from_str, json, to_string, Value}; use serde_norway::{from_reader, from_str as from_yaml_str}; use crate::pdl::ast::{ - ArrayBlock, CallBlock, Closure, DataBlock, EmptyBlock, FunctionBlock, IfBlock, ImportBlock, - IncludeBlock, ListOrString, MessageBlock, ModelBlock, ObjectBlock, PdlBlock, PdlParser, - PdlResult, PdlUsage, PythonCodeBlock, ReadBlock, RepeatBlock, Role, Scope, SequencingBlock, - StringOrBoolean, StringOrNull, + ArrayBlock, CallBlock, Closure, DataBlock, EmptyBlock, EvalsTo, Expr, FunctionBlock, IfBlock, + ImportBlock, IncludeBlock, ListOrString, MessageBlock, ModelBlock, ObjectBlock, PdlBlock, + PdlParser, PdlResult, PdlUsage, PythonCodeBlock, ReadBlock, RepeatBlock, Role, Scope, + SequencingBlock, StringOrBoolean, StringOrNull, }; type Messages = Vec; @@ -207,6 +207,33 @@ impl<'a> Interpreter<'a> { })) } + /// Evaluate an Expr to a bool + fn eval_to_bool( + &self, + expr: &EvalsTo, + state: &State, + ) -> Result { + match expr { + EvalsTo::Const(b) + | EvalsTo::Expr(Expr { + pdl_expr: StringOrBoolean::Boolean(b), + .. + }) => Ok(b.clone()), + + EvalsTo::Jinja(s) + | EvalsTo::Expr(Expr { + pdl_expr: StringOrBoolean::String(s), + .. + }) => match self.eval(s, state)? { + PdlResult::Bool(b) => Ok(b.clone()), + x => Err(Box::from(format!( + "Expression {s} evaluated to non-boolean {:?}", + x + ))), + }, + } + } + /// Evaluate String as a Jinja2 expression, expecting a string in response fn eval_to_string(&self, expr: &String, state: &State) -> Result { match self.eval(expr, state)? { @@ -432,21 +459,13 @@ impl<'a> Interpreter<'a> { self.process_defs(&meta.defs, state).await?; } - let cond = match &block.condition { - StringOrBoolean::Boolean(b) => PdlResult::Bool(*b), - StringOrBoolean::String(s) => self.eval(s, state)?, - }; - - match cond { - PdlResult::Bool(true) => self.run_quiet(&block.then, state).await, - PdlResult::Bool(false) => match &block.else_ { + if self.eval_to_bool(&block.condition, state)? { + self.run_quiet(&block.then, state).await + } else { + match &block.else_ { Some(else_block) => self.run_quiet(&else_block, state).await, None => Ok(("".into(), vec![], PdlBlock::If(block.clone()))), - }, - x => Err(Box::from(format!( - "if block condition evaluated to non-boolean value: {:?}", - x - ))), + } } } From 7a747079a1b48b33c8033b6fccddbba7c0ed4b58 Mon Sep 17 00:00:00 2001 From: Nick Mitchell Date: Fri, 11 Apr 2025 11:30:22 -0400 Subject: [PATCH 19/39] feat: update rust Call AST to use Expr for condition attr Signed-off-by: Nick Mitchell --- pdl-live-react/src-tauri/src/compile/beeai.rs | 8 ++++---- pdl-live-react/src-tauri/src/pdl/ast.rs | 6 +++--- pdl-live-react/src-tauri/src/pdl/interpreter.rs | 15 ++++++++++++++- 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/pdl-live-react/src-tauri/src/compile/beeai.rs b/pdl-live-react/src-tauri/src/compile/beeai.rs index 76d2d3c7c..41e74e11b 100644 --- a/pdl-live-react/src-tauri/src/compile/beeai.rs +++ b/pdl-live-react/src-tauri/src/compile/beeai.rs @@ -12,9 +12,9 @@ use serde_json::{from_reader, json, to_string, Map, Value}; use tempfile::Builder; use crate::pdl::ast::{ - ArrayBlock, CallBlock, FunctionBlock, ListOrString, MessageBlock, Metadata, ModelBlock, - ObjectBlock, PdlBaseType, PdlBlock, PdlOptionalType, PdlParser, PdlType, PythonCodeBlock, - RepeatBlock, Role, TextBlock, + ArrayBlock, CallBlock, EvalsTo, FunctionBlock, ListOrString, MessageBlock, Metadata, + ModelBlock, ObjectBlock, PdlBaseType, PdlBlock, PdlOptionalType, PdlParser, PdlType, + PythonCodeBlock, RepeatBlock, Role, TextBlock, }; use crate::pdl::pip::pip_install_if_needed; use crate::pdl::requirements::BEEAI_FRAMEWORK; @@ -213,7 +213,7 @@ fn call_tools(model: &String, parameters: &HashMap) -> PdlBlock { &"${ tool.function.arguments }", ), }), - call: "${ pdl__tools[tool.function.name] }".to_string(), // look up tool in tool_declarations def (see below) + call: EvalsTo::Jinja("${ pdl__tools[tool.function.name] }".to_string()), // look up tool in tool_declarations def (see below) args: Some("${ args }".into()), // invoke with arguments as specified by the model })), })], diff --git a/pdl-live-react/src-tauri/src/pdl/ast.rs b/pdl-live-react/src-tauri/src/pdl/ast.rs index 32666ddee..7b16738ff 100644 --- a/pdl-live-react/src-tauri/src/pdl/ast.rs +++ b/pdl-live-react/src-tauri/src/pdl/ast.rs @@ -74,7 +74,7 @@ impl Default for Metadata { #[serde(tag = "kind", rename = "call")] pub struct CallBlock { /// Function to call - pub call: String, + pub call: EvalsTo>, /// Arguments of the function with their values #[serde(skip_serializing_if = "Option::is_none")] @@ -88,7 +88,7 @@ pub struct CallBlock { impl CallBlock { pub fn new(call: String) -> Self { CallBlock { - call: call, + call: EvalsTo::Jinja(call), args: None, metadata: None, } @@ -531,8 +531,8 @@ pub struct Expr { #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(untagged)] pub enum EvalsTo { - Const(T), Jinja(String), + Const(T), Expr(Expr), } diff --git a/pdl-live-react/src-tauri/src/pdl/interpreter.rs b/pdl-live-react/src-tauri/src/pdl/interpreter.rs index 5d7a5120d..f94c8ea28 100644 --- a/pdl-live-react/src-tauri/src/pdl/interpreter.rs +++ b/pdl-live-react/src-tauri/src/pdl/interpreter.rs @@ -207,6 +207,18 @@ impl<'a> Interpreter<'a> { })) } + fn eval_string( + &self, + expr: &EvalsTo>, + state: &State, + ) -> Result { + match expr { + EvalsTo::Const(t) => Ok(*t.clone()), + EvalsTo::Jinja(s) => self.eval(s, state), + EvalsTo::Expr(e) => self.eval(&e.pdl_expr, state), + } + } + /// Evaluate an Expr to a bool fn eval_to_bool( &self, @@ -414,7 +426,7 @@ impl<'a> Interpreter<'a> { eprintln!("Call scope {:?}", state.scope); } - match self.eval(&block.call, state)? { + match self.eval_string(&block.call, state)? { PdlResult::Closure(c) => { let mut new_state = match &block.args { None => Ok(state.clone()), @@ -1060,6 +1072,7 @@ pub async fn run_file<'a>( let path = PathBuf::from(source_file_path); let cwd = path.parent().and_then(|cwd| Some(cwd.to_path_buf())); let program = parse_file(&path)?; + // println!("{}", serde_json::to_string_pretty(&program)?); run(&program, cwd, options, initial_scope).await } From ee0b5c6eeaee68db677a3e59836037c9ec06fded Mon Sep 17 00:00:00 2001 From: Nick Mitchell Date: Fri, 11 Apr 2025 14:09:39 -0400 Subject: [PATCH 20/39] chore: bump to rust 2024 edition Signed-off-by: Nick Mitchell --- pdl-live-react/src-tauri/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pdl-live-react/src-tauri/Cargo.toml b/pdl-live-react/src-tauri/Cargo.toml index 0bf71e50c..b91cf5450 100644 --- a/pdl-live-react/src-tauri/Cargo.toml +++ b/pdl-live-react/src-tauri/Cargo.toml @@ -3,7 +3,7 @@ name = "pdl" version = "0.6.0" description = "Prompt Declaration Language" authors = ["nickm@us.ibm.com"] -edition = "2021" +edition = "2024" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html From ef276249202dac6678574f3a878c2234fe5e46e5 Mon Sep 17 00:00:00 2001 From: Nick Mitchell Date: Fri, 11 Apr 2025 09:05:41 -0400 Subject: [PATCH 21/39] feat: continue to flesh out block metadata structure in rust Signed-off-by: Nick Mitchell --- pdl-live-react/src-tauri/Cargo.lock | 32 ++++ pdl-live-react/src-tauri/Cargo.toml | 1 + pdl-live-react/src-tauri/src/compile/beeai.rs | 125 ++++++++------ pdl-live-react/src-tauri/src/pdl/ast.rs | 162 ++++++++---------- .../src-tauri/src/pdl/interpreter.rs | 28 +-- 5 files changed, 191 insertions(+), 157 deletions(-) diff --git a/pdl-live-react/src-tauri/Cargo.lock b/pdl-live-react/src-tauri/Cargo.lock index 74def7f13..9a130aa22 100644 --- a/pdl-live-react/src-tauri/Cargo.lock +++ b/pdl-live-react/src-tauri/Cargo.lock @@ -956,6 +956,37 @@ dependencies = [ "serde", ] +[[package]] +name = "derive_builder" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947" +dependencies = [ + "derive_builder_macro", +] + +[[package]] +name = "derive_builder_core" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "derive_builder_macro" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" +dependencies = [ + "derive_builder_core", + "syn 2.0.100", +] + [[package]] name = "derive_more" version = "0.99.19" @@ -3375,6 +3406,7 @@ version = "0.6.0" dependencies = [ "async-recursion", "base64ct", + "derive_builder", "dirs", "duct", "fs4", diff --git a/pdl-live-react/src-tauri/Cargo.toml b/pdl-live-react/src-tauri/Cargo.toml index b91cf5450..7c4dfa22b 100644 --- a/pdl-live-react/src-tauri/Cargo.toml +++ b/pdl-live-react/src-tauri/Cargo.toml @@ -46,6 +46,7 @@ indexmap = { version = "2.9.0", features = ["serde"] } rustpython-stdlib = { version = "0.4.0", features = ["zlib"] } schemars = "0.8.22" fs4 = "0.13.1" +derive_builder = "0.20.2" [target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies] tauri-plugin-cli = "2" diff --git a/pdl-live-react/src-tauri/src/compile/beeai.rs b/pdl-live-react/src-tauri/src/compile/beeai.rs index 41e74e11b..4a0862055 100644 --- a/pdl-live-react/src-tauri/src/compile/beeai.rs +++ b/pdl-live-react/src-tauri/src/compile/beeai.rs @@ -12,9 +12,9 @@ use serde_json::{from_reader, json, to_string, Map, Value}; use tempfile::Builder; use crate::pdl::ast::{ - ArrayBlock, CallBlock, EvalsTo, FunctionBlock, ListOrString, MessageBlock, Metadata, + ArrayBlock, CallBlock, EvalsTo, FunctionBlock, ListOrString, MessageBlock, MetadataBuilder, ModelBlock, ObjectBlock, PdlBaseType, PdlBlock, PdlOptionalType, PdlParser, PdlType, - PythonCodeBlock, RepeatBlock, Role, TextBlock, + PythonCodeBlock, RepeatBlock, Role, TextBlock, TextBlockBuilder, }; use crate::pdl::pip::pip_install_if_needed; use crate::pdl::requirements::BEEAI_FRAMEWORK; @@ -191,28 +191,35 @@ fn with_tools( fn call_tools(model: &String, parameters: &HashMap) -> PdlBlock { let repeat = PdlBlock::Text(TextBlock { - metadata: None, + metadata: Some( + MetadataBuilder::default() + .description("Calling tool ${ tool.function.name }".to_string()) + .build() + .unwrap(), + ), role: None, parser: None, - description: Some("Calling tool ${ tool.function.name }".to_string()), text: vec![PdlBlock::Model( ModelBlock::new(model.as_str()) .parameters(&strip_nulls(parameters)) .input(PdlBlock::Array(ArrayBlock { array: vec![PdlBlock::Message(MessageBlock { + metadata: None, role: Role::Tool, - description: None, + defsite: None, name: Some("${ tool.function.name }".to_string()), tool_call_id: Some("${ tool.id }".to_string()), content: Box::new(PdlBlock::Call(CallBlock { - metadata: Some(Metadata { - def: None, - defs: json_loads( - &"args", - &"pdl__args", - &"${ tool.function.arguments }", - ), - }), + metadata: Some( + MetadataBuilder::default() + .defs(json_loads( + &"args", + &"pdl__args", + &"${ tool.function.arguments }", + )) + .build() + .unwrap(), + ), call: EvalsTo::Jinja("${ pdl__tools[tool.function.name] }".to_string()), // look up tool in tool_declarations def (see below) args: Some("${ args }".into()), // invoke with arguments as specified by the model })), @@ -239,21 +246,28 @@ fn json_loads( outer_name: &str, inner_name: &str, value: &str, -) -> Option> { +) -> indexmap::IndexMap { let mut m = indexmap::IndexMap::new(); m.insert( outer_name.to_owned(), PdlBlock::Text( - TextBlock::new(vec![PdlBlock::String(format!( - "{{\"{}\": {}}}", - inner_name, value - ))]) - .description(format!("Parsing json for {}={}", inner_name, value)) - .parser(PdlParser::Json) - .build(), + TextBlockBuilder::default() + .text(vec![PdlBlock::String(format!( + "{{\"{}\": {}}}", + inner_name, value + ))]) + .metadata( + MetadataBuilder::default() + .description(format!("Parsing json for {}={}", inner_name, value)) + .build() + .unwrap(), + ) + .parser(PdlParser::Json) + .build() + .unwrap(), ), ); - Some(m) + m } fn json_schema_type_to_pdl_type(spec: &Value) -> PdlType { @@ -465,9 +479,13 @@ asyncio.run(invoke()) model_call.push(PdlBlock::Text(TextBlock { role: Some(Role::System), text: vec![PdlBlock::String(instructions)], - metadata: None, + metadata: Some( + MetadataBuilder::default() + .description("Model instructions".to_string()) + .build() + .unwrap(), + ), parser: None, - description: Some("Model instructions".into()), })); } @@ -481,12 +499,15 @@ asyncio.run(invoke()) }; model_call.push(PdlBlock::Model(ModelBlock { - metadata: None, + metadata: Some( + MetadataBuilder::default() + .description(description) + .build() + .unwrap(), + ), input: None, - description: Some(description), model: model.clone(), model_response: model_response, - pdl_result: None, pdl_usage: None, parameters: Some(with_tools(&tools, ¶meters.state.dict)), })); @@ -504,22 +525,28 @@ asyncio.run(invoke()) PdlBlock::Function(FunctionBlock { function: HashMap::new(), return_: Box::new(PdlBlock::Text(TextBlock { - metadata: None, + metadata: Some( + MetadataBuilder::default() + .description(format!("Model call {}", &model)) + .build() + .unwrap(), + ), role: None, parser: None, - description: Some(format!("Model call {}", &model)), text: model_call, })), }), ); PdlBlock::Text(TextBlock { - metadata: Some(Metadata { - def: None, - defs: Some(defs), - }), + metadata: Some( + MetadataBuilder::default() + .description("Model call wrapper".to_string()) + .defs(defs) + .build() + .unwrap(), + ), role: None, parser: None, - description: Some("Model call wrapper".to_string()), text: vec![PdlBlock::Call(CallBlock::new(format!( "${{ {} }}", closure_name @@ -533,23 +560,21 @@ asyncio.run(invoke()) .flat_map(|(a, b)| [a, b]) .collect::>(); + let mut metadata = MetadataBuilder::default(); + metadata.description(bee.workflow.workflow.name); + if tool_declarations.len() > 0 { + let mut defs = indexmap::IndexMap::new(); + defs.insert( + "pdl__tools".to_string(), + PdlBlock::Object(ObjectBlock { + object: tool_declarations, + }), + ); + metadata.defs(defs); + } + let pdl: PdlBlock = PdlBlock::Text(TextBlock { - metadata: if tool_declarations.len() == 0 { - None - } else { - let mut m = indexmap::IndexMap::new(); - m.insert( - "pdl__tools".to_string(), - PdlBlock::Object(ObjectBlock { - object: tool_declarations, - }), - ); - Some(Metadata { - def: None, - defs: Some(m), - }) - }, - description: Some(bee.workflow.workflow.name), + metadata: Some(metadata.build().unwrap()), role: None, parser: None, text: body, diff --git a/pdl-live-react/src-tauri/src/pdl/ast.rs b/pdl-live-react/src-tauri/src/pdl/ast.rs index 7b16738ff..555db9412 100644 --- a/pdl-live-react/src-tauri/src/pdl/ast.rs +++ b/pdl-live-react/src-tauri/src/pdl/ast.rs @@ -51,38 +51,55 @@ pub enum PdlType { Object(HashMap), } -/// Common metadata of blocks +/// Timing information #[derive(Serialize, Deserialize, Debug, Clone)] +pub struct Timing { + start_nanos: u64, + end_nanos: u64, + timezone: String, +} + +/// Common metadata of blocks +#[derive(Serialize, Deserialize, Debug, Clone, Default, derive_builder::Builder)] +#[serde(default)] +#[builder(setter(into, strip_option), default)] pub struct Metadata { + #[serde(skip_serializing_if = "Option::is_none")] + pub description: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub defs: Option>, #[serde(skip_serializing_if = "Option::is_none")] pub def: Option, -} -impl Default for Metadata { - fn default() -> Self { - Self { - defs: None, - def: None, - } - } + + #[serde(rename = "pdl__id", skip_serializing_if = "Option::is_none")] + pub pdl_id: Option, + + #[serde(rename = "pdl__result", skip_serializing_if = "Option::is_none")] + pub pdl_result: Option>, + + #[serde(rename = "pdl__is_leaf", skip_serializing_if = "Option::is_none")] + pub pdl_is_leaf: Option, + + #[serde(rename = "pdl__timing", skip_serializing_if = "Option::is_none")] + pub pdl_timing: Option, } /// Call a function #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(tag = "kind", rename = "call")] pub struct CallBlock { + #[serde(flatten)] + #[serde(skip_serializing_if = "Option::is_none")] + pub metadata: Option, + /// Function to call pub call: EvalsTo>, /// Arguments of the function with their values #[serde(skip_serializing_if = "Option::is_none")] pub args: Option, - - #[serde(flatten)] - #[serde(skip_serializing_if = "Option::is_none")] - pub metadata: Option, } impl CallBlock { @@ -97,7 +114,6 @@ impl CallBlock { pub trait SequencingBlock { fn kind(&self) -> &str; - fn description(&self) -> &Option; fn role(&self) -> &Option; fn metadata(&self) -> &Option; fn items(&self) -> &Vec; @@ -116,9 +132,6 @@ pub struct LastOfBlock { #[serde(rename = "lastOf")] pub last_of: Vec, - #[serde(skip_serializing_if = "Option::is_none")] - pub description: Option, - #[serde(skip_serializing_if = "Option::is_none")] pub role: Option, @@ -133,9 +146,6 @@ impl SequencingBlock for LastOfBlock { fn kind(&self) -> &str { "lastOf" } - fn description(&self) -> &Option { - &self.description - } fn role(&self) -> &Option { &self.role } @@ -172,32 +182,30 @@ impl SequencingBlock for LastOfBlock { /// Create the concatenation of the stringify version of the result of /// each block of the list of blocks. -#[derive(Serialize, Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone, Default, derive_builder::Builder)] #[serde(tag = "kind", rename = "text")] +#[builder(setter(into, strip_option), default)] pub struct TextBlock { + #[serde(default, flatten)] + #[serde(skip_serializing_if = "Option::is_none")] + pub metadata: Option, + /// Body of the text + // Note: do NOT apply #[serde(default)] here. This seems to give + // permission for the deserializer to match everything to + // TextBlock, since ... all fields are optional/have defaults. pub text: Vec, - #[serde(skip_serializing_if = "Option::is_none")] - pub description: Option, - - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub role: Option, - #[serde(flatten)] - #[serde(skip_serializing_if = "Option::is_none")] - pub metadata: Option, - - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub parser: Option, } impl SequencingBlock for TextBlock { fn kind(&self) -> &str { "text" } - fn description(&self) -> &Option { - &self.description - } fn role(&self) -> &Option { &self.role } @@ -232,52 +240,6 @@ impl SequencingBlock for TextBlock { } } -impl TextBlock { - pub fn new(text: Vec) -> Self { - TextBlock { - metadata: None, - description: None, - role: None, - parser: None, - text: text, - } - } - - pub fn def(&mut self, def: &str) -> &mut Self { - match &mut self.metadata { - Some(metadata) => { - metadata.def = Some(def.into()); - } - None => { - let mut metadata: Metadata = Default::default(); - metadata.def = Some(def.into()); - self.metadata = Some(metadata); - } - } - self - } - - pub fn description(&mut self, description: String) -> &mut Self { - self.description = Some(description); - self - } - - pub fn parser(&mut self, parser: PdlParser) -> &mut Self { - self.parser = Some(parser); - self - } - - pub fn build(&self) -> Self { - self.clone() - } -} - -impl From> for TextBlock { - fn from(v: Vec) -> Self { - TextBlock::new(v).build() - } -} - #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(tag = "kind", rename = "function")] pub struct FunctionBlock { @@ -305,8 +267,6 @@ pub struct ModelBlock { #[serde(skip_serializing_if = "Option::is_none")] pub metadata: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub description: Option, pub model: String, #[serde(skip_serializing_if = "Option::is_none")] pub parameters: Option>, @@ -315,9 +275,6 @@ pub struct ModelBlock { #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "modelResponse")] pub model_response: Option, - #[serde(rename = "pdl__result")] - #[serde(skip_serializing_if = "Option::is_none")] - pub pdl_result: Option, #[serde(rename = "pdl__usage")] #[serde(skip_serializing_if = "Option::is_none")] pub pdl_usage: Option, @@ -326,17 +283,31 @@ pub struct ModelBlock { impl ModelBlock { pub fn new(model: &str) -> Self { ModelBlock { - metadata: None, - description: None, + metadata: Default::default(), model_response: None, parameters: None, - pdl_result: None, pdl_usage: None, model: model.into(), input: None, } } + pub fn with_result(&self, result: PdlResult) -> Self { + let mut c = self.clone(); + let mut metadata = if let Some(meta) = c.metadata { + meta + } else { + Default::default() + }; + metadata.pdl_result = Some(Box::from(result)); + c.metadata = Some(metadata); + c + } + + pub fn description(&self) -> Option { + self.metadata.as_ref().and_then(|m| m.description.clone()) + } + pub fn input(&mut self, input: PdlBlock) -> &mut Self { self.input = Some(Box::new(input)); self @@ -389,14 +360,19 @@ pub struct RepeatBlock { #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(tag = "kind", rename = "message")] pub struct MessageBlock { + #[serde(flatten)] + #[serde(skip_serializing_if = "Option::is_none")] + pub metadata: Option, + /// Role of associated to the message, e.g. User or Assistant pub role: Role, /// Content of the message pub content: Box, + /// pdl_id of block that defined the `content of this message #[serde(skip_serializing_if = "Option::is_none")] - pub description: Option, + pub defsite: Option, /// For example, the name of the tool that was invoked, for which this message is the tool response #[serde(skip_serializing_if = "Option::is_none")] @@ -439,7 +415,7 @@ pub struct ObjectBlock { /// def: EXTRACTED_GROUND_TRUTH /// ``` #[derive(Serialize, Deserialize, Debug, Clone)] -#[serde(tag = "kind")] +#[serde(tag = "kind", rename = "data")] pub struct DataBlock { #[serde(flatten)] #[serde(skip_serializing_if = "Option::is_none")] @@ -614,12 +590,12 @@ pub enum PdlBlock { Array(ArrayBlock), Message(MessageBlock), Repeat(RepeatBlock), - Text(TextBlock), - LastOf(LastOfBlock), - Model(ModelBlock), Function(FunctionBlock), PythonCode(PythonCodeBlock), Read(ReadBlock), + Model(ModelBlock), + LastOf(LastOfBlock), + Text(TextBlock), // must be last to prevent serde from aggressively matching on it, since other block types also (may) have a `defs` Empty(EmptyBlock), @@ -663,8 +639,8 @@ pub enum PdlResult { Number(Number), String(String), Bool(bool), - Block(PdlBlock), Closure(Closure), + Block(PdlBlock), List(Vec), Dict(HashMap), } diff --git a/pdl-live-react/src-tauri/src/pdl/interpreter.rs b/pdl-live-react/src-tauri/src/pdl/interpreter.rs index f94c8ea28..ac2bf706a 100644 --- a/pdl-live-react/src-tauri/src/pdl/interpreter.rs +++ b/pdl-live-react/src-tauri/src/pdl/interpreter.rs @@ -192,7 +192,8 @@ impl<'a> Interpreter<'a> { /// Evaluate String as a Jinja2 expression fn eval(&self, expr: &String, state: &State) -> Result { - let result = self.jinja_env.render_str(expr.as_str(), &state.scope)?; + let tmpl = self.jinja_env.template_from_str(expr.as_str())?; + let result = tmpl.render(&state.scope)?; if self.options.debug { eprintln!("Eval {} -> {} with scope {:?}", expr, result, state.scope); } @@ -644,8 +645,8 @@ impl<'a> Interpreter<'a> { let (options, tools) = self.to_ollama_model_options(&block.parameters); if self.options.debug { - eprintln!("Model options {:?} {:?}", block.description, options); - eprintln!("Model tools {:?} {:?}", block.description, tools); + eprintln!("Model options {:?} {:?}", block.description(), options); + eprintln!("Model tools {:?} {:?}", block.description(), tools); } // The input messages to the model is either: @@ -668,7 +669,7 @@ impl<'a> Interpreter<'a> { if self.options.debug { eprintln!( "Ollama {:?} model={:?} prompt={:?} history={:?}", - block.description.clone().unwrap_or("".into()), + block.description(), block.model, prompt, history @@ -741,9 +742,7 @@ impl<'a> Interpreter<'a> { } } - let mut trace = block.clone(); - trace.pdl_result = Some(response_string.clone()); - + let mut trace = block.with_result(response_string.clone().into()); if let Some(res) = last_res { if let Some(usage) = res.final_data { trace.pdl_usage = Some(PdlUsage { @@ -912,12 +911,12 @@ impl<'a> Interpreter<'a> { state: &mut State, ) -> Interpretation { if self.options.debug { - let description = if let Some(d) = block.description() { - d - } else { - &"".to_string() - }; - eprintln!("{} {description}", block.kind()); + let description = block + .metadata() + .as_ref() + .and_then(|m| m.description.clone()) + .or(Some("".to_string())); + eprintln!("{} {:?}", block.kind(), description); } let mut output_results = vec![]; @@ -1022,10 +1021,11 @@ impl<'a> Interpreter<'a> { .map(|m| ChatMessage::new(self.to_ollama_role(&block.role), m.content)) .collect(), PdlBlock::Message(MessageBlock { + metadata: block.metadata.clone(), role: block.role.clone(), content: Box::new(content_trace), - description: block.description.clone(), name: name, + defsite: None, tool_call_id: tool_call_id, }), )) From a0855d3adadcfa16c7c2946ef0c0f291c5436f37 Mon Sep 17 00:00:00 2001 From: Nick Mitchell Date: Fri, 11 Apr 2025 15:47:36 -0400 Subject: [PATCH 22/39] refactor: add metadata attr to remaining rust block asts This also adds initial scaffolding for timing, and adds a ModelBlock and ArrayBlockBuilder. Signed-off-by: Nick Mitchell --- pdl-live-react/src-tauri/src/cli.rs | 2 +- pdl-live-react/src-tauri/src/compile/beeai.rs | 104 +++++++++-------- pdl-live-react/src-tauri/src/pdl/ast.rs | 106 ++++++++++-------- .../src-tauri/src/pdl/interpreter.rs | 15 ++- .../src-tauri/src/pdl/interpreter_tests.rs | 11 +- 5 files changed, 135 insertions(+), 103 deletions(-) diff --git a/pdl-live-react/src-tauri/src/cli.rs b/pdl-live-react/src-tauri/src/cli.rs index 65102b1f4..a85a6b6fd 100644 --- a/pdl-live-react/src-tauri/src/cli.rs +++ b/pdl-live-react/src-tauri/src/cli.rs @@ -5,7 +5,7 @@ use urlencoding::encode; use crate::compile; use crate::gui::new_window; -use crate::pdl::interpreter::{load_scope, run_file_sync, RunOptions}; +use crate::pdl::interpreter::{RunOptions, load_scope, run_file_sync}; #[cfg(desktop)] pub fn setup(app: &mut tauri::App) -> Result> { diff --git a/pdl-live-react/src-tauri/src/compile/beeai.rs b/pdl-live-react/src-tauri/src/compile/beeai.rs index 4a0862055..081c75c34 100644 --- a/pdl-live-react/src-tauri/src/compile/beeai.rs +++ b/pdl-live-react/src-tauri/src/compile/beeai.rs @@ -8,13 +8,13 @@ use ::std::path::{Path, PathBuf}; use duct::cmd; use futures::executor::block_on; use serde::Deserialize; -use serde_json::{from_reader, json, to_string, Map, Value}; +use serde_json::{Map, Value, from_reader, json, to_string}; use tempfile::Builder; use crate::pdl::ast::{ - ArrayBlock, CallBlock, EvalsTo, FunctionBlock, ListOrString, MessageBlock, MetadataBuilder, - ModelBlock, ObjectBlock, PdlBaseType, PdlBlock, PdlOptionalType, PdlParser, PdlType, - PythonCodeBlock, RepeatBlock, Role, TextBlock, TextBlockBuilder, + ArrayBlockBuilder, CallBlock, EvalsTo, FunctionBlock, ListOrString, MessageBlock, + MetadataBuilder, ModelBlockBuilder, ObjectBlock, PdlBaseType, PdlBlock, PdlOptionalType, + PdlParser, PdlType, PythonCodeBlock, RepeatBlock, Role, TextBlock, TextBlockBuilder, }; use crate::pdl::pip::pip_install_if_needed; use crate::pdl::requirements::BEEAI_FRAMEWORK; @@ -200,32 +200,39 @@ fn call_tools(model: &String, parameters: &HashMap) -> PdlBlock { role: None, parser: None, text: vec![PdlBlock::Model( - ModelBlock::new(model.as_str()) - .parameters(&strip_nulls(parameters)) - .input(PdlBlock::Array(ArrayBlock { - array: vec![PdlBlock::Message(MessageBlock { - metadata: None, - role: Role::Tool, - defsite: None, - name: Some("${ tool.function.name }".to_string()), - tool_call_id: Some("${ tool.id }".to_string()), - content: Box::new(PdlBlock::Call(CallBlock { - metadata: Some( - MetadataBuilder::default() - .defs(json_loads( - &"args", - &"pdl__args", - &"${ tool.function.arguments }", - )) - .build() - .unwrap(), - ), - call: EvalsTo::Jinja("${ pdl__tools[tool.function.name] }".to_string()), // look up tool in tool_declarations def (see below) - args: Some("${ args }".into()), // invoke with arguments as specified by the model - })), - })], - })) - .build(), + ModelBlockBuilder::default() + .model(model.as_str()) + .parameters(strip_nulls(parameters)) + .input(PdlBlock::Array( + ArrayBlockBuilder::default() + .array(vec![PdlBlock::Message(MessageBlock { + metadata: None, + role: Role::Tool, + defsite: None, + name: Some("${ tool.function.name }".to_string()), + tool_call_id: Some("${ tool.id }".to_string()), + content: Box::new(PdlBlock::Call(CallBlock { + metadata: Some( + MetadataBuilder::default() + .defs(json_loads( + &"args", + &"pdl__args", + &"${ tool.function.arguments }", + )) + .build() + .unwrap(), + ), + call: EvalsTo::Jinja( + "${ pdl__tools[tool.function.name] }".to_string(), + ), // look up tool in tool_declarations def (see below) + args: Some("${ args }".into()), // invoke with arguments as specified by the model + })), + })]) + .build() + .unwrap(), + )) + .build() + .unwrap(), )], }); @@ -237,6 +244,7 @@ fn call_tools(model: &String, parameters: &HashMap) -> PdlBlock { // response.choices[0].message.tool_calls PdlBlock::Repeat(RepeatBlock { + metadata: None, for_: for_, repeat: Box::new(repeat), }) @@ -416,6 +424,7 @@ pub fn compile(source_file_path: &str, debug: bool) -> Result None, - _ => Some("response".to_string()), - } - } else { - None - }; - - model_call.push(PdlBlock::Model(ModelBlock { - metadata: Some( + let mut model_builder = ModelBlockBuilder::default(); + model_builder + .metadata( MetadataBuilder::default() .description(description) .build() .unwrap(), - ), - input: None, - model: model.clone(), - model_response: model_response, - pdl_usage: None, - parameters: Some(with_tools(&tools, ¶meters.state.dict)), - })); + ) + .model(model.clone()) + .parameters(with_tools(&tools, ¶meters.state.dict)); + + if let Some(tools) = &tools { + if tools.len() > 0 { + // then we want the model response as a + // "response" variable, so we can scan for + // tool calls + model_builder.model_response("response".to_string()); + } + } + + model_call.push(PdlBlock::Model(model_builder.build().unwrap())); if let Some(tools) = tools { if tools.len() > 0 { diff --git a/pdl-live-react/src-tauri/src/pdl/ast.rs b/pdl-live-react/src-tauri/src/pdl/ast.rs index 555db9412..0b8d88ed5 100644 --- a/pdl-live-react/src-tauri/src/pdl/ast.rs +++ b/pdl-live-react/src-tauri/src/pdl/ast.rs @@ -1,7 +1,9 @@ use ::std::collections::HashMap; +use ::std::time::{SystemTime, SystemTimeError}; + use indexmap::IndexMap; use serde::{Deserialize, Serialize}; -use serde_json::{to_string, Number, Value}; +use serde_json::{Number, Value, to_string}; #[derive(Serialize, Deserialize, Debug, Clone)] //why doesn't this work? #[serde(rename_all_fields(serialize = "lowercase"))] @@ -52,10 +54,10 @@ pub enum PdlType { } /// Timing information -#[derive(Serialize, Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone, Default)] pub struct Timing { - start_nanos: u64, - end_nanos: u64, + start_nanos: u128, + end_nanos: u128, timezone: String, } @@ -86,6 +88,23 @@ pub struct Metadata { pub pdl_timing: Option, } +impl Metadata { + fn start(&mut self) -> Result<(), SystemTimeError> { + let nanos = ::std::time::SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH)? + .as_nanos(); + if let Some(t) = &mut self.pdl_timing { + t.start_nanos = nanos; + } else { + let mut t = Timing::default(); + t.start_nanos = nanos; + self.pdl_timing = Some(t) + } + + Ok(()) + } +} + /// Call a function #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(tag = "kind", rename = "call")] @@ -128,6 +147,10 @@ pub trait SequencingBlock { #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(tag = "kind", rename = "lastOf")] pub struct LastOfBlock { + #[serde(flatten)] + #[serde(skip_serializing_if = "Option::is_none")] + pub metadata: Option, + /// Sequence of blocks to execute #[serde(rename = "lastOf")] pub last_of: Vec, @@ -135,10 +158,6 @@ pub struct LastOfBlock { #[serde(skip_serializing_if = "Option::is_none")] pub role: Option, - #[serde(flatten)] - #[serde(skip_serializing_if = "Option::is_none")] - pub metadata: Option, - #[serde(skip_serializing_if = "Option::is_none")] pub parser: Option, } @@ -260,8 +279,9 @@ pub struct PdlUsage { pub prompt_nanos: u64, } -#[derive(Serialize, Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone, Default, derive_builder::Builder)] #[serde(tag = "kind", rename = "model")] +#[builder(setter(into, strip_option), default)] pub struct ModelBlock { #[serde(flatten)] #[serde(skip_serializing_if = "Option::is_none")] @@ -281,17 +301,6 @@ pub struct ModelBlock { } impl ModelBlock { - pub fn new(model: &str) -> Self { - ModelBlock { - metadata: Default::default(), - model_response: None, - parameters: None, - pdl_usage: None, - model: model.into(), - input: None, - } - } - pub fn with_result(&self, result: PdlResult) -> Self { let mut c = self.clone(); let mut metadata = if let Some(meta) = c.metadata { @@ -307,25 +316,6 @@ impl ModelBlock { pub fn description(&self) -> Option { self.metadata.as_ref().and_then(|m| m.description.clone()) } - - pub fn input(&mut self, input: PdlBlock) -> &mut Self { - self.input = Some(Box::new(input)); - self - } - - pub fn input_str(&mut self, input: &str) -> &mut Self { - self.input = Some(Box::new(PdlBlock::String(input.into()))); - self - } - - pub fn parameters(&mut self, parameters: &HashMap) -> &mut Self { - self.parameters = Some(parameters.clone()); - self - } - - pub fn build(&self) -> Self { - self.clone() - } } #[derive(Serialize, Deserialize, Debug, Clone)] @@ -348,6 +338,10 @@ pub enum ListOrString { #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(tag = "kind", rename = "repeat")] pub struct RepeatBlock { + #[serde(flatten)] + #[serde(skip_serializing_if = "Option::is_none")] + pub metadata: Option, + /// Arrays to iterate over #[serde(rename = "for")] pub for_: HashMap, @@ -444,7 +438,14 @@ pub struct DataBlock { #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(tag = "kind", rename = "code")] pub struct PythonCodeBlock { + #[serde(flatten)] + #[serde(skip_serializing_if = "Option::is_none")] + pub metadata: Option, + + /// Programming language of the code pub lang: String, + + /// Code to execute pub code: String, } @@ -526,6 +527,10 @@ pub enum EvalsTo { #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(tag = "kind", rename = "if")] pub struct IfBlock { + #[serde(flatten)] + #[serde(skip_serializing_if = "Option::is_none")] + pub metadata: Option, + /// The condition to check #[serde(rename = "if")] pub condition: EvalsTo, @@ -537,16 +542,17 @@ pub struct IfBlock { #[serde(rename = "else")] #[serde(skip_serializing_if = "Option::is_none")] pub else_: Option>, - - #[serde(flatten)] - #[serde(skip_serializing_if = "Option::is_none")] - pub metadata: Option, } /// Return the array of values computed by each block of the list of blocks -#[derive(Serialize, Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone, Default, derive_builder::Builder)] #[serde(tag = "kind", rename = "array")] +#[builder(setter(into, strip_option), default)] pub struct ArrayBlock { + #[serde(flatten)] + #[serde(skip_serializing_if = "Option::is_none")] + pub metadata: Option, + /// Elements of the array pub array: Vec, } @@ -555,6 +561,10 @@ pub struct ArrayBlock { #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(tag = "kind", rename = "include")] pub struct IncludeBlock { + #[serde(flatten)] + #[serde(skip_serializing_if = "Option::is_none")] + pub metadata: Option, + /// Name of the file to include. pub include: String, } @@ -563,6 +573,10 @@ pub struct IncludeBlock { #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(tag = "kind", rename = "import")] pub struct ImportBlock { + #[serde(flatten)] + #[serde(skip_serializing_if = "Option::is_none")] + pub metadata: Option, + /// Name of the file to include. pub import: String, } @@ -581,6 +595,9 @@ pub enum PdlBlock { Bool(bool), Number(Number), String(String), + Function(FunctionBlock), + + // the rest have Metadata; TODO refactor to make this more explicit If(IfBlock), Import(ImportBlock), Include(IncludeBlock), @@ -590,7 +607,6 @@ pub enum PdlBlock { Array(ArrayBlock), Message(MessageBlock), Repeat(RepeatBlock), - Function(FunctionBlock), PythonCode(PythonCodeBlock), Read(ReadBlock), Model(ModelBlock), diff --git a/pdl-live-react/src-tauri/src/pdl/interpreter.rs b/pdl-live-react/src-tauri/src/pdl/interpreter.rs index ac2bf706a..521f161fa 100644 --- a/pdl-live-react/src-tauri/src/pdl/interpreter.rs +++ b/pdl-live-react/src-tauri/src/pdl/interpreter.rs @@ -3,21 +3,21 @@ use ::std::error::Error; use ::std::path::PathBuf; use async_recursion::async_recursion; -use minijinja::{syntax::SyntaxConfig, Environment}; +use minijinja::{Environment, syntax::SyntaxConfig}; use owo_colors::OwoColorize; -use tokio::io::{stdout, AsyncWriteExt}; +use tokio::io::{AsyncWriteExt, stdout}; use tokio_stream::StreamExt; use ollama_rs::{ + Ollama, generation::{ - chat::{request::ChatMessageRequest, ChatMessage, ChatMessageResponse, MessageRole}, + chat::{ChatMessage, ChatMessageResponse, MessageRole, request::ChatMessageRequest}, tools::{ToolFunctionInfo, ToolInfo, ToolType}, }, models::ModelOptions, - Ollama, }; -use serde_json::{from_str, json, to_string, Value}; +use serde_json::{Value, from_str, json, to_string}; use serde_norway::{from_reader, from_str as from_yaml_str}; use crate::pdl::ast::{ @@ -979,10 +979,13 @@ impl<'a> Interpreter<'a> { trace_items.push(trace); } + let mut trace = block.clone(); + trace.array = trace_items; + Ok(( PdlResult::List(result_items), all_messages, - PdlBlock::Array(ArrayBlock { array: trace_items }), + PdlBlock::Array(trace), )) } diff --git a/pdl-live-react/src-tauri/src/pdl/interpreter_tests.rs b/pdl-live-react/src-tauri/src/pdl/interpreter_tests.rs index 991a18d93..49c495bb5 100644 --- a/pdl-live-react/src-tauri/src/pdl/interpreter_tests.rs +++ b/pdl-live-react/src-tauri/src/pdl/interpreter_tests.rs @@ -5,8 +5,8 @@ mod tests { use serde_json::json; use crate::pdl::{ - ast::{ModelBlock, PdlBlock, Scope}, - interpreter::{load_scope, run_json_sync as run_json, run_sync as run, RunOptions}, + ast::{ModelBlockBuilder, PdlBlock, Scope}, + interpreter::{RunOptions, load_scope, run_json_sync as run_json, run_sync as run}, }; use ollama_rs::generation::chat::MessageRole; @@ -61,7 +61,12 @@ mod tests { #[test] fn single_model_via_input_string() -> Result<(), Box> { let (_, messages, _) = run( - &PdlBlock::Model(ModelBlock::new(DEFAULT_MODEL).input_str("hello").build()), + &PdlBlock::Model( + ModelBlockBuilder::default() + .model(DEFAULT_MODEL) + .input(Box::from(PdlBlock::String("hello".to_string()))) + .build()?, + ), None, streaming(), initial_scope(), From db348ab697ae3d0fa4e206c9f76759255d797c83 Mon Sep 17 00:00:00 2001 From: Louis Mandel Date: Fri, 11 Apr 2025 16:46:52 -0400 Subject: [PATCH 23/39] feat: update rust Repeat AST to use Expr for `for` attr (#904) Signed-off-by: Louis Mandel --- pdl-live-react/src-tauri/src/compile/beeai.rs | 9 +++++++-- pdl-live-react/src-tauri/src/pdl/ast.rs | 2 +- .../src-tauri/src/pdl/interpreter.rs | 20 +++++++++++++------ 3 files changed, 22 insertions(+), 9 deletions(-) diff --git a/pdl-live-react/src-tauri/src/compile/beeai.rs b/pdl-live-react/src-tauri/src/compile/beeai.rs index 081c75c34..9e5371afb 100644 --- a/pdl-live-react/src-tauri/src/compile/beeai.rs +++ b/pdl-live-react/src-tauri/src/compile/beeai.rs @@ -12,7 +12,7 @@ use serde_json::{Map, Value, from_reader, json, to_string}; use tempfile::Builder; use crate::pdl::ast::{ - ArrayBlockBuilder, CallBlock, EvalsTo, FunctionBlock, ListOrString, MessageBlock, + ArrayBlockBuilder, CallBlock, EvalsTo, Expr, FunctionBlock, ListOrString, MessageBlock, MetadataBuilder, ModelBlockBuilder, ObjectBlock, PdlBaseType, PdlBlock, PdlOptionalType, PdlParser, PdlType, PythonCodeBlock, RepeatBlock, Role, TextBlock, TextBlockBuilder, }; @@ -239,7 +239,12 @@ fn call_tools(model: &String, parameters: &HashMap) -> PdlBlock { let mut for_ = HashMap::new(); for_.insert( "tool".to_string(), - ListOrString::String("${ response.choices[0].message.tool_calls }".to_string()), + EvalsTo::Expr(Expr { + pdl_expr: ListOrString::String( + "${ response.choices[0].message.tool_calls }".to_string(), + ), + pdl_result: None, + }), ); // response.choices[0].message.tool_calls diff --git a/pdl-live-react/src-tauri/src/pdl/ast.rs b/pdl-live-react/src-tauri/src/pdl/ast.rs index 0b8d88ed5..1fe8d76a5 100644 --- a/pdl-live-react/src-tauri/src/pdl/ast.rs +++ b/pdl-live-react/src-tauri/src/pdl/ast.rs @@ -344,7 +344,7 @@ pub struct RepeatBlock { /// Arrays to iterate over #[serde(rename = "for")] - pub for_: HashMap, + pub for_: HashMap>>, /// Body of the loop pub repeat: Box, diff --git a/pdl-live-react/src-tauri/src/pdl/interpreter.rs b/pdl-live-react/src-tauri/src/pdl/interpreter.rs index 521f161fa..6c09697fc 100644 --- a/pdl-live-react/src-tauri/src/pdl/interpreter.rs +++ b/pdl-live-react/src-tauri/src/pdl/interpreter.rs @@ -284,18 +284,26 @@ impl<'a> Interpreter<'a> { /// Evaluate an string or list of Values into a list of Values fn eval_list_or_string( &self, - expr: &ListOrString, + expr: &EvalsTo>, state: &State, ) -> Result, PdlError> { match expr { - ListOrString::String(s) => match self.eval(s, state)? { - PdlResult::List(a) => Ok(a), + EvalsTo::Const(c) => Ok(c.clone()), + EvalsTo::Jinja(s) + | EvalsTo::Expr(Expr { + pdl_expr: ListOrString::String(s), + .. + }) => match self.eval(s, state)? { + PdlResult::List(l) => Ok(l), x => Err(Box::from(format!( - "Jinja string expanded to non-list. {} -> {:?}", - s, x + "Expression {s} evaluated to non-list {:?}", + x ))), }, - ListOrString::List(l) => l.iter().map(|v| self.eval_json(v, state)).collect(), + EvalsTo::Expr(Expr { + pdl_expr: ListOrString::List(l), + .. + }) => l.iter().map(|v| self.eval_json(v, state)).collect(), } } From 73b2b7d74fcf93e9a86791b7a106cf714d79c176 Mon Sep 17 00:00:00 2001 From: Nick Mitchell Date: Fri, 11 Apr 2025 18:03:27 -0400 Subject: [PATCH 24/39] refactor: introduce Advanced enum to rust AST Signed-off-by: Nick Mitchell --- pdl-live-react/src-tauri/src/compile/beeai.rs | 65 ++++++++--------- pdl-live-react/src-tauri/src/pdl/ast.rs | 16 +++-- pdl-live-react/src-tauri/src/pdl/extract.rs | 35 +++++----- .../src-tauri/src/pdl/interpreter.rs | 70 +++++++++---------- .../src-tauri/src/pdl/interpreter_tests.rs | 6 +- 5 files changed, 96 insertions(+), 96 deletions(-) diff --git a/pdl-live-react/src-tauri/src/compile/beeai.rs b/pdl-live-react/src-tauri/src/compile/beeai.rs index 9e5371afb..d46597ec4 100644 --- a/pdl-live-react/src-tauri/src/compile/beeai.rs +++ b/pdl-live-react/src-tauri/src/compile/beeai.rs @@ -12,9 +12,10 @@ use serde_json::{Map, Value, from_reader, json, to_string}; use tempfile::Builder; use crate::pdl::ast::{ - ArrayBlockBuilder, CallBlock, EvalsTo, Expr, FunctionBlock, ListOrString, MessageBlock, - MetadataBuilder, ModelBlockBuilder, ObjectBlock, PdlBaseType, PdlBlock, PdlOptionalType, - PdlParser, PdlType, PythonCodeBlock, RepeatBlock, Role, TextBlock, TextBlockBuilder, + ArrayBlockBuilder, Block::*, CallBlock, EvalsTo, Expr, FunctionBlock, ListOrString, + MessageBlock, MetadataBuilder, ModelBlockBuilder, ObjectBlock, PdlBaseType, PdlBlock, + PdlBlock::Advanced, PdlOptionalType, PdlParser, PdlType, PythonCodeBlock, RepeatBlock, Role, + TextBlock, TextBlockBuilder, }; use crate::pdl::pip::pip_install_if_needed; use crate::pdl::requirements::BEEAI_FRAMEWORK; @@ -190,7 +191,7 @@ fn with_tools( } fn call_tools(model: &String, parameters: &HashMap) -> PdlBlock { - let repeat = PdlBlock::Text(TextBlock { + let repeat = Advanced(Text(TextBlock { metadata: Some( MetadataBuilder::default() .description("Calling tool ${ tool.function.name }".to_string()) @@ -199,19 +200,19 @@ fn call_tools(model: &String, parameters: &HashMap) -> PdlBlock { ), role: None, parser: None, - text: vec![PdlBlock::Model( + text: vec![Advanced(Model( ModelBlockBuilder::default() .model(model.as_str()) .parameters(strip_nulls(parameters)) - .input(PdlBlock::Array( + .input(Advanced(Array( ArrayBlockBuilder::default() - .array(vec![PdlBlock::Message(MessageBlock { + .array(vec![Advanced(Message(MessageBlock { metadata: None, role: Role::Tool, defsite: None, name: Some("${ tool.function.name }".to_string()), tool_call_id: Some("${ tool.id }".to_string()), - content: Box::new(PdlBlock::Call(CallBlock { + content: Box::new(Advanced(Call(CallBlock { metadata: Some( MetadataBuilder::default() .defs(json_loads( @@ -226,15 +227,15 @@ fn call_tools(model: &String, parameters: &HashMap) -> PdlBlock { "${ pdl__tools[tool.function.name] }".to_string(), ), // look up tool in tool_declarations def (see below) args: Some("${ args }".into()), // invoke with arguments as specified by the model - })), - })]) + }))), + }))]) .build() .unwrap(), - )) + ))) .build() .unwrap(), - )], - }); + ))], + })); let mut for_ = HashMap::new(); for_.insert( @@ -248,11 +249,11 @@ fn call_tools(model: &String, parameters: &HashMap) -> PdlBlock { ); // response.choices[0].message.tool_calls - PdlBlock::Repeat(RepeatBlock { + Advanced(Repeat(RepeatBlock { metadata: None, for_: for_, repeat: Box::new(repeat), - }) + })) } fn json_loads( @@ -263,7 +264,7 @@ fn json_loads( let mut m = indexmap::IndexMap::new(); m.insert( outer_name.to_owned(), - PdlBlock::Text( + Advanced(Text( TextBlockBuilder::default() .text(vec![PdlBlock::String(format!( "{{\"{}\": {}}}", @@ -278,7 +279,7 @@ fn json_loads( .parser(PdlParser::Json) .build() .unwrap(), - ), + )), ); m } @@ -427,7 +428,7 @@ pub fn compile(source_file_path: &str, debug: bool) -> Result 0 { @@ -537,7 +538,7 @@ asyncio.run(invoke()) closure_name.clone(), PdlBlock::Function(FunctionBlock { function: HashMap::new(), - return_: Box::new(PdlBlock::Text(TextBlock { + return_: Box::new(Advanced(Text(TextBlock { metadata: Some( MetadataBuilder::default() .description(format!("Model call {}", &model)) @@ -547,10 +548,10 @@ asyncio.run(invoke()) role: None, parser: None, text: model_call, - })), + }))), }), ); - PdlBlock::Text(TextBlock { + Advanced(Text(TextBlock { metadata: Some( MetadataBuilder::default() .description("Model call wrapper".to_string()) @@ -560,11 +561,11 @@ asyncio.run(invoke()) ), role: None, parser: None, - text: vec![PdlBlock::Call(CallBlock::new(format!( + text: vec![Advanced(Call(CallBlock::new(format!( "${{ {} }}", closure_name - )))], - }) + ))))], + })) }, ) .collect::>(); @@ -579,19 +580,19 @@ asyncio.run(invoke()) let mut defs = indexmap::IndexMap::new(); defs.insert( "pdl__tools".to_string(), - PdlBlock::Object(ObjectBlock { + Advanced(Object(ObjectBlock { object: tool_declarations, - }), + })), ); metadata.defs(defs); } - let pdl: PdlBlock = PdlBlock::Text(TextBlock { + let pdl: PdlBlock = Advanced(Text(TextBlock { metadata: Some(metadata.build().unwrap()), role: None, parser: None, text: body, - }); + })); Ok(pdl) } diff --git a/pdl-live-react/src-tauri/src/pdl/ast.rs b/pdl-live-react/src-tauri/src/pdl/ast.rs index 1fe8d76a5..52b7eb7e9 100644 --- a/pdl-live-react/src-tauri/src/pdl/ast.rs +++ b/pdl-live-react/src-tauri/src/pdl/ast.rs @@ -183,7 +183,7 @@ impl SequencingBlock for LastOfBlock { &self.parser } fn to_block(&self) -> PdlBlock { - PdlBlock::LastOf(self.clone()) + PdlBlock::Advanced(Block::LastOf(self.clone())) } fn result_for(&self, output_results: Vec) -> PdlResult { match output_results.last() { @@ -243,7 +243,7 @@ impl SequencingBlock for TextBlock { &self.parser } fn to_block(&self) -> PdlBlock { - PdlBlock::Text(self.clone()) + PdlBlock::Advanced(Block::Text(self.clone())) } fn result_for(&self, output_results: Vec) -> PdlResult { PdlResult::String( @@ -596,8 +596,15 @@ pub enum PdlBlock { Number(Number), String(String), Function(FunctionBlock), + Advanced(Block), + // must be last to prevent serde from aggressively matching on it, since other block types also (may) have a `defs` + Empty(EmptyBlock), +} - // the rest have Metadata; TODO refactor to make this more explicit +/// A PDL block that has structure and metadata +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(untagged)] +pub enum Block { If(IfBlock), Import(ImportBlock), Include(IncludeBlock), @@ -612,9 +619,6 @@ pub enum PdlBlock { Model(ModelBlock), LastOf(LastOfBlock), Text(TextBlock), - - // must be last to prevent serde from aggressively matching on it, since other block types also (may) have a `defs` - Empty(EmptyBlock), } impl From for PdlBlock { diff --git a/pdl-live-react/src-tauri/src/pdl/extract.rs b/pdl-live-react/src-tauri/src/pdl/extract.rs index 61af006e1..62f9c4cc9 100644 --- a/pdl-live-react/src-tauri/src/pdl/extract.rs +++ b/pdl-live-react/src-tauri/src/pdl/extract.rs @@ -1,4 +1,4 @@ -use crate::pdl::ast::{Metadata, PdlBlock}; +use crate::pdl::ast::{Block::*, Metadata, PdlBlock, PdlBlock::Advanced}; /// Extract models referenced by the programs pub fn extract_models(program: &PdlBlock) -> Vec { @@ -20,18 +20,26 @@ pub fn extract_values(program: &PdlBlock, field: &str) -> Vec { /// Take one Yaml fragment and produce a vector of the string-valued entries of the given field fn extract_values_iter(program: &PdlBlock, field: &str, values: &mut Vec) { match program { - PdlBlock::Model(b) => values.push(b.model.clone()), - PdlBlock::Repeat(b) => { + PdlBlock::Empty(b) => { + b.defs + .values() + .for_each(|p| extract_values_iter(p, field, values)); + } + PdlBlock::Function(b) => { + extract_values_iter(&b.return_, field, values); + } + Advanced(Model(b)) => values.push(b.model.clone()), + Advanced(Repeat(b)) => { extract_values_iter(&b.repeat, field, values); } - PdlBlock::Message(b) => { + Advanced(Message(b)) => { extract_values_iter(&b.content, field, values); } - PdlBlock::Array(b) => b + Advanced(Array(b)) => b .array .iter() .for_each(|p| extract_values_iter(p, field, values)), - PdlBlock::Text(b) => { + Advanced(Text(b)) => { b.text .iter() .for_each(|p| extract_values_iter(p, field, values)); @@ -43,7 +51,7 @@ fn extract_values_iter(program: &PdlBlock, field: &str, values: &mut Vec .for_each(|p| extract_values_iter(p, field, values)); } } - PdlBlock::LastOf(b) => { + Advanced(LastOf(b)) => { b.last_of .iter() .for_each(|p| extract_values_iter(p, field, values)); @@ -55,7 +63,7 @@ fn extract_values_iter(program: &PdlBlock, field: &str, values: &mut Vec .for_each(|p| extract_values_iter(p, field, values)); } } - PdlBlock::If(b) => { + Advanced(If(b)) => { extract_values_iter(&b.then, field, values); if let Some(else_) = &b.else_ { extract_values_iter(else_, field, values); @@ -68,20 +76,11 @@ fn extract_values_iter(program: &PdlBlock, field: &str, values: &mut Vec .for_each(|p| extract_values_iter(p, field, values)); } } - PdlBlock::Empty(b) => { - b.defs - .values() - .for_each(|p| extract_values_iter(p, field, values)); - } - PdlBlock::Object(b) => b + Advanced(Object(b)) => b .object .values() .for_each(|p| extract_values_iter(p, field, values)), - PdlBlock::Function(b) => { - extract_values_iter(&b.return_, field, values); - } - _ => {} } } diff --git a/pdl-live-react/src-tauri/src/pdl/interpreter.rs b/pdl-live-react/src-tauri/src/pdl/interpreter.rs index 6c09697fc..43d15469b 100644 --- a/pdl-live-react/src-tauri/src/pdl/interpreter.rs +++ b/pdl-live-react/src-tauri/src/pdl/interpreter.rs @@ -21,10 +21,10 @@ use serde_json::{Value, from_str, json, to_string}; use serde_norway::{from_reader, from_str as from_yaml_str}; use crate::pdl::ast::{ - ArrayBlock, CallBlock, Closure, DataBlock, EmptyBlock, EvalsTo, Expr, FunctionBlock, IfBlock, - ImportBlock, IncludeBlock, ListOrString, MessageBlock, ModelBlock, ObjectBlock, PdlBlock, - PdlParser, PdlResult, PdlUsage, PythonCodeBlock, ReadBlock, RepeatBlock, Role, Scope, - SequencingBlock, StringOrBoolean, StringOrNull, + ArrayBlock, Block::*, CallBlock, Closure, DataBlock, EmptyBlock, EvalsTo, Expr, FunctionBlock, + IfBlock, ImportBlock, IncludeBlock, ListOrString, MessageBlock, ModelBlock, ObjectBlock, + PdlBlock, PdlBlock::Advanced, PdlParser, PdlResult, PdlUsage, PythonCodeBlock, ReadBlock, + RepeatBlock, Role, Scope, SequencingBlock, StringOrBoolean, StringOrNull, }; type Messages = Vec; @@ -137,31 +137,27 @@ impl<'a> Interpreter<'a> { PdlBlock::Function(f.clone()), )), PdlBlock::String(s) => self.run_string(s, state).await, - PdlBlock::Call(block) => self.run_call(block, state).await, PdlBlock::Empty(block) => self.run_empty(block, state).await, - PdlBlock::If(block) => self.run_if(block, state).await, - PdlBlock::Import(block) => self.run_import(block, state).await, - PdlBlock::Include(block) => self.run_include(block, state).await, - PdlBlock::Model(block) => self.run_model(block, state).await, - PdlBlock::Data(block) => self.run_data(block, state).await, - PdlBlock::Object(block) => self.run_object(block, state).await, - PdlBlock::PythonCode(block) => self.run_python_code(block, state).await, - PdlBlock::Read(block) => self.run_read(block, state).await, - PdlBlock::Repeat(block) => self.run_repeat(block, state).await, - PdlBlock::LastOf(block) => self.run_sequence(block, state).await, - PdlBlock::Text(block) => self.run_sequence(block, state).await, - PdlBlock::Array(block) => self.run_array(block, state).await, - PdlBlock::Message(block) => self.run_message(block, state).await, + Advanced(Call(block)) => self.run_call(block, state).await, + Advanced(If(block)) => self.run_if(block, state).await, + Advanced(Import(block)) => self.run_import(block, state).await, + Advanced(Include(block)) => self.run_include(block, state).await, + Advanced(Model(block)) => self.run_model(block, state).await, + Advanced(Data(block)) => self.run_data(block, state).await, + Advanced(Object(block)) => self.run_object(block, state).await, + Advanced(PythonCode(block)) => self.run_python_code(block, state).await, + Advanced(Read(block)) => self.run_read(block, state).await, + Advanced(Repeat(block)) => self.run_repeat(block, state).await, + Advanced(LastOf(block)) => self.run_sequence(block, state).await, + Advanced(Text(block)) => self.run_sequence(block, state).await, + Advanced(Array(block)) => self.run_array(block, state).await, + Advanced(Message(block)) => self.run_message(block, state).await, }?; if match program { - PdlBlock::Message(_) - | PdlBlock::Text(_) - | PdlBlock::Import(_) - | PdlBlock::Include(_) - | PdlBlock::LastOf(_) - | PdlBlock::Call(_) - | PdlBlock::Model(_) => false, + Advanced(Message(_)) | Advanced(Text(_)) | Advanced(Import(_)) + | Advanced(Include(_)) | Advanced(LastOf(_)) | Advanced(Call(_)) + | Advanced(Model(_)) => false, _ => state.emit, } { println!("{}", pretty_print(&messages)); @@ -424,7 +420,7 @@ impl<'a> Interpreter<'a> { Ok(( result, vec![ChatMessage::user(buffer)], - PdlBlock::Read(trace), + Advanced(Read(trace)), )) } @@ -485,7 +481,7 @@ impl<'a> Interpreter<'a> { } else { match &block.else_ { Some(else_block) => self.run_quiet(&else_block, state).await, - None => Ok(("".into(), vec![], PdlBlock::If(block.clone()))), + None => Ok(("".into(), vec![], Advanced(If(block.clone())))), } } } @@ -628,7 +624,7 @@ impl<'a> Interpreter<'a> { } }?; let messages = vec![ChatMessage::user(result_string.as_str().to_string())]; - let trace = PdlBlock::PythonCode(block.clone()); + let trace = Advanced(PythonCode(block.clone())); Ok((messages[0].content.clone().into(), messages, trace)) } Err(_) => Err(Box::from( @@ -764,11 +760,11 @@ impl<'a> Interpreter<'a> { Ok(( res.message.content.into(), output_messages, - PdlBlock::Model(trace), + Advanced(Model(trace)), )) } else { // nothing came out of the model - Ok(("".into(), vec![], PdlBlock::Model(trace))) + Ok(("".into(), vec![], Advanced(Model(trace)))) } // dbg!(history); } @@ -791,7 +787,7 @@ impl<'a> Interpreter<'a> { state, true, )?; - Ok((result, vec![], PdlBlock::Data(trace))) + Ok((result, vec![], Advanced(Data(trace)))) } else { let result = self.def( &block.metadata.as_ref().and_then(|m| m.def.clone()), @@ -801,7 +797,7 @@ impl<'a> Interpreter<'a> { true, )?; trace.data = from_str(to_string(&result)?.as_str())?; - Ok((result, vec![], PdlBlock::Data(trace))) + Ok((result, vec![], Advanced(Data(trace)))) } } @@ -825,7 +821,7 @@ impl<'a> Interpreter<'a> { Ok(( PdlResult::Dict(result_map), messages, - PdlBlock::Object(ObjectBlock { object: trace_map }), + Advanced(Object(ObjectBlock { object: trace_map })), )) } @@ -872,7 +868,7 @@ impl<'a> Interpreter<'a> { Ok(( PdlResult::List(results), messages, - PdlBlock::Repeat(block.clone()), + Advanced(Repeat(block.clone())), )) } @@ -993,7 +989,7 @@ impl<'a> Interpreter<'a> { Ok(( PdlResult::List(result_items), all_messages, - PdlBlock::Array(trace), + Advanced(Array(trace)), )) } @@ -1031,14 +1027,14 @@ impl<'a> Interpreter<'a> { .into_iter() .map(|m| ChatMessage::new(self.to_ollama_role(&block.role), m.content)) .collect(), - PdlBlock::Message(MessageBlock { + Advanced(Message(MessageBlock { metadata: block.metadata.clone(), role: block.role.clone(), content: Box::new(content_trace), name: name, defsite: None, tool_call_id: tool_call_id, - }), + })), )) } } diff --git a/pdl-live-react/src-tauri/src/pdl/interpreter_tests.rs b/pdl-live-react/src-tauri/src/pdl/interpreter_tests.rs index 49c495bb5..fcb10546d 100644 --- a/pdl-live-react/src-tauri/src/pdl/interpreter_tests.rs +++ b/pdl-live-react/src-tauri/src/pdl/interpreter_tests.rs @@ -5,7 +5,7 @@ mod tests { use serde_json::json; use crate::pdl::{ - ast::{ModelBlockBuilder, PdlBlock, Scope}, + ast::{Block::*, ModelBlockBuilder, PdlBlock, PdlBlock::Advanced, Scope}, interpreter::{RunOptions, load_scope, run_json_sync as run_json, run_sync as run}, }; @@ -61,12 +61,12 @@ mod tests { #[test] fn single_model_via_input_string() -> Result<(), Box> { let (_, messages, _) = run( - &PdlBlock::Model( + &Advanced(Model( ModelBlockBuilder::default() .model(DEFAULT_MODEL) .input(Box::from(PdlBlock::String("hello".to_string()))) .build()?, - ), + )), None, streaming(), initial_scope(), From 3966a6721bfc6e616585969a8ad799334c189492 Mon Sep 17 00:00:00 2001 From: Nick Mitchell Date: Sun, 13 Apr 2025 14:36:52 -0400 Subject: [PATCH 25/39] refactor: refactor rust ast to place metadata in common struct And start populating the timing info (incomplete). Signed-off-by: Nick Mitchell --- pdl-live-react/src-tauri/Cargo.lock | 1 + pdl-live-react/src-tauri/Cargo.toml | 1 + pdl-live-react/src-tauri/src/compile/beeai.rs | 262 ++++++++------- pdl-live-react/src-tauri/src/pdl/ast.rs | 159 +++------ pdl-live-react/src-tauri/src/pdl/extract.rs | 58 ++-- .../src-tauri/src/pdl/interpreter.rs | 312 ++++++++++++------ .../src-tauri/src/pdl/interpreter_tests.rs | 18 +- 7 files changed, 449 insertions(+), 362 deletions(-) diff --git a/pdl-live-react/src-tauri/Cargo.lock b/pdl-live-react/src-tauri/Cargo.lock index 9a130aa22..4615770b1 100644 --- a/pdl-live-react/src-tauri/Cargo.lock +++ b/pdl-live-react/src-tauri/Cargo.lock @@ -3411,6 +3411,7 @@ dependencies = [ "duct", "fs4", "futures", + "iana-time-zone", "indexmap 2.9.0", "minijinja", "ollama-rs", diff --git a/pdl-live-react/src-tauri/Cargo.toml b/pdl-live-react/src-tauri/Cargo.toml index 7c4dfa22b..6b56c1978 100644 --- a/pdl-live-react/src-tauri/Cargo.toml +++ b/pdl-live-react/src-tauri/Cargo.toml @@ -47,6 +47,7 @@ rustpython-stdlib = { version = "0.4.0", features = ["zlib"] } schemars = "0.8.22" fs4 = "0.13.1" derive_builder = "0.20.2" +iana-time-zone = "0.1.63" [target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies] tauri-plugin-cli = "2" diff --git a/pdl-live-react/src-tauri/src/compile/beeai.rs b/pdl-live-react/src-tauri/src/compile/beeai.rs index d46597ec4..27c1e9fc4 100644 --- a/pdl-live-react/src-tauri/src/compile/beeai.rs +++ b/pdl-live-react/src-tauri/src/compile/beeai.rs @@ -12,7 +12,7 @@ use serde_json::{Map, Value, from_reader, json, to_string}; use tempfile::Builder; use crate::pdl::ast::{ - ArrayBlockBuilder, Block::*, CallBlock, EvalsTo, Expr, FunctionBlock, ListOrString, + ArrayBlockBuilder, Block, Body::*, CallBlock, EvalsTo, Expr, FunctionBlock, ListOrString, MessageBlock, MetadataBuilder, ModelBlockBuilder, ObjectBlock, PdlBaseType, PdlBlock, PdlBlock::Advanced, PdlOptionalType, PdlParser, PdlType, PythonCodeBlock, RepeatBlock, Role, TextBlock, TextBlockBuilder, @@ -191,51 +191,67 @@ fn with_tools( } fn call_tools(model: &String, parameters: &HashMap) -> PdlBlock { - let repeat = Advanced(Text(TextBlock { + let repeat = Advanced(Block { metadata: Some( MetadataBuilder::default() .description("Calling tool ${ tool.function.name }".to_string()) .build() .unwrap(), ), - role: None, - parser: None, - text: vec![Advanced(Model( - ModelBlockBuilder::default() - .model(model.as_str()) - .parameters(strip_nulls(parameters)) - .input(Advanced(Array( - ArrayBlockBuilder::default() - .array(vec![Advanced(Message(MessageBlock { + + body: Text(TextBlock { + role: None, + parser: None, + text: vec![Advanced(Block { + metadata: None, + body: Model( + ModelBlockBuilder::default() + .model(model.as_str()) + .parameters(strip_nulls(parameters)) + .input(Advanced(Block { metadata: None, - role: Role::Tool, - defsite: None, - name: Some("${ tool.function.name }".to_string()), - tool_call_id: Some("${ tool.id }".to_string()), - content: Box::new(Advanced(Call(CallBlock { - metadata: Some( - MetadataBuilder::default() - .defs(json_loads( - &"args", - &"pdl__args", - &"${ tool.function.arguments }", - )) - .build() - .unwrap(), - ), - call: EvalsTo::Jinja( - "${ pdl__tools[tool.function.name] }".to_string(), - ), // look up tool in tool_declarations def (see below) - args: Some("${ args }".into()), // invoke with arguments as specified by the model - }))), - }))]) + body: Array( + ArrayBlockBuilder::default() + .array(vec![Advanced(Block { + metadata: None, + body: Message(MessageBlock { + role: Role::Tool, + defsite: None, + name: Some("${ tool.function.name }".to_string()), + tool_call_id: Some("${ tool.id }".to_string()), + content: Box::new(Advanced(Block { + metadata: Some( + MetadataBuilder::default() + .defs(json_loads( + &"args", + &"pdl__args", + &"${ tool.function.arguments }", + )) + .build() + .unwrap(), + ), + + body: Call(CallBlock { + call: EvalsTo::Jinja( + "${ pdl__tools[tool.function.name] }" + .to_string(), + ), // look up tool in tool_declarations def (see below) + args: Some("${ args }".into()), // invoke with arguments as specified by the model + pdl_trace: None, + }), + })), + }), + })]) + .build() + .unwrap(), + ), + })) .build() .unwrap(), - ))) - .build() - .unwrap(), - ))], - })); + ), + })], + }), + }); let mut for_ = HashMap::new(); for_.insert( @@ -249,11 +265,13 @@ fn call_tools(model: &String, parameters: &HashMap) -> PdlBlock { ); // response.choices[0].message.tool_calls - Advanced(Repeat(RepeatBlock { + Advanced(Block { metadata: None, - for_: for_, - repeat: Box::new(repeat), - })) + body: Repeat(RepeatBlock { + for_: for_, + repeat: Box::new(repeat), + }), + }) } fn json_loads( @@ -264,22 +282,25 @@ fn json_loads( let mut m = indexmap::IndexMap::new(); m.insert( outer_name.to_owned(), - Advanced(Text( - TextBlockBuilder::default() - .text(vec![PdlBlock::String(format!( - "{{\"{}\": {}}}", - inner_name, value - ))]) - .metadata( - MetadataBuilder::default() - .description(format!("Parsing json for {}={}", inner_name, value)) - .build() - .unwrap(), - ) - .parser(PdlParser::Json) - .build() - .unwrap(), - )), + Advanced(Block { + metadata: Some( + MetadataBuilder::default() + .description(format!("Parsing json for {}={}", inner_name, value)) + .build() + .unwrap(), + ), + + body: Text( + TextBlockBuilder::default() + .text(vec![PdlBlock::String(format!( + "{{\"{}\": {}}}", + inner_name, value + ))]) + .parser(PdlParser::Json) + .build() + .unwrap(), + ), + }), ); m } @@ -428,12 +449,13 @@ pub fn compile(source_file_path: &str, debug: bool) -> Result 0 { @@ -538,20 +565,22 @@ asyncio.run(invoke()) closure_name.clone(), PdlBlock::Function(FunctionBlock { function: HashMap::new(), - return_: Box::new(Advanced(Text(TextBlock { + return_: Box::new(Advanced(Block { metadata: Some( MetadataBuilder::default() .description(format!("Model call {}", &model)) .build() .unwrap(), ), - role: None, - parser: None, - text: model_call, - }))), + body: Text(TextBlock { + role: None, + parser: None, + text: model_call, + }), + })), }), ); - Advanced(Text(TextBlock { + Advanced(Block { metadata: Some( MetadataBuilder::default() .description("Model call wrapper".to_string()) @@ -559,13 +588,15 @@ asyncio.run(invoke()) .build() .unwrap(), ), - role: None, - parser: None, - text: vec![Advanced(Call(CallBlock::new(format!( - "${{ {} }}", - closure_name - ))))], - })) + body: Text(TextBlock { + role: None, + parser: None, + text: vec![Advanced(Block { + metadata: None, + body: Call(CallBlock::new(format!("${{ {} }}", closure_name))), + })], + }), + }) }, ) .collect::>(); @@ -580,19 +611,24 @@ asyncio.run(invoke()) let mut defs = indexmap::IndexMap::new(); defs.insert( "pdl__tools".to_string(), - Advanced(Object(ObjectBlock { - object: tool_declarations, - })), + Advanced(Block { + metadata: None, + body: Object(ObjectBlock { + object: tool_declarations, + }), + }), ); metadata.defs(defs); } - let pdl: PdlBlock = Advanced(Text(TextBlock { + let pdl: PdlBlock = Advanced(Block { metadata: Some(metadata.build().unwrap()), - role: None, - parser: None, - text: body, - })); + body: Text(TextBlock { + role: None, + parser: None, + text: body, + }), + }); Ok(pdl) } diff --git a/pdl-live-react/src-tauri/src/pdl/ast.rs b/pdl-live-react/src-tauri/src/pdl/ast.rs index 52b7eb7e9..fb153121e 100644 --- a/pdl-live-react/src-tauri/src/pdl/ast.rs +++ b/pdl-live-react/src-tauri/src/pdl/ast.rs @@ -1,5 +1,6 @@ use ::std::collections::HashMap; -use ::std::time::{SystemTime, SystemTimeError}; +use ::std::error::Error; +use ::std::time::SystemTime; use indexmap::IndexMap; use serde::{Deserialize, Serialize}; @@ -61,6 +62,27 @@ pub struct Timing { timezone: String, } +type TimingError = Box; +impl Timing { + fn now() -> Result { + Ok(::std::time::SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH)? + .as_nanos()) + } + + pub fn start() -> Result { + let mut t = Timing::default(); + t.start_nanos = Timing::now()?; + t.timezone = iana_time_zone::get_timezone()?; + Ok(t) + } + + pub fn end(&mut self) -> Result<(), TimingError> { + self.end_nanos = Timing::now()?; + Ok(()) + } +} + /// Common metadata of blocks #[derive(Serialize, Deserialize, Debug, Clone, Default, derive_builder::Builder)] #[serde(default)] @@ -88,37 +110,18 @@ pub struct Metadata { pub pdl_timing: Option, } -impl Metadata { - fn start(&mut self) -> Result<(), SystemTimeError> { - let nanos = ::std::time::SystemTime::now() - .duration_since(SystemTime::UNIX_EPOCH)? - .as_nanos(); - if let Some(t) = &mut self.pdl_timing { - t.start_nanos = nanos; - } else { - let mut t = Timing::default(); - t.start_nanos = nanos; - self.pdl_timing = Some(t) - } - - Ok(()) - } -} - /// Call a function #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(tag = "kind", rename = "call")] pub struct CallBlock { - #[serde(flatten)] - #[serde(skip_serializing_if = "Option::is_none")] - pub metadata: Option, - - /// Function to call pub call: EvalsTo>, /// Arguments of the function with their values #[serde(skip_serializing_if = "Option::is_none")] pub args: Option, + + #[serde(rename = "pdl__trace", skip_serializing_if = "Option::is_none")] + pub pdl_trace: Option>, } impl CallBlock { @@ -126,7 +129,7 @@ impl CallBlock { CallBlock { call: EvalsTo::Jinja(call), args: None, - metadata: None, + pdl_trace: None, } } } @@ -134,11 +137,10 @@ impl CallBlock { pub trait SequencingBlock { fn kind(&self) -> &str; fn role(&self) -> &Option; - fn metadata(&self) -> &Option; fn items(&self) -> &Vec; fn with_items(&self, items: Vec) -> Self; fn parser(&self) -> &Option; - fn to_block(&self) -> PdlBlock; + fn to_block(&self) -> Body; fn result_for(&self, output_results: Vec) -> PdlResult; fn messages_for(&self, output_messages: &Vec) -> Vec; } @@ -147,10 +149,6 @@ pub trait SequencingBlock { #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(tag = "kind", rename = "lastOf")] pub struct LastOfBlock { - #[serde(flatten)] - #[serde(skip_serializing_if = "Option::is_none")] - pub metadata: Option, - /// Sequence of blocks to execute #[serde(rename = "lastOf")] pub last_of: Vec, @@ -168,9 +166,6 @@ impl SequencingBlock for LastOfBlock { fn role(&self) -> &Option { &self.role } - fn metadata(&self) -> &Option { - &self.metadata - } fn items(&self) -> &Vec { &self.last_of } @@ -182,8 +177,8 @@ impl SequencingBlock for LastOfBlock { fn parser(&self) -> &Option { &self.parser } - fn to_block(&self) -> PdlBlock { - PdlBlock::Advanced(Block::LastOf(self.clone())) + fn to_block(&self) -> Body { + Body::LastOf(self.clone()) } fn result_for(&self, output_results: Vec) -> PdlResult { match output_results.last() { @@ -205,10 +200,6 @@ impl SequencingBlock for LastOfBlock { #[serde(tag = "kind", rename = "text")] #[builder(setter(into, strip_option), default)] pub struct TextBlock { - #[serde(default, flatten)] - #[serde(skip_serializing_if = "Option::is_none")] - pub metadata: Option, - /// Body of the text // Note: do NOT apply #[serde(default)] here. This seems to give // permission for the deserializer to match everything to @@ -228,9 +219,6 @@ impl SequencingBlock for TextBlock { fn role(&self) -> &Option { &self.role } - fn metadata(&self) -> &Option { - &self.metadata - } fn items(&self) -> &Vec { &self.text } @@ -242,8 +230,8 @@ impl SequencingBlock for TextBlock { fn parser(&self) -> &Option { &self.parser } - fn to_block(&self) -> PdlBlock { - PdlBlock::Advanced(Block::Text(self.clone())) + fn to_block(&self) -> Body { + Body::Text(self.clone()) } fn result_for(&self, output_results: Vec) -> PdlResult { PdlResult::String( @@ -283,10 +271,6 @@ pub struct PdlUsage { #[serde(tag = "kind", rename = "model")] #[builder(setter(into, strip_option), default)] pub struct ModelBlock { - #[serde(flatten)] - #[serde(skip_serializing_if = "Option::is_none")] - pub metadata: Option, - pub model: String, #[serde(skip_serializing_if = "Option::is_none")] pub parameters: Option>, @@ -300,24 +284,6 @@ pub struct ModelBlock { pub pdl_usage: Option, } -impl ModelBlock { - pub fn with_result(&self, result: PdlResult) -> Self { - let mut c = self.clone(); - let mut metadata = if let Some(meta) = c.metadata { - meta - } else { - Default::default() - }; - metadata.pdl_result = Some(Box::from(result)); - c.metadata = Some(metadata); - c - } - - pub fn description(&self) -> Option { - self.metadata.as_ref().and_then(|m| m.description.clone()) - } -} - #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(untagged)] pub enum ListOrString { @@ -338,10 +304,6 @@ pub enum ListOrString { #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(tag = "kind", rename = "repeat")] pub struct RepeatBlock { - #[serde(flatten)] - #[serde(skip_serializing_if = "Option::is_none")] - pub metadata: Option, - /// Arrays to iterate over #[serde(rename = "for")] pub for_: HashMap>>, @@ -354,10 +316,6 @@ pub struct RepeatBlock { #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(tag = "kind", rename = "message")] pub struct MessageBlock { - #[serde(flatten)] - #[serde(skip_serializing_if = "Option::is_none")] - pub metadata: Option, - /// Role of associated to the message, e.g. User or Assistant pub role: Role, @@ -411,10 +369,6 @@ pub struct ObjectBlock { #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(tag = "kind", rename = "data")] pub struct DataBlock { - #[serde(flatten)] - #[serde(skip_serializing_if = "Option::is_none")] - pub metadata: Option, - pub data: Value, /// Do not evaluate expressions inside strings. @@ -438,10 +392,6 @@ pub struct DataBlock { #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(tag = "kind", rename = "code")] pub struct PythonCodeBlock { - #[serde(flatten)] - #[serde(skip_serializing_if = "Option::is_none")] - pub metadata: Option, - /// Programming language of the code pub lang: String, @@ -472,10 +422,6 @@ pub enum StringOrNull { #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(tag = "kind", rename = "read")] pub struct ReadBlock { - #[serde(flatten)] - #[serde(skip_serializing_if = "Option::is_none")] - pub metadata: Option, - /// Name of the file to read. If `None`, read the standard input. pub read: StringOrNull, @@ -527,10 +473,6 @@ pub enum EvalsTo { #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(tag = "kind", rename = "if")] pub struct IfBlock { - #[serde(flatten)] - #[serde(skip_serializing_if = "Option::is_none")] - pub metadata: Option, - /// The condition to check #[serde(rename = "if")] pub condition: EvalsTo, @@ -542,6 +484,9 @@ pub struct IfBlock { #[serde(rename = "else")] #[serde(skip_serializing_if = "Option::is_none")] pub else_: Option>, + + #[serde(skip_serializing_if = "Option::is_none")] + pub if_result: Option, } /// Return the array of values computed by each block of the list of blocks @@ -549,10 +494,6 @@ pub struct IfBlock { #[serde(tag = "kind", rename = "array")] #[builder(setter(into, strip_option), default)] pub struct ArrayBlock { - #[serde(flatten)] - #[serde(skip_serializing_if = "Option::is_none")] - pub metadata: Option, - /// Elements of the array pub array: Vec, } @@ -561,24 +502,22 @@ pub struct ArrayBlock { #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(tag = "kind", rename = "include")] pub struct IncludeBlock { - #[serde(flatten)] - #[serde(skip_serializing_if = "Option::is_none")] - pub metadata: Option, - /// Name of the file to include. pub include: String, + + #[serde(rename = "pdl__trace", skip_serializing_if = "Option::is_none")] + pub pdl_trace: Option>, } /// Import a PDL file #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(tag = "kind", rename = "import")] pub struct ImportBlock { - #[serde(flatten)] - #[serde(skip_serializing_if = "Option::is_none")] - pub metadata: Option, - /// Name of the file to include. pub import: String, + + #[serde(rename = "pdl__trace", skip_serializing_if = "Option::is_none")] + pub pdl_trace: Option>, } /// Block containing only defs @@ -597,14 +536,26 @@ pub enum PdlBlock { String(String), Function(FunctionBlock), Advanced(Block), - // must be last to prevent serde from aggressively matching on it, since other block types also (may) have a `defs` + + // Must be last to prevent serde from aggressively matching on it, + // since other block types also (may) have a `defs`. Empty(EmptyBlock), } +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct Block { + #[serde(flatten)] + #[serde(skip_serializing_if = "Option::is_none")] + pub metadata: Option, + + #[serde(flatten)] + pub body: Body, +} + /// A PDL block that has structure and metadata #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(untagged)] -pub enum Block { +pub enum Body { If(IfBlock), Import(ImportBlock), Include(IncludeBlock), diff --git a/pdl-live-react/src-tauri/src/pdl/extract.rs b/pdl-live-react/src-tauri/src/pdl/extract.rs index 62f9c4cc9..41eb492fc 100644 --- a/pdl-live-react/src-tauri/src/pdl/extract.rs +++ b/pdl-live-react/src-tauri/src/pdl/extract.rs @@ -1,4 +1,4 @@ -use crate::pdl::ast::{Block::*, Metadata, PdlBlock, PdlBlock::Advanced}; +use crate::pdl::ast::{Block, Body::*, Metadata, PdlBlock, PdlBlock::Advanced}; /// Extract models referenced by the programs pub fn extract_models(program: &PdlBlock) -> Vec { @@ -28,59 +28,57 @@ fn extract_values_iter(program: &PdlBlock, field: &str, values: &mut Vec PdlBlock::Function(b) => { extract_values_iter(&b.return_, field, values); } - Advanced(Model(b)) => values.push(b.model.clone()), - Advanced(Repeat(b)) => { + Advanced(Block { body: Model(b), .. }) => values.push(b.model.clone()), + Advanced(Block { + body: Repeat(b), .. + }) => { extract_values_iter(&b.repeat, field, values); } - Advanced(Message(b)) => { + Advanced(Block { + body: Message(b), .. + }) => { extract_values_iter(&b.content, field, values); } - Advanced(Array(b)) => b + Advanced(Block { body: Array(b), .. }) => b .array .iter() .for_each(|p| extract_values_iter(p, field, values)), - Advanced(Text(b)) => { + Advanced(Block { body: Text(b), .. }) => { b.text .iter() .for_each(|p| extract_values_iter(p, field, values)); - if let Some(Metadata { - defs: Some(defs), .. - }) = &b.metadata - { - defs.values() - .for_each(|p| extract_values_iter(p, field, values)); - } } - Advanced(LastOf(b)) => { + Advanced(Block { + body: LastOf(b), .. + }) => { b.last_of .iter() .for_each(|p| extract_values_iter(p, field, values)); - if let Some(Metadata { - defs: Some(defs), .. - }) = &b.metadata - { - defs.values() - .for_each(|p| extract_values_iter(p, field, values)); - } } - Advanced(If(b)) => { + Advanced(Block { body: If(b), .. }) => { extract_values_iter(&b.then, field, values); if let Some(else_) = &b.else_ { extract_values_iter(else_, field, values); } - if let Some(Metadata { - defs: Some(defs), .. - }) = &b.metadata - { - defs.values() - .for_each(|p| extract_values_iter(p, field, values)); - } } - Advanced(Object(b)) => b + Advanced(Block { + body: Object(b), .. + }) => b .object .values() .for_each(|p| extract_values_iter(p, field, values)), _ => {} } + + if let Advanced(Block { + metadata: Some(Metadata { + defs: Some(defs), .. + }), + .. + }) = program + { + defs.values() + .for_each(|p| extract_values_iter(p, field, values)); + } } diff --git a/pdl-live-react/src-tauri/src/pdl/interpreter.rs b/pdl-live-react/src-tauri/src/pdl/interpreter.rs index 43d15469b..28981459d 100644 --- a/pdl-live-react/src-tauri/src/pdl/interpreter.rs +++ b/pdl-live-react/src-tauri/src/pdl/interpreter.rs @@ -21,16 +21,20 @@ use serde_json::{Value, from_str, json, to_string}; use serde_norway::{from_reader, from_str as from_yaml_str}; use crate::pdl::ast::{ - ArrayBlock, Block::*, CallBlock, Closure, DataBlock, EmptyBlock, EvalsTo, Expr, FunctionBlock, - IfBlock, ImportBlock, IncludeBlock, ListOrString, MessageBlock, ModelBlock, ObjectBlock, - PdlBlock, PdlBlock::Advanced, PdlParser, PdlResult, PdlUsage, PythonCodeBlock, ReadBlock, - RepeatBlock, Role, Scope, SequencingBlock, StringOrBoolean, StringOrNull, + ArrayBlock, Block, + Body::{self, *}, + CallBlock, Closure, DataBlock, EmptyBlock, EvalsTo, Expr, FunctionBlock, IfBlock, ImportBlock, + IncludeBlock, ListOrString, MessageBlock, Metadata, ModelBlock, ObjectBlock, PdlBlock, + PdlBlock::Advanced, + PdlParser, PdlResult, PdlUsage, PythonCodeBlock, ReadBlock, RepeatBlock, Role, Scope, + SequencingBlock, StringOrBoolean, StringOrNull, Timing, }; type Messages = Vec; type ThreadSafeError = dyn Error + Send + Sync; type PdlError = Box; type Interpretation = Result<(PdlResult, Messages, PdlBlock), PdlError>; +type BodyInterpretation = Result<(PdlResult, Messages, Body), PdlError>; type InterpretationSync = Result<(PdlResult, Messages, PdlBlock), Box>; pub struct RunOptions<'a> { @@ -120,7 +124,7 @@ impl<'a> Interpreter<'a> { state: &mut State, parent_scope: &mut Scope, ) -> Interpretation { - let (result, messages, trace) = match program { + let res = match program { PdlBlock::Bool(b) => Ok(( b.into(), vec![ChatMessage::user(format!("{b}"))], @@ -136,33 +140,11 @@ impl<'a> Interpreter<'a> { vec![], PdlBlock::Function(f.clone()), )), + PdlBlock::Empty(b) => self.run_empty(b, state).await, PdlBlock::String(s) => self.run_string(s, state).await, - PdlBlock::Empty(block) => self.run_empty(block, state).await, - Advanced(Call(block)) => self.run_call(block, state).await, - Advanced(If(block)) => self.run_if(block, state).await, - Advanced(Import(block)) => self.run_import(block, state).await, - Advanced(Include(block)) => self.run_include(block, state).await, - Advanced(Model(block)) => self.run_model(block, state).await, - Advanced(Data(block)) => self.run_data(block, state).await, - Advanced(Object(block)) => self.run_object(block, state).await, - Advanced(PythonCode(block)) => self.run_python_code(block, state).await, - Advanced(Read(block)) => self.run_read(block, state).await, - Advanced(Repeat(block)) => self.run_repeat(block, state).await, - Advanced(LastOf(block)) => self.run_sequence(block, state).await, - Advanced(Text(block)) => self.run_sequence(block, state).await, - Advanced(Array(block)) => self.run_array(block, state).await, - Advanced(Message(block)) => self.run_message(block, state).await, + Advanced(b) => self.run_advanced(b, state).await, }?; - if match program { - Advanced(Message(_)) | Advanced(Text(_)) | Advanced(Import(_)) - | Advanced(Include(_)) | Advanced(LastOf(_)) | Advanced(Call(_)) - | Advanced(Model(_)) => false, - _ => state.emit, - } { - println!("{}", pretty_print(&messages)); - } - // copy any escaped variable bindings to the parent scope parent_scope.extend( state @@ -171,7 +153,70 @@ impl<'a> Interpreter<'a> { .filter_map(|variable| state.scope.remove_entry(&variable.clone())), ); - Ok((result, messages, trace)) + if match &program { + Advanced(Block { + body: Message(_), .. + }) + | Advanced(Block { body: Text(_), .. }) + | Advanced(Block { + body: Import(_), .. + }) + | Advanced(Block { + body: Include(_), .. + }) + | Advanced(Block { + body: LastOf(_), .. + }) + | Advanced(Block { body: Call(_), .. }) + | Advanced(Block { body: Model(_), .. }) => false, + _ => state.emit, + } { + println!("{}", pretty_print(&res.1)); + } + + Ok(res) + } + + async fn run_advanced(&mut self, block: &Block, state: &mut State) -> Interpretation { + let mut timing = Timing::start()?; + + // This is just so we can avoid Option in the run_* + // functions. We pass in an immutable reference, so no harm in + // the or_default() part. + let m = &block.metadata.clone().unwrap_or_default(); + + self.process_defs(&m.defs, state).await?; + + let (result, messages, trace_body) = match &block.body { + Call(b) => self.run_call(b, m, state).await, + Data(b) => self.run_data(b, m, state).await, + If(b) => self.run_if(b, m, state).await, + Import(b) => self.run_import(b, m, state).await, + Include(b) => self.run_include(b, m, state).await, + Model(b) => self.run_model(b, m, state).await, + Object(b) => self.run_object(b, m, state).await, + PythonCode(b) => self.run_python_code(b, m, state).await, + Read(b) => self.run_read(b, m, state).await, + Repeat(b) => self.run_repeat(b, m, state).await, + LastOf(b) => self.run_sequence(b, m, state).await, + Text(b) => self.run_sequence(b, m, state).await, + Array(b) => self.run_array(b, m, state).await, + Message(b) => self.run_message(b, m, state).await, + }?; + + let mut trace = Block { + metadata: block.metadata.clone(), + body: trace_body, + }; + + timing.end()?; + + let mut trace_metadata = m.clone(); + trace_metadata.pdl_timing = Some(timing); + trace_metadata.pdl_result = Some(Box::new(result.clone())); + trace.metadata = Some(trace_metadata); + + Ok((result, messages, Advanced(trace))) } #[async_recursion] @@ -376,7 +421,12 @@ impl<'a> Interpreter<'a> { } /// Run a PdlBlock::Read - async fn run_read(&mut self, block: &ReadBlock, state: &mut State) -> Interpretation { + async fn run_read( + &mut self, + block: &ReadBlock, + metadata: &Metadata, + state: &mut State, + ) -> BodyInterpretation { let trace = block.clone(); match (&block.read, &block.message) { @@ -410,22 +460,23 @@ impl<'a> Interpreter<'a> { }; let result = self.def( - &block.metadata.as_ref().and_then(|m| m.def.clone()), + &metadata.def, &buffer.clone().into(), &block.parser, state, true, )?; - Ok(( - result, - vec![ChatMessage::user(buffer)], - Advanced(Read(trace)), - )) + Ok((result, vec![ChatMessage::user(buffer)], Read(trace))) } /// Run a PdlBlock::Call - async fn run_call(&mut self, block: &CallBlock, state: &mut State) -> Interpretation { + async fn run_call( + &mut self, + block: &CallBlock, + _metadata: &Metadata, + state: &mut State, + ) -> BodyInterpretation { if self.options.debug { eprintln!("Call {:?}({:?})", block.call, block.args); eprintln!("Call scope {:?}", state.scope); @@ -441,7 +492,11 @@ impl<'a> Interpreter<'a> { }, }?; - self.run(&c.function.return_, &mut new_state).await + let (result, messages, call_trace) = + self.run(&c.function.return_, &mut new_state).await?; + let mut trace = block.clone(); + trace.pdl_trace = Some(Box::new(call_trace)); + Ok((result, messages, Body::Call(trace))) } x => Err(Box::from(format!( "call of non-function {:?}->{:?}", @@ -467,27 +522,42 @@ impl<'a> Interpreter<'a> { } /// Run a PdlBlock::Call - async fn run_if(&mut self, block: &IfBlock, state: &mut State) -> Interpretation { + async fn run_if( + &mut self, + block: &IfBlock, + _metadata: &Metadata, + state: &mut State, + ) -> BodyInterpretation { if self.options.debug { eprintln!("If {:?}({:?})", block.condition, block.then); } - if let Some(meta) = &block.metadata { - self.process_defs(&meta.defs, state).await?; - } - - if self.eval_to_bool(&block.condition, state)? { - self.run_quiet(&block.then, state).await + let mut trace = block.clone(); + let if_result = self.eval_to_bool(&block.condition, state)?; + trace.if_result = Some(if_result); + + let (result, messages) = if if_result { + let (result, messages, then_trace) = self.run_quiet(&block.then, state).await?; + trace.then = Box::new(then_trace); + (result, messages) + } else if let Some(else_block) = &block.else_ { + let (result, messages, else_trace) = self.run_quiet(&else_block, state).await?; + trace.else_ = Some(Box::new(else_trace)); + (result, messages) } else { - match &block.else_ { - Some(else_block) => self.run_quiet(&else_block, state).await, - None => Ok(("".into(), vec![], Advanced(If(block.clone())))), - } - } + ("".into(), vec![]) + }; + + Ok((result, messages, If(trace))) } /// Run a PdlBlock::Include - async fn run_include(&mut self, block: &IncludeBlock, state: &mut State) -> Interpretation { + async fn run_include( + &mut self, + block: &IncludeBlock, + _metadata: &Metadata, + state: &mut State, + ) -> BodyInterpretation { if self.options.debug { eprintln!("Include {:?}", block.include); } @@ -499,11 +569,22 @@ impl<'a> Interpreter<'a> { state.clone() }; - self.run(&parse_file(&path)?, &mut new_state).await + let (result, messages, include_trace) = + self.run(&parse_file(&path)?, &mut new_state).await?; + + let mut trace = block.clone(); + trace.pdl_trace = Some(Box::new(include_trace)); + + Ok((result, messages, Include(trace))) } /// Run a PdlBlock::Import - async fn run_import(&mut self, block: &ImportBlock, state: &mut State) -> Interpretation { + async fn run_import( + &mut self, + block: &ImportBlock, + _metadata: &Metadata, + state: &mut State, + ) -> BodyInterpretation { if self.options.debug { eprintln!("Import {:?}", block.import); } @@ -515,7 +596,13 @@ impl<'a> Interpreter<'a> { state.clone() }; - self.run(&parse_file(&path)?, &mut new_state).await + let (result, messages, import_trace) = + self.run(&parse_file(&path)?, &mut new_state).await?; + + let mut trace = block.clone(); + trace.pdl_trace = Some(Box::new(import_trace)); + + Ok((result, messages, Import(trace))) } fn to_ollama_model_options( @@ -583,13 +670,14 @@ impl<'a> Interpreter<'a> { async fn run_python_code( &mut self, block: &PythonCodeBlock, + _metadata: &Metadata, _state: &mut State, - ) -> Interpretation { + ) -> BodyInterpretation { use rustpython_vm as vm; let interp = vm::Interpreter::with_init(vm::Settings::default(), |vm| { vm.add_native_modules(rustpython_stdlib::get_module_inits()); }); - interp.enter(|vm| -> Interpretation { + interp.enter(|vm| -> BodyInterpretation { let scope = vm.new_scope_with_builtins(); // TODO vm.new_syntax_error(&err, Some(block.code.as_str())) @@ -624,7 +712,7 @@ impl<'a> Interpreter<'a> { } }?; let messages = vec![ChatMessage::user(result_string.as_str().to_string())]; - let trace = Advanced(PythonCode(block.clone())); + let trace = PythonCode(block.clone()); Ok((messages[0].content.clone().into(), messages, trace)) } Err(_) => Err(Box::from( @@ -635,7 +723,12 @@ impl<'a> Interpreter<'a> { } /// Run a PdlBlock::Model - async fn run_model(&mut self, block: &ModelBlock, state: &mut State) -> Interpretation { + async fn run_model( + &mut self, + block: &ModelBlock, + metadata: &Metadata, + state: &mut State, + ) -> BodyInterpretation { match &block.model { pdl_model if pdl_model.starts_with("ollama/") || pdl_model.starts_with("ollama_chat/") => @@ -649,8 +742,8 @@ impl<'a> Interpreter<'a> { let (options, tools) = self.to_ollama_model_options(&block.parameters); if self.options.debug { - eprintln!("Model options {:?} {:?}", block.description(), options); - eprintln!("Model tools {:?} {:?}", block.description(), tools); + eprintln!("Model options {:?} {:?}", metadata.description, options); + eprintln!("Model tools {:?} {:?}", metadata.description, tools); } // The input messages to the model is either: @@ -673,10 +766,7 @@ impl<'a> Interpreter<'a> { if self.options.debug { eprintln!( "Ollama {:?} model={:?} prompt={:?} history={:?}", - block.description(), - block.model, - prompt, - history + metadata.description, block.model, prompt, history ); } @@ -746,7 +836,7 @@ impl<'a> Interpreter<'a> { } } - let mut trace = block.with_result(response_string.clone().into()); + let mut trace = block.clone(); if let Some(res) = last_res { if let Some(usage) = res.final_data { trace.pdl_usage = Some(PdlUsage { @@ -757,14 +847,10 @@ impl<'a> Interpreter<'a> { }); } let output_messages = vec![ChatMessage::assistant(response_string)]; - Ok(( - res.message.content.into(), - output_messages, - Advanced(Model(trace)), - )) + Ok((res.message.content.into(), output_messages, Model(trace))) } else { // nothing came out of the model - Ok(("".into(), vec![], Advanced(Model(trace)))) + Ok(("".into(), vec![], Model(trace))) } // dbg!(history); } @@ -773,7 +859,12 @@ impl<'a> Interpreter<'a> { } /// Run a PdlBlock::Data - async fn run_data(&mut self, block: &DataBlock, state: &mut State) -> Interpretation { + async fn run_data( + &mut self, + block: &DataBlock, + metadata: &Metadata, + state: &mut State, + ) -> BodyInterpretation { if self.options.debug { eprintln!("Data raw={:?} {:?}", block.raw, block.data); } @@ -781,27 +872,32 @@ impl<'a> Interpreter<'a> { let mut trace = block.clone(); if let Some(true) = block.raw { let result = self.def( - &block.metadata.as_ref().and_then(|m| m.def.clone()), + &metadata.def, &resultify(&block.data), &block.parser, state, true, )?; - Ok((result, vec![], Advanced(Data(trace)))) + Ok((result, vec![], Data(trace))) } else { let result = self.def( - &block.metadata.as_ref().and_then(|m| m.def.clone()), + &metadata.def, &self.eval_json(&block.data, state)?, &block.parser, state, true, )?; trace.data = from_str(to_string(&result)?.as_str())?; - Ok((result, vec![], Advanced(Data(trace)))) + Ok((result, vec![], Data(trace))) } } - async fn run_object(&mut self, block: &ObjectBlock, state: &mut State) -> Interpretation { + async fn run_object( + &mut self, + block: &ObjectBlock, + _metadata: &Metadata, + state: &mut State, + ) -> BodyInterpretation { if self.options.debug { eprintln!("Object {:?}", block.object); } @@ -821,12 +917,17 @@ impl<'a> Interpreter<'a> { Ok(( PdlResult::Dict(result_map), messages, - Advanced(Object(ObjectBlock { object: trace_map })), + Object(ObjectBlock { object: trace_map }), )) } /// Run a PdlBlock::Repeat - async fn run_repeat(&mut self, block: &RepeatBlock, state: &mut State) -> Interpretation { + async fn run_repeat( + &mut self, + block: &RepeatBlock, + _metadata: &Metadata, + state: &mut State, + ) -> BodyInterpretation { // { i:[1,2,3], j: [4,5,6]} -> ([i,j], [[1,2,3],[4,5,6]]) // let (variables, values): (Vec<_>, Vec>) = block // .into_iter() @@ -865,11 +966,7 @@ impl<'a> Interpreter<'a> { } } - Ok(( - PdlResult::List(results), - messages, - Advanced(Repeat(block.clone())), - )) + Ok((PdlResult::List(results), messages, Repeat(block.clone()))) } fn to_ollama_role(&self, role: &Role) -> MessageRole { @@ -912,25 +1009,21 @@ impl<'a> Interpreter<'a> { async fn run_sequence( &mut self, block: &impl SequencingBlock, + metadata: &Metadata, state: &mut State, - ) -> Interpretation { + ) -> BodyInterpretation { if self.options.debug { - let description = block - .metadata() - .as_ref() - .and_then(|m| m.description.clone()) - .or(Some("".to_string())); - eprintln!("{} {:?}", block.kind(), description); + eprintln!( + "{} {:?}", + block.kind(), + metadata.description.clone().unwrap_or_default() + ); } let mut output_results = vec![]; let mut output_messages = vec![]; let mut output_blocks = vec![]; - if let Some(meta) = block.metadata() { - self.process_defs(&meta.defs, state).await?; - } - // here is where we iterate over the sequence items let mut iter = block.items().iter(); while let Some(block) = iter.next() { @@ -948,7 +1041,7 @@ impl<'a> Interpreter<'a> { let trace = block.with_items(output_blocks); let result = self.def( - &block.metadata().as_ref().and_then(|m| m.def.clone()), + &metadata.def, &trace.result_for(output_results), trace.parser(), state, @@ -969,7 +1062,12 @@ impl<'a> Interpreter<'a> { } /// Run a PdlBlock::Array - async fn run_array(&mut self, block: &ArrayBlock, state: &mut State) -> Interpretation { + async fn run_array( + &mut self, + block: &ArrayBlock, + _metadata: &Metadata, + state: &mut State, + ) -> BodyInterpretation { let mut result_items = vec![]; let mut all_messages = vec![]; let mut trace_items = vec![]; @@ -986,15 +1084,16 @@ impl<'a> Interpreter<'a> { let mut trace = block.clone(); trace.array = trace_items; - Ok(( - PdlResult::List(result_items), - all_messages, - Advanced(Array(trace)), - )) + Ok((PdlResult::List(result_items), all_messages, Array(trace))) } /// Run a PdlBlock::Message - async fn run_message(&mut self, block: &MessageBlock, state: &mut State) -> Interpretation { + async fn run_message( + &mut self, + block: &MessageBlock, + _metadata: &Metadata, + state: &mut State, + ) -> BodyInterpretation { let (content_result, content_messages, content_trace) = self.run(&block.content, state).await?; let name = if let Some(name) = &block.name { @@ -1027,14 +1126,13 @@ impl<'a> Interpreter<'a> { .into_iter() .map(|m| ChatMessage::new(self.to_ollama_role(&block.role), m.content)) .collect(), - Advanced(Message(MessageBlock { - metadata: block.metadata.clone(), + Message(MessageBlock { role: block.role.clone(), content: Box::new(content_trace), name: name, defsite: None, tool_call_id: tool_call_id, - })), + }), )) } } diff --git a/pdl-live-react/src-tauri/src/pdl/interpreter_tests.rs b/pdl-live-react/src-tauri/src/pdl/interpreter_tests.rs index fcb10546d..ba171ee74 100644 --- a/pdl-live-react/src-tauri/src/pdl/interpreter_tests.rs +++ b/pdl-live-react/src-tauri/src/pdl/interpreter_tests.rs @@ -1,11 +1,10 @@ #[cfg(test)] mod tests { - // use super::*; use ::std::error::Error; use serde_json::json; use crate::pdl::{ - ast::{Block::*, ModelBlockBuilder, PdlBlock, PdlBlock::Advanced, Scope}, + ast::{Block, Body::*, ModelBlockBuilder, PdlBlock, PdlBlock::Advanced, Scope}, interpreter::{RunOptions, load_scope, run_json_sync as run_json, run_sync as run}, }; @@ -61,12 +60,15 @@ mod tests { #[test] fn single_model_via_input_string() -> Result<(), Box> { let (_, messages, _) = run( - &Advanced(Model( - ModelBlockBuilder::default() - .model(DEFAULT_MODEL) - .input(Box::from(PdlBlock::String("hello".to_string()))) - .build()?, - )), + &Advanced(Block { + metadata: None, + body: Model( + ModelBlockBuilder::default() + .model(DEFAULT_MODEL) + .input(Box::from(PdlBlock::String("hello".to_string()))) + .build()?, + ), + }), None, streaming(), initial_scope(), From a5400ebcbb7ed61a91d116ba920a5a6393800113 Mon Sep 17 00:00:00 2001 From: Nick Mitchell Date: Sun, 13 Apr 2025 15:27:45 -0400 Subject: [PATCH 26/39] fix: improve deserialization of python-generated model block traces Signed-off-by: Nick Mitchell --- pdl-live-react/src-tauri/src/pdl/ast.rs | 29 ++++++++++++------- .../src-tauri/src/pdl/interpreter.rs | 4 +-- 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/pdl-live-react/src-tauri/src/pdl/ast.rs b/pdl-live-react/src-tauri/src/pdl/ast.rs index fb153121e..0c4df38d0 100644 --- a/pdl-live-react/src-tauri/src/pdl/ast.rs +++ b/pdl-live-react/src-tauri/src/pdl/ast.rs @@ -57,17 +57,18 @@ pub enum PdlType { /// Timing information #[derive(Serialize, Deserialize, Debug, Clone, Default)] pub struct Timing { - start_nanos: u128, - end_nanos: u128, + // TODO serde_json doesn't support u128, but (below) as_nanos() returns u128... + start_nanos: u64, + end_nanos: u64, timezone: String, } type TimingError = Box; impl Timing { - fn now() -> Result { + fn now() -> Result { Ok(::std::time::SystemTime::now() .duration_since(SystemTime::UNIX_EPOCH)? - .as_nanos()) + .as_nanos() as u64) } pub fn start() -> Result { @@ -257,14 +258,16 @@ pub struct FunctionBlock { #[derive(Serialize, Deserialize, Debug, Clone)] pub struct PdlUsage { - // Completion tokens consumed + /// Completion tokens consumed pub completion_tokens: u64, - // Prompt tokens consumed + /// Prompt tokens consumed pub prompt_tokens: u64, - // Completion nanos - pub completion_nanos: u64, - // Prompt nanos - pub prompt_nanos: u64, + /// Completion nanos + #[serde(skip_serializing_if = "Option::is_none")] + pub completion_nanos: Option, + /// Prompt nanos + #[serde(skip_serializing_if = "Option::is_none")] + pub prompt_nanos: Option, } #[derive(Serialize, Deserialize, Debug, Clone, Default, derive_builder::Builder)] @@ -277,11 +280,17 @@ pub struct ModelBlock { #[serde(skip_serializing_if = "Option::is_none")] pub input: Option>, #[serde(skip_serializing_if = "Option::is_none")] + pub platform: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub context: Option>, + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "modelResponse")] pub model_response: Option, #[serde(rename = "pdl__usage")] #[serde(skip_serializing_if = "Option::is_none")] pub pdl_usage: Option, + #[serde(rename = "pdl__model_input", skip_serializing_if = "Option::is_none")] + pub pdl_model_input: Option>, } #[derive(Serialize, Deserialize, Debug, Clone)] diff --git a/pdl-live-react/src-tauri/src/pdl/interpreter.rs b/pdl-live-react/src-tauri/src/pdl/interpreter.rs index 28981459d..a69595b56 100644 --- a/pdl-live-react/src-tauri/src/pdl/interpreter.rs +++ b/pdl-live-react/src-tauri/src/pdl/interpreter.rs @@ -841,9 +841,9 @@ impl<'a> Interpreter<'a> { if let Some(usage) = res.final_data { trace.pdl_usage = Some(PdlUsage { prompt_tokens: usage.prompt_eval_count, - prompt_nanos: usage.prompt_eval_duration, + prompt_nanos: Some(usage.prompt_eval_duration), completion_tokens: usage.eval_count, - completion_nanos: usage.eval_duration, + completion_nanos: Some(usage.eval_duration), }); } let output_messages = vec![ChatMessage::assistant(response_string)]; From 316a31bc6c4d27a90204ba5f51f678617738e046 Mon Sep 17 00:00:00 2001 From: Nick Mitchell Date: Sun, 13 Apr 2025 17:16:34 -0400 Subject: [PATCH 27/39] fix: in rust ast, allow ModelBlock model to be an expr Signed-off-by: Nick Mitchell --- pdl-live-react/src-tauri/src/pdl/ast.rs | 19 +- pdl-live-react/src-tauri/src/pdl/extract.rs | 29 +- .../src-tauri/src/pdl/interpreter.rs | 263 ++++++++++-------- .../src-tauri/src/pdl/interpreter_tests.rs | 20 ++ 4 files changed, 207 insertions(+), 124 deletions(-) diff --git a/pdl-live-react/src-tauri/src/pdl/ast.rs b/pdl-live-react/src-tauri/src/pdl/ast.rs index 0c4df38d0..4a0fa4d94 100644 --- a/pdl-live-react/src-tauri/src/pdl/ast.rs +++ b/pdl-live-react/src-tauri/src/pdl/ast.rs @@ -274,7 +274,7 @@ pub struct PdlUsage { #[serde(tag = "kind", rename = "model")] #[builder(setter(into, strip_option), default)] pub struct ModelBlock { - pub model: String, + pub model: EvalsTo, #[serde(skip_serializing_if = "Option::is_none")] pub parameters: Option>, #[serde(skip_serializing_if = "Option::is_none")] @@ -468,6 +468,23 @@ pub enum EvalsTo { Expr(Expr), } +impl Default for EvalsTo { + fn default() -> Self { + EvalsTo::Const("".to_string()) + } +} + +impl From<&str> for EvalsTo { + fn from(s: &str) -> Self { + EvalsTo::Const(s.to_string()) + } +} +impl From for EvalsTo { + fn from(s: String) -> Self { + EvalsTo::Const(s) + } +} + /// Conditional control structure. /// /// Example: diff --git a/pdl-live-react/src-tauri/src/pdl/extract.rs b/pdl-live-react/src-tauri/src/pdl/extract.rs index 41eb492fc..5b9d34514 100644 --- a/pdl-live-react/src-tauri/src/pdl/extract.rs +++ b/pdl-live-react/src-tauri/src/pdl/extract.rs @@ -1,4 +1,6 @@ -use crate::pdl::ast::{Block, Body::*, Metadata, PdlBlock, PdlBlock::Advanced}; +use crate::pdl::ast::{ + Block, Body::*, EvalsTo, Expr, Metadata, ModelBlock, PdlBlock, PdlBlock::Advanced, +}; /// Extract models referenced by the programs pub fn extract_models(program: &PdlBlock) -> Vec { @@ -28,7 +30,30 @@ fn extract_values_iter(program: &PdlBlock, field: &str, values: &mut Vec PdlBlock::Function(b) => { extract_values_iter(&b.return_, field, values); } - Advanced(Block { body: Model(b), .. }) => values.push(b.model.clone()), + Advanced(Block { + body: + Model(ModelBlock { + model: EvalsTo::::Const(m), + .. + }), + .. + }) => values.push(m.clone()), + Advanced(Block { + body: + Model(ModelBlock { + model: EvalsTo::::Jinja(m), + .. + }), + .. + }) => values.push(m.clone()), + Advanced(Block { + body: + Model(ModelBlock { + model: EvalsTo::::Expr(Expr { pdl_expr: m, .. }), + .. + }), + .. + }) => values.push(m.clone()), Advanced(Block { body: Repeat(b), .. }) => { diff --git a/pdl-live-react/src-tauri/src/pdl/interpreter.rs b/pdl-live-react/src-tauri/src/pdl/interpreter.rs index a69595b56..2a5548996 100644 --- a/pdl-live-react/src-tauri/src/pdl/interpreter.rs +++ b/pdl-live-react/src-tauri/src/pdl/interpreter.rs @@ -261,6 +261,18 @@ impl<'a> Interpreter<'a> { } } + // TODO how can we better cope with the expected String return? + fn eval_string_to_string( + &self, + expr: &EvalsTo, + state: &State, + ) -> Result { + match expr { + EvalsTo::Const(s) | EvalsTo::Jinja(s) => self.eval(s, state), + EvalsTo::Expr(e) => self.eval(&e.pdl_expr, state), + } + } + /// Evaluate an Expr to a bool fn eval_to_bool( &self, @@ -722,140 +734,149 @@ impl<'a> Interpreter<'a> { }) } - /// Run a PdlBlock::Model - async fn run_model( + async fn run_ollama_model( &mut self, + pdl_model: String, block: &ModelBlock, metadata: &Metadata, state: &mut State, ) -> BodyInterpretation { - match &block.model { - pdl_model - if pdl_model.starts_with("ollama/") || pdl_model.starts_with("ollama_chat/") => - { - let mut ollama = Ollama::default(); - let model = if pdl_model.starts_with("ollama/") { - &pdl_model[7..] - } else { - &pdl_model[12..] - }; + let mut ollama = Ollama::default(); + let model = if pdl_model.starts_with("ollama/") { + &pdl_model[7..] + } else { + &pdl_model[12..] + }; - let (options, tools) = self.to_ollama_model_options(&block.parameters); - if self.options.debug { - eprintln!("Model options {:?} {:?}", metadata.description, options); - eprintln!("Model tools {:?} {:?}", metadata.description, tools); - } + let (options, tools) = self.to_ollama_model_options(&block.parameters); + if self.options.debug { + eprintln!("Model options {:?} {:?}", metadata.description, options); + eprintln!("Model tools {:?} {:?}", metadata.description, tools); + } - // The input messages to the model is either: - // a) block.input, if given - // b) the current state's accumulated messages - let input_messages = match &block.input { - Some(input) => { - // TODO ignoring result, trace - let (_result, messages, _trace) = self.run_quiet(&*input, state).await?; - messages - } - None => state.messages.clone(), - }; - let (prompt, history_slice): (&ChatMessage, &[ChatMessage]) = - match input_messages.split_last() { - Some(x) => x, - None => (&ChatMessage::user("".into()), &[]), - }; - let mut history = Vec::from(history_slice); - if self.options.debug { - eprintln!( - "Ollama {:?} model={:?} prompt={:?} history={:?}", - metadata.description, block.model, prompt, history - ); - } + // The input messages to the model is either: + // a) block.input, if given + // b) the current state's accumulated messages + let input_messages = match &block.input { + Some(input) => { + // TODO ignoring result, trace + let (_result, messages, _trace) = self.run_quiet(&*input, state).await?; + messages + } + None => state.messages.clone(), + }; + let (prompt, history_slice): (&ChatMessage, &[ChatMessage]) = + match input_messages.split_last() { + Some(x) => x, + None => (&ChatMessage::user("".into()), &[]), + }; + let mut history = Vec::from(history_slice); + if self.options.debug { + eprintln!( + "Ollama {:?} model={:?} prompt={:?} history={:?}", + metadata.description, block.model, prompt, history + ); + } - //if state.emit { - //println!("{}", pretty_print(&input_messages)); - //} - - let req = ChatMessageRequest::new(model.into(), vec![prompt.clone()]) - .options(options) - .tools(tools); - - let (last_res, response_string) = if !self.options.stream { - let res = ollama - .send_chat_messages_with_history(&mut history, req) - .await?; - let response_string = res.message.content.clone(); - print!("{}", response_string); - (Some(res), response_string) - } else { - let mut stream = ollama - .send_chat_messages_with_history_stream( - ::std::sync::Arc::new(::std::sync::Mutex::new(history)), - req, - //ollama.generate(GenerationRequest::new(model.into(), prompt), - ) - .await?; - // dbg!("Model result {:?}", &res); - - let emit = if let Some(_) = &block.model_response { - false - } else { - true - }; - - let mut last_res: Option = None; - let mut response_string = String::new(); - let mut stdout = stdout(); - if emit { - stdout.write_all(b"\x1b[1mAssistant: \x1b[0m").await?; - } - while let Some(Ok(res)) = stream.next().await { - if emit { - stdout.write_all(b"\x1b[32m").await?; // green - stdout.write_all(res.message.content.as_bytes()).await?; - stdout.flush().await?; - stdout.write_all(b"\x1b[0m").await?; // reset color - } - response_string += res.message.content.as_str(); - last_res = Some(res); - } - if emit { - stdout.write_all(b"\n").await?; - } + //if state.emit { + //println!("{}", pretty_print(&input_messages)); + //} + + let req = ChatMessageRequest::new(model.into(), vec![prompt.clone()]) + .options(options) + .tools(tools); + + let (last_res, response_string) = if !self.options.stream { + let res = ollama + .send_chat_messages_with_history(&mut history, req) + .await?; + let response_string = res.message.content.clone(); + print!("{}", response_string); + (Some(res), response_string) + } else { + let mut stream = ollama + .send_chat_messages_with_history_stream( + ::std::sync::Arc::new(::std::sync::Mutex::new(history)), + req, + //ollama.generate(GenerationRequest::new(model.into(), prompt), + ) + .await?; + // dbg!("Model result {:?}", &res); - (last_res, response_string) - }; - - if let Some(_) = &block.model_response { - if let Some(ref res) = last_res { - self.def( - &block.model_response, - &resultify_as_litellm(&from_str(&to_string(&res)?)?), - &None, - state, - true, - )?; - } - } + let emit = if let Some(_) = &block.model_response { + false + } else { + true + }; - let mut trace = block.clone(); - if let Some(res) = last_res { - if let Some(usage) = res.final_data { - trace.pdl_usage = Some(PdlUsage { - prompt_tokens: usage.prompt_eval_count, - prompt_nanos: Some(usage.prompt_eval_duration), - completion_tokens: usage.eval_count, - completion_nanos: Some(usage.eval_duration), - }); - } - let output_messages = vec![ChatMessage::assistant(response_string)]; - Ok((res.message.content.into(), output_messages, Model(trace))) - } else { - // nothing came out of the model - Ok(("".into(), vec![], Model(trace))) + let mut last_res: Option = None; + let mut response_string = String::new(); + let mut stdout = stdout(); + if emit { + stdout.write_all(b"\x1b[1mAssistant: \x1b[0m").await?; + } + while let Some(Ok(res)) = stream.next().await { + if emit { + stdout.write_all(b"\x1b[32m").await?; // green + stdout.write_all(res.message.content.as_bytes()).await?; + stdout.flush().await?; + stdout.write_all(b"\x1b[0m").await?; // reset color } - // dbg!(history); + response_string += res.message.content.as_str(); + last_res = Some(res); + } + if emit { + stdout.write_all(b"\n").await?; + } + + (last_res, response_string) + }; + + if let Some(_) = &block.model_response { + if let Some(ref res) = last_res { + self.def( + &block.model_response, + &resultify_as_litellm(&from_str(&to_string(&res)?)?), + &None, + state, + true, + )?; } - _ => Err(Box::from(format!("Unsupported model {}", block.model))), } + + let mut trace = block.clone(); + if let Some(res) = last_res { + if let Some(usage) = res.final_data { + trace.pdl_usage = Some(PdlUsage { + prompt_tokens: usage.prompt_eval_count, + prompt_nanos: Some(usage.prompt_eval_duration), + completion_tokens: usage.eval_count, + completion_nanos: Some(usage.eval_duration), + }); + } + let output_messages = vec![ChatMessage::assistant(response_string)]; + Ok((res.message.content.into(), output_messages, Model(trace))) + } else { + // nothing came out of the model + Ok(("".into(), vec![], Model(trace))) + } + // dbg!(history); + } + + /// Run a PdlBlock::Model + async fn run_model( + &mut self, + block: &ModelBlock, + metadata: &Metadata, + state: &mut State, + ) -> BodyInterpretation { + if let PdlResult::String(s) = self.eval_string_to_string(&block.model, state)? { + if s.starts_with("ollama/") || s.starts_with("ollama_chat/") { + return self.run_ollama_model(s, block, metadata, state).await; + } + } + + Err(Box::from(format!("Unsupported model {:?}", block.model))) } /// Run a PdlBlock::Data diff --git a/pdl-live-react/src-tauri/src/pdl/interpreter_tests.rs b/pdl-live-react/src-tauri/src/pdl/interpreter_tests.rs index ba171ee74..b285016b0 100644 --- a/pdl-live-react/src-tauri/src/pdl/interpreter_tests.rs +++ b/pdl-live-react/src-tauri/src/pdl/interpreter_tests.rs @@ -79,6 +79,26 @@ mod tests { Ok(()) } + #[test] + fn single_model_via_text_chain_expr() -> Result<(), Box> { + let (_, messages, _) = run_json( + json!({ + "text": [ + "hello", + {"model": { "pdl__expr": DEFAULT_MODEL }} + ] + }), + streaming(), + initial_scope(), + )?; + assert_eq!(messages.len(), 2); + assert_eq!(messages[0].role, MessageRole::User); + assert_eq!(messages[0].content, "hello"); + assert_eq!(messages[1].role, MessageRole::Assistant); + assert!(messages[1].content.contains("Hello!")); + Ok(()) + } + #[test] fn single_model_via_text_chain() -> Result<(), Box> { let (_, messages, _) = run_json( From 10d0f54346f61f9baa765a3d716ccfff2e6c435f Mon Sep 17 00:00:00 2001 From: Nick Mitchell Date: Sun, 13 Apr 2025 18:51:51 -0400 Subject: [PATCH 28/39] feat: initial pdl__id and --trace support for rust interpreter Signed-off-by: Nick Mitchell --- .../src-tauri/src/pdl/interpreter.rs | 139 ++++++++++++++---- 1 file changed, 109 insertions(+), 30 deletions(-) diff --git a/pdl-live-react/src-tauri/src/pdl/interpreter.rs b/pdl-live-react/src-tauri/src/pdl/interpreter.rs index 2a5548996..2c8c4b1f8 100644 --- a/pdl-live-react/src-tauri/src/pdl/interpreter.rs +++ b/pdl-live-react/src-tauri/src/pdl/interpreter.rs @@ -60,6 +60,7 @@ struct State { scope: Scope, escaped_variables: Vec, messages: Messages, + id_stack: Vec, } impl State { @@ -70,6 +71,7 @@ impl State { scope: initial_scope, escaped_variables: vec![], messages: vec![], + id_stack: vec![], } } @@ -85,6 +87,19 @@ impl State { s } + fn with_iter(&self, iter: usize) -> Self { + let mut s = self.clone(); + s.id_stack.push(format!("{iter}")); + s + } + + fn incr_iter(&self, iter: usize) -> Self { + let mut s = self.clone(); + s.id_stack.pop(); + s.id_stack.push(format!("{iter}")); + s + } + fn extend_scope(&self, scopes: Vec) -> Self { let mut s = self.clone(); scopes.into_iter().for_each(|m| s.scope.extend(m)); @@ -94,7 +109,6 @@ impl State { struct Interpreter<'a> { // batch: u32, - // id_stack: Vec, options: RunOptions<'a>, jinja_env: Environment<'a>, } @@ -188,20 +202,62 @@ impl<'a> Interpreter<'a> { self.process_defs(&m.defs, state).await?; let (result, messages, trace_body) = match &block.body { - Call(b) => self.run_call(b, m, state).await, - Data(b) => self.run_data(b, m, state).await, - If(b) => self.run_if(b, m, state).await, - Import(b) => self.run_import(b, m, state).await, - Include(b) => self.run_include(b, m, state).await, - Model(b) => self.run_model(b, m, state).await, - Object(b) => self.run_object(b, m, state).await, - PythonCode(b) => self.run_python_code(b, m, state).await, - Read(b) => self.run_read(b, m, state).await, - Repeat(b) => self.run_repeat(b, m, state).await, - LastOf(b) => self.run_sequence(b, m, state).await, - Text(b) => self.run_sequence(b, m, state).await, - Array(b) => self.run_array(b, m, state).await, - Message(b) => self.run_message(b, m, state).await, + Call(b) => { + state.id_stack.push("call".to_string()); + self.run_call(b, m, state).await + } + Data(b) => { + state.id_stack.push("data".to_string()); + self.run_data(b, m, state).await + } + If(b) => { + state.id_stack.push("if".to_string()); + self.run_if(b, m, state).await + } + Import(b) => { + state.id_stack.push("import".to_string()); + self.run_import(b, m, state).await + } + Include(b) => { + state.id_stack.push("include".to_string()); + self.run_include(b, m, state).await + } + Model(b) => { + state.id_stack.push("model".to_string()); + self.run_model(b, m, state).await + } + Object(b) => { + state.id_stack.push("object".to_string()); + self.run_object(b, m, state).await + } + PythonCode(b) => { + state.id_stack.push("code".to_string()); + self.run_python_code(b, m, state).await + } + Read(b) => { + state.id_stack.push("read".to_string()); + self.run_read(b, m, state).await + } + Repeat(b) => { + state.id_stack.push("repeat".to_string()); + self.run_repeat(b, m, state).await + } + LastOf(b) => { + state.id_stack.push("lastOf".to_string()); + self.run_sequence(b, m, state).await + } + Text(b) => { + state.id_stack.push("text".to_string()); + self.run_sequence(b, m, state).await + } + Array(b) => { + state.id_stack.push("array".to_string()); + self.run_array(b, m, state).await + } + Message(b) => { + state.id_stack.push("message".to_string()); + self.run_message(b, m, state).await + } }?; let mut trace = Block { @@ -212,10 +268,13 @@ impl<'a> Interpreter<'a> { timing.end()?; let mut trace_metadata = m.clone(); + trace_metadata.pdl_id = Some(state.id_stack.join(".")); trace_metadata.pdl_timing = Some(timing); trace_metadata.pdl_result = Some(Box::new(result.clone())); trace.metadata = Some(trace_metadata); + state.id_stack.pop(); + Ok((result, messages, Advanced(trace))) } @@ -854,8 +913,12 @@ impl<'a> Interpreter<'a> { completion_nanos: Some(usage.eval_duration), }); } - let output_messages = vec![ChatMessage::assistant(response_string)]; - Ok((res.message.content.into(), output_messages, Model(trace))) + let output_messages = vec![ChatMessage::assistant(response_string.clone())]; + Ok(( + PdlResult::String(response_string), + output_messages, + Model(trace), + )) } else { // nothing came out of the model Ok(("".into(), vec![], Model(trace))) @@ -949,10 +1012,6 @@ impl<'a> Interpreter<'a> { _metadata: &Metadata, state: &mut State, ) -> BodyInterpretation { - // { i:[1,2,3], j: [4,5,6]} -> ([i,j], [[1,2,3],[4,5,6]]) - // let (variables, values): (Vec<_>, Vec>) = block - // .into_iter() - // .unzip(); let iter_scopes = block .for_ .iter() @@ -971,14 +1030,16 @@ impl<'a> Interpreter<'a> { let mut results = vec![]; let mut messages = vec![]; let mut trace = vec![]; - let mut iter_state = state.clone(); + let mut iter_state = state.with_iter(0); if let Some(n) = iter_scopes.iter().map(|(_, v)| v.len()).min() { for iter in 0..n { let this_iter_scope = iter_scopes .iter() .map(|(k, v)| (k.clone(), v[iter].clone())) .collect(); - iter_state = iter_state.extend_scope(vec![this_iter_scope]); + iter_state = iter_state + .incr_iter(iter) + .extend_scope(vec![this_iter_scope]); let (result, ms, t) = self.run_quiet(&block.repeat, &mut iter_state).await?; results.push(result); messages.extend(ms); @@ -987,6 +1048,9 @@ impl<'a> Interpreter<'a> { } } + state.scope = iter_state.scope; + state.escaped_variables = iter_state.escaped_variables; + Ok((PdlResult::List(results), messages, Repeat(block.clone()))) } @@ -1047,28 +1111,35 @@ impl<'a> Interpreter<'a> { // here is where we iterate over the sequence items let mut iter = block.items().iter(); + let mut idx = 0; + let mut iter_state = state.with_iter(idx); while let Some(block) = iter.next() { + idx += 1; + // run each element of the Text block - let (this_result, this_messages, trace) = self.run(&block, state).await?; + let (this_result, this_messages, trace) = self.run(&block, &mut iter_state).await?; - state.messages.extend(this_messages.iter().cloned()); + iter_state = iter_state.incr_iter(idx); + iter_state.messages.extend(this_messages.iter().cloned()); output_results.push(this_result); output_messages.extend(this_messages.iter().cloned()); output_blocks.push(trace); } - // self.scope.pop(); - let trace = block.with_items(output_blocks); let result = self.def( &metadata.def, &trace.result_for(output_results), trace.parser(), - state, + &mut iter_state, true, )?; let result_messages = trace.messages_for::(&output_messages); + + state.scope = iter_state.scope; + state.escaped_variables = iter_state.escaped_variables; + Ok(( result, match block.role() { @@ -1165,13 +1236,21 @@ pub async fn run<'a>( initial_scope: Scope, ) -> Interpretation { crate::pdl::pull::pull_if_needed(&program).await?; - + let trace_file = options.trace.clone(); let mut interpreter = Interpreter::new(options); let mut state = State::new(initial_scope); if let Some(cwd) = cwd { state.cwd = cwd } - interpreter.run(&program, &mut state).await + + let res = interpreter.run(&program, &mut state).await?; + if let Some(trace_file) = trace_file { + let file = ::std::fs::File::create(trace_file)?; + let mut writer = ::std::io::BufWriter::new(file); + serde_json::to_writer(&mut writer, &res.2)?; + } + + Ok(res) } #[allow(dead_code)] From 1bf9482e3ff92cfd5bacf59145c130f5c82ffa6f Mon Sep 17 00:00:00 2001 From: Nick Mitchell Date: Mon, 14 Apr 2025 09:44:22 -0400 Subject: [PATCH 29/39] fix: update rust interpreter to create Data blocks for expr eval, and model_input trace field Signed-off-by: Nick Mitchell --- pdl-live-react/src-tauri/src/pdl/ast.rs | 5 +- .../src-tauri/src/pdl/interpreter.rs | 72 ++++++++++++++----- 2 files changed, 58 insertions(+), 19 deletions(-) diff --git a/pdl-live-react/src-tauri/src/pdl/ast.rs b/pdl-live-react/src-tauri/src/pdl/ast.rs index 4a0fa4d94..81e7cdcba 100644 --- a/pdl-live-react/src-tauri/src/pdl/ast.rs +++ b/pdl-live-react/src-tauri/src/pdl/ast.rs @@ -238,7 +238,10 @@ impl SequencingBlock for TextBlock { PdlResult::String( output_results .into_iter() - .map(|m| m.to_string()) + .map(|m| match m { + PdlResult::String(s) => s, + x => x.to_string(), + }) .collect::>() .join("\n"), ) diff --git a/pdl-live-react/src-tauri/src/pdl/interpreter.rs b/pdl-live-react/src-tauri/src/pdl/interpreter.rs index 2c8c4b1f8..aa06432e3 100644 --- a/pdl-live-react/src-tauri/src/pdl/interpreter.rs +++ b/pdl-live-react/src-tauri/src/pdl/interpreter.rs @@ -24,7 +24,8 @@ use crate::pdl::ast::{ ArrayBlock, Block, Body::{self, *}, CallBlock, Closure, DataBlock, EmptyBlock, EvalsTo, Expr, FunctionBlock, IfBlock, ImportBlock, - IncludeBlock, ListOrString, MessageBlock, Metadata, ModelBlock, ObjectBlock, PdlBlock, + IncludeBlock, ListOrString, MessageBlock, Metadata, MetadataBuilder, ModelBlock, ObjectBlock, + PdlBlock, PdlBlock::Advanced, PdlParser, PdlResult, PdlUsage, PythonCodeBlock, ReadBlock, RepeatBlock, Role, Scope, SequencingBlock, StringOrBoolean, StringOrNull, Timing, @@ -75,6 +76,10 @@ impl State { } } + fn id(&self) -> String { + self.id_stack.join(".") + } + fn with_cwd(&self, cwd: PathBuf) -> Self { let mut s = self.clone(); s.cwd = cwd; @@ -268,7 +273,7 @@ impl<'a> Interpreter<'a> { timing.end()?; let mut trace_metadata = m.clone(); - trace_metadata.pdl_id = Some(state.id_stack.join(".")); + trace_metadata.pdl_id = Some(state.id()); trace_metadata.pdl_timing = Some(timing); trace_metadata.pdl_result = Some(Box::new(result.clone())); trace.metadata = Some(trace_metadata); @@ -429,18 +434,27 @@ impl<'a> Interpreter<'a> { /// Run a PdlBlock::String async fn run_string(&self, msg: &String, state: &State) -> Interpretation { - let trace = self.eval(msg, state)?; + let result = self.eval(msg, state)?; if self.options.debug { - eprintln!("String {} -> {:?}", msg, trace); + eprintln!("String {} -> {:?}", msg, result); } - let result_string = match &trace { + let result_string = match &result { PdlResult::String(s) => s.clone(), x => to_string(&x)?, }; + let messages = vec![ChatMessage::user(result_string)]; + let trace = Advanced(Block { + metadata: Some(MetadataBuilder::default().pdl_id(state.id()).build()?), + body: Data(DataBlock { + data: json!({ "pdl__expr": msg.clone(), "pdl__result": result.clone() }), + parser: None, + raw: None, + }), + }); - Ok((trace, messages, PdlBlock::String(msg.clone()))) + Ok((result, messages, trace)) } /// If `file_path` is not absolute, join it with state.cwd @@ -904,6 +918,19 @@ impl<'a> Interpreter<'a> { } let mut trace = block.clone(); + trace.pdl_model_input = Some( + input_messages + .into_iter() + .map(|m| MessageBlock { + role: self.from_ollama_role(m.role), + content: Box::new(PdlBlock::String(m.content)), + name: None, + tool_call_id: None, + defsite: None, + }) + .collect(), + ); + if let Some(res) = last_res { if let Some(usage) = res.final_data { trace.pdl_usage = Some(PdlUsage { @@ -1063,6 +1090,15 @@ impl<'a> Interpreter<'a> { } } + fn from_ollama_role(&self, role: MessageRole) -> Role { + match role { + MessageRole::User => Role::User, + MessageRole::Assistant => Role::Assistant, + MessageRole::System => Role::System, + MessageRole::Tool => Role::Tool, + } + } + fn parse_result(&self, parser: &PdlParser, result: &String) -> Result { match parser { PdlParser::Json => from_str(result).map_err(|e| Box::from(e)), @@ -1135,22 +1171,22 @@ impl<'a> Interpreter<'a> { &mut iter_state, true, )?; - let result_messages = trace.messages_for::(&output_messages); state.scope = iter_state.scope; state.escaped_variables = iter_state.escaped_variables; - Ok(( - result, - match block.role() { - Some(role) => result_messages - .into_iter() - .map(|m| ChatMessage::new(self.to_ollama_role(role), m.content)) - .collect(), - None => result_messages, - }, - trace.to_block(), - )) + // We may be asked to overlay a role on to the messages (TODO, + // does this belong in common code, i.e. run_advanced()?) + let result_messages = trace.messages_for::(&output_messages); + let messages = match block.role() { + Some(role) => result_messages + .into_iter() + .map(|m| ChatMessage::new(self.to_ollama_role(role), m.content)) + .collect(), + None => result_messages, + }; + + Ok((result, messages, trace.to_block())) } /// Run a PdlBlock::Array From e9b02f693a698bfdd9f9ba4753613ed4a5d47fb9 Mon Sep 17 00:00:00 2001 From: Nick Mitchell Date: Mon, 14 Apr 2025 10:24:23 -0400 Subject: [PATCH 30/39] fix: populate trace context field in rust interpreter Signed-off-by: Nick Mitchell --- pdl-live-react/src-tauri/src/pdl/ast.rs | 8 +++-- .../src-tauri/src/pdl/interpreter.rs | 29 +++++++++++++++---- 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/pdl-live-react/src-tauri/src/pdl/ast.rs b/pdl-live-react/src-tauri/src/pdl/ast.rs index 81e7cdcba..67dbf7f19 100644 --- a/pdl-live-react/src-tauri/src/pdl/ast.rs +++ b/pdl-live-react/src-tauri/src/pdl/ast.rs @@ -285,15 +285,19 @@ pub struct ModelBlock { #[serde(skip_serializing_if = "Option::is_none")] pub platform: Option, #[serde(skip_serializing_if = "Option::is_none")] - pub context: Option>, - #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "modelResponse")] pub model_response: Option, #[serde(rename = "pdl__usage")] #[serde(skip_serializing_if = "Option::is_none")] pub pdl_usage: Option, + + /// The result of evaluating the `input` field (if given) #[serde(rename = "pdl__model_input", skip_serializing_if = "Option::is_none")] pub pdl_model_input: Option>, + + /// The actual input given to the model (whether via `input` or from the incoming messages) + #[serde(skip_serializing_if = "Option::is_none")] + pub context: Option>, } #[derive(Serialize, Deserialize, Debug, Clone)] diff --git a/pdl-live-react/src-tauri/src/pdl/interpreter.rs b/pdl-live-react/src-tauri/src/pdl/interpreter.rs index aa06432e3..2e0a2033c 100644 --- a/pdl-live-react/src-tauri/src/pdl/interpreter.rs +++ b/pdl-live-react/src-tauri/src/pdl/interpreter.rs @@ -827,12 +827,14 @@ impl<'a> Interpreter<'a> { eprintln!("Model tools {:?} {:?}", metadata.description, tools); } + let mut trace = block.clone(); + // The input messages to the model is either: // a) block.input, if given // b) the current state's accumulated messages let input_messages = match &block.input { Some(input) => { - // TODO ignoring result, trace + // TODO ignoring result and trace let (_result, messages, _trace) = self.run_quiet(&*input, state).await?; messages } @@ -917,13 +919,28 @@ impl<'a> Interpreter<'a> { } } - let mut trace = block.clone(); + // TODO, does this belong in run_advanced(), and does + // trace.context belong in Metadata rather than ModelBlock + trace.context = Some( + state + .messages + .iter() + .map(|m| MessageBlock { + role: self.from_ollama_role(&m.role), + content: Box::new(PdlBlock::String(m.content.clone())), + name: None, + tool_call_id: None, + defsite: None, + }) + .collect(), + ); + // TODO, what is the difference between context and pdl_model_input fields? trace.pdl_model_input = Some( input_messages - .into_iter() + .iter() .map(|m| MessageBlock { - role: self.from_ollama_role(m.role), - content: Box::new(PdlBlock::String(m.content)), + role: self.from_ollama_role(&m.role), + content: Box::new(PdlBlock::String(m.content.clone())), name: None, tool_call_id: None, defsite: None, @@ -1090,7 +1107,7 @@ impl<'a> Interpreter<'a> { } } - fn from_ollama_role(&self, role: MessageRole) -> Role { + fn from_ollama_role(&self, role: &MessageRole) -> Role { match role { MessageRole::User => Role::User, MessageRole::Assistant => Role::Assistant, From 6a5b1defc126adab5331275f34bdd74230dc634f Mon Sep 17 00:00:00 2001 From: Jing Chen Date: Mon, 14 Apr 2025 12:30:50 -0400 Subject: [PATCH 31/39] Update stop sequences in parameters (#861) * Stop sequence parameter update to align with ollama Signed-off-by: Jing Chen * Update results Signed-off-by: Jing Chen --------- Signed-off-by: Jing Chen --- examples/callback/repair_prompt.pdl | 1 - examples/cldk/cldk-assistant.pdl | 2 +- examples/demo/10-sdg.pdl | 8 +++---- examples/notebooks/demo.ipynb | 6 ++--- examples/notebooks/notebook.ipynb | 4 ++-- examples/notebooks/notebook_debug.ipynb | 4 ++-- examples/notebooks/pdl.ipynb | 10 ++++---- examples/sdk/hello_dict.py | 6 +++-- examples/sdk/hello_prog.py | 4 ++-- examples/sdk/hello_str.py | 4 ++-- examples/teacher/teacher.pdl | 8 +++---- tests/results/examples/demo/9-react.0.result | 24 +++++++++----------- tests/results/examples/react/demo.0.result | 24 +++++++++----------- 13 files changed, 51 insertions(+), 54 deletions(-) diff --git a/examples/callback/repair_prompt.pdl b/examples/callback/repair_prompt.pdl index bc795842e..2bc6796b5 100644 --- a/examples/callback/repair_prompt.pdl +++ b/examples/callback/repair_prompt.pdl @@ -11,7 +11,6 @@ lastOf: - def: raw_output model: ollama_chat/granite3.2:2b parameters: - #stop_sequences: "\n\n" temperature: 0 - lang: python diff --git a/examples/cldk/cldk-assistant.pdl b/examples/cldk/cldk-assistant.pdl index 339790d0d..0253724e8 100644 --- a/examples/cldk/cldk-assistant.pdl +++ b/examples/cldk/cldk-assistant.pdl @@ -130,7 +130,7 @@ text: parameters: - stop_sequences: "Question" + stop: ["Question"] temperature: 0 - "\n\n***Executing the above PDL code:\n\n" - lang: python diff --git a/examples/demo/10-sdg.pdl b/examples/demo/10-sdg.pdl index 68dbe11db..ac1ba52ea 100644 --- a/examples/demo/10-sdg.pdl +++ b/examples/demo/10-sdg.pdl @@ -68,7 +68,7 @@ defs: input: ${teacher_input} parameters: temperature: 0 - stop_sequences: "${teacher_stop_token}" + stop: ["${teacher_stop_token}"] max_new_tokens: ${prompt_data.max_new_tokens} parser: regex: '### Question [0-9]+:\s*([^#\n]+)' @@ -156,7 +156,7 @@ defs: model: ${teacher_model} input: ${teacher_input} parameters: - stop_sequences: "${teacher_stop_token}" + stop: ["${teacher_stop_token}"] max_new_tokens: ${prompt_data.max_new_tokens} temperature: 0 parser: @@ -252,7 +252,7 @@ defs: model: ${teacher_model} input: ${teacher_input} parameters: - stop_sequences: ${ ([teacher_stop_token] + prompt_data.additional_stop_tokens) | join(',') } + stop: ["${ ([teacher_stop_token] + prompt_data.additional_stop_tokens) | join(',') }"] max_new_tokens: ${prompt_data.max_new_tokens} temperature: 0 parsed_answer: @@ -339,7 +339,7 @@ defs: model: ${teacher_model} input: ${teacher_input} parameters: - stop_sequences: "${teacher_stop_token}" + stop: ["${teacher_stop_token}"] max_new_tokens: ${prompt_data.max_new_tokens} temperature: 0 parser: diff --git a/examples/notebooks/demo.ipynb b/examples/notebooks/demo.ipynb index 87b0f63be..251f99fb9 100644 --- a/examples/notebooks/demo.ipynb +++ b/examples/notebooks/demo.ipynb @@ -347,7 +347,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "id": "dfef7096-b7a6-4966-8356-a306e701974b", "metadata": { "scrolled": true @@ -395,12 +395,12 @@ " - def: thought\n", " model: replicate/ibm-granite/granite-3.1-8b-instruct\n", " parameters:\n", - " stop_sequences: \"Act:\"\n", + " stop: [\"Act:\"]\n", " temperature: 0\n", " - def: rawAction\n", " model: replicate/ibm-granite/granite-3.1-8b-instruct\n", " parameters:\n", - " stop_sequences: \"\\n\"\n", + " stop: [\"\\n\"]\n", " temperature: 0\n", " - def: action\n", " lang: python\n", diff --git a/examples/notebooks/notebook.ipynb b/examples/notebooks/notebook.ipynb index d384c5dde..03586e617 100644 --- a/examples/notebooks/notebook.ipynb +++ b/examples/notebooks/notebook.ipynb @@ -12,7 +12,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": null, "id": "f3c62df1-0347-4711-acd7-3892cfd5df30", "metadata": {}, "outputs": [ @@ -32,7 +32,7 @@ "- \"Hello\\n\"\n", "- model: \"replicate/ibm-granite/granite-3.1-8b-instruct\"\n", " parameters:\n", - " stop_sequences: \"!\"\n", + " stop: [\"!\"]\n", " " ] }, diff --git a/examples/notebooks/notebook_debug.ipynb b/examples/notebooks/notebook_debug.ipynb index 0287f66a0..b1b45accc 100644 --- a/examples/notebooks/notebook_debug.ipynb +++ b/examples/notebooks/notebook_debug.ipynb @@ -12,7 +12,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "id": "f3c62df1-0347-4711-acd7-3892cfd5df30", "metadata": {}, "outputs": [ @@ -166,7 +166,7 @@ "- Hello,\n", "- model: \"replicate/ibm-granite/granite-3.1-8b-instruct\"\n", " parameters:\n", - " stop_sequences: \"!\"" + " stop: [\"!\"]" ] }, { diff --git a/examples/notebooks/pdl.ipynb b/examples/notebooks/pdl.ipynb index 461415e4e..e644721e5 100644 --- a/examples/notebooks/pdl.ipynb +++ b/examples/notebooks/pdl.ipynb @@ -46,7 +46,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "id": "f3c62df1-0347-4711-acd7-3892cfd5df30", "metadata": {}, "outputs": [ @@ -75,7 +75,7 @@ "- \"What is the meaning of life?\\n\"\n", "- model: replicate/ibm-granite/granite-3.2-8b-instruct\n", " parameters:\n", - " stop_sequences: \"!\"\n", + " stop: [\"!\"]\n", " include_stop_sequence: true" ] }, @@ -153,7 +153,7 @@ ] }, { - "name": "stdin", + "name": "stdout", "output_type": "stream", "text": [ ">>> What is APR?\n" @@ -169,7 +169,7 @@ ] }, { - "name": "stdin", + "name": "stdout", "output_type": "stream", "text": [ ">>> say it like a poem\n" @@ -200,7 +200,7 @@ ] }, { - "name": "stdin", + "name": "stdout", "output_type": "stream", "text": [ ">>> quit\n" diff --git a/examples/sdk/hello_dict.py b/examples/sdk/hello_dict.py index b83c22493..4313898ce 100644 --- a/examples/sdk/hello_dict.py +++ b/examples/sdk/hello_dict.py @@ -4,8 +4,10 @@ "text": [ "Hello\n", { - "model": "ollama_chat/granite3.2:8b", - "parameters": {"stop_sequences": "!"}, + "model": "ollama_chat/granite3.2:2b", + "parameters": { + "stop": ["!"], + }, }, ] } diff --git a/examples/sdk/hello_prog.py b/examples/sdk/hello_prog.py index f33718656..de0c8b8e8 100644 --- a/examples/sdk/hello_prog.py +++ b/examples/sdk/hello_prog.py @@ -6,8 +6,8 @@ text=[ "Hello\n", LitellmModelBlock( - model="ollama_chat/granite3.2:8b", - parameters=LitellmParameters(stop_sequences="!"), # pyright: ignore + model="ollama_chat/granite3.2:2b", + parameters=LitellmParameters(stop=["!"]), # pyright: ignore ), ] ) diff --git a/examples/sdk/hello_str.py b/examples/sdk/hello_str.py index b0711587e..8a6b52c90 100644 --- a/examples/sdk/hello_str.py +++ b/examples/sdk/hello_str.py @@ -3,9 +3,9 @@ HELLO = """ text: - "Hello\n" -- model: ollama_chat/granite3.2:8b +- model: ollama_chat/granite3.2:2b parameters: - stop_sequences: '!' + stop: ['!'] """ diff --git a/examples/teacher/teacher.pdl b/examples/teacher/teacher.pdl index 68dbe11db..ac1ba52ea 100644 --- a/examples/teacher/teacher.pdl +++ b/examples/teacher/teacher.pdl @@ -68,7 +68,7 @@ defs: input: ${teacher_input} parameters: temperature: 0 - stop_sequences: "${teacher_stop_token}" + stop: ["${teacher_stop_token}"] max_new_tokens: ${prompt_data.max_new_tokens} parser: regex: '### Question [0-9]+:\s*([^#\n]+)' @@ -156,7 +156,7 @@ defs: model: ${teacher_model} input: ${teacher_input} parameters: - stop_sequences: "${teacher_stop_token}" + stop: ["${teacher_stop_token}"] max_new_tokens: ${prompt_data.max_new_tokens} temperature: 0 parser: @@ -252,7 +252,7 @@ defs: model: ${teacher_model} input: ${teacher_input} parameters: - stop_sequences: ${ ([teacher_stop_token] + prompt_data.additional_stop_tokens) | join(',') } + stop: ["${ ([teacher_stop_token] + prompt_data.additional_stop_tokens) | join(',') }"] max_new_tokens: ${prompt_data.max_new_tokens} temperature: 0 parsed_answer: @@ -339,7 +339,7 @@ defs: model: ${teacher_model} input: ${teacher_input} parameters: - stop_sequences: "${teacher_stop_token}" + stop: ["${teacher_stop_token}"] max_new_tokens: ${prompt_data.max_new_tokens} temperature: 0 parser: diff --git a/tests/results/examples/demo/9-react.0.result b/tests/results/examples/demo/9-react.0.result index 553886bd1..679bcf2cd 100644 --- a/tests/results/examples/demo/9-react.0.result +++ b/tests/results/examples/demo/9-react.0.result @@ -1,16 +1,14 @@ -How many years ago was the discoverer of the Hudson River born? Keep in mind we are in 2025. -Thought: I need to find out who discovered the Hudson River and then calculate how many years ago they were born, given that it's currently 2025. +How many years ago was the discoverer of the Hudson River born? Keep in mind we are in 2025. When searching for a birthday for a person, simply ask for the name of that person. +Thought: I need to find out who discovered the Hudson River and then calculate how many years ago their birthdate was. Action: -[{"name": "Search", "arguments": {"topic": "Hudson River discoverer"}}] -Observation: The Hudson River was discovered by Henry Hudson in 1609. -Thought: Henry Hudson was born around 1565 to 1570. I need to find the exact birth year and then calculate how many years ago it was from 2025. +[{"name": "Search", "arguments": {"topic": "Henry Hudson"}}] +Observation: Henry Hudson (c. 1565 – disappeared 23 June 1611) was an English sea explorer and navigator during the early 17th century, best known for his explorations of present-day Canada and parts of the Northeastern United States. +In 1607 and 1608, Hudson made two attempts on behalf of English merchants to find a rumoured Northeast Passage to Cathay via a route above the Arctic Circle. In 1609, he landed in North America on behalf of the Dutch East India Company and explored the region around the modern New York metropolitan area. Looking for a Northwest Passage to Asia on his ship Halve Maen ("Half Moon"), he sailed up the Hudson River, which was later named after him, and thereby laid the foundation for Dutch colonization of the region. His contributions to the exploration of the New World were significant and lasting. His voyages helped to establish European contact with the native peoples of North America and contributed to the development of trade and commerce. +On his final expedition, while still searching for the Northwest Passage, Hudson became the first European to see Hudson Strait and the immense Hudson Bay. In 1611, after wintering on the shore of James Bay, Hudson wanted to press on to the west, but most of his crew mutinied. The mutineers cast Hudson, his son, and six others adrift; what then happened to the Hudsons and their companions is unknown. +Thought: Henry Hudson discovered the Hudson River in 1609. He was born around 1565. Action: -[{"name": "Search", "arguments": {"topic": "Henry Hudson birth year"}}] -Observation: Henry Hudson was born in 1565. -Thought: To find out how many years ago he was born, I subtract his birth year from the current year (2025). -Action: -[{"name": "Calc", "arguments": {"expr": "2025 - 1565"}}] -Observation: The result of the calculation is 460. -Thought: Henry Hudson was born 460 years ago.Action: -[{"name": "Finish", "arguments": {"topic": "460 years ago"}}] +[{"name": "Calc", "arguments": {"expr": "2025 - 1565"}}] +Observation: 460 +Henry Hudson was born around 1565, so he was approximately 460 years ago.Action: +[{"name": "Finish", "arguments": {"topic": "460"}}] Observation: diff --git a/tests/results/examples/react/demo.0.result b/tests/results/examples/react/demo.0.result index 2623d9115..ce67247d3 100644 --- a/tests/results/examples/react/demo.0.result +++ b/tests/results/examples/react/demo.0.result @@ -1,15 +1,13 @@ -How many years ago was the discoverer of the Hudson River born? Keep in mind we are in 2025. -Thought: I need to find out who discovered the Hudson River and then calculate how many years ago they were born, given that it's currently 2025. +How many years ago was the discoverer of the Hudson River born? Keep in mind we are in 2025. When searching for a birthday for a person, simply ask for the name of that person. +Thought: I need to find out who discovered the Hudson River and then calculate how many years ago their birthdate was. Action: -[{"name": "Search", "arguments": {"topic": "Hudson River discoverer"}}] -Observation: The Hudson River was discovered by Henry Hudson in 1609. -Thought: Henry Hudson was born around 1565 to 1570. I need to find the exact birth year and then calculate how many years ago it was from 2025. +[{"name": "Search", "arguments": {"topic": "Henry Hudson"}}] +Observation: Henry Hudson (c. 1565 – disappeared 23 June 1611) was an English sea explorer and navigator during the early 17th century, best known for his explorations of present-day Canada and parts of the Northeastern United States. +In 1607 and 1608, Hudson made two attempts on behalf of English merchants to find a rumoured Northeast Passage to Cathay via a route above the Arctic Circle. In 1609, he landed in North America on behalf of the Dutch East India Company and explored the region around the modern New York metropolitan area. Looking for a Northwest Passage to Asia on his ship Halve Maen ("Half Moon"), he sailed up the Hudson River, which was later named after him, and thereby laid the foundation for Dutch colonization of the region. His contributions to the exploration of the New World were significant and lasting. His voyages helped to establish European contact with the native peoples of North America and contributed to the development of trade and commerce. +On his final expedition, while still searching for the Northwest Passage, Hudson became the first European to see Hudson Strait and the immense Hudson Bay. In 1611, after wintering on the shore of James Bay, Hudson wanted to press on to the west, but most of his crew mutinied. The mutineers cast Hudson, his son, and six others adrift; what then happened to the Hudsons and their companions is unknown. +Thought: Henry Hudson discovered the Hudson River in 1609. He was born around 1565. Action: -[{"name": "Search", "arguments": {"topic": "Henry Hudson birth year"}}] -Observation: Henry Hudson was born in 1565. -Thought: To find out how many years ago he was born, I subtract his birth year from the current year (2025). -Action: -[{"name": "Calc", "arguments": {"expr": "2025 - 1565"}}] -Observation: The result of the calculation is 460. -Thought: Henry Hudson was born 460 years ago.Action: -[{"name": "Finish", "arguments": {"topic": "460 years ago"}}] +[{"name": "Calc", "arguments": {"expr": "2025 - 1565"}}] +Observation: 460 +Henry Hudson was born around 1565, so he was approximately 460 years ago.Action: +[{"name": "Finish", "arguments": {"topic": "460"}}] From 3e313a64cab09410a9bda0b5b4e4abb860d13f73 Mon Sep 17 00:00:00 2001 From: Nick Mitchell Date: Mon, 14 Apr 2025 11:56:32 -0400 Subject: [PATCH 32/39] refactor: extract platform-generic logic from run_ollama_model() handler Signed-off-by: Nick Mitchell --- .../src-tauri/src/pdl/interpreter.rs | 115 ++++++++++-------- 1 file changed, 63 insertions(+), 52 deletions(-) diff --git a/pdl-live-react/src-tauri/src/pdl/interpreter.rs b/pdl-live-react/src-tauri/src/pdl/interpreter.rs index 2e0a2033c..8bab94bb8 100644 --- a/pdl-live-react/src-tauri/src/pdl/interpreter.rs +++ b/pdl-live-react/src-tauri/src/pdl/interpreter.rs @@ -813,7 +813,8 @@ impl<'a> Interpreter<'a> { block: &ModelBlock, metadata: &Metadata, state: &mut State, - ) -> BodyInterpretation { + input_messages: Vec, + ) -> Result<(String, Option), PdlError> { let mut ollama = Ollama::default(); let model = if pdl_model.starts_with("ollama/") { &pdl_model[7..] @@ -827,19 +828,6 @@ impl<'a> Interpreter<'a> { eprintln!("Model tools {:?} {:?}", metadata.description, tools); } - let mut trace = block.clone(); - - // The input messages to the model is either: - // a) block.input, if given - // b) the current state's accumulated messages - let input_messages = match &block.input { - Some(input) => { - // TODO ignoring result and trace - let (_result, messages, _trace) = self.run_quiet(&*input, state).await?; - messages - } - None => state.messages.clone(), - }; let (prompt, history_slice): (&ChatMessage, &[ChatMessage]) = match input_messages.split_last() { Some(x) => x, @@ -853,10 +841,6 @@ impl<'a> Interpreter<'a> { ); } - //if state.emit { - //println!("{}", pretty_print(&input_messages)); - //} - let req = ChatMessageRequest::new(model.into(), vec![prompt.clone()]) .options(options) .tools(tools); @@ -919,6 +903,45 @@ impl<'a> Interpreter<'a> { } } + let usage = if let Some(res) = last_res { + if let Some(usage) = res.final_data { + Some(PdlUsage { + prompt_tokens: usage.prompt_eval_count, + prompt_nanos: Some(usage.prompt_eval_duration), + completion_tokens: usage.eval_count, + completion_nanos: Some(usage.eval_duration), + }) + } else { + None + } + } else { + None + }; + + Ok((response_string, usage)) + } + + /// Run a PdlBlock::Model + async fn run_model( + &mut self, + block: &ModelBlock, + metadata: &Metadata, + state: &mut State, + ) -> BodyInterpretation { + // The input messages to the model is either: + // a) block.input, if given + // b) the current state's accumulated messages + let input_messages = match &block.input { + Some(input) => { + // TODO ignoring result and trace + let (_result, messages, _trace) = self.run_quiet(&*input, state).await?; + messages + } + None => state.messages.clone(), + }; + + let mut trace = block.clone(); + // TODO, does this belong in run_advanced(), and does // trace.context belong in Metadata rather than ModelBlock trace.context = Some( @@ -948,42 +971,30 @@ impl<'a> Interpreter<'a> { .collect(), ); - if let Some(res) = last_res { - if let Some(usage) = res.final_data { - trace.pdl_usage = Some(PdlUsage { - prompt_tokens: usage.prompt_eval_count, - prompt_nanos: Some(usage.prompt_eval_duration), - completion_tokens: usage.eval_count, - completion_nanos: Some(usage.eval_duration), - }); - } - let output_messages = vec![ChatMessage::assistant(response_string.clone())]; - Ok(( - PdlResult::String(response_string), - output_messages, - Model(trace), - )) - } else { - // nothing came out of the model - Ok(("".into(), vec![], Model(trace))) - } - // dbg!(history); - } + let (response_string, usage) = + if let PdlResult::String(s) = self.eval_string_to_string(&block.model, state)? { + if s.starts_with("ollama/") || s.starts_with("ollama_chat/") { + self.run_ollama_model(s, block, metadata, state, input_messages) + .await + /*} else if s.starts_with("openai/") { + return self.run_openai_model(s, block, metadata, state, input_messages).await;*/ + } else { + Err(Box::from(format!("Unsupported model {:?}", block.model))) + } + } else { + Err(Box::from(format!( + "Model expression evaluated to non-string {:?}", + block.model + ))) + }?; - /// Run a PdlBlock::Model - async fn run_model( - &mut self, - block: &ModelBlock, - metadata: &Metadata, - state: &mut State, - ) -> BodyInterpretation { - if let PdlResult::String(s) = self.eval_string_to_string(&block.model, state)? { - if s.starts_with("ollama/") || s.starts_with("ollama_chat/") { - return self.run_ollama_model(s, block, metadata, state).await; - } - } + trace.pdl_usage = usage; - Err(Box::from(format!("Unsupported model {:?}", block.model))) + Ok(( + PdlResult::String(response_string.clone()), + vec![ChatMessage::assistant(response_string)], + Model(trace), + )) } /// Run a PdlBlock::Data From 424e33fca6b9d211c0a50704251accecdbaedd6d Mon Sep 17 00:00:00 2001 From: Nick Mitchell Date: Tue, 15 Apr 2025 10:09:18 -0400 Subject: [PATCH 33/39] fix: rust interpreter was not handling pdl__context for re-runs of traces This only covers the run_model code path, but it's a start. Signed-off-by: Nick Mitchell --- .../src-tauri/src/pdl/interpreter.rs | 34 ++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/pdl-live-react/src-tauri/src/pdl/interpreter.rs b/pdl-live-react/src-tauri/src/pdl/interpreter.rs index 8bab94bb8..2402bd4a7 100644 --- a/pdl-live-react/src-tauri/src/pdl/interpreter.rs +++ b/pdl-live-react/src-tauri/src/pdl/interpreter.rs @@ -931,6 +931,22 @@ impl<'a> Interpreter<'a> { // The input messages to the model is either: // a) block.input, if given // b) the current state's accumulated messages + if let Some(c) = &block.context { + state.scope.insert( + "pdl_context".to_string(), + PdlResult::List( + c.iter() + .map(|m| { + PdlResult::Block(PdlBlock::Advanced(Block { + metadata: None, + body: Body::Message(m.clone()), + })) + }) + .collect(), + ), + ); + } + let input_messages = match &block.input { Some(input) => { // TODO ignoring result and trace @@ -1027,7 +1043,23 @@ impl<'a> Interpreter<'a> { true, )?; trace.data = from_str(to_string(&result)?.as_str())?; - Ok((result, vec![], Data(trace))) + let messages = match &trace.data { + Value::Object(m) => match m.get("pdl__result") { + Some(Value::Array(a)) => a + .iter() + .filter_map(|m| match m { + Value::Object(d) => match d.get("content") { + Some(Value::String(s)) => Some(ChatMessage::user(s.clone())), + _ => None, + }, + _ => None, + }) + .collect(), + _ => vec![], + }, + _ => vec![], + }; + Ok((result, messages, Data(trace))) } } From 10f0c3d8d087e1a40cad78ccc0bfbde0ee95828f Mon Sep 17 00:00:00 2001 From: Nick Mitchell Date: Tue, 15 Apr 2025 13:02:21 -0400 Subject: [PATCH 34/39] feat: improve support for importing stdlib in python code blocks Signed-off-by: Nick Mitchell --- pdl-live-react/src-tauri/Cargo.lock | 739 +++++++++--------- pdl-live-react/src-tauri/Cargo.toml | 5 +- .../src-tauri/src/pdl/interpreter.rs | 3 +- .../src-tauri/tests/cli/code-python.pdl | 5 +- 4 files changed, 375 insertions(+), 377 deletions(-) diff --git a/pdl-live-react/src-tauri/Cargo.lock b/pdl-live-react/src-tauri/Cargo.lock index 4615770b1..eaf12d281 100644 --- a/pdl-live-react/src-tauri/Cargo.lock +++ b/pdl-live-react/src-tauri/Cargo.lock @@ -346,17 +346,6 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" -[[package]] -name = "atty" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -dependencies = [ - "hermit-abi 0.1.19", - "libc", - "winapi", -] - [[package]] name = "autocfg" version = "1.4.0" @@ -378,12 +367,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "base64" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" - [[package]] name = "base64" version = "0.21.7" @@ -489,13 +472,13 @@ dependencies = [ [[package]] name = "bstr" -version = "0.2.17" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" +checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4" dependencies = [ - "lazy_static", "memchr", - "regex-automata 0.1.10", + "regex-automata", + "serde", ] [[package]] @@ -601,6 +584,15 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "castaway" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0abae9be0aaf9ea96a3b1b8b1b55c602ca751eba1b1500220cea4ecbafe7c0d5" +dependencies = [ + "rustversion", +] + [[package]] name = "cc" version = "1.2.18" @@ -722,6 +714,20 @@ dependencies = [ "memchr", ] +[[package]] +name = "compact_str" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b79c4069c6cad78e2e0cdfcbd26275770669fb39fd308a752dc110e83b9af32" +dependencies = [ + "castaway", + "cfg-if", + "itoa 1.0.15", + "rustversion", + "ryu", + "static_assertions", +] + [[package]] name = "concurrent-queue" version = "2.5.0" @@ -1340,7 +1346,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece" dependencies = [ "crc32fast", - "libz-sys", + "libz-rs-sys 0.5.0", "miniz_oxide", ] @@ -1649,12 +1655,12 @@ dependencies = [ [[package]] name = "gethostname" -version = "0.2.3" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1ebd34e35c46e00bb73e81363248d627782724609fe1b6396f553f68fe3862e" +checksum = "ed7131e57abbde63513e0e6636f76668a1ca9798dcae2df4e283cae9ee83859e" dependencies = [ - "libc", - "winapi", + "rustix 1.0.5", + "windows-targets 0.52.6", ] [[package]] @@ -1663,7 +1669,7 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" dependencies = [ - "unicode-width", + "unicode-width 0.1.14", ] [[package]] @@ -1684,10 +1690,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", - "js-sys", "libc", "wasi 0.11.0+wasi-snapshot-preview1", - "wasm-bindgen", ] [[package]] @@ -1697,9 +1701,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" dependencies = [ "cfg-if", + "js-sys", "libc", "r-efi", "wasi 0.14.2+wasi-0.2.4", + "wasm-bindgen", ] [[package]] @@ -1858,9 +1864,13 @@ dependencies = [ [[package]] name = "half" -version = "1.8.3" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b43ede17f21864e81be2fa654110bf1e793774238d86ef8555c37e6519c0403" +checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" +dependencies = [ + "cfg-if", + "crunchy", +] [[package]] name = "hashbrown" @@ -1868,15 +1878,6 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" -[[package]] -name = "hashbrown" -version = "0.14.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" -dependencies = [ - "ahash", -] - [[package]] name = "hashbrown" version = "0.15.2" @@ -1907,15 +1908,6 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" -[[package]] -name = "hermit-abi" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] - [[package]] name = "hermit-abi" version = "0.3.9" @@ -2070,7 +2062,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core 0.61.0", + "windows-core 0.60.1", ] [[package]] @@ -2320,6 +2312,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "0.4.8" @@ -2453,10 +2454,14 @@ dependencies = [ ] [[package]] -name = "lalrpop-util" -version = "0.20.2" +name = "lambert_w" +version = "1.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "507460a910eb7b32ee961886ff48539633b788a36b65692b95f225b844c82553" +checksum = "91e80e7f3ce8e01aebf9062b5b4e1adfd928a0f4a903ffcf2d7787817615fb89" +dependencies = [ + "num-complex", + "num-traits", +] [[package]] name = "lazy_static" @@ -2466,9 +2471,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "lexical-parse-float" -version = "0.8.5" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "683b3a5ebd0130b8fb52ba0bdc718cc56815b6a097e28ae5a6997d0ad17dc05f" +checksum = "de6f9cb01fb0b08060209a057c048fcbab8717b4c1ecd2eac66ebfe39a65b0f2" dependencies = [ "lexical-parse-integer", "lexical-util", @@ -2477,9 +2482,9 @@ dependencies = [ [[package]] name = "lexical-parse-integer" -version = "0.8.6" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d0994485ed0c312f6d965766754ea177d07f9c00c9b82a5ee62ed5b47945ee9" +checksum = "72207aae22fc0a121ba7b6d479e42cbfea549af1479c3f3a4f12c70dd66df12e" dependencies = [ "lexical-util", "static_assertions", @@ -2487,9 +2492,9 @@ dependencies = [ [[package]] name = "lexical-util" -version = "0.8.5" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5255b9ff16ff898710eb9eb63cb39248ea8a5bb036bea8085b1a767ff6c4e3fc" +checksum = "5a82e24bf537fd24c177ffbbdc6ebcc8d54732c35b50a3f28cc3f4e4c949a0b3" dependencies = [ "static_assertions", ] @@ -2514,7 +2519,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e9ec52138abedcc58dc17a7c6c0c00a2bdb4f3427c7f63fa97fd0d859155caf" dependencies = [ "gtk-sys", - "libloading", + "libloading 0.7.4", "once_cell", ] @@ -2524,6 +2529,25 @@ version = "0.2.171" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" +[[package]] +name = "libffi" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce826c243048e3d5cec441799724de52e2d42f820468431fc3fceee2341871e2" +dependencies = [ + "libc", + "libffi-sys", +] + +[[package]] +name = "libffi-sys" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f36115160c57e8529781b4183c2bb51fdc1f6d6d1ed345591d84be7703befb3c" +dependencies = [ + "cc", +] + [[package]] name = "libloading" version = "0.7.4" @@ -2534,6 +2558,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "libloading" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" +dependencies = [ + "cfg-if", + "windows-targets 0.52.6", +] + [[package]] name = "libm" version = "0.2.11" @@ -2551,25 +2585,21 @@ dependencies = [ ] [[package]] -name = "libsqlite3-sys" -version = "0.28.0" +name = "libz-rs-sys" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c10584274047cb335c23d3e61bcef8e323adae7c5c8c760540f73610177fc3f" +checksum = "902bc563b5d65ad9bba616b490842ef0651066a1a1dc3ce1087113ffcb873c8d" dependencies = [ - "cc", - "pkg-config", - "vcpkg", + "zlib-rs 0.4.2", ] [[package]] -name = "libz-sys" -version = "1.1.22" +name = "libz-rs-sys" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b70e7a7df205e92a1a4cd9aaae7898dac0aa555503cc0a649494d0d60e7651d" +checksum = "6489ca9bd760fe9642d7644e827b0c9add07df89857b0416ee15c1cc1a3b8c5a" dependencies = [ - "cc", - "pkg-config", - "vcpkg", + "zlib-rs 0.5.0", ] [[package]] @@ -2631,37 +2661,27 @@ dependencies = [ "winapi", ] -[[package]] -name = "malachite" -version = "0.4.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fbdf9cb251732db30a7200ebb6ae5d22fe8e11397364416617d2c2cf0c51cb5" -dependencies = [ - "malachite-base", - "malachite-nz", - "malachite-q", -] - [[package]] name = "malachite-base" -version = "0.4.22" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ea0ed76adf7defc1a92240b5c36d5368cfe9251640dcce5bd2d0b7c1fd87aeb" +checksum = "5063891d2cec8fd20cabccbd3fc277bf8d5666f481fb3f79d999559b39a62713" dependencies = [ - "hashbrown 0.14.5", - "itertools", + "hashbrown 0.15.2", + "itertools 0.11.0", "libm", "ryu", ] [[package]] name = "malachite-bigint" -version = "0.2.3" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d149aaa2965d70381709d9df4c7ee1fc0de1c614a4efc2ee356f5e43d68749f8" +checksum = "7b1b1fec8b370139968919a5b77071c94b282eaba3da1cf179ae5299060d4e75" dependencies = [ "derive_more 1.0.0", - "malachite", + "malachite-base", + "malachite-nz", "num-integer", "num-traits", "paste", @@ -2669,22 +2689,22 @@ dependencies = [ [[package]] name = "malachite-nz" -version = "0.4.22" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34a79feebb2bc9aa7762047c8e5495269a367da6b5a90a99882a0aeeac1841f7" +checksum = "175263cd5b846c552b9afb9d4b03ca465b4ad10717d789cad7dac24441c4fcbe" dependencies = [ - "itertools", + "itertools 0.11.0", "libm", "malachite-base", ] [[package]] name = "malachite-q" -version = "0.4.22" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50f235d5747b1256b47620f5640c2a17a88c7569eebdf27cd9cb130e1a619191" +checksum = "5261ba8feb1ad20cddab3d625af28206c663c08014b2e5c5f9bd54b0f230234f" dependencies = [ - "itertools", + "itertools 0.11.0", "malachite-base", "malachite-nz", ] @@ -2788,11 +2808,11 @@ dependencies = [ [[package]] name = "mt19937" -version = "2.0.1" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12ca7f22ed370d5991a9caec16a83187e865bc8a532f889670337d5a5689e3a1" +checksum = "df7151a832e54d2d6b2c827a20e5bcdd80359281cd2c354e725d4b82e7c471de" dependencies = [ - "rand_core 0.6.4", + "rand_core 0.9.3", ] [[package]] @@ -2878,18 +2898,6 @@ dependencies = [ "smallvec", ] -[[package]] -name = "nix" -version = "0.27.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" -dependencies = [ - "bitflags 2.9.0", - "cfg-if", - "libc", - "memoffset", -] - [[package]] name = "nix" version = "0.28.0" @@ -2979,7 +2987,7 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" dependencies = [ - "proc-macro-crate 3.3.0", + "proc-macro-crate 1.3.1", "proc-macro2", "quote", "syn 2.0.100", @@ -3274,6 +3282,15 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" +[[package]] +name = "openssl-src" +version = "300.5.0+3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8ce546f549326b0e6052b649198487d91320875da901e7bd11a06d1ee3f9c2f" +dependencies = [ + "cc", +] + [[package]] name = "openssl-sys" version = "0.9.107" @@ -3282,6 +3299,7 @@ checksum = "8288979acd84749c744a9014b4382d42b8f7b2592847b5afb2ed29e5d16ede07" dependencies = [ "cc", "libc", + "openssl-src", "pkg-config", "vcpkg", ] @@ -3326,9 +3344,9 @@ checksum = "1036865bb9422d3300cf723f657c2851d0e9ab12567854b1f4eba3d77decf564" [[package]] name = "page_size" -version = "0.4.2" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eebde548fbbf1ea81a99b128872779c437752fb99f217c45245e1a61dcd9edcd" +checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da" dependencies = [ "libc", "winapi", @@ -3417,6 +3435,7 @@ dependencies = [ "ollama-rs", "owo-colors", "rayon", + "rustpython-pylib", "rustpython-stdlib", "rustpython-vm", "schemars", @@ -3631,13 +3650,13 @@ dependencies = [ [[package]] name = "pmutil" -version = "0.5.3" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3894e5d549cccbe44afecf72922f277f603cd4bb0219c8342631ef18fffbe004" +checksum = "52a40bc70c2c58040d2d8b167ba9a5ff59fc9dab7ad44771cfde3dcfde7a09c6" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.100", ] [[package]] @@ -3781,9 +3800,13 @@ dependencies = [ [[package]] name = "puruspe" -version = "0.2.5" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3804877ffeba468c806c2ad9057bbbae92e4b2c410c2f108baaa0042f241fa4c" +checksum = "d76c522e44709f541a403db419a7e34d6fbbc8e6b208589ae29a030cddeefd96" +dependencies = [ + "lambert_w", + "num-complex", +] [[package]] name = "quick-xml" @@ -3850,17 +3873,6 @@ dependencies = [ "rand_core 0.6.4", ] -[[package]] -name = "rand" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" -dependencies = [ - "rand_chacha 0.9.0", - "rand_core 0.9.3", - "zerocopy 0.8.24", -] - [[package]] name = "rand_chacha" version = "0.2.2" @@ -3881,16 +3893,6 @@ dependencies = [ "rand_core 0.6.4", ] -[[package]] -name = "rand_chacha" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" -dependencies = [ - "ppv-lite86", - "rand_core 0.9.3", -] - [[package]] name = "rand_core" version = "0.5.1" @@ -3990,16 +3992,10 @@ checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.9", + "regex-automata", "regex-syntax", ] -[[package]] -name = "regex-automata" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" - [[package]] name = "regex-automata" version = "0.4.9" @@ -4061,26 +4057,86 @@ dependencies = [ [[package]] name = "result-like" -version = "0.4.6" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccc7ce6435c33898517a30e85578cd204cbb696875efb93dec19a2d31294f810" +checksum = "abf7172fef6a7d056b5c26bf6c826570267562d51697f4982ff3ba4aec68a9df" dependencies = [ "result-like-derive", ] [[package]] name = "result-like-derive" -version = "0.4.6" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fabf0a2e54f711c68c50d49f648a1a8a37adcb57353f518ac4df374f0788f42" +checksum = "a8d6574c02e894d66370cfc681e5d68fedbc9a548fb55b30a96b3f0ae22d0fe5" dependencies = [ "pmutil", "proc-macro2", "quote", - "syn 1.0.109", - "syn-ext", + "syn 2.0.100", ] +[[package]] +name = "ruff_python_ast" +version = "0.0.0" +source = "git+https://github.com/astral-sh/ruff.git?tag=0.11.0#2cd25ef6410fb5fca96af1578728a3d828d2d53a" +dependencies = [ + "aho-corasick", + "bitflags 2.9.0", + "compact_str", + "is-macro", + "itertools 0.14.0", + "memchr", + "ruff_python_trivia", + "ruff_source_file", + "ruff_text_size", + "rustc-hash", +] + +[[package]] +name = "ruff_python_parser" +version = "0.0.0" +source = "git+https://github.com/astral-sh/ruff.git?tag=0.11.0#2cd25ef6410fb5fca96af1578728a3d828d2d53a" +dependencies = [ + "bitflags 2.9.0", + "bstr", + "compact_str", + "memchr", + "ruff_python_ast", + "ruff_python_trivia", + "ruff_text_size", + "rustc-hash", + "static_assertions", + "unicode-ident", + "unicode-normalization", + "unicode_names2", +] + +[[package]] +name = "ruff_python_trivia" +version = "0.0.0" +source = "git+https://github.com/astral-sh/ruff.git?tag=0.11.0#2cd25ef6410fb5fca96af1578728a3d828d2d53a" +dependencies = [ + "itertools 0.14.0", + "ruff_source_file", + "ruff_text_size", + "unicode-ident", +] + +[[package]] +name = "ruff_source_file" +version = "0.0.0" +source = "git+https://github.com/astral-sh/ruff.git?tag=0.11.0#2cd25ef6410fb5fca96af1578728a3d828d2d53a" +dependencies = [ + "memchr", + "ruff_text_size", +] + +[[package]] +name = "ruff_text_size" +version = "0.0.0" +source = "git+https://github.com/astral-sh/ruff.git?tag=0.11.0#2cd25ef6410fb5fca96af1578728a3d828d2d53a" + [[package]] name = "rustc-demangle" version = "0.1.24" @@ -4089,9 +4145,9 @@ checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustc-hash" -version = "1.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" [[package]] name = "rustc_version" @@ -4143,116 +4199,121 @@ version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" -[[package]] -name = "rustpython-ast" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cdaf8ee5c1473b993b398c174641d3aa9da847af36e8d5eb8291930b72f31a5" -dependencies = [ - "is-macro", - "malachite-bigint", - "rustpython-literal", - "rustpython-parser-core", - "static_assertions", -] - [[package]] name = "rustpython-codegen" version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f101783403a69155ba7b52d8365d796c772a0bfca7df0a5f16d267f3443986" +source = "git+https://github.com/RustPython/RustPython.git#a917da3b1a0b025f4d82708eb11e802377128dd0" dependencies = [ "ahash", "bitflags 2.9.0", "indexmap 2.9.0", - "itertools", + "itertools 0.14.0", "log", + "malachite-bigint", + "memchr", "num-complex", "num-traits", - "rustpython-ast", + "ruff_python_ast", + "ruff_source_file", + "ruff_text_size", "rustpython-compiler-core", - "rustpython-parser-core", + "rustpython-compiler-source", + "rustpython-literal", + "rustpython-wtf8", + "thiserror 2.0.12", + "unicode_names2", ] [[package]] name = "rustpython-common" version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d22a5c520662f0ff98d717e2c4e52d8ba35eb1d99ee771dbdba7f09908b75bbb" +source = "git+https://github.com/RustPython/RustPython.git#a917da3b1a0b025f4d82708eb11e802377128dd0" dependencies = [ "ascii", "bitflags 2.9.0", "bstr", "cfg-if", - "itertools", + "getrandom 0.3.2", + "itertools 0.14.0", "libc", "lock_api", "malachite-base", "malachite-bigint", "malachite-q", - "num-complex", + "memchr", "num-traits", "once_cell", "radium", - "rand 0.8.5", - "rustpython-format", - "siphasher 0.3.11", + "rustpython-literal", + "rustpython-wtf8", + "siphasher 1.0.1", + "unicode_names2", "volatile", "widestring", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "rustpython-compiler" version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1c0ad9d5b948970d41b113cfc9ffe2337b10fdd41e0c92a859b9ed33c218efe" +source = "git+https://github.com/RustPython/RustPython.git#a917da3b1a0b025f4d82708eb11e802377128dd0" dependencies = [ + "ruff_python_ast", + "ruff_python_parser", + "ruff_source_file", + "ruff_text_size", "rustpython-codegen", "rustpython-compiler-core", - "rustpython-parser", + "rustpython-compiler-source", + "thiserror 2.0.12", ] [[package]] name = "rustpython-compiler-core" version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bd4e0c9fb7b3c70eb27b38d533edc0aa4875ea38cb06e12d76e234d00ef9766" +source = "git+https://github.com/RustPython/RustPython.git#a917da3b1a0b025f4d82708eb11e802377128dd0" dependencies = [ "bitflags 2.9.0", - "itertools", + "itertools 0.14.0", "lz4_flex", "malachite-bigint", "num-complex", - "rustpython-parser-core", + "ruff_source_file", + "rustpython-wtf8", +] + +[[package]] +name = "rustpython-compiler-source" +version = "0.4.0" +source = "git+https://github.com/RustPython/RustPython.git#a917da3b1a0b025f4d82708eb11e802377128dd0" +dependencies = [ + "ruff_source_file", + "ruff_text_size", ] [[package]] name = "rustpython-derive" version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71c39620497116ce2996bcc679f9be4f47c1e8915c7ff9a9f0324e9584280660" +source = "git+https://github.com/RustPython/RustPython.git#a917da3b1a0b025f4d82708eb11e802377128dd0" dependencies = [ + "proc-macro2", "rustpython-compiler", "rustpython-derive-impl", - "syn 1.0.109", + "syn 2.0.100", ] [[package]] name = "rustpython-derive-impl" version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd18fa95c71a08ecc9cce739a608f5bff38805963152e671b66f5f266f0e58d" +source = "git+https://github.com/RustPython/RustPython.git#a917da3b1a0b025f4d82708eb11e802377128dd0" dependencies = [ - "itertools", + "itertools 0.14.0", "maplit", - "once_cell", "proc-macro2", "quote", "rustpython-compiler-core", "rustpython-doc", - "rustpython-parser-core", - "syn 1.0.109", + "syn 2.0.100", "syn-ext", "textwrap", ] @@ -4260,104 +4321,54 @@ dependencies = [ [[package]] name = "rustpython-doc" version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885d19895d9d29656a8a2b33e967a482b92f3d891b4fd923e40849714051bcd" +source = "git+https://github.com/RustPython/__doc__?tag=0.3.0#8b62ce5d796d68a091969c9fa5406276cb483f79" dependencies = [ "once_cell", ] -[[package]] -name = "rustpython-format" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0389039b132ad8e350552d771270ccd03186985696764bcee2239694e7839942" -dependencies = [ - "bitflags 2.9.0", - "itertools", - "malachite-bigint", - "num-traits", - "rustpython-literal", -] - [[package]] name = "rustpython-literal" version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8304be3cae00232a1721a911033e55877ca3810215f66798e964a2d8d22281d" +source = "git+https://github.com/RustPython/RustPython.git#a917da3b1a0b025f4d82708eb11e802377128dd0" dependencies = [ "hexf-parse", "is-macro", "lexical-parse-float", "num-traits", + "rustpython-wtf8", "unic-ucd-category", ] [[package]] -name = "rustpython-parser" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "868f724daac0caf9bd36d38caf45819905193a901e8f1c983345a68e18fb2abb" -dependencies = [ - "anyhow", - "is-macro", - "itertools", - "lalrpop-util", - "log", - "malachite-bigint", - "num-traits", - "phf 0.11.3", - "phf_codegen 0.11.3", - "rustc-hash", - "rustpython-ast", - "rustpython-parser-core", - "tiny-keccak", - "unic-emoji-char", - "unic-ucd-ident", - "unicode_names2", -] - -[[package]] -name = "rustpython-parser-core" +name = "rustpython-pylib" version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4b6c12fa273825edc7bccd9a734f0ad5ba4b8a2f4da5ff7efe946f066d0f4ad" +source = "git+https://github.com/RustPython/RustPython.git#a917da3b1a0b025f4d82708eb11e802377128dd0" dependencies = [ - "is-macro", - "memchr", - "rustpython-parser-vendored", -] - -[[package]] -name = "rustpython-parser-vendored" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04fcea49a4630a3a5d940f4d514dc4f575ed63c14c3e3ed07146634aed7f67a6" -dependencies = [ - "memchr", - "once_cell", + "glob", + "rustpython-compiler-core", + "rustpython-derive", ] [[package]] name = "rustpython-sre_engine" version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39367be5d48e1e5caaa146904ea8d35fe43928168fbeb5c1ab295a0031b179c6" +source = "git+https://github.com/RustPython/RustPython.git#a917da3b1a0b025f4d82708eb11e802377128dd0" dependencies = [ "bitflags 2.9.0", "num_enum", "optional", + "rustpython-wtf8", ] [[package]] name = "rustpython-stdlib" version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "014cb19d897ca26566dd91104f446c6c396091d99561aa32fcc1e461000192b4" +source = "git+https://github.com/RustPython/RustPython.git#a917da3b1a0b025f4d82708eb11e802377128dd0" dependencies = [ "adler32", "ahash", "ascii", - "base64 0.13.1", + "base64 0.22.1", "blake2", "cfg-if", "crc32fast", @@ -4367,32 +4378,34 @@ dependencies = [ "dns-lookup", "dyn-clone", "flate2", + "foreign-types-shared 0.1.1", "gethostname", "hex", "indexmap 2.9.0", - "itertools", + "itertools 0.14.0", "junction", "libc", - "libsqlite3-sys", - "libz-sys", + "libz-rs-sys 0.4.2", "mac_address", "malachite-bigint", "md-5", "memchr", "memmap2", "mt19937", - "nix 0.27.1", + "nix 0.29.0", "num-complex", "num-integer", "num-traits", "num_enum", - "once_cell", + "openssl", + "openssl-probe", + "openssl-sys", "page_size", "parking_lot", "paste", "puruspe", - "rand 0.8.5", - "rand_core 0.6.4", + "rand_core 0.9.3", + "rustix 0.38.44", "rustpython-common", "rustpython-derive", "rustpython-vm", @@ -4403,7 +4416,6 @@ dependencies = [ "socket2", "system-configuration", "termios", - "thread_local", "ucd", "unic-char-property", "unic-normal", @@ -4415,41 +4427,41 @@ dependencies = [ "unicode_names2", "uuid", "widestring", - "winapi", - "windows-sys 0.52.0", + "windows-sys 0.59.0", "xml-rs", ] [[package]] name = "rustpython-vm" version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2878cc4b5679f35fa762891d812ca7e011ae7cd41b5c532eb0ad13959b522493" +source = "git+https://github.com/RustPython/RustPython.git#a917da3b1a0b025f4d82708eb11e802377128dd0" dependencies = [ "ahash", "ascii", - "atty", "bitflags 2.9.0", "bstr", "caseless", "cfg-if", "chrono", "crossbeam-utils", + "errno", "exitcode", - "getrandom 0.2.15", + "getrandom 0.3.2", "glob", "half", "hex", "indexmap 2.9.0", "is-macro", - "itertools", + "itertools 0.14.0", "junction", "libc", + "libffi", + "libloading 0.8.6", "log", "malachite-bigint", "memchr", "memoffset", - "nix 0.27.1", + "nix 0.29.0", "num-complex", "num-integer", "num-traits", @@ -4459,26 +4471,26 @@ dependencies = [ "optional", "parking_lot", "paste", - "rand 0.8.5", "result-like", - "rustc_version", - "rustpython-ast", + "ruff_python_ast", + "ruff_python_parser", + "ruff_source_file", + "ruff_text_size", + "rustix 0.38.44", "rustpython-codegen", "rustpython-common", "rustpython-compiler", "rustpython-compiler-core", + "rustpython-compiler-source", "rustpython-derive", - "rustpython-format", "rustpython-literal", - "rustpython-parser", - "rustpython-parser-core", "rustpython-sre_engine", "rustyline", "schannel", "static_assertions", "strum", "strum_macros", - "thiserror 1.0.69", + "thiserror 2.0.12", "thread_local", "timsort", "uname", @@ -4491,8 +4503,19 @@ dependencies = [ "which", "widestring", "windows 0.52.0", - "windows-sys 0.52.0", - "winreg 0.10.1", + "windows-sys 0.59.0", + "winreg 0.55.0", +] + +[[package]] +name = "rustpython-wtf8" +version = "0.4.0" +source = "git+https://github.com/RustPython/RustPython.git#a917da3b1a0b025f4d82708eb11e802377128dd0" +dependencies = [ + "ascii", + "bstr", + "itertools 0.14.0", + "memchr", ] [[package]] @@ -4503,9 +4526,9 @@ checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" [[package]] name = "rustyline" -version = "14.0.0" +version = "15.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7803e8936da37efd9b6d4478277f4b2b9bb5cdb37a113e8d63222e58da647e63" +checksum = "2ee1e066dc922e513bda599c6ccb5f3bb2b0ea5870a579448f2622993f0a9a2f" dependencies = [ "bitflags 2.9.0", "cfg-if", @@ -4515,12 +4538,12 @@ dependencies = [ "libc", "log", "memchr", - "nix 0.28.0", + "nix 0.29.0", "radix_trie", "unicode-segmentation", - "unicode-width", + "unicode-width 0.2.0", "utf8parse", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -5013,21 +5036,21 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "strum" -version = "0.24.1" +version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" +checksum = "f64def088c51c9510a8579e3c5d67c65349dcf755e5479ad3d010aa6454e2c32" [[package]] name = "strum_macros" -version = "0.24.3" +version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" +checksum = "c77a8c5abcaf0f9ce05d62342b7d298c346515365c36b673df4ebe3ced01fde8" dependencies = [ - "heck 0.4.1", + "heck 0.5.0", "proc-macro2", "quote", "rustversion", - "syn 1.0.109", + "syn 2.0.100", ] [[package]] @@ -5071,11 +5094,13 @@ dependencies = [ [[package]] name = "syn-ext" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b86cb2b68c5b3c078cac02588bc23f3c04bb828c5d3aedd17980876ec6a7be6" +checksum = "b126de4ef6c2a628a68609dd00733766c3b015894698a438ebdf374933fc31d1" dependencies = [ - "syn 1.0.109", + "proc-macro2", + "quote", + "syn 2.0.100", ] [[package]] @@ -5513,9 +5538,9 @@ dependencies = [ [[package]] name = "textwrap" -version = "0.15.2" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7b3e525a49ec206798b40326a44121291b530c963cfb01018f63e135bac543d" +checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057" [[package]] name = "thin-slice" @@ -5610,15 +5635,6 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "639ce8ef6d2ba56be0383a94dd13b92138d58de44c62618303bb798fa92bdc00" -[[package]] -name = "tiny-keccak" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" -dependencies = [ - "crunchy", -] - [[package]] name = "tinystr" version = "0.7.6" @@ -5918,17 +5934,6 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" -[[package]] -name = "unic-emoji-char" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b07221e68897210270a38bde4babb655869637af0f69407f96053a34f76494d" -dependencies = [ - "unic-char-property", - "unic-char-range", - "unic-ucd-version", -] - [[package]] name = "unic-normal" version = "0.9.0" @@ -6046,6 +6051,12 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" +[[package]] +name = "unicode-width" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" + [[package]] name = "unicode-xid" version = "0.2.6" @@ -6142,20 +6153,7 @@ checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9" dependencies = [ "atomic", "getrandom 0.3.2", - "rand 0.9.0", "serde", - "uuid-macro-internal", -] - -[[package]] -name = "uuid-macro-internal" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72dcd78c4f979627a754f5522cea6e6a25e55139056535fe6e69c506cd64a862" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.100", ] [[package]] @@ -6390,7 +6388,7 @@ dependencies = [ "webview2-com-sys", "windows 0.60.0", "windows-core 0.60.1", - "windows-implement 0.59.0", + "windows-implement", "windows-interface", ] @@ -6418,14 +6416,14 @@ dependencies = [ [[package]] name = "which" -version = "4.4.2" +version = "6.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +checksum = "b4ee928febd44d98f2f459a4a79bd4d928591333a494a10a868418ac1b39cf1f" dependencies = [ "either", "home", - "once_cell", "rustix 0.38.44", + "winsafe", ] [[package]] @@ -6527,24 +6525,11 @@ version = "0.60.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca21a92a9cae9bf4ccae5cf8368dce0837100ddf6e6d57936749e85f152f6247" dependencies = [ - "windows-implement 0.59.0", - "windows-interface", - "windows-link", - "windows-result", - "windows-strings 0.3.1", -] - -[[package]] -name = "windows-core" -version = "0.61.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980" -dependencies = [ - "windows-implement 0.60.0", + "windows-implement", "windows-interface", "windows-link", "windows-result", - "windows-strings 0.4.0", + "windows-strings", ] [[package]] @@ -6568,17 +6553,6 @@ dependencies = [ "syn 2.0.100", ] -[[package]] -name = "windows-implement" -version = "0.60.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.100", -] - [[package]] name = "windows-interface" version = "0.59.1" @@ -6613,7 +6587,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3" dependencies = [ "windows-result", - "windows-strings 0.3.1", + "windows-strings", "windows-targets 0.53.0", ] @@ -6635,15 +6609,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-strings" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97" -dependencies = [ - "windows-link", -] - [[package]] name = "windows-sys" version = "0.45.0" @@ -6968,6 +6933,22 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "winreg" +version = "0.55.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb5a765337c50e9ec252c2069be9bf91c7df47afb103b642ba3a53bf8101be97" +dependencies = [ + "cfg-if", + "windows-sys 0.59.0", +] + +[[package]] +name = "winsafe" +version = "0.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" + [[package]] name = "wit-bindgen-rt" version = "0.39.0" @@ -7251,6 +7232,18 @@ dependencies = [ "syn 2.0.100", ] +[[package]] +name = "zlib-rs" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b20717f0917c908dc63de2e44e97f1e6b126ca58d0e391cee86d504eb8fbd05" + +[[package]] +name = "zlib-rs" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "868b928d7949e09af2f6086dfc1e01936064cc7a819253bce650d4e2a2d63ba8" + [[package]] name = "zvariant" version = "5.4.0" diff --git a/pdl-live-react/src-tauri/Cargo.toml b/pdl-live-react/src-tauri/Cargo.toml index 6b56c1978..59b16c598 100644 --- a/pdl-live-react/src-tauri/Cargo.toml +++ b/pdl-live-react/src-tauri/Cargo.toml @@ -38,12 +38,13 @@ minijinja = { version = "2.9.0", features = ["custom_syntax"] } #ollama-rs = { version = "0.3.0", features = ["stream"] } ollama-rs = { git = "https://github.com/starpit/ollama-rs.git", branch = "tools-pub-7", features = ["stream"] } owo-colors = "4.2.0" -rustpython-vm = "0.4.0" +rustpython-vm = { git="https://github.com/RustPython/RustPython.git" } # "0.4.0" async-recursion = "1.1.1" tokio-stream = "0.1.17" tokio = { version = "1.44.1", features = ["io-std"] } indexmap = { version = "2.9.0", features = ["serde"] } -rustpython-stdlib = { version = "0.4.0", features = ["zlib"] } +rustpython-stdlib = { git="https://github.com/RustPython/RustPython.git", features = ["ssl-vendor"] } # 0.4.0 +rustpython-pylib = { git="https://github.com/RustPython/RustPython.git", features = ["freeze-stdlib"] } # 0.4.0 schemars = "0.8.22" fs4 = "0.13.1" derive_builder = "0.20.2" diff --git a/pdl-live-react/src-tauri/src/pdl/interpreter.rs b/pdl-live-react/src-tauri/src/pdl/interpreter.rs index 2402bd4a7..7296faa69 100644 --- a/pdl-live-react/src-tauri/src/pdl/interpreter.rs +++ b/pdl-live-react/src-tauri/src/pdl/interpreter.rs @@ -760,7 +760,8 @@ impl<'a> Interpreter<'a> { ) -> BodyInterpretation { use rustpython_vm as vm; let interp = vm::Interpreter::with_init(vm::Settings::default(), |vm| { - vm.add_native_modules(rustpython_stdlib::get_module_inits()); + //vm.add_native_modules(rustpython_stdlib::get_module_inits()); + vm.add_frozen(rustpython_pylib::FROZEN_STDLIB); }); interp.enter(|vm| -> BodyInterpretation { let scope = vm.new_scope_with_builtins(); diff --git a/pdl-live-react/src-tauri/tests/cli/code-python.pdl b/pdl-live-react/src-tauri/tests/cli/code-python.pdl index 674e60aff..576387964 100644 --- a/pdl-live-react/src-tauri/tests/cli/code-python.pdl +++ b/pdl-live-react/src-tauri/tests/cli/code-python.pdl @@ -1,2 +1,5 @@ lang: python -code: 'print(''hi ho''); result = {"foo": 3}' +code: | + import os + print(f"hi ho {os.getcwd()}") + result = {"foo": 3} From b96056b50fe949d55abc0cea3d4fbf8064c9c4df Mon Sep 17 00:00:00 2001 From: Mandana Vaziri Date: Tue, 15 Apr 2025 14:31:01 -0400 Subject: [PATCH 35/39] skeleton-of-thought example (#919) Signed-off-by: Mandana Vaziri --- examples/skeleton-of-thought/tips.pdl | 89 ++++++++++++++++++++++++ examples/skeleton-of-thought/topic.jsonl | 50 +++++++++++++ 2 files changed, 139 insertions(+) create mode 100644 examples/skeleton-of-thought/tips.pdl create mode 100644 examples/skeleton-of-thought/topic.jsonl diff --git a/examples/skeleton-of-thought/tips.pdl b/examples/skeleton-of-thought/tips.pdl new file mode 100644 index 000000000..e3e9e367d --- /dev/null +++ b/examples/skeleton-of-thought/tips.pdl @@ -0,0 +1,89 @@ +description: Tip suggestion program illustration skeleton-of-thought +defs: + MAX_ITERATIONS: 50 + topics: + read: topic.jsonl + parser: jsonl + + expand_tip: + function: + topic: str + tip: str + return: + lastOf: + - | + Please expand a tip for a topic into a detailed paragraph. + Topic: staying healthy + Tip: Regular Exercise + Paragraph: Incorporate physical activity into your daily routine. This doesn't necessarily mean intense gym workouts; it can be as simple as walking, cycling, or yoga. Regular exercise helps in maintaining a healthy weight, improves cardiovascular health, boosts mental health, and can enhance cognitive function, which is crucial for fields that require intense intellectual engagement. + + Topic: building a campfire + Tip: Choose the Right Location + Paragraph: Always build your campfire in a safe spot. This means selecting a location that's away from trees, bushes, and other flammable materials. Ideally, use a fire ring if available. If you're building a fire pit, it should be on bare soil or on a bed of stones, not on grass or near roots which can catch fire underground. Make sure the area above is clear of low-hanging branches. + + Topic: writing a blog post + Tip: structure your content effectively + Paragraph: A well-structured post is easier to read and more enjoyable. Start with an engaging introduction that hooks the reader and clearly states the purpose of your post. Use headings and subheadings to break up the text and guide readers through your content. Bullet points and numbered lists can make information more digestible. Ensure each paragraph flows logically into the next, and conclude with a summary or call-to-action that encourages reader engagement. + + Topic: ${ topic } + Tip: ${ tip } + Paragraph: + + - model: ollama_chat/granite3.2:8b + parameters: + temperature: 0 + max_tokens: 128 + stop: [".\n\n"] + + suggest_tips: + function: + topic: str + number: int + return: + lastOf: + - "Please act as a helpful assistant. Your job is to provide users with useful tips on a specific topic.\n" + - "Give ${ number } tips for ${ topic }, each under 8 words. Do not use quotes. Make sure there are ${ number } tips.\n" + - model: ollama_chat/granite3.2:8b + parameters: + temperature: 0 + +text: +- for: + topic: ${ topics } + repeat: + call: ${ suggest_tips } + args: + topic: ${ topic.topic } + number: ${ topic.number } + pdl_context: [] + join: + as: array + max_iterations: ${ MAX_ITERATIONS } + def: skeletons +- "\n\n" +- for: + skeleton: ${ skeletons } + repeat: + lang: python + code: | + skel = """ ${skeleton} """ + result = skel.split("\n") + join: + as: array + def: skeletons_array +- for: + skeleton: ${ skeletons_array } + topic: ${ topics[:MAX_ITERATIONS] } + repeat: + for: + tip: ${ skeleton } + repeat: + call: ${ expand_tip } + args: + topic: ${ topic.topic } + tip: ${ tip } + pdl_context: [] + join: + as: array + + \ No newline at end of file diff --git a/examples/skeleton-of-thought/topic.jsonl b/examples/skeleton-of-thought/topic.jsonl new file mode 100644 index 000000000..c8ac6d12e --- /dev/null +++ b/examples/skeleton-of-thought/topic.jsonl @@ -0,0 +1,50 @@ +{"topic": "organizing a successful charity event", "number": 6} +{"topic": "improving personal credit scores", "number": 7} +{"topic": "staying motivated during job searches", "number": 5} +{"topic": "maintaining a work-life balance", "number": 9} +{"topic": "reducing carbon footprint at home", "number": 8} +{"topic": "starting a book club", "number": 5} +{"topic": "learning to play a musical instrument", "number": 7} +{"topic": "getting into freelance writing", "number": 6} +{"topic": "beginner yoga poses", "number": 8} +{"topic": "preparing for graduate school exams", "number": 5} +{"topic": "exploring minimalist living", "number": 9} +{"topic": "effective grocery shopping", "number": 7} +{"topic": "winter camping", "number": 5} +{"topic": "starting a podcast on a budget", "number": 8} +{"topic": "creating a capsule wardrobe", "number": 6} +{"topic": "improving your writing skills", "number": 7} +{"topic": "learning a new software quickly", "number": 9} +{"topic": "reducing anxiety before public speaking", "number": 5} +{"topic": "planning a solo travel adventure", "number": 8} +{"topic": "beginner skateboarders", "number": 6} +{"topic": "studying abroad", "number": 7} +{"topic": "planting a vegetable garden", "number": 5} +{"topic": "adopting a shelter pet", "number": 9} +{"topic": "learning to cook ethnic cuisines", "number": 8} +{"topic": "effective conflict resolution", "number": 5} +{"topic": "starting a vlog", "number": 7} +{"topic": "keeping a daily journal", "number": 6} +{"topic": "improving sleep hygiene", "number": 8} +{"topic": "beginner mountain climbers", "number": 5} +{"topic": "creating a mobile app", "number": 9} +{"topic": "maintaining a saltwater aquarium", "number": 7} +{"topic": "preparing for a baby's arrival", "number": 6} +{"topic": "writing a fantasy novel", "number": 5} +{"topic": "effective team leadership", "number": 8} +{"topic": "making a documentary film", "number": 9} +{"topic": "learning about historical events", "number": 7} +{"topic": "baking gluten-free treats", "number": 6} +{"topic": "improving mental arithmetic skills", "number": 5} +{"topic": "building a treehouse", "number": 8} +{"topic": "getting started with watercolor painting", "number": 9} +{"topic": "creating a YouTube tutorial series", "number": 7} +{"topic": "landscape photography", "number": 5} +{"topic": "navigating cultural differences", "number": 6} +{"topic": "preparing for a marathon", "number": 8} +{"topic": "building an online business", "number": 9} +{"topic": "learning to dance at home", "number": 5} +{"topic": "self-publishing a book", "number": 7} +{"topic": "starting an urban farm", "number": 6} +{"topic": "improving your memory", "number": 8} +{"topic": "creating a personal brand online", "number": 9} From 5e516bf7625868ac6e963bcfe46f62ed3b18208e Mon Sep 17 00:00:00 2001 From: Mandana Vaziri Date: Wed, 16 Apr 2025 08:28:17 -0400 Subject: [PATCH 36/39] Bump litellm and openai versions (#920) Signed-off-by: Mandana Vaziri --- examples/rag/rag.py | 2 +- examples/rag/rag_library1.pdl | 12 ++++++------ pyproject.toml | 4 ++-- tests/test_ast_utils.py | 4 ++-- tests/test_examples_run.py | 5 +++++ 5 files changed, 16 insertions(+), 11 deletions(-) diff --git a/examples/rag/rag.py b/examples/rag/rag.py index 0e81fae89..d4f89a067 100644 --- a/examples/rag/rag.py +++ b/examples/rag/rag.py @@ -105,7 +105,7 @@ def rag_retrieve( # Typically litellm.exceptions.APIConnectionError return f"Unexpected {type(be)}: be={be}" - data = response.data[0]["embedding"] + data = response.data[0]["embedding"] # type: ignore milvus_client = get_or_create_client(database_name) search_res = milvus_client.search( diff --git a/examples/rag/rag_library1.pdl b/examples/rag/rag_library1.pdl index 5b5eaca0b..3e81fd224 100644 --- a/examples/rag/rag_library1.pdl +++ b/examples/rag/rag_library1.pdl @@ -10,8 +10,8 @@ text: return: lang: python code: | - from examples.rag.rag import parse - result = parse(filename, chunk_size, chunk_overlap) + import rag + result = rag.parse(filename, chunk_size, chunk_overlap) - def: rag_index function: inp: list # This is a list[str], but PDL doesn't allow that type @@ -22,8 +22,8 @@ text: return: lang: python code: | - from examples.rag.rag import rag_index - result = rag_index(inp, encoder_model, embed_dimension, database_name, collection_name) + import rag + result = rag.rag_index(inp, encoder_model, embed_dimension, database_name, collection_name) - def: rag_retrieve function: inp: str @@ -34,5 +34,5 @@ text: return: lang: python code: | - from examples.rag.rag import rag_retrieve - result = rag_retrieve(inp, encoder_model, limit, database_name, collection_name) + import rag + result = rag.rag_retrieve(inp, encoder_model, limit, database_name, collection_name) diff --git a/pyproject.toml b/pyproject.toml index bad003d1f..8ffe50962 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,8 +12,8 @@ dependencies = [ "jinja2~=3.0", "PyYAML~=6.0", "jsonschema~=4.0", - "litellm>=1.57.3,!=1.59.9,!=1.63.14", - "openai==1.61.0", + "litellm>=1.66.0", + "openai>=1.66.1", "termcolor~=2.0", "ipython>=8,<10", "json-repair~=0.35", diff --git a/tests/test_ast_utils.py b/tests/test_ast_utils.py index 36b6b1796..27c0a0444 100644 --- a/tests/test_ast_utils.py +++ b/tests/test_ast_utils.py @@ -25,11 +25,11 @@ class MapCounter: def __init__(self): self.cpt = 0 - def count(map_self, ast): # pylint: disable=no-self-argument + def count(map_self, ast): # pylint: disable=no-self-argument # type: ignore map_self.cpt += 1 class C(MappedFunctions): - def f_block(_, block): # pylint: disable=no-self-argument + def f_block(_, block): # pylint: disable=no-self-argument # type: ignore return map_self.count(block) _ = map_block_children(C(), ast) diff --git a/tests/test_examples_run.py b/tests/test_examples_run.py index d3e08689a..326d291d5 100644 --- a/tests/test_examples_run.py +++ b/tests/test_examples_run.py @@ -46,6 +46,11 @@ pathlib.Path("pdl-live-react") / "demos" / "error.pdl", pathlib.Path("pdl-live-react") / "demos" / "demo1.pdl", pathlib.Path("pdl-live-react") / "demos" / "demo2.pdl", + pathlib.Path("pdl-live-react") + / "src-tauri" + / "tests" + / "cli" + / "read-stdin.pdl", # For now, skip the granite-io examples pathlib.Path("examples") / "granite-io" / "granite_io_hallucinations.pdl", pathlib.Path("examples") / "granite-io" / "granite_io_openai.pdl", From 4381488f1f009022eec02da9304db5e5792358cb Mon Sep 17 00:00:00 2001 From: Nick Mitchell Date: Wed, 16 Apr 2025 10:06:31 -0400 Subject: [PATCH 37/39] fix: improve support for rust interpreter python imports from venv Signed-off-by: Nick Mitchell --- .github/workflows/rust-interpreter.yml | 6 +- pdl-live-react/src-tauri/Cargo.lock | 3 +- pdl-live-react/src-tauri/Cargo.toml | 2 +- .../src-tauri/src/pdl/interpreter.rs | 80 ++++++++++++++++--- .../src-tauri/src/pdl/interpreter_tests.rs | 39 +++++++++ .../src-tauri/tests/cli/code-python.pdl | 11 ++- 6 files changed, 122 insertions(+), 19 deletions(-) diff --git a/.github/workflows/rust-interpreter.yml b/.github/workflows/rust-interpreter.yml index 18c0de249..2d05d439d 100644 --- a/.github/workflows/rust-interpreter.yml +++ b/.github/workflows/rust-interpreter.yml @@ -31,4 +31,8 @@ jobs: (curl -fsSL https://ollama.com/install.sh | sudo -E sh && sleep 2) wait - name: Run interpreter tests - run: npm run test:interpreter + run: | + python3.12 -mvenv venv + source venv/bin/activate + pip install nested-diff + npm run test:interpreter diff --git a/pdl-live-react/src-tauri/Cargo.lock b/pdl-live-react/src-tauri/Cargo.lock index eaf12d281..96b006c6e 100644 --- a/pdl-live-react/src-tauri/Cargo.lock +++ b/pdl-live-react/src-tauri/Cargo.lock @@ -2987,7 +2987,7 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" dependencies = [ - "proc-macro-crate 1.3.1", + "proc-macro-crate 2.0.0", "proc-macro2", "quote", "syn 2.0.100", @@ -4243,6 +4243,7 @@ dependencies = [ "memchr", "num-traits", "once_cell", + "parking_lot", "radium", "rustpython-literal", "rustpython-wtf8", diff --git a/pdl-live-react/src-tauri/Cargo.toml b/pdl-live-react/src-tauri/Cargo.toml index 59b16c598..c9574bd05 100644 --- a/pdl-live-react/src-tauri/Cargo.toml +++ b/pdl-live-react/src-tauri/Cargo.toml @@ -38,7 +38,7 @@ minijinja = { version = "2.9.0", features = ["custom_syntax"] } #ollama-rs = { version = "0.3.0", features = ["stream"] } ollama-rs = { git = "https://github.com/starpit/ollama-rs.git", branch = "tools-pub-7", features = ["stream"] } owo-colors = "4.2.0" -rustpython-vm = { git="https://github.com/RustPython/RustPython.git" } # "0.4.0" +rustpython-vm = { git="https://github.com/RustPython/RustPython.git", features= ["importlib", "threading", "encodings"] } # "0.4.0" async-recursion = "1.1.1" tokio-stream = "0.1.17" tokio = { version = "1.44.1", features = ["io-std"] } diff --git a/pdl-live-react/src-tauri/src/pdl/interpreter.rs b/pdl-live-react/src-tauri/src/pdl/interpreter.rs index 7296faa69..81d13cfe6 100644 --- a/pdl-live-react/src-tauri/src/pdl/interpreter.rs +++ b/pdl-live-react/src-tauri/src/pdl/interpreter.rs @@ -759,28 +759,55 @@ impl<'a> Interpreter<'a> { _state: &mut State, ) -> BodyInterpretation { use rustpython_vm as vm; - let interp = vm::Interpreter::with_init(vm::Settings::default(), |vm| { - //vm.add_native_modules(rustpython_stdlib::get_module_inits()); + + let mut settings = rustpython_vm::Settings::default(); + + // add PYTHONPATH to sys.path + settings.path_list.extend(get_paths("PDLPYTHONPATH")); + settings.path_list.extend(get_paths("PYTHONPATH")); + + if let Ok(venv) = ::std::env::var("VIRTUAL_ENV") { + let path = ::std::path::PathBuf::from(venv).join(if cfg!(windows) { + "lib/site-packages" + } else { + // TODO generalize this! + "lib/python3.12/site-packages" + }); + settings = settings.with_path(path.display().to_string()); + } + + let interp = vm::Interpreter::with_init(settings, |vm| { + vm.add_native_modules(rustpython_stdlib::get_module_inits()); vm.add_frozen(rustpython_pylib::FROZEN_STDLIB); }); interp.enter(|vm| -> BodyInterpretation { let scope = vm.new_scope_with_builtins(); - // TODO vm.new_syntax_error(&err, Some(block.code.as_str())) - let code_obj = match vm.compile( - block.code.as_str(), - vm::compiler::Mode::Exec, + // Sigh, this is copy-pasted from RustPython/src/lib.rs + // `run_rustpython` as of 20250416 commit hash + // a917da3b1. Without this (and also: importlib and + // encodings features on rustpython-vm crate), then + // pulling in venvs does not work. + match vm.run_code_string( + vm.new_scope_with_builtins(), + "import sys; sys.path.insert(0, '')", "".to_owned(), ) { - Ok(x) => Ok(x), - Err(exc) => Err(PdlError::from(format!( - "Syntax error in Python code {:?}", - exc - ))), + Ok(_) => Ok(()), + Err(exc) => { + vm.print_exception(exc); + Err(PdlError::from("Error setting up Python site path")) + } }?; + let site_result = vm.import("site", 0); + if site_result.is_err() { + println!( + "Failed to import site, consider adding the Lib directory to your RUSTPYTHONPATH \ + environment variable", + ); + } - // TODO vm.print_exception(exc); - match vm.run_code_obj(code_obj, scope.clone()) { + match vm.run_code_string(scope.clone(), block.code.as_str(), "".to_owned()) { Ok(_) => Ok(()), Err(exc) => { vm.print_exception(exc); @@ -1500,3 +1527,30 @@ pub fn load_scope( Ok(scope) } + +/// Helper function to retrieve a sequence of paths from an environment variable. +fn get_paths(env_variable_name: &str) -> impl Iterator + '_ { + ::std::env::var_os(env_variable_name) + .into_iter() + .flat_map(move |paths| { + split_paths(&paths) + .map(|path| { + path.into_os_string() + .into_string() + .unwrap_or_else(|_| panic!("{env_variable_name} isn't valid unicode")) + }) + .collect::>() + }) +} + +#[cfg(not(target_os = "wasi"))] +pub(crate) use ::std::env::split_paths; +#[cfg(target_os = "wasi")] +pub(crate) fn split_paths + ?Sized>( + s: &T, +) -> impl Iterator + '_ { + use std::os::wasi::ffi::OsStrExt; + let s = s.as_ref().as_bytes(); + s.split(|b| *b == b':') + .map(|x| std::ffi::OsStr::from_bytes(x).to_owned().into()) +} diff --git a/pdl-live-react/src-tauri/src/pdl/interpreter_tests.rs b/pdl-live-react/src-tauri/src/pdl/interpreter_tests.rs index b285016b0..187bf1690 100644 --- a/pdl-live-react/src-tauri/src/pdl/interpreter_tests.rs +++ b/pdl-live-react/src-tauri/src/pdl/interpreter_tests.rs @@ -318,6 +318,45 @@ mod tests { Ok(()) } + #[test] + fn text_python_two_code_result_dict() -> Result<(), Box> { + let program = json!({ + "text": [ + { "lang": "python", + "code":"print('hi ho'); result = {\"foo\": 3}" + }, + { "lang": "python", + "code":"import os; print('hi ho'); result = {\"foo\": 4}" + } + ] + }); + + let (_, messages, _) = run_json(program, streaming(), initial_scope())?; + assert_eq!(messages.len(), 2); + assert_eq!(messages[0].role, MessageRole::User); + assert_eq!(messages[0].content, "{'foo': 3}"); + assert_eq!(messages[1].role, MessageRole::User); + assert_eq!(messages[1].content, "{'foo': 4}"); + Ok(()) + } + + // TODO: illegal instruction, but only during tests + #[test] + fn text_python_code_import_venv() -> Result<(), Box> { + let program = json!({ + "include": "./tests/cli/code-python.pdl" + }); + + let (_, messages, _) = run_json(program, streaming(), initial_scope())?; + assert_eq!(messages.len(), 1); + assert_eq!(messages[0].role, MessageRole::User); + assert_eq!( + messages[0].content, + "{'foo': None, 'diff': {'D': {'two': {'N': 3}}}}" + ); + Ok(()) + } + #[test] fn text_read_file_text() -> Result<(), Box> { let program = json!({ diff --git a/pdl-live-react/src-tauri/tests/cli/code-python.pdl b/pdl-live-react/src-tauri/tests/cli/code-python.pdl index 576387964..70f203120 100644 --- a/pdl-live-react/src-tauri/tests/cli/code-python.pdl +++ b/pdl-live-react/src-tauri/tests/cli/code-python.pdl @@ -1,5 +1,10 @@ lang: python code: | - import os - print(f"hi ho {os.getcwd()}") - result = {"foo": 3} + import sys # test import stdlib + import os # test import stdlib + print(f"!!! {sys.path}") + #import textdistance # test import from venv + from nested_diff import diff # test import from venv + a = {'one': 1, 'two': 2, 'three': 3} + b = {'one': 1, 'two': 3, 'three': 3} + result = {"foo": os.getenv("FOO999999999999999999999999"), "diff": diff(a, b, O=False, U=False)} From 342983427a16c4c26d4eb23e8f2e7a61144e179f Mon Sep 17 00:00:00 2001 From: Nick Mitchell Date: Wed, 16 Apr 2025 16:17:18 -0400 Subject: [PATCH 38/39] chore: bump tauri and npm dependencies And leverage the new prevent overflow feature of tauri. Signed-off-by: Nick Mitchell --- pdl-live-react/package-lock.json | 413 ++++++++++++++-------------- pdl-live-react/src-tauri/Cargo.lock | 190 +++++++++---- pdl-live-react/src-tauri/src/gui.rs | 1 + 3 files changed, 343 insertions(+), 261 deletions(-) diff --git a/pdl-live-react/package-lock.json b/pdl-live-react/package-lock.json index c3cb2ffaa..09b44ea13 100644 --- a/pdl-live-react/package-lock.json +++ b/pdl-live-react/package-lock.json @@ -806,9 +806,9 @@ } }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.5.1.tgz", - "integrity": "sha512-soEIOALTfTK6EjmKMMoLugwaP0rzkad90iIWd1hMO9ARkSAyjfMfkRRhLvD5qH7vvM0Cg72pieUfR6yh6XxC4w==", + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.6.1.tgz", + "integrity": "sha512-KTsJMmobmbrFLe3LDh0PC2FXpcSYJt/MLjlkh/9LEnmKYLSYmT/0EW9JWANjeoemiuZrmogti0tW5Ch+qNUYDw==", "dev": true, "license": "MIT", "dependencies": { @@ -1269,9 +1269,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.39.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.39.0.tgz", - "integrity": "sha512-lGVys55Qb00Wvh8DMAocp5kIcaNzEFTmGhfFd88LfaogYTRKrdxgtlO5H6S49v2Nd8R2C6wLOal0qv6/kCkOwA==", + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.40.0.tgz", + "integrity": "sha512-+Fbls/diZ0RDerhE8kyC6hjADCXA1K4yVNlH0EYfd2XjyH0UGgzaQ8MlT0pCXAThfxv3QUAczHaL+qSv1E4/Cg==", "cpu": [ "arm" ], @@ -1283,9 +1283,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.39.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.39.0.tgz", - "integrity": "sha512-It9+M1zE31KWfqh/0cJLrrsCPiF72PoJjIChLX+rEcujVRCb4NLQ5QzFkzIZW8Kn8FTbvGQBY5TkKBau3S8cCQ==", + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.40.0.tgz", + "integrity": "sha512-PPA6aEEsTPRz+/4xxAmaoWDqh67N7wFbgFUJGMnanCFs0TV99M0M8QhhaSCks+n6EbQoFvLQgYOGXxlMGQe/6w==", "cpu": [ "arm64" ], @@ -1297,9 +1297,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.39.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.39.0.tgz", - "integrity": "sha512-lXQnhpFDOKDXiGxsU9/l8UEGGM65comrQuZ+lDcGUx+9YQ9dKpF3rSEGepyeR5AHZ0b5RgiligsBhWZfSSQh8Q==", + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.40.0.tgz", + "integrity": "sha512-GwYOcOakYHdfnjjKwqpTGgn5a6cUX7+Ra2HeNj/GdXvO2VJOOXCiYYlRFU4CubFM67EhbmzLOmACKEfvp3J1kQ==", "cpu": [ "arm64" ], @@ -1311,9 +1311,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.39.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.39.0.tgz", - "integrity": "sha512-mKXpNZLvtEbgu6WCkNij7CGycdw9cJi2k9v0noMb++Vab12GZjFgUXD69ilAbBh034Zwn95c2PNSz9xM7KYEAQ==", + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.40.0.tgz", + "integrity": "sha512-CoLEGJ+2eheqD9KBSxmma6ld01czS52Iw0e2qMZNpPDlf7Z9mj8xmMemxEucinev4LgHalDPczMyxzbq+Q+EtA==", "cpu": [ "x64" ], @@ -1325,9 +1325,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.39.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.39.0.tgz", - "integrity": "sha512-jivRRlh2Lod/KvDZx2zUR+I4iBfHcu2V/BA2vasUtdtTN2Uk3jfcZczLa81ESHZHPHy4ih3T/W5rPFZ/hX7RtQ==", + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.40.0.tgz", + "integrity": "sha512-r7yGiS4HN/kibvESzmrOB/PxKMhPTlz+FcGvoUIKYoTyGd5toHp48g1uZy1o1xQvybwwpqpe010JrcGG2s5nkg==", "cpu": [ "arm64" ], @@ -1339,9 +1339,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.39.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.39.0.tgz", - "integrity": "sha512-8RXIWvYIRK9nO+bhVz8DwLBepcptw633gv/QT4015CpJ0Ht8punmoHU/DuEd3iw9Hr8UwUV+t+VNNuZIWYeY7Q==", + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.40.0.tgz", + "integrity": "sha512-mVDxzlf0oLzV3oZOr0SMJ0lSDd3xC4CmnWJ8Val8isp9jRGl5Dq//LLDSPFrasS7pSm6m5xAcKaw3sHXhBjoRw==", "cpu": [ "x64" ], @@ -1353,9 +1353,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.39.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.39.0.tgz", - "integrity": "sha512-mz5POx5Zu58f2xAG5RaRRhp3IZDK7zXGk5sdEDj4o96HeaXhlUwmLFzNlc4hCQi5sGdR12VDgEUqVSHer0lI9g==", + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.40.0.tgz", + "integrity": "sha512-y/qUMOpJxBMy8xCXD++jeu8t7kzjlOCkoxxajL58G62PJGBZVl/Gwpm7JK9+YvlB701rcQTzjUZ1JgUoPTnoQA==", "cpu": [ "arm" ], @@ -1367,9 +1367,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.39.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.39.0.tgz", - "integrity": "sha512-+YDwhM6gUAyakl0CD+bMFpdmwIoRDzZYaTWV3SDRBGkMU/VpIBYXXEvkEcTagw/7VVkL2vA29zU4UVy1mP0/Yw==", + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.40.0.tgz", + "integrity": "sha512-GoCsPibtVdJFPv/BOIvBKO/XmwZLwaNWdyD8TKlXuqp0veo2sHE+A/vpMQ5iSArRUz/uaoj4h5S6Pn0+PdhRjg==", "cpu": [ "arm" ], @@ -1381,9 +1381,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.39.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.39.0.tgz", - "integrity": "sha512-EKf7iF7aK36eEChvlgxGnk7pdJfzfQbNvGV/+l98iiMwU23MwvmV0Ty3pJ0p5WQfm3JRHOytSIqD9LB7Bq7xdQ==", + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.40.0.tgz", + "integrity": "sha512-L5ZLphTjjAD9leJzSLI7rr8fNqJMlGDKlazW2tX4IUF9P7R5TMQPElpH82Q7eNIDQnQlAyiNVfRPfP2vM5Avvg==", "cpu": [ "arm64" ], @@ -1395,9 +1395,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.39.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.39.0.tgz", - "integrity": "sha512-vYanR6MtqC7Z2SNr8gzVnzUul09Wi1kZqJaek3KcIlI/wq5Xtq4ZPIZ0Mr/st/sv/NnaPwy/D4yXg5x0B3aUUA==", + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.40.0.tgz", + "integrity": "sha512-ATZvCRGCDtv1Y4gpDIXsS+wfFeFuLwVxyUBSLawjgXK2tRE6fnsQEkE4csQQYWlBlsFztRzCnBvWVfcae/1qxQ==", "cpu": [ "arm64" ], @@ -1409,9 +1409,9 @@ ] }, "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.39.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.39.0.tgz", - "integrity": "sha512-NMRUT40+h0FBa5fb+cpxtZoGAggRem16ocVKIv5gDB5uLDgBIwrIsXlGqYbLwW8YyO3WVTk1FkFDjMETYlDqiw==", + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.40.0.tgz", + "integrity": "sha512-wG9e2XtIhd++QugU5MD9i7OnpaVb08ji3P1y/hNbxrQ3sYEelKJOq1UJ5dXczeo6Hj2rfDEL5GdtkMSVLa/AOg==", "cpu": [ "loong64" ], @@ -1423,9 +1423,9 @@ ] }, "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.39.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.39.0.tgz", - "integrity": "sha512-0pCNnmxgduJ3YRt+D+kJ6Ai/r+TaePu9ZLENl+ZDV/CdVczXl95CbIiwwswu4L+K7uOIGf6tMo2vm8uadRaICQ==", + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.40.0.tgz", + "integrity": "sha512-vgXfWmj0f3jAUvC7TZSU/m/cOE558ILWDzS7jBhiCAFpY2WEBn5jqgbqvmzlMjtp8KlLcBlXVD2mkTSEQE6Ixw==", "cpu": [ "ppc64" ], @@ -1437,9 +1437,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.39.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.39.0.tgz", - "integrity": "sha512-t7j5Zhr7S4bBtksT73bO6c3Qa2AV/HqiGlj9+KB3gNF5upcVkx+HLgxTm8DK4OkzsOYqbdqbLKwvGMhylJCPhQ==", + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.40.0.tgz", + "integrity": "sha512-uJkYTugqtPZBS3Z136arevt/FsKTF/J9dEMTX/cwR7lsAW4bShzI2R0pJVw+hcBTWF4dxVckYh72Hk3/hWNKvA==", "cpu": [ "riscv64" ], @@ -1451,9 +1451,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.39.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.39.0.tgz", - "integrity": "sha512-m6cwI86IvQ7M93MQ2RF5SP8tUjD39Y7rjb1qjHgYh28uAPVU8+k/xYWvxRO3/tBN2pZkSMa5RjnPuUIbrwVxeA==", + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.40.0.tgz", + "integrity": "sha512-rKmSj6EXQRnhSkE22+WvrqOqRtk733x3p5sWpZilhmjnkHkpeCgWsFFo0dGnUGeA+OZjRl3+VYq+HyCOEuwcxQ==", "cpu": [ "riscv64" ], @@ -1465,9 +1465,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.39.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.39.0.tgz", - "integrity": "sha512-iRDJd2ebMunnk2rsSBYlsptCyuINvxUfGwOUldjv5M4tpa93K8tFMeYGpNk2+Nxl+OBJnBzy2/JCscGeO507kA==", + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.40.0.tgz", + "integrity": "sha512-SpnYlAfKPOoVsQqmTFJ0usx0z84bzGOS9anAC0AZ3rdSo3snecihbhFTlJZ8XMwzqAcodjFU4+/SM311dqE5Sw==", "cpu": [ "s390x" ], @@ -1479,9 +1479,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.39.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.39.0.tgz", - "integrity": "sha512-t9jqYw27R6Lx0XKfEFe5vUeEJ5pF3SGIM6gTfONSMb7DuG6z6wfj2yjcoZxHg129veTqU7+wOhY6GX8wmf90dA==", + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.40.0.tgz", + "integrity": "sha512-RcDGMtqF9EFN8i2RYN2W+64CdHruJ5rPqrlYw+cgM3uOVPSsnAQps7cpjXe9be/yDp8UC7VLoCoKC8J3Kn2FkQ==", "cpu": [ "x64" ], @@ -1493,9 +1493,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.39.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.39.0.tgz", - "integrity": "sha512-ThFdkrFDP55AIsIZDKSBWEt/JcWlCzydbZHinZ0F/r1h83qbGeenCt/G/wG2O0reuENDD2tawfAj2s8VK7Bugg==", + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.40.0.tgz", + "integrity": "sha512-HZvjpiUmSNx5zFgwtQAV1GaGazT2RWvqeDi0hV+AtC8unqqDSsaFjPxfsO6qPtKRRg25SisACWnJ37Yio8ttaw==", "cpu": [ "x64" ], @@ -1507,9 +1507,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.39.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.39.0.tgz", - "integrity": "sha512-jDrLm6yUtbOg2TYB3sBF3acUnAwsIksEYjLeHL+TJv9jg+TmTwdyjnDex27jqEMakNKf3RwwPahDIt7QXCSqRQ==", + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.40.0.tgz", + "integrity": "sha512-UtZQQI5k/b8d7d3i9AZmA/t+Q4tk3hOC0tMOMSq2GlMYOfxbesxG4mJSeDp0EHs30N9bsfwUvs3zF4v/RzOeTQ==", "cpu": [ "arm64" ], @@ -1521,9 +1521,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.39.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.39.0.tgz", - "integrity": "sha512-6w9uMuza+LbLCVoNKL5FSLE7yvYkq9laSd09bwS0tMjkwXrmib/4KmoJcrKhLWHvw19mwU+33ndC69T7weNNjQ==", + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.40.0.tgz", + "integrity": "sha512-+m03kvI2f5syIqHXCZLPVYplP8pQch9JHyXKZ3AGMKlg8dCyr2PKHjwRLiW53LTrN/Nc3EqHOKxUxzoSPdKddA==", "cpu": [ "ia32" ], @@ -1535,9 +1535,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.39.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.39.0.tgz", - "integrity": "sha512-yAkUOkIKZlK5dl7u6dg897doBgLXmUHhIINM2c+sND3DZwnrdQkkSiDh7N75Ll4mM4dxSkYfXqU9fW3lLkMFug==", + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.40.0.tgz", + "integrity": "sha512-lpPE1cLfP5oPzVjKMx10pgBmKELQnFJXHgvtHCtuJWOv8MxqdEIMNtgHgBFf7Ea2/7EuVwa9fodWUfXAlXZLZQ==", "cpu": [ "x64" ], @@ -1549,9 +1549,9 @@ ] }, "node_modules/@tauri-apps/api": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.4.1.tgz", - "integrity": "sha512-5sYwZCSJb6PBGbBL4kt7CnE5HHbBqwH+ovmOW6ZVju3nX4E3JX6tt2kRklFEH7xMOIwR0btRkZktuLhKvyEQYg==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.5.0.tgz", + "integrity": "sha512-Ldux4ip+HGAcPUmuLT8EIkk6yafl5vK0P0c0byzAKzxJh7vxelVtdPONjfgTm96PbN24yjZNESY8CKo8qniluA==", "license": "Apache-2.0 OR MIT", "funding": { "type": "opencollective", @@ -1559,9 +1559,9 @@ } }, "node_modules/@tauri-apps/cli": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-2.4.1.tgz", - "integrity": "sha512-9Ta81jx9+57FhtU/mPIckDcOBtPTUdKM75t4+aA0X84b8Sclb0jy1xA8NplmcRzp2fsfIHNngU2NiRxsW5+yOQ==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-2.5.0.tgz", + "integrity": "sha512-rAtHqG0Gh/IWLjN2zTf3nZqYqbo81oMbqop56rGTjrlWk9pTTAjkqOjSL9XQLIMZ3RbeVjveCqqCA0s8RnLdMg==", "dev": true, "license": "Apache-2.0 OR MIT", "bin": { @@ -1575,23 +1575,23 @@ "url": "https://opencollective.com/tauri" }, "optionalDependencies": { - "@tauri-apps/cli-darwin-arm64": "2.4.1", - "@tauri-apps/cli-darwin-x64": "2.4.1", - "@tauri-apps/cli-linux-arm-gnueabihf": "2.4.1", - "@tauri-apps/cli-linux-arm64-gnu": "2.4.1", - "@tauri-apps/cli-linux-arm64-musl": "2.4.1", - "@tauri-apps/cli-linux-riscv64-gnu": "2.4.1", - "@tauri-apps/cli-linux-x64-gnu": "2.4.1", - "@tauri-apps/cli-linux-x64-musl": "2.4.1", - "@tauri-apps/cli-win32-arm64-msvc": "2.4.1", - "@tauri-apps/cli-win32-ia32-msvc": "2.4.1", - "@tauri-apps/cli-win32-x64-msvc": "2.4.1" + "@tauri-apps/cli-darwin-arm64": "2.5.0", + "@tauri-apps/cli-darwin-x64": "2.5.0", + "@tauri-apps/cli-linux-arm-gnueabihf": "2.5.0", + "@tauri-apps/cli-linux-arm64-gnu": "2.5.0", + "@tauri-apps/cli-linux-arm64-musl": "2.5.0", + "@tauri-apps/cli-linux-riscv64-gnu": "2.5.0", + "@tauri-apps/cli-linux-x64-gnu": "2.5.0", + "@tauri-apps/cli-linux-x64-musl": "2.5.0", + "@tauri-apps/cli-win32-arm64-msvc": "2.5.0", + "@tauri-apps/cli-win32-ia32-msvc": "2.5.0", + "@tauri-apps/cli-win32-x64-msvc": "2.5.0" } }, "node_modules/@tauri-apps/cli-darwin-arm64": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-2.4.1.tgz", - "integrity": "sha512-QME7s8XQwy3LWClTVlIlwXVSLKkeJ/z88pr917Mtn9spYOjnBfsgHAgGdmpWD3NfJxjg7CtLbhH49DxoFL+hLg==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-2.5.0.tgz", + "integrity": "sha512-VuVAeTFq86dfpoBDNYAdtQVLbP0+2EKCHIIhkaxjeoPARR0sLpFHz2zs0PcFU76e+KAaxtEtAJAXGNUc8E1PzQ==", "cpu": [ "arm64" ], @@ -1606,9 +1606,9 @@ } }, "node_modules/@tauri-apps/cli-darwin-x64": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-2.4.1.tgz", - "integrity": "sha512-/r89IcW6Ya1sEsFUEH7wLNruDTj7WmDWKGpPy7gATFtQr5JEY4heernqE82isjTUimnHZD8SCr0jA3NceI4ybw==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-2.5.0.tgz", + "integrity": "sha512-hUF01sC06cZVa8+I0/VtsHOk9BbO75rd+YdtHJ48xTdcYaQ5QIwL4yZz9OR1AKBTaUYhBam8UX9Pvd5V2/4Dpw==", "cpu": [ "x64" ], @@ -1623,9 +1623,9 @@ } }, "node_modules/@tauri-apps/cli-linux-arm-gnueabihf": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-2.4.1.tgz", - "integrity": "sha512-9tDijkRB+CchAGjXxYdY9l/XzFpLp1yihUtGXJz9eh+3qIoRI043n3e+6xmU8ZURr7XPnu+R4sCmXs6HD+NCEQ==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-2.5.0.tgz", + "integrity": "sha512-LQKqttsK252LlqYyX8R02MinUsfFcy3+NZiJwHFgi5Y3+ZUIAED9cSxJkyNtuY5KMnR4RlpgWyLv4P6akN1xhg==", "cpu": [ "arm" ], @@ -1640,9 +1640,9 @@ } }, "node_modules/@tauri-apps/cli-linux-arm64-gnu": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-2.4.1.tgz", - "integrity": "sha512-pnFGDEXBAzS4iDYAVxTRhAzNu3K2XPGflYyBc0czfHDBXopqRgMyj5Q9Wj7HAwv6cM8BqzXINxnb2ZJFGmbSgA==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-2.5.0.tgz", + "integrity": "sha512-mTQufsPcpdHg5RW0zypazMo4L55EfeE5snTzrPqbLX4yCK2qalN7+rnP8O8GT06xhp6ElSP/Ku1M2MR297SByQ==", "cpu": [ "arm64" ], @@ -1657,9 +1657,9 @@ } }, "node_modules/@tauri-apps/cli-linux-arm64-musl": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.4.1.tgz", - "integrity": "sha512-Hp0zXgeZNKmT+eoJSCxSBUm2QndNuRxR55tmIeNm3vbyUMJN/49uW7nurZ5fBPsacN4Pzwlx1dIMK+Gnr9A69w==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.5.0.tgz", + "integrity": "sha512-rQO1HhRUQqyEaal5dUVOQruTRda/TD36s9kv1hTxZiFuSq3558lsTjAcUEnMAtBcBkps20sbyTJNMT0AwYIk8Q==", "cpu": [ "arm64" ], @@ -1674,9 +1674,9 @@ } }, "node_modules/@tauri-apps/cli-linux-riscv64-gnu": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-riscv64-gnu/-/cli-linux-riscv64-gnu-2.4.1.tgz", - "integrity": "sha512-3T3bo2E4fdYRvzcXheWUeQOVB+LunEEi92iPRgOyuSVexVE4cmHYl+MPJF+EUV28Et0hIVTsHibmDO0/04lAFg==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-riscv64-gnu/-/cli-linux-riscv64-gnu-2.5.0.tgz", + "integrity": "sha512-7oS18FN46yDxyw1zX/AxhLAd7T3GrLj3Ai6s8hZKd9qFVzrAn36ESL7d3G05s8wEtsJf26qjXnVF4qleS3dYsA==", "cpu": [ "riscv64" ], @@ -1691,9 +1691,9 @@ } }, "node_modules/@tauri-apps/cli-linux-x64-gnu": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-2.4.1.tgz", - "integrity": "sha512-kLN0FdNONO+2i+OpU9+mm6oTGufRC00e197TtwjpC0N6K2K8130w7Q3FeODIM2CMyg0ov3tH+QWqKW7GNhHFzg==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-2.5.0.tgz", + "integrity": "sha512-SG5sFNL7VMmDBdIg3nO3EzNRT306HsiEQ0N90ILe3ZABYAVoPDO/ttpCO37ApLInTzrq/DLN+gOlC/mgZvLw1w==", "cpu": [ "x64" ], @@ -1708,9 +1708,9 @@ } }, "node_modules/@tauri-apps/cli-linux-x64-musl": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-2.4.1.tgz", - "integrity": "sha512-a8exvA5Ub9eg66a6hsMQKJIkf63QAf9OdiuFKOsEnKZkNN2x0NLgfvEcqdw88VY0UMs9dBoZ1AGbWMeYnLrLwQ==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-2.5.0.tgz", + "integrity": "sha512-QXDM8zp/6v05PNWju5ELsVwF0VH1n6b5pk2E6W/jFbbiwz80Vs1lACl9pv5kEHkrxBj+aWU/03JzGuIj2g3SkQ==", "cpu": [ "x64" ], @@ -1725,9 +1725,9 @@ } }, "node_modules/@tauri-apps/cli-win32-arm64-msvc": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-2.4.1.tgz", - "integrity": "sha512-4JFrslsMCJQG1c573T9uqQSAbF3j/tMKkMWzsIssv8jvPiP++OG61A2/F+y9te9/Q/O95cKhDK63kaiO5xQaeg==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-2.5.0.tgz", + "integrity": "sha512-pFSHFK6b+o9y4Un8w0gGLwVyFTZaC3P0kQ7umRt/BLDkzD5RnQ4vBM7CF8BCU5nkwmEBUCZd7Wt3TWZxe41o6Q==", "cpu": [ "arm64" ], @@ -1742,9 +1742,9 @@ } }, "node_modules/@tauri-apps/cli-win32-ia32-msvc": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-2.4.1.tgz", - "integrity": "sha512-9eXfFORehYSCRwxg2KodfmX/mhr50CI7wyBYGbPLePCjr5z0jK/9IyW6r0tC+ZVjwpX48dkk7hKiUgI25jHjzA==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-2.5.0.tgz", + "integrity": "sha512-EArv1IaRlogdLAQyGlKmEqZqm5RfHCUMhJoedWu7GtdbOMUfSAz6FMX2boE1PtEmNO4An+g188flLeVErrxEKg==", "cpu": [ "ia32" ], @@ -1759,9 +1759,9 @@ } }, "node_modules/@tauri-apps/cli-win32-x64-msvc": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-2.4.1.tgz", - "integrity": "sha512-60a4Ov7Jrwqz2hzDltlS7301dhSAmM9dxo+IRBD3xz7yobKrgaHXYpWvnRomYItHcDd51VaKc9292H8/eE/gsw==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-2.5.0.tgz", + "integrity": "sha512-lj43EFYbnAta8pd9JnUq87o+xRUR0odz+4rixBtTUwUgdRdwQ2V9CzFtsMu6FQKpFQ6mujRK6P1IEwhL6ADRsQ==", "cpu": [ "x64" ], @@ -1977,17 +1977,17 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.29.1.tgz", - "integrity": "sha512-ba0rr4Wfvg23vERs3eB+P3lfj2E+2g3lhWcCVukUuhtcdUx5lSIFZlGFEBHKr+3zizDa/TvZTptdNHVZWAkSBg==", + "version": "8.30.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.30.1.tgz", + "integrity": "sha512-v+VWphxMjn+1t48/jO4t950D6KR8JaJuNXzi33Ve6P8sEmPr5k6CEXjdGwT6+LodVnEa91EQCtwjWNUCPweo+Q==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.29.1", - "@typescript-eslint/type-utils": "8.29.1", - "@typescript-eslint/utils": "8.29.1", - "@typescript-eslint/visitor-keys": "8.29.1", + "@typescript-eslint/scope-manager": "8.30.1", + "@typescript-eslint/type-utils": "8.30.1", + "@typescript-eslint/utils": "8.30.1", + "@typescript-eslint/visitor-keys": "8.30.1", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -2007,16 +2007,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.29.1.tgz", - "integrity": "sha512-zczrHVEqEaTwh12gWBIJWj8nx+ayDcCJs06yoNMY0kwjMWDM6+kppljY+BxWI06d2Ja+h4+WdufDcwMnnMEWmg==", + "version": "8.30.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.30.1.tgz", + "integrity": "sha512-H+vqmWwT5xoNrXqWs/fesmssOW70gxFlgcMlYcBaWNPIEWDgLa4W9nkSPmhuOgLnXq9QYgkZ31fhDyLhleCsAg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.29.1", - "@typescript-eslint/types": "8.29.1", - "@typescript-eslint/typescript-estree": "8.29.1", - "@typescript-eslint/visitor-keys": "8.29.1", + "@typescript-eslint/scope-manager": "8.30.1", + "@typescript-eslint/types": "8.30.1", + "@typescript-eslint/typescript-estree": "8.30.1", + "@typescript-eslint/visitor-keys": "8.30.1", "debug": "^4.3.4" }, "engines": { @@ -2032,14 +2032,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.29.1.tgz", - "integrity": "sha512-2nggXGX5F3YrsGN08pw4XpMLO1Rgtnn4AzTegC2MDesv6q3QaTU5yU7IbS1tf1IwCR0Hv/1EFygLn9ms6LIpDA==", + "version": "8.30.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.30.1.tgz", + "integrity": "sha512-+C0B6ChFXZkuaNDl73FJxRYT0G7ufVPOSQkqkpM/U198wUwUFOtgo1k/QzFh1KjpBitaK7R1tgjVz6o9HmsRPg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.29.1", - "@typescript-eslint/visitor-keys": "8.29.1" + "@typescript-eslint/types": "8.30.1", + "@typescript-eslint/visitor-keys": "8.30.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2050,14 +2050,14 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.29.1.tgz", - "integrity": "sha512-DkDUSDwZVCYN71xA4wzySqqcZsHKic53A4BLqmrWFFpOpNSoxX233lwGu/2135ymTCR04PoKiEEEvN1gFYg4Tw==", + "version": "8.30.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.30.1.tgz", + "integrity": "sha512-64uBF76bfQiJyHgZISC7vcNz3adqQKIccVoKubyQcOnNcdJBvYOILV1v22Qhsw3tw3VQu5ll8ND6hycgAR5fEA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.29.1", - "@typescript-eslint/utils": "8.29.1", + "@typescript-eslint/typescript-estree": "8.30.1", + "@typescript-eslint/utils": "8.30.1", "debug": "^4.3.4", "ts-api-utils": "^2.0.1" }, @@ -2074,9 +2074,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.29.1.tgz", - "integrity": "sha512-VT7T1PuJF1hpYC3AGm2rCgJBjHL3nc+A/bhOp9sGMKfi5v0WufsX/sHCFBfNTx2F+zA6qBc/PD0/kLRLjdt8mQ==", + "version": "8.30.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.30.1.tgz", + "integrity": "sha512-81KawPfkuulyWo5QdyG/LOKbspyyiW+p4vpn4bYO7DM/hZImlVnFwrpCTnmNMOt8CvLRr5ojI9nU1Ekpw4RcEw==", "dev": true, "license": "MIT", "engines": { @@ -2088,14 +2088,14 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.29.1.tgz", - "integrity": "sha512-l1enRoSaUkQxOQnbi0KPUtqeZkSiFlqrx9/3ns2rEDhGKfTa+88RmXqedC1zmVTOWrLc2e6DEJrTA51C9iLH5g==", + "version": "8.30.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.30.1.tgz", + "integrity": "sha512-kQQnxymiUy9tTb1F2uep9W6aBiYODgq5EMSk6Nxh4Z+BDUoYUSa029ISs5zTzKBFnexQEh71KqwjKnRz58lusQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.29.1", - "@typescript-eslint/visitor-keys": "8.29.1", + "@typescript-eslint/types": "8.30.1", + "@typescript-eslint/visitor-keys": "8.30.1", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -2154,16 +2154,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.29.1.tgz", - "integrity": "sha512-QAkFEbytSaB8wnmB+DflhUPz6CLbFWE2SnSCrRMEa+KnXIzDYbpsn++1HGvnfAsUY44doDXmvRkO5shlM/3UfA==", + "version": "8.30.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.30.1.tgz", + "integrity": "sha512-T/8q4R9En2tcEsWPQgB5BQ0XJVOtfARcUvOa8yJP3fh9M/mXraLxZrkCfGb6ChrO/V3W+Xbd04RacUEqk1CFEQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.29.1", - "@typescript-eslint/types": "8.29.1", - "@typescript-eslint/typescript-estree": "8.29.1" + "@typescript-eslint/scope-manager": "8.30.1", + "@typescript-eslint/types": "8.30.1", + "@typescript-eslint/typescript-estree": "8.30.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2178,13 +2178,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.29.1.tgz", - "integrity": "sha512-RGLh5CRaUEf02viP5c1Vh1cMGffQscyHe7HPAzGpfmfflFg1wUz2rYxd+OZqwpeypYvZ8UxSxuIpF++fmOzEcg==", + "version": "8.30.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.30.1.tgz", + "integrity": "sha512-aEhgas7aJ6vZnNFC7K4/vMGDGyOiqWcYZPpIWrTKuTAlsvDNKy2GFDqh9smL+iq069ZvR0YzEeq0B8NJlLzjFA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.29.1", + "@typescript-eslint/types": "8.30.1", "eslint-visitor-keys": "^4.2.0" }, "engines": { @@ -2202,17 +2202,17 @@ "license": "ISC" }, "node_modules/@vitejs/plugin-react": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.4.tgz", - "integrity": "sha512-SCCPBJtYLdE8PX/7ZQAs1QAZ8Jqwih+0VBLum1EGqmCCQal+MIUqLCzj3ZUy8ufbC0cAM4LRlSTm7IQJwWT4ug==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.4.0.tgz", + "integrity": "sha512-x/EztcTKVj+TDeANY1WjNeYsvZjZdfWRMP/KXi5Yn8BoTzpa13ZltaQqKfvWYbX8CE10GOHHdC5v86jY9x8i/g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/core": "^7.26.0", + "@babel/core": "^7.26.10", "@babel/plugin-transform-react-jsx-self": "^7.25.9", "@babel/plugin-transform-react-jsx-source": "^7.25.9", "@types/babel__core": "^7.20.5", - "react-refresh": "^0.14.2" + "react-refresh": "^0.17.0" }, "engines": { "node": "^14.18.0 || >=16.0.0" @@ -2463,9 +2463,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001713", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001713.tgz", - "integrity": "sha512-wCIWIg+A4Xr7NfhTuHdX+/FKh3+Op3LBbSp2N5Pfx6T/LhdQy3GTyoTg48BReaW/MyMNZAkTadsBtai3ldWK0Q==", + "version": "1.0.30001714", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001714.tgz", + "integrity": "sha512-mtgapdwDLSSBnCI3JokHM7oEQBLxiJKVRtg10AxM1AyeiKcM96f0Mkbqeq+1AbiCtvMcHRulAAEMu693JrSWqg==", "dev": true, "funding": [ { @@ -2885,9 +2885,9 @@ } }, "node_modules/dotenv": { - "version": "16.4.7", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", - "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", + "version": "16.5.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz", + "integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==", "dev": true, "license": "BSD-2-Clause", "engines": { @@ -2924,9 +2924,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.135", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.135.tgz", - "integrity": "sha512-8gXUdEmvb+WCaYUhA0Svr08uSeRjM2w3x5uHOc1QbaEVzJXB8rgm5eptieXzyKoVEtinLvW6MtTcurA65PeS1Q==", + "version": "1.5.137", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.137.tgz", + "integrity": "sha512-/QSJaU2JyIuTbbABAo/crOs+SuAZLS+fVVS10PVrIT9hrRkmZl8Hb0xPSkKRUUWHQtYzXHpQUW3Dy5hwMzGZkA==", "dev": true, "license": "ISC" }, @@ -5296,9 +5296,9 @@ } }, "node_modules/react-refresh": { - "version": "0.14.2", - "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", - "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", "dev": true, "license": "MIT", "engines": { @@ -5561,9 +5561,9 @@ } }, "node_modules/rollup": { - "version": "4.39.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.39.0.tgz", - "integrity": "sha512-thI8kNc02yNvnmJp8dr3fNWJ9tCONDhp6TV35X6HkKGGs9E6q7YWCHbe5vKiTa7TAiNcFEmXKj3X/pG2b3ci0g==", + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.40.0.tgz", + "integrity": "sha512-Noe455xmA96nnqH5piFtLobsGbCij7Tu+tb3c1vYjNbTkfzGqXqQXG3wJaYXkRZuQ0vEYN4bhwg7QnIrqB5B+w==", "dev": true, "license": "MIT", "dependencies": { @@ -5577,26 +5577,26 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.39.0", - "@rollup/rollup-android-arm64": "4.39.0", - "@rollup/rollup-darwin-arm64": "4.39.0", - "@rollup/rollup-darwin-x64": "4.39.0", - "@rollup/rollup-freebsd-arm64": "4.39.0", - "@rollup/rollup-freebsd-x64": "4.39.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.39.0", - "@rollup/rollup-linux-arm-musleabihf": "4.39.0", - "@rollup/rollup-linux-arm64-gnu": "4.39.0", - "@rollup/rollup-linux-arm64-musl": "4.39.0", - "@rollup/rollup-linux-loongarch64-gnu": "4.39.0", - "@rollup/rollup-linux-powerpc64le-gnu": "4.39.0", - "@rollup/rollup-linux-riscv64-gnu": "4.39.0", - "@rollup/rollup-linux-riscv64-musl": "4.39.0", - "@rollup/rollup-linux-s390x-gnu": "4.39.0", - "@rollup/rollup-linux-x64-gnu": "4.39.0", - "@rollup/rollup-linux-x64-musl": "4.39.0", - "@rollup/rollup-win32-arm64-msvc": "4.39.0", - "@rollup/rollup-win32-ia32-msvc": "4.39.0", - "@rollup/rollup-win32-x64-msvc": "4.39.0", + "@rollup/rollup-android-arm-eabi": "4.40.0", + "@rollup/rollup-android-arm64": "4.40.0", + "@rollup/rollup-darwin-arm64": "4.40.0", + "@rollup/rollup-darwin-x64": "4.40.0", + "@rollup/rollup-freebsd-arm64": "4.40.0", + "@rollup/rollup-freebsd-x64": "4.40.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.40.0", + "@rollup/rollup-linux-arm-musleabihf": "4.40.0", + "@rollup/rollup-linux-arm64-gnu": "4.40.0", + "@rollup/rollup-linux-arm64-musl": "4.40.0", + "@rollup/rollup-linux-loongarch64-gnu": "4.40.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.40.0", + "@rollup/rollup-linux-riscv64-gnu": "4.40.0", + "@rollup/rollup-linux-riscv64-musl": "4.40.0", + "@rollup/rollup-linux-s390x-gnu": "4.40.0", + "@rollup/rollup-linux-x64-gnu": "4.40.0", + "@rollup/rollup-linux-x64-musl": "4.40.0", + "@rollup/rollup-win32-arm64-msvc": "4.40.0", + "@rollup/rollup-win32-ia32-msvc": "4.40.0", + "@rollup/rollup-win32-x64-msvc": "4.40.0", "fsevents": "~2.3.2" } }, @@ -6010,15 +6010,15 @@ } }, "node_modules/typescript-eslint": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.29.1.tgz", - "integrity": "sha512-f8cDkvndhbQMPcysk6CUSGBWV+g1utqdn71P5YKwMumVMOG/5k7cHq0KyG4O52nB0oKS4aN2Tp5+wB4APJGC+w==", + "version": "8.30.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.30.1.tgz", + "integrity": "sha512-D7lC0kcehVH7Mb26MRQi64LMyRJsj3dToJxM1+JVTl53DQSV5/7oUGWQLcKl1C1KnoVHxMMU2FNQMffr7F3Row==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.29.1", - "@typescript-eslint/parser": "8.29.1", - "@typescript-eslint/utils": "8.29.1" + "@typescript-eslint/eslint-plugin": "8.30.1", + "@typescript-eslint/parser": "8.30.1", + "@typescript-eslint/utils": "8.30.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -6206,15 +6206,18 @@ } }, "node_modules/vite": { - "version": "6.2.6", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.2.6.tgz", - "integrity": "sha512-9xpjNl3kR4rVDZgPNdTL0/c6ao4km69a/2ihNQbcANz8RuCOK3hQBmLSJf3bRKVQjVMda+YvizNE8AwvogcPbw==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.0.tgz", + "integrity": "sha512-9aC0n4pr6hIbvi1YOpFjwQ+QOTGssvbJKoeYkuHHGWwlXfdxQlI8L2qNMo9awEEcCPSiS+5mJZk5jH1PAqoDeQ==", "dev": true, "license": "MIT", "dependencies": { "esbuild": "^0.25.0", + "fdir": "^6.4.3", + "picomatch": "^4.0.2", "postcss": "^8.5.3", - "rollup": "^4.30.1" + "rollup": "^4.34.9", + "tinyglobby": "^0.2.12" }, "bin": { "vite": "bin/vite.js" diff --git a/pdl-live-react/src-tauri/Cargo.lock b/pdl-live-react/src-tauri/Cargo.lock index 96b006c6e..6974e0fd9 100644 --- a/pdl-live-react/src-tauri/Cargo.lock +++ b/pdl-live-react/src-tauri/Cargo.lock @@ -127,9 +127,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.97" +version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f" +checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" [[package]] name = "arraydeque" @@ -595,9 +595,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.18" +version = "1.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "525046617d8376e3db1deffb079e91cef90a89fc3ca5c185bbf8c9ecdd15cd5c" +checksum = "8e3a13707ac958681c13b39b458c073d0d9bc8a22cb1b2f4c8e55eb72c13f362" dependencies = [ "shlex", ] @@ -664,18 +664,18 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.35" +version = "4.5.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8aa86934b44c19c50f87cc2790e19f54f7a67aedb64101c2e1a2e5ecfb73944" +checksum = "2df961d8c8a0d08aa9945718ccf584145eee3f3aa06cddbeac12933781102e04" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.35" +version = "4.5.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2414dbb2dd0695280da6ea9261e327479e9d37b0630f6b53ba2a11c60c679fd9" +checksum = "132dbda40fb6753878316a489d5a1242a8ef2f0d9e47ba01c951ea8aa7d013a5" dependencies = [ "anstream", "anstyle", @@ -2062,7 +2062,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core 0.60.1", + "windows-core 0.61.0", ] [[package]] @@ -2525,9 +2525,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.171" +version = "0.2.172" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" +checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" [[package]] name = "libffi" @@ -2987,7 +2987,7 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" dependencies = [ - "proc-macro-crate 2.0.0", + "proc-macro-crate 3.3.0", "proc-macro2", "quote", "syn 2.0.100", @@ -3791,9 +3791,9 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" [[package]] name = "proc-macro2" -version = "1.0.94" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" dependencies = [ "unicode-ident", ] @@ -5160,9 +5160,9 @@ dependencies = [ [[package]] name = "tao" -version = "0.32.8" +version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63c8b1020610b9138dd7b1e06cf259ae91aa05c30f3bd0d6b42a03997b92dec1" +checksum = "1e59c1f38e657351a2e822eadf40d6a2ad4627b9c25557bc1180ec1b3295ef82" dependencies = [ "bitflags 2.9.0", "core-foundation 0.10.0", @@ -5191,8 +5191,8 @@ dependencies = [ "tao-macros", "unicode-segmentation", "url", - "windows 0.60.0", - "windows-core 0.60.1", + "windows 0.61.1", + "windows-core 0.61.0", "windows-version", "x11-dl", ] @@ -5216,9 +5216,9 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] name = "tauri" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d08db1ff9e011e04014e737ec022610d756c0eae0b3b3a9037bccaf3003173a" +checksum = "be03adf68fba02f87c4653da7bd73f40b0ecf9c6b7c2c39830f6981d0651912f" dependencies = [ "anyhow", "bytes", @@ -5239,6 +5239,7 @@ dependencies = [ "objc2 0.6.0", "objc2-app-kit", "objc2-foundation 0.3.0", + "objc2-ui-kit", "percent-encoding", "plist", "raw-window-handle", @@ -5261,14 +5262,14 @@ dependencies = [ "webkit2gtk", "webview2-com", "window-vibrancy", - "windows 0.60.0", + "windows 0.61.1", ] [[package]] name = "tauri-build" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fd20e4661c2cce65343319e6e8da256958f5af958cafc47c0d0af66a55dcd17" +checksum = "d7a0350f0df1db385ca5c02888a83e0e66655c245b7443db8b78a70da7d7f8fc" dependencies = [ "anyhow", "cargo_toml", @@ -5288,9 +5289,9 @@ dependencies = [ [[package]] name = "tauri-codegen" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "458258b19032450ccf975840116ecf013e539eadbb74420bd890e8c56ab2b1a4" +checksum = "f93f035551bf7b11b3f51ad9bc231ebbe5e085565527991c16cf326aa38cdf47" dependencies = [ "base64 0.22.1", "brotli", @@ -5315,9 +5316,9 @@ dependencies = [ [[package]] name = "tauri-macros" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d402813d3b9c773a0fa58697c457c771f10e735498fdcb7b343264d18e5a601f" +checksum = "8db4df25e2d9d45de0c4c910da61cd5500190da14ae4830749fee3466dddd112" dependencies = [ "heck 0.5.0", "proc-macro2", @@ -5329,9 +5330,9 @@ dependencies = [ [[package]] name = "tauri-plugin" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4190775d6ff73fe66d9af44c012739a2659720efd9c0e1e56a918678038699d" +checksum = "37a5ebe6a610d1b78a94650896e6f7c9796323f408800cef436e0fa0539de601" dependencies = [ "anyhow", "glob", @@ -5411,29 +5412,31 @@ dependencies = [ [[package]] name = "tauri-runtime" -version = "2.5.1" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00ada7ac2f9276f09b8c3afffd3215fd5d9bff23c22df8a7c70e7ef67cacd532" +checksum = "00f004905d549854069e6774533d742b03cacfd6f03deb08940a8677586cbe39" dependencies = [ "cookie", "dpi", "gtk", "http", "jni", + "objc2 0.6.0", + "objc2-ui-kit", "raw-window-handle", "serde", "serde_json", "tauri-utils", "thiserror 2.0.12", "url", - "windows 0.60.0", + "windows 0.61.1", ] [[package]] name = "tauri-runtime-wry" -version = "2.5.1" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf2e5842c57e154af43a20a49c7efee0ce2578c20b4c2bdf266852b422d2e421" +checksum = "f85d056f4d4b014fe874814034f3416d57114b617a493a4fe552580851a3f3a2" dependencies = [ "gtk", "http", @@ -5452,15 +5455,15 @@ dependencies = [ "url", "webkit2gtk", "webview2-com", - "windows 0.60.0", + "windows 0.61.1", "wry", ] [[package]] name = "tauri-utils" -version = "2.3.1" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f037e66c7638cc0a2213f61566932b9a06882b8346486579c90e4b019bac447" +checksum = "b2900399c239a471bcff7f15c4399eb1a8c4fe511ba2853e07c996d771a5e0a4" dependencies = [ "anyhow", "brotli", @@ -6381,15 +6384,15 @@ dependencies = [ [[package]] name = "webview2-com" -version = "0.36.0" +version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0d606f600e5272b514dbb66539dd068211cc20155be8d3958201b4b5bd79ed3" +checksum = "b542b5cfbd9618c46c2784e4d41ba218c336ac70d44c55e47b251033e7d85601" dependencies = [ "webview2-com-macros", "webview2-com-sys", - "windows 0.60.0", - "windows-core 0.60.1", - "windows-implement", + "windows 0.61.1", + "windows-core 0.61.0", + "windows-implement 0.60.0", "windows-interface", ] @@ -6406,13 +6409,13 @@ dependencies = [ [[package]] name = "webview2-com-sys" -version = "0.36.0" +version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfb27fccd3c27f68e9a6af1bcf48c2d82534b8675b83608a4d81446d095a17ac" +checksum = "8ae2d11c4a686e4409659d7891791254cf9286d3cfe0eef54df1523533d22295" dependencies = [ "thiserror 2.0.12", - "windows 0.60.0", - "windows-core 0.60.1", + "windows 0.61.1", + "windows-core 0.61.0", ] [[package]] @@ -6495,11 +6498,24 @@ version = "0.60.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ddf874e74c7a99773e62b1c671427abf01a425e77c3d3fb9fb1e4883ea934529" dependencies = [ - "windows-collections", + "windows-collections 0.1.1", "windows-core 0.60.1", - "windows-future", + "windows-future 0.1.1", + "windows-link", + "windows-numerics 0.1.1", +] + +[[package]] +name = "windows" +version = "0.61.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5ee8f3d025738cb02bad7868bbb5f8a6327501e870bf51f1b455b0a2454a419" +dependencies = [ + "windows-collections 0.2.0", + "windows-core 0.61.0", + "windows-future 0.2.0", "windows-link", - "windows-numerics", + "windows-numerics 0.2.0", ] [[package]] @@ -6511,6 +6527,15 @@ dependencies = [ "windows-core 0.60.1", ] +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core 0.61.0", +] + [[package]] name = "windows-core" version = "0.52.0" @@ -6526,11 +6551,24 @@ version = "0.60.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca21a92a9cae9bf4ccae5cf8368dce0837100ddf6e6d57936749e85f152f6247" dependencies = [ - "windows-implement", + "windows-implement 0.59.0", "windows-interface", "windows-link", "windows-result", - "windows-strings", + "windows-strings 0.3.1", +] + +[[package]] +name = "windows-core" +version = "0.61.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980" +dependencies = [ + "windows-implement 0.60.0", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings 0.4.0", ] [[package]] @@ -6543,6 +6581,16 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-future" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a1d6bbefcb7b60acd19828e1bc965da6fcf18a7e39490c5f8be71e54a19ba32" +dependencies = [ + "windows-core 0.61.0", + "windows-link", +] + [[package]] name = "windows-implement" version = "0.59.0" @@ -6554,6 +6602,17 @@ dependencies = [ "syn 2.0.100", ] +[[package]] +name = "windows-implement" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + [[package]] name = "windows-interface" version = "0.59.1" @@ -6581,6 +6640,16 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-numerics" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core 0.61.0", + "windows-link", +] + [[package]] name = "windows-registry" version = "0.4.0" @@ -6588,7 +6657,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3" dependencies = [ "windows-result", - "windows-strings", + "windows-strings 0.3.1", "windows-targets 0.53.0", ] @@ -6610,6 +6679,15 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-strings" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-sys" version = "0.45.0" @@ -6973,9 +7051,9 @@ checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" [[package]] name = "wry" -version = "0.50.5" +version = "0.51.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b19b78efae8b853c6c817e8752fc1dbf9cab8a8ffe9c30f399bd750ccf0f0730" +checksum = "c886a0a9d2a94fd90cfa1d929629b79cfefb1546e2c7430c63a47f0664c0e4e2" dependencies = [ "base64 0.22.1", "block2 0.6.0", @@ -7009,8 +7087,8 @@ dependencies = [ "webkit2gtk", "webkit2gtk-sys", "webview2-com", - "windows 0.60.0", - "windows-core 0.60.1", + "windows 0.61.1", + "windows-core 0.61.0", "windows-version", "x11-dl", ] diff --git a/pdl-live-react/src-tauri/src/gui.rs b/pdl-live-react/src-tauri/src/gui.rs index 8b2515e42..60672534a 100644 --- a/pdl-live-react/src-tauri/src/gui.rs +++ b/pdl-live-react/src-tauri/src/gui.rs @@ -11,6 +11,7 @@ pub fn new_window( tauri::WebviewUrl::App(path.unwrap_or("".into())), ) .title("Prompt Declaration Language") + .prevent_overflow() .zoom_hotkeys_enabled(true) .inner_size(1400.0, 1050.0) .build()?; From 7f2246bf208c10b4c19234919ca606c1b3836b1f Mon Sep 17 00:00:00 2001 From: Nick Mitchell Date: Fri, 18 Apr 2025 15:10:00 -0400 Subject: [PATCH 39/39] chore: bump ui to 0.6.1 (#921) Signed-off-by: Nick Mitchell --- pdl-live-react/package-lock.json | 4 ++-- pdl-live-react/package.json | 2 +- pdl-live-react/src-tauri/Cargo.lock | 2 +- pdl-live-react/src-tauri/Cargo.toml | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pdl-live-react/package-lock.json b/pdl-live-react/package-lock.json index 09b44ea13..74144f1b7 100644 --- a/pdl-live-react/package-lock.json +++ b/pdl-live-react/package-lock.json @@ -1,12 +1,12 @@ { "name": "PDL", - "version": "0.6.0", + "version": "0.6.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "PDL", - "version": "0.6.0", + "version": "0.6.1", "dependencies": { "@patternfly/react-code-editor": "^6.1.0", "@patternfly/react-core": "^6.1.0", diff --git a/pdl-live-react/package.json b/pdl-live-react/package.json index c9d3b6111..f5d186779 100644 --- a/pdl-live-react/package.json +++ b/pdl-live-react/package.json @@ -1,7 +1,7 @@ { "name": "PDL", "private": true, - "version": "0.6.0", + "version": "0.6.1", "type": "module", "scripts": { "prod:mac:1": "npm run tauri build -- --no-bundle --target=universal-apple-darwin", diff --git a/pdl-live-react/src-tauri/Cargo.lock b/pdl-live-react/src-tauri/Cargo.lock index 6974e0fd9..bc0570ac8 100644 --- a/pdl-live-react/src-tauri/Cargo.lock +++ b/pdl-live-react/src-tauri/Cargo.lock @@ -3420,7 +3420,7 @@ checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" [[package]] name = "pdl" -version = "0.6.0" +version = "0.6.1" dependencies = [ "async-recursion", "base64ct", diff --git a/pdl-live-react/src-tauri/Cargo.toml b/pdl-live-react/src-tauri/Cargo.toml index c9574bd05..ae0f93ba4 100644 --- a/pdl-live-react/src-tauri/Cargo.toml +++ b/pdl-live-react/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pdl" -version = "0.6.0" +version = "0.6.1" description = "Prompt Declaration Language" authors = ["nickm@us.ibm.com"] edition = "2024"