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(),