From c9ced46c5f94d458f5335886f647e0fb284d2805 Mon Sep 17 00:00:00 2001 From: Antonio Sarosi <sarosiantonio@gmail.com> Date: Tue, 12 Nov 2024 02:35:02 +0000 Subject: [PATCH 01/51] Add grammar for type aliases --- .../class/invalid_type_aliases.baml | 6 ++++++ .../validation_files/class/type_aliases.baml | 7 +++++++ .../schema-ast/src/parser/datamodel.pest | 2 +- .../schema-ast/src/parser/parse_schema.rs | 19 ++++++++----------- fern/03-reference/baml/types.mdx | 16 ++++++++++++++++ 5 files changed, 38 insertions(+), 12 deletions(-) create mode 100644 engine/baml-lib/baml/tests/validation_files/class/invalid_type_aliases.baml create mode 100644 engine/baml-lib/baml/tests/validation_files/class/type_aliases.baml diff --git a/engine/baml-lib/baml/tests/validation_files/class/invalid_type_aliases.baml b/engine/baml-lib/baml/tests/validation_files/class/invalid_type_aliases.baml new file mode 100644 index 000000000..471e54239 --- /dev/null +++ b/engine/baml-lib/baml/tests/validation_files/class/invalid_type_aliases.baml @@ -0,0 +1,6 @@ +class One { + f int +} + +// Already existing name. +type One = int \ No newline at end of file diff --git a/engine/baml-lib/baml/tests/validation_files/class/type_aliases.baml b/engine/baml-lib/baml/tests/validation_files/class/type_aliases.baml new file mode 100644 index 000000000..c4c14570b --- /dev/null +++ b/engine/baml-lib/baml/tests/validation_files/class/type_aliases.baml @@ -0,0 +1,7 @@ +type Primitive = int | string | bool | float + +type List = string[] + +type Graph = map<string, string[]> + +type Combination = Primitive | List | Graph \ No newline at end of file diff --git a/engine/baml-lib/schema-ast/src/parser/datamodel.pest b/engine/baml-lib/schema-ast/src/parser/datamodel.pest index 3373c5024..755834646 100644 --- a/engine/baml-lib/schema-ast/src/parser/datamodel.pest +++ b/engine/baml-lib/schema-ast/src/parser/datamodel.pest @@ -78,7 +78,7 @@ single_word = @{ ASCII_ALPHA ~ (ASCII_ALPHANUMERIC | "_" | "-")* } // ###################################### // Type Alias // ###################################### -type_alias = { TYPE_KEYWORD ~ identifier ~ base_type ~ (NEWLINE? ~ field_attribute)* } +type_alias = { TYPE_KEYWORD ~ identifier ~ assignment ~ field_type } // ###################################### // Arguments diff --git a/engine/baml-lib/schema-ast/src/parser/parse_schema.rs b/engine/baml-lib/schema-ast/src/parser/parse_schema.rs index c8e00dd6c..bc7e9eb08 100644 --- a/engine/baml-lib/schema-ast/src/parser/parse_schema.rs +++ b/engine/baml-lib/schema-ast/src/parser/parse_schema.rs @@ -77,17 +77,13 @@ pub fn parse_schema( &mut diagnostics, ); match val_expr { - Ok(val) => { - if let Some(top) = match val.block_type { - ValueExprBlockType::Function => Some(Top::Function(val)), - ValueExprBlockType::Test => Some(Top::TestCase(val)), - ValueExprBlockType::Client => Some(Top::Client(val)), - ValueExprBlockType::RetryPolicy => Some(Top::RetryPolicy(val)), - ValueExprBlockType::Generator => Some(Top::Generator(val)), - } { - top_level_definitions.push(top); - } - } + Ok(val) => top_level_definitions.push(match val.block_type { + ValueExprBlockType::Function => Top::Function(val), + ValueExprBlockType::Test => Top::TestCase(val), + ValueExprBlockType::Client => Top::Client(val), + ValueExprBlockType::RetryPolicy => Top::RetryPolicy(val), + ValueExprBlockType::Generator => Top::Generator(val), + }), Err(e) => diagnostics.push_error(e), } } @@ -126,6 +122,7 @@ pub fn parse_schema( } // We do nothing here. Rule::raw_string_literal => (), + Rule::type_alias => (), // TODO: Store aliases Rule::empty_lines => (), _ => unreachable!("Encountered an unknown rule: {:?}", current.as_rule()), } diff --git a/fern/03-reference/baml/types.mdx b/fern/03-reference/baml/types.mdx index c94edc7dd..d1cac1913 100644 --- a/fern/03-reference/baml/types.mdx +++ b/fern/03-reference/baml/types.mdx @@ -340,6 +340,22 @@ A mapping of strings to elements of another type. - Not yet supported. Use a `class` instead. +## Type Aliases + +A _type alias_ is an alternative name for an existing type. It can be used to +simplify complex types or to give a more descriptive name to a type. Type +aliases are defined using the `type` keyword: + +```baml +type Graph = map<string, string[]> +``` + +Type aliases can point to other aliases: + +```baml +type DataStructure = string[] | Graph +``` + ## Examples and Equivalents Here are some examples and what their equivalents are in different languages. From ce258f20f606921f663156200a623a705fd9649d Mon Sep 17 00:00:00 2001 From: Antonio Sarosi <sarosiantonio@gmail.com> Date: Tue, 12 Nov 2024 19:06:04 +0000 Subject: [PATCH 02/51] Parse type aliases --- engine/baml-lib/diagnostics/src/collection.rs | 6 +- engine/baml-lib/schema-ast/src/ast.rs | 9 +- .../baml-lib/schema-ast/src/ast/argument.rs | 4 +- .../baml-lib/schema-ast/src/ast/assignment.rs | 41 +++++ .../baml-lib/schema-ast/src/ast/attribute.rs | 5 +- .../baml-lib/schema-ast/src/ast/expression.rs | 44 ++--- engine/baml-lib/schema-ast/src/ast/field.rs | 9 +- engine/baml-lib/schema-ast/src/ast/top.rs | 19 ++- .../schema-ast/src/parser/datamodel.pest | 3 +- engine/baml-lib/schema-ast/src/parser/mod.rs | 1 + .../schema-ast/src/parser/parse_assignment.rs | 151 ++++++++++++++++++ .../schema-ast/src/parser/parse_schema.rs | 7 +- 12 files changed, 254 insertions(+), 45 deletions(-) create mode 100644 engine/baml-lib/schema-ast/src/ast/assignment.rs create mode 100644 engine/baml-lib/schema-ast/src/parser/parse_assignment.rs diff --git a/engine/baml-lib/diagnostics/src/collection.rs b/engine/baml-lib/diagnostics/src/collection.rs index 5bdc03aa9..34e7081e0 100644 --- a/engine/baml-lib/diagnostics/src/collection.rs +++ b/engine/baml-lib/diagnostics/src/collection.rs @@ -44,8 +44,12 @@ impl Diagnostics { } pub fn span(&self, p: pest::Span<'_>) -> Span { + self.span_from(p.start(), p.end()) + } + + pub fn span_from(&self, start: usize, end: usize) -> Span { match self.current_file { - Some(ref file) => Span::new(file.clone(), p.start(), p.end()), + Some(ref file) => Span::new(file.clone(), start, end), None => panic!("No current file set."), } } diff --git a/engine/baml-lib/schema-ast/src/ast.rs b/engine/baml-lib/schema-ast/src/ast.rs index 04d7b2c1e..998af4245 100644 --- a/engine/baml-lib/schema-ast/src/ast.rs +++ b/engine/baml-lib/schema-ast/src/ast.rs @@ -1,4 +1,5 @@ mod argument; +mod assignment; mod attribute; mod comment; @@ -18,7 +19,8 @@ mod type_expression_block; mod value_expression_block; pub(crate) use self::comment::Comment; -pub use argument::{ArgumentId, Argument, ArgumentsList}; +pub use argument::{Argument, ArgumentId, ArgumentsList}; +pub use assignment::Assignment; pub use attribute::{Attribute, AttributeContainer, AttributeId}; pub use config::ConfigBlockProperty; pub use expression::{Expression, RawString}; @@ -31,9 +33,7 @@ pub use template_string::TemplateString; pub use top::Top; pub use traits::{WithAttributes, WithDocumentation, WithIdentifier, WithName, WithSpan}; pub use type_expression_block::{FieldId, SubType, TypeExpressionBlock}; -pub use value_expression_block::{ - BlockArg, BlockArgs, ValueExprBlock, ValueExprBlockType, -}; +pub use value_expression_block::{BlockArg, BlockArgs, ValueExprBlock, ValueExprBlockType}; /// AST representation of a prisma schema. /// @@ -234,6 +234,7 @@ fn top_idx_to_top_id(top_idx: usize, top: &Top) -> TopId { Top::Enum(_) => TopId::Enum(TypeExpId(top_idx as u32)), Top::Class(_) => TopId::Class(TypeExpId(top_idx as u32)), Top::Function(_) => TopId::Function(ValExpId(top_idx as u32)), + Top::TypeAlias(_) => todo!(), Top::Client(_) => TopId::Client(ValExpId(top_idx as u32)), Top::TemplateString(_) => TopId::TemplateString(TemplateStringId(top_idx as u32)), Top::Generator(_) => TopId::Generator(ValExpId(top_idx as u32)), diff --git a/engine/baml-lib/schema-ast/src/ast/argument.rs b/engine/baml-lib/schema-ast/src/ast/argument.rs index 5fc04d019..2d01d3163 100644 --- a/engine/baml-lib/schema-ast/src/ast/argument.rs +++ b/engine/baml-lib/schema-ast/src/ast/argument.rs @@ -22,7 +22,7 @@ impl std::ops::Index<ArgumentId> for ArgumentsList { } /// A list of arguments inside parentheses. -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone, Default, PartialEq)] pub struct ArgumentsList { /// The arguments themselves. /// @@ -43,7 +43,7 @@ impl ArgumentsList { } /// An argument, either for attributes or for function call expressions. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub struct Argument { /// The argument value. /// diff --git a/engine/baml-lib/schema-ast/src/ast/assignment.rs b/engine/baml-lib/schema-ast/src/ast/assignment.rs new file mode 100644 index 000000000..4f8e672cb --- /dev/null +++ b/engine/baml-lib/schema-ast/src/ast/assignment.rs @@ -0,0 +1,41 @@ +//! Assignment expressions. +//! +//! As of right now the only supported "assignments" are type aliases. + +use super::{ + traits::WithAttributes, Attribute, BlockArgs, Comment, Field, FieldType, Identifier, Span, + WithDocumentation, WithIdentifier, WithSpan, +}; + +/// Assignment expression. `left = right`. +#[derive(Debug, Clone, PartialEq)] +pub struct Assignment { + /// Left side of the assignment. + /// + /// For now this can only be an identifier, but if we end up needing to + /// support stuff like destructuring then change it to some sort of + /// expression. + pub identifier: Identifier, + + /// Right side of the assignment. + /// + /// Since for now it's only used for type aliases then it's just a type. + pub value: FieldType, + + /// Span of the entire assignment. + pub span: Span, +} + +impl WithSpan for Assignment { + fn span(&self) -> &Span { + &self.span + } +} + +// TODO: Right now the left side is always an identifier, but if ends up being +// an expression we'll have to refactor this somehow. +impl WithIdentifier for Assignment { + fn identifier(&self) -> &Identifier { + &self.identifier + } +} diff --git a/engine/baml-lib/schema-ast/src/ast/attribute.rs b/engine/baml-lib/schema-ast/src/ast/attribute.rs index 0ccd549d1..8b0c3adc8 100644 --- a/engine/baml-lib/schema-ast/src/ast/attribute.rs +++ b/engine/baml-lib/schema-ast/src/ast/attribute.rs @@ -3,7 +3,7 @@ use std::ops::Index; /// An attribute (following `@` or `@@``) on a model, model field, enum, enum value or composite /// type field. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub struct Attribute { /// The name of the attribute: /// @@ -36,8 +36,7 @@ impl Attribute { pub fn assert_eq_up_to_span(&self, other: &Attribute) { assert_eq!(self.name.to_string(), other.name.to_string()); assert_eq!(self.parenthesized, other.parenthesized); - self - .arguments + self.arguments .iter() .zip(other.arguments.iter()) .for_each(|(arg1, arg2)| { diff --git a/engine/baml-lib/schema-ast/src/ast/expression.rs b/engine/baml-lib/schema-ast/src/ast/expression.rs index b7677beb2..8ee112e86 100644 --- a/engine/baml-lib/schema-ast/src/ast/expression.rs +++ b/engine/baml-lib/schema-ast/src/ast/expression.rs @@ -6,7 +6,7 @@ use std::fmt; use super::{Identifier, WithName, WithSpan}; use baml_types::JinjaExpression; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub struct RawString { raw_span: Span, #[allow(dead_code)] @@ -144,7 +144,7 @@ impl RawString { } /// Represents arbitrary, even nested, expressions. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub enum Expression { /// Boolean values aka true or false BoolValue(bool, Span), @@ -174,7 +174,7 @@ impl fmt::Display for Expression { Expression::RawStringValue(val, ..) => { write!(f, "{}", crate::string_literal(val.value())) } - Expression::JinjaExpressionValue(val,..) => fmt::Display::fmt(val, f), + Expression::JinjaExpressionValue(val, ..) => fmt::Display::fmt(val, f), Expression::Array(vals, _) => { let vals = vals .iter() @@ -196,7 +196,6 @@ impl fmt::Display for Expression { } impl Expression { - pub fn from_json(value: serde_json::Value, span: Span, empty_span: Span) -> Expression { match value { serde_json::Value::Null => Expression::StringValue("Null".to_string(), empty_span), @@ -297,7 +296,7 @@ impl Expression { Self::NumericValue(_, span) => span, Self::StringValue(_, span) => span, Self::RawStringValue(r) => r.span(), - Self::JinjaExpressionValue(_,span) => span, + Self::JinjaExpressionValue(_, span) => span, Self::Identifier(id) => id.span(), Self::Map(_, span) => span, Self::Array(_, span) => span, @@ -350,32 +349,35 @@ impl Expression { pub fn assert_eq_up_to_span(&self, other: &Expression) { use Expression::*; match (self, other) { - (BoolValue(v1,_), BoolValue(v2,_)) => assert_eq!(v1,v2), - (BoolValue(_,_), _) => panic!("Types do not match: {:?} and {:?}", self, other), - (NumericValue(n1,_), NumericValue(n2,_)) => assert_eq!(n1, n2), - (NumericValue(_,_), _) => panic!("Types do not match: {:?} and {:?}", self, other), - (Identifier(i1), Identifier(i2)) => assert_eq!(i1,i2), + (BoolValue(v1, _), BoolValue(v2, _)) => assert_eq!(v1, v2), + (BoolValue(_, _), _) => panic!("Types do not match: {:?} and {:?}", self, other), + (NumericValue(n1, _), NumericValue(n2, _)) => assert_eq!(n1, n2), + (NumericValue(_, _), _) => panic!("Types do not match: {:?} and {:?}", self, other), + (Identifier(i1), Identifier(i2)) => assert_eq!(i1, i2), (Identifier(_), _) => panic!("Types do not match: {:?} and {:?}", self, other), - (StringValue(s1,_), StringValue(s2,_)) => assert_eq!(s1, s2), - (StringValue(_,_), _) => panic!("Types do not match: {:?} and {:?}", self, other), + (StringValue(s1, _), StringValue(s2, _)) => assert_eq!(s1, s2), + (StringValue(_, _), _) => panic!("Types do not match: {:?} and {:?}", self, other), (RawStringValue(s1), RawStringValue(s2)) => s1.assert_eq_up_to_span(s2), (RawStringValue(_), _) => panic!("Types do not match: {:?} and {:?}", self, other), (JinjaExpressionValue(j1, _), JinjaExpressionValue(j2, _)) => assert_eq!(j1, j2), - (JinjaExpressionValue(_,_), _) => panic!("Types do not match: {:?} and {:?}", self, other), - (Array(xs,_), Array(ys,_)) => { + (JinjaExpressionValue(_, _), _) => { + panic!("Types do not match: {:?} and {:?}", self, other) + } + (Array(xs, _), Array(ys, _)) => { assert_eq!(xs.len(), ys.len()); - xs.iter().zip(ys).for_each(|(x,y)| { x.assert_eq_up_to_span(y); }) - }, - (Array(_,_), _) => panic!("Types do not match: {:?} and {:?}", self, other), - (Map(m1,_), Map(m2,_)) => { + xs.iter().zip(ys).for_each(|(x, y)| { + x.assert_eq_up_to_span(y); + }) + } + (Array(_, _), _) => panic!("Types do not match: {:?} and {:?}", self, other), + (Map(m1, _), Map(m2, _)) => { assert_eq!(m1.len(), m2.len()); m1.iter().zip(m2).for_each(|((k1, v1), (k2, v2))| { k1.assert_eq_up_to_span(k2); v1.assert_eq_up_to_span(v2); }); - }, - (Map(_,_), _) => panic!("Types do not match: {:?} and {:?}", self, other), - + } + (Map(_, _), _) => panic!("Types do not match: {:?} and {:?}", self, other), } } } diff --git a/engine/baml-lib/schema-ast/src/ast/field.rs b/engine/baml-lib/schema-ast/src/ast/field.rs index 1641ca83b..b3fc040d8 100644 --- a/engine/baml-lib/schema-ast/src/ast/field.rs +++ b/engine/baml-lib/schema-ast/src/ast/field.rs @@ -2,7 +2,8 @@ use baml_types::{LiteralValue, TypeValue}; use internal_baml_diagnostics::DatamodelError; use super::{ - traits::WithAttributes, Attribute, Comment, Identifier, SchemaAst, Span, WithDocumentation, WithIdentifier, WithName, WithSpan + traits::WithAttributes, Attribute, Comment, Identifier, SchemaAst, Span, WithDocumentation, + WithIdentifier, WithName, WithSpan, }; /// A field definition in a model or a composite type. @@ -108,7 +109,7 @@ impl FieldArity { } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub enum FieldType { Symbol(FieldArity, Identifier, Option<Vec<Attribute>>), Primitive(FieldArity, TypeValue, Span, Option<Vec<Attribute>>), @@ -257,7 +258,9 @@ impl FieldType { } pub fn has_checks(&self) -> bool { - self.attributes().iter().any(|Attribute{name,..}| name.to_string().as_str() == "check") + self.attributes() + .iter() + .any(|Attribute { name, .. }| name.to_string().as_str() == "check") } pub fn assert_eq_up_to_span(&self, other: &Self) { diff --git a/engine/baml-lib/schema-ast/src/ast/top.rs b/engine/baml-lib/schema-ast/src/ast/top.rs index 595e27e54..2092a7007 100644 --- a/engine/baml-lib/schema-ast/src/ast/top.rs +++ b/engine/baml-lib/schema-ast/src/ast/top.rs @@ -1,24 +1,26 @@ use super::{ - traits::WithSpan, Identifier, Span, TemplateString, TypeExpressionBlock, ValueExprBlock, - WithIdentifier, + assignment::Assignment, traits::WithSpan, Identifier, Span, TemplateString, + TypeExpressionBlock, ValueExprBlock, WithIdentifier, }; /// Enum for distinguishing between top-level entries #[derive(Debug, Clone)] pub enum Top { - /// An enum declaration + /// An enum declaration. Enum(TypeExpressionBlock), - // A class declaration + /// A class declaration. Class(TypeExpressionBlock), - // A function declaration + /// A function declaration. Function(ValueExprBlock), + /// Type alias expression. + TypeAlias(Assignment), - // Clients to run + /// Clients to run. Client(ValueExprBlock), TemplateString(TemplateString), - // Generator + /// Generator. Generator(ValueExprBlock), TestCase(ValueExprBlock), @@ -34,6 +36,7 @@ impl Top { Top::Enum(_) => "enum", Top::Class(_) => "class", Top::Function(_) => "function", + Top::TypeAlias(_) => "type", Top::Client(_) => "client<llm>", Top::TemplateString(_) => "template_string", Top::Generator(_) => "generator", @@ -78,6 +81,7 @@ impl WithIdentifier for Top { Top::Enum(x) => x.identifier(), Top::Class(x) => x.identifier(), Top::Function(x) => x.identifier(), + Top::TypeAlias(_) => todo!(), Top::Client(x) => x.identifier(), Top::TemplateString(x) => x.identifier(), Top::Generator(x) => x.identifier(), @@ -93,6 +97,7 @@ impl WithSpan for Top { Top::Enum(en) => en.span(), Top::Class(class) => class.span(), Top::Function(func) => func.span(), + Top::TypeAlias(alias) => alias.span(), Top::TemplateString(template) => template.span(), Top::Client(client) => client.span(), Top::Generator(gen) => gen.span(), diff --git a/engine/baml-lib/schema-ast/src/parser/datamodel.pest b/engine/baml-lib/schema-ast/src/parser/datamodel.pest index 755834646..4cae81089 100644 --- a/engine/baml-lib/schema-ast/src/parser/datamodel.pest +++ b/engine/baml-lib/schema-ast/src/parser/datamodel.pest @@ -78,7 +78,7 @@ single_word = @{ ASCII_ALPHA ~ (ASCII_ALPHANUMERIC | "_" | "-")* } // ###################################### // Type Alias // ###################################### -type_alias = { TYPE_KEYWORD ~ identifier ~ assignment ~ field_type } +type_alias = { identifier ~ identifier ~ assignment ~ field_type } // ###################################### // Arguments @@ -177,7 +177,6 @@ BLOCK_CLOSE = { "}" } BLOCK_LEVEL_CATCH_ALL = { !BLOCK_CLOSE ~ CATCH_ALL } CATCH_ALL = { (!NEWLINE ~ ANY)+ ~ NEWLINE? } -TYPE_KEYWORD = { "type" } FUNCTION_KEYWORD = { "function" } TEMPLATE_KEYWORD = { "template_string" | "string_template" } TEST_KEYWORD = { "test" } diff --git a/engine/baml-lib/schema-ast/src/parser/mod.rs b/engine/baml-lib/schema-ast/src/parser/mod.rs index 82ec4c6a7..5f9bf74de 100644 --- a/engine/baml-lib/schema-ast/src/parser/mod.rs +++ b/engine/baml-lib/schema-ast/src/parser/mod.rs @@ -1,5 +1,6 @@ mod helpers; mod parse_arguments; +mod parse_assignment; mod parse_attribute; mod parse_comments; mod parse_expression; diff --git a/engine/baml-lib/schema-ast/src/parser/parse_assignment.rs b/engine/baml-lib/schema-ast/src/parser/parse_assignment.rs new file mode 100644 index 000000000..6d1760ea9 --- /dev/null +++ b/engine/baml-lib/schema-ast/src/parser/parse_assignment.rs @@ -0,0 +1,151 @@ +use super::{ + helpers::{parsing_catch_all, Pair}, + parse_identifier::parse_identifier, + parse_named_args_list::parse_named_argument_list, + Rule, +}; + +use crate::{assert_correct_parser, ast::*, parser::parse_types::parse_field_type}; + +use internal_baml_diagnostics::{DatamodelError, Diagnostics}; + +/// Parses an assignment in the form of `keyword identifier = FieldType`. +/// +/// It only works with type aliases for now, it's not generic over all +/// expressions. +pub(crate) fn parse_assignment(pair: Pair<'_>, diagnostics: &mut Diagnostics) -> Assignment { + assert_correct_parser!(pair, Rule::type_alias); + + let span = pair.as_span(); + + let mut is_type_keyword = false; + let mut identifier: Option<Identifier> = None; + let mut field_type: Option<FieldType> = None; + + for current in pair.into_inner() { + match current.as_rule() { + Rule::identifier => { + if !is_type_keyword { + match current.as_str() { + "type" => is_type_keyword = true, + other => diagnostics.push_error(DatamodelError::new_validation_error( + &format!("Unexpected keyword used in assignment: {other}"), + diagnostics.span(current.as_span()), + )), + } + } else { + // There are two identifiers, the second one is the name of + // the type alias. + identifier = Some(parse_identifier(current, diagnostics)); + } + } + + Rule::assignment => {} // Ok, equal sign. + + Rule::field_type => field_type = parse_field_type(current, diagnostics), + + _ => todo!(), + } + } + + match (identifier, field_type) { + (Some(identifier), Some(field_type)) => Assignment { + identifier, + value: field_type, + span: diagnostics.span(span), + }, + + _ => panic!("Encountered impossible type_alias declaration during parsing"), + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::parser::{BAMLParser, Rule}; + use baml_types::TypeValue; + use internal_baml_diagnostics::{Diagnostics, SourceFile}; + use pest::{consumes_to, fails_with, parses_to, Parser}; + + fn parse_type_alias(input: &'static str) -> (Assignment, Diagnostics) { + let path = "test.baml"; + let source = SourceFile::new_static(path.into(), input); + + let mut diagnostics = Diagnostics::new(path.into()); + diagnostics.set_source(&source); + + let pairs = BAMLParser::parse(Rule::type_alias, input) + .unwrap() + .next() + .unwrap(); + + let assignment = super::parse_assignment(pairs, &mut diagnostics); + + (assignment, diagnostics) + } + + #[test] + fn parse_type_alias_assignment_tokens() { + parses_to! { + parser: BAMLParser, + input: "type Test = int", + rule: Rule::type_alias, + tokens: [ + type_alias(0, 15, [ + identifier(0, 4, [single_word(0, 4)]), + identifier(5, 9, [single_word(5, 9)]), + assignment(10, 11), + field_type(12, 15, [ + non_union(12, 15, [ + identifier(12, 15, [single_word(12, 15)]) + ]) + ]), + ]) + ] + } + + // This is parsed as identifier ~ identifier because of how Pest handles + // whitespaces. + // https://github.com/pest-parser/pest/discussions/967 + fails_with! { + parser: BAMLParser, + input: "typeTest = int", + rule: Rule::type_alias, + positives: [Rule::identifier], + negatives: [], + pos: 9 + } + } + + #[test] + fn parse_union_type_alias() { + let (assignment, diagnostics) = parse_type_alias("type Test = int | string"); + + assert_eq!( + assignment, + Assignment { + identifier: Identifier::Local("Test".into(), diagnostics.span_from(5, 9)), + value: FieldType::Union( + FieldArity::Required, + vec![ + FieldType::Primitive( + FieldArity::Required, + TypeValue::Int, + diagnostics.span_from(12, 15), + Some(vec![]) + ), + FieldType::Primitive( + FieldArity::Required, + TypeValue::String, + diagnostics.span_from(18, 24), + Some(vec![]) + ) + ], + diagnostics.span_from(12, 24), + Some(vec![]) + ), + span: diagnostics.span_from(0, 24), + } + ) + } +} diff --git a/engine/baml-lib/schema-ast/src/parser/parse_schema.rs b/engine/baml-lib/schema-ast/src/parser/parse_schema.rs index bc7e9eb08..99e74408a 100644 --- a/engine/baml-lib/schema-ast/src/parser/parse_schema.rs +++ b/engine/baml-lib/schema-ast/src/parser/parse_schema.rs @@ -1,7 +1,7 @@ use std::path::PathBuf; use super::{ - parse_template_string::parse_template_string, + parse_assignment::parse_assignment, parse_template_string::parse_template_string, parse_type_expression_block::parse_type_expression_block, parse_value_expression_block::parse_value_expression_block, BAMLParser, Rule, }; @@ -87,6 +87,10 @@ pub fn parse_schema( Err(e) => diagnostics.push_error(e), } } + Rule::type_alias => { + let _assignment = parse_assignment(current, &mut diagnostics); + // top_level_definitions.push(Top::TypeAlias(assignment)); + } Rule::template_declaration => { match parse_template_string( @@ -122,7 +126,6 @@ pub fn parse_schema( } // We do nothing here. Rule::raw_string_literal => (), - Rule::type_alias => (), // TODO: Store aliases Rule::empty_lines => (), _ => unreachable!("Encountered an unknown rule: {:?}", current.as_rule()), } From bb8f8ba91daad7b3df1c6ecb2b7c3d6aaea04d3f Mon Sep 17 00:00:00 2001 From: Antonio Sarosi <sarosiantonio@gmail.com> Date: Tue, 12 Nov 2024 19:54:10 +0000 Subject: [PATCH 03/51] Fix wrong keyword parsing error in type alias def --- .../validation_files/class/invalid_type_aliases.baml | 12 +++++++++++- .../schema-ast/src/parser/parse_assignment.rs | 9 ++++++--- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/engine/baml-lib/baml/tests/validation_files/class/invalid_type_aliases.baml b/engine/baml-lib/baml/tests/validation_files/class/invalid_type_aliases.baml index 471e54239..e37b46e03 100644 --- a/engine/baml-lib/baml/tests/validation_files/class/invalid_type_aliases.baml +++ b/engine/baml-lib/baml/tests/validation_files/class/invalid_type_aliases.baml @@ -3,4 +3,14 @@ class One { } // Already existing name. -type One = int \ No newline at end of file +type One = int + +// Unexpected keyword. +typpe Two = float + +// error: Error validating: Unexpected keyword used in assignment: typpe +// --> class/invalid_type_aliases.baml:9 +// | +// 8 | // Unexpected keyword. +// 9 | typpe Two = float +// | diff --git a/engine/baml-lib/schema-ast/src/parser/parse_assignment.rs b/engine/baml-lib/schema-ast/src/parser/parse_assignment.rs index 6d1760ea9..bc2f6c59f 100644 --- a/engine/baml-lib/schema-ast/src/parser/parse_assignment.rs +++ b/engine/baml-lib/schema-ast/src/parser/parse_assignment.rs @@ -18,16 +18,19 @@ pub(crate) fn parse_assignment(pair: Pair<'_>, diagnostics: &mut Diagnostics) -> let span = pair.as_span(); - let mut is_type_keyword = false; + let mut consumed_definition_keyword = false; + let mut identifier: Option<Identifier> = None; let mut field_type: Option<FieldType> = None; for current in pair.into_inner() { match current.as_rule() { Rule::identifier => { - if !is_type_keyword { + if !consumed_definition_keyword { + consumed_definition_keyword = true; match current.as_str() { - "type" => is_type_keyword = true, + "type" => {} // Ok, type alias. + other => diagnostics.push_error(DatamodelError::new_validation_error( &format!("Unexpected keyword used in assignment: {other}"), diagnostics.span(current.as_span()), From b334a63a78ec72271f3106b8588dfbb061c78f0c Mon Sep 17 00:00:00 2001 From: Antonio Sarosi <sarosiantonio@gmail.com> Date: Tue, 12 Nov 2024 20:49:18 +0000 Subject: [PATCH 04/51] Push `TypeAlias` tops --- engine/baml-lib/schema-ast/src/ast.rs | 20 +++--- engine/baml-lib/schema-ast/src/ast/field.rs | 2 +- .../schema-ast/src/ast/template_string.rs | 2 +- engine/baml-lib/schema-ast/src/ast/top.rs | 4 +- .../src/ast/type_expression_block.rs | 4 +- .../src/ast/value_expression_block.rs | 10 +-- .../schema-ast/src/parser/parse_assignment.rs | 2 +- .../schema-ast/src/parser/parse_schema.rs | 72 ++++++++++++++++--- 8 files changed, 85 insertions(+), 31 deletions(-) diff --git a/engine/baml-lib/schema-ast/src/ast.rs b/engine/baml-lib/schema-ast/src/ast.rs index 998af4245..7f0ec5e6b 100644 --- a/engine/baml-lib/schema-ast/src/ast.rs +++ b/engine/baml-lib/schema-ast/src/ast.rs @@ -132,25 +132,28 @@ impl std::ops::Index<TemplateStringId> for SchemaAst { /// syntax to resolve the id to an `ast::Top`. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum TopId { - /// An enum declaration + /// An enum declaration. Enum(TypeExpId), - // A class declaration + /// A class declaration. Class(TypeExpId), - // A function declaration + /// A function declaration. Function(ValExpId), - // A client declaration + /// A type alias declaration. + TypeAlias(TypeExpId), + + /// A client declaration. Client(ValExpId), - // A generator declaration + /// A generator declaration. Generator(ValExpId), - // Template Strings + /// Template Strings. TemplateString(TemplateStringId), - // A config block + /// A config block. TestCase(ValExpId), RetryPolicy(ValExpId), @@ -217,6 +220,7 @@ impl std::ops::Index<TopId> for SchemaAst { let idx = match index { TopId::Enum(TypeExpId(idx)) => idx, TopId::Class(TypeExpId(idx)) => idx, + TopId::TypeAlias(TypeExpId(idx)) => idx, TopId::Function(ValExpId(idx)) => idx, TopId::TemplateString(TemplateStringId(idx)) => idx, TopId::Client(ValExpId(idx)) => idx, @@ -234,7 +238,7 @@ fn top_idx_to_top_id(top_idx: usize, top: &Top) -> TopId { Top::Enum(_) => TopId::Enum(TypeExpId(top_idx as u32)), Top::Class(_) => TopId::Class(TypeExpId(top_idx as u32)), Top::Function(_) => TopId::Function(ValExpId(top_idx as u32)), - Top::TypeAlias(_) => todo!(), + Top::TypeAlias(_) => TopId::TypeAlias(TypeExpId(top_idx as u32)), Top::Client(_) => TopId::Client(ValExpId(top_idx as u32)), Top::TemplateString(_) => TopId::TemplateString(TemplateStringId(top_idx as u32)), Top::Generator(_) => TopId::Generator(ValExpId(top_idx as u32)), diff --git a/engine/baml-lib/schema-ast/src/ast/field.rs b/engine/baml-lib/schema-ast/src/ast/field.rs index b3fc040d8..3dfb3a301 100644 --- a/engine/baml-lib/schema-ast/src/ast/field.rs +++ b/engine/baml-lib/schema-ast/src/ast/field.rs @@ -7,7 +7,7 @@ use super::{ }; /// A field definition in a model or a composite type. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub struct Field<T> { /// The field's type. /// diff --git a/engine/baml-lib/schema-ast/src/ast/template_string.rs b/engine/baml-lib/schema-ast/src/ast/template_string.rs index 2be1d7d06..ea659ad89 100644 --- a/engine/baml-lib/schema-ast/src/ast/template_string.rs +++ b/engine/baml-lib/schema-ast/src/ast/template_string.rs @@ -4,7 +4,7 @@ use super::{ }; /// A model declaration. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub struct TemplateString { /// The name of the variable. /// diff --git a/engine/baml-lib/schema-ast/src/ast/top.rs b/engine/baml-lib/schema-ast/src/ast/top.rs index 2092a7007..a7f87af4b 100644 --- a/engine/baml-lib/schema-ast/src/ast/top.rs +++ b/engine/baml-lib/schema-ast/src/ast/top.rs @@ -4,7 +4,7 @@ use super::{ }; /// Enum for distinguishing between top-level entries -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub enum Top { /// An enum declaration. Enum(TypeExpressionBlock), @@ -81,7 +81,7 @@ impl WithIdentifier for Top { Top::Enum(x) => x.identifier(), Top::Class(x) => x.identifier(), Top::Function(x) => x.identifier(), - Top::TypeAlias(_) => todo!(), + Top::TypeAlias(x) => x.identifier(), Top::Client(x) => x.identifier(), Top::TemplateString(x) => x.identifier(), Top::Generator(x) => x.identifier(), diff --git a/engine/baml-lib/schema-ast/src/ast/type_expression_block.rs b/engine/baml-lib/schema-ast/src/ast/type_expression_block.rs index 87d5147c9..40cfaa0d5 100644 --- a/engine/baml-lib/schema-ast/src/ast/type_expression_block.rs +++ b/engine/baml-lib/schema-ast/src/ast/type_expression_block.rs @@ -23,7 +23,7 @@ impl std::ops::Index<FieldId> for TypeExpressionBlock { } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub enum SubType { Enum, Class, @@ -31,7 +31,7 @@ pub enum SubType { } /// A class or enum declaration. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub struct TypeExpressionBlock { /// The name of the class or enum. /// diff --git a/engine/baml-lib/schema-ast/src/ast/value_expression_block.rs b/engine/baml-lib/schema-ast/src/ast/value_expression_block.rs index ccebc25fa..fecabca0a 100644 --- a/engine/baml-lib/schema-ast/src/ast/value_expression_block.rs +++ b/engine/baml-lib/schema-ast/src/ast/value_expression_block.rs @@ -1,8 +1,8 @@ +use super::argument::ArgumentId; use super::{ traits::WithAttributes, Attribute, Comment, Expression, Field, FieldType, Identifier, Span, WithDocumentation, WithIdentifier, WithSpan, }; -use super::argument::ArgumentId; use std::fmt::Display; use std::fmt::Formatter; @@ -43,7 +43,7 @@ impl std::ops::Index<ArgumentId> for BlockArg { } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub struct BlockArg { /// The field's type. pub field_type: FieldType, @@ -57,14 +57,14 @@ impl BlockArg { self.field_type.name() } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub struct BlockArgs { pub(crate) documentation: Option<Comment>, pub args: Vec<(Identifier, BlockArg)>, pub(crate) span: Span, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub enum ValueExprBlockType { Function, Client, @@ -108,7 +108,7 @@ impl BlockArgs { /// A block declaration. /// A complete Function, Client, Generator, Test, or RetryPolicy. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub struct ValueExprBlock { /// The name of the block. /// diff --git a/engine/baml-lib/schema-ast/src/parser/parse_assignment.rs b/engine/baml-lib/schema-ast/src/parser/parse_assignment.rs index bc2f6c59f..8838c59e1 100644 --- a/engine/baml-lib/schema-ast/src/parser/parse_assignment.rs +++ b/engine/baml-lib/schema-ast/src/parser/parse_assignment.rs @@ -47,7 +47,7 @@ pub(crate) fn parse_assignment(pair: Pair<'_>, diagnostics: &mut Diagnostics) -> Rule::field_type => field_type = parse_field_type(current, diagnostics), - _ => todo!(), + _ => parsing_catch_all(current, "type_alias"), } } diff --git a/engine/baml-lib/schema-ast/src/parser/parse_schema.rs b/engine/baml-lib/schema-ast/src/parser/parse_schema.rs index 99e74408a..9fd471c8e 100644 --- a/engine/baml-lib/schema-ast/src/parser/parse_schema.rs +++ b/engine/baml-lib/schema-ast/src/parser/parse_schema.rs @@ -88,8 +88,8 @@ pub fn parse_schema( } } Rule::type_alias => { - let _assignment = parse_assignment(current, &mut diagnostics); - // top_level_definitions.push(Top::TypeAlias(assignment)); + let assignment = parse_assignment(current, &mut diagnostics); + top_level_definitions.push(Top::TypeAlias(assignment)); } Rule::template_declaration => { @@ -165,11 +165,24 @@ pub fn parse_schema( } } +fn get_expected_from_error(positives: &[Rule]) -> String { + use std::fmt::Write as _; + let mut out = String::with_capacity(positives.len() * 6); + + for positive in positives { + write!(out, "{positive:?}").unwrap(); + } + + out +} + #[cfg(test)] mod tests { use super::parse_schema; - use crate::ast::*; // Add this line to import the ast module + use crate::ast::*; + use baml_types::TypeValue; + // Add this line to import the ast module use internal_baml_diagnostics::SourceFile; #[test] @@ -244,15 +257,52 @@ mod tests { let result = parse_schema(&root_path.into(), &source).unwrap(); assert_eq!(result.1.errors().len(), 0); } -} -fn get_expected_from_error(positives: &[Rule]) -> String { - use std::fmt::Write as _; - let mut out = String::with_capacity(positives.len() * 6); + #[test] + fn test_push_type_aliases() { + let input = "type One = int\ntype Two = string | One"; - for positive in positives { - write!(out, "{positive:?}").unwrap(); - } + let root_path = "example_file.baml"; + let source = SourceFile::new_static(root_path.into(), input); - out + let (ast, diagnostics) = parse_schema(&root_path.into(), &source).unwrap(); + + assert_eq!( + ast.tops, + vec![ + Top::TypeAlias(Assignment { + identifier: Identifier::Local("One".into(), diagnostics.span_from(5, 8)), + value: FieldType::Primitive( + FieldArity::Required, + TypeValue::Int, + diagnostics.span_from(11, 14), + None + ), + span: diagnostics.span_from(0, 14), + }), + Top::TypeAlias(Assignment { + identifier: Identifier::Local("Two".into(), diagnostics.span_from(20, 23)), + value: FieldType::Union( + FieldArity::Required, + vec![ + FieldType::Primitive( + FieldArity::Required, + TypeValue::String, + diagnostics.span_from(26, 32), + Some(vec![]), + ), + FieldType::Symbol( + FieldArity::Required, + Identifier::Local("One".into(), diagnostics.span_from(35, 38)), + Some(vec![]), + ) + ], + diagnostics.span_from(26, 38), + Some(vec![]), + ), + span: diagnostics.span_from(15, 38), + }) + ] + ) + } } From c1021d175a781af26042b66674213a70b5434788 Mon Sep 17 00:00:00 2001 From: Antonio Sarosi <sarosiantonio@gmail.com> Date: Thu, 14 Nov 2024 00:48:10 +0000 Subject: [PATCH 05/51] Revert `PartialEq` derives in AST --- .../baml-lib/schema-ast/src/ast/argument.rs | 4 +- .../baml-lib/schema-ast/src/ast/assignment.rs | 2 +- .../baml-lib/schema-ast/src/ast/attribute.rs | 2 +- .../baml-lib/schema-ast/src/ast/expression.rs | 4 +- engine/baml-lib/schema-ast/src/ast/field.rs | 4 +- .../schema-ast/src/ast/template_string.rs | 2 +- engine/baml-lib/schema-ast/src/ast/top.rs | 2 +- .../src/ast/type_expression_block.rs | 4 +- .../src/ast/value_expression_block.rs | 6 +- .../schema-ast/src/parser/parse_assignment.rs | 46 +++++------- .../schema-ast/src/parser/parse_schema.rs | 70 ++++++++----------- 11 files changed, 60 insertions(+), 86 deletions(-) diff --git a/engine/baml-lib/schema-ast/src/ast/argument.rs b/engine/baml-lib/schema-ast/src/ast/argument.rs index 2d01d3163..5fc04d019 100644 --- a/engine/baml-lib/schema-ast/src/ast/argument.rs +++ b/engine/baml-lib/schema-ast/src/ast/argument.rs @@ -22,7 +22,7 @@ impl std::ops::Index<ArgumentId> for ArgumentsList { } /// A list of arguments inside parentheses. -#[derive(Debug, Clone, Default, PartialEq)] +#[derive(Debug, Clone, Default)] pub struct ArgumentsList { /// The arguments themselves. /// @@ -43,7 +43,7 @@ impl ArgumentsList { } /// An argument, either for attributes or for function call expressions. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone)] pub struct Argument { /// The argument value. /// diff --git a/engine/baml-lib/schema-ast/src/ast/assignment.rs b/engine/baml-lib/schema-ast/src/ast/assignment.rs index 4f8e672cb..646f4a701 100644 --- a/engine/baml-lib/schema-ast/src/ast/assignment.rs +++ b/engine/baml-lib/schema-ast/src/ast/assignment.rs @@ -8,7 +8,7 @@ use super::{ }; /// Assignment expression. `left = right`. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone)] pub struct Assignment { /// Left side of the assignment. /// diff --git a/engine/baml-lib/schema-ast/src/ast/attribute.rs b/engine/baml-lib/schema-ast/src/ast/attribute.rs index 8b0c3adc8..18c05be40 100644 --- a/engine/baml-lib/schema-ast/src/ast/attribute.rs +++ b/engine/baml-lib/schema-ast/src/ast/attribute.rs @@ -3,7 +3,7 @@ use std::ops::Index; /// An attribute (following `@` or `@@``) on a model, model field, enum, enum value or composite /// type field. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone)] pub struct Attribute { /// The name of the attribute: /// diff --git a/engine/baml-lib/schema-ast/src/ast/expression.rs b/engine/baml-lib/schema-ast/src/ast/expression.rs index 8ee112e86..1910f354e 100644 --- a/engine/baml-lib/schema-ast/src/ast/expression.rs +++ b/engine/baml-lib/schema-ast/src/ast/expression.rs @@ -6,7 +6,7 @@ use std::fmt; use super::{Identifier, WithName, WithSpan}; use baml_types::JinjaExpression; -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone)] pub struct RawString { raw_span: Span, #[allow(dead_code)] @@ -144,7 +144,7 @@ impl RawString { } /// Represents arbitrary, even nested, expressions. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone)] pub enum Expression { /// Boolean values aka true or false BoolValue(bool, Span), diff --git a/engine/baml-lib/schema-ast/src/ast/field.rs b/engine/baml-lib/schema-ast/src/ast/field.rs index 3dfb3a301..ed89bcb60 100644 --- a/engine/baml-lib/schema-ast/src/ast/field.rs +++ b/engine/baml-lib/schema-ast/src/ast/field.rs @@ -7,7 +7,7 @@ use super::{ }; /// A field definition in a model or a composite type. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone)] pub struct Field<T> { /// The field's type. /// @@ -109,7 +109,7 @@ impl FieldArity { } } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone)] pub enum FieldType { Symbol(FieldArity, Identifier, Option<Vec<Attribute>>), Primitive(FieldArity, TypeValue, Span, Option<Vec<Attribute>>), diff --git a/engine/baml-lib/schema-ast/src/ast/template_string.rs b/engine/baml-lib/schema-ast/src/ast/template_string.rs index ea659ad89..2be1d7d06 100644 --- a/engine/baml-lib/schema-ast/src/ast/template_string.rs +++ b/engine/baml-lib/schema-ast/src/ast/template_string.rs @@ -4,7 +4,7 @@ use super::{ }; /// A model declaration. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone)] pub struct TemplateString { /// The name of the variable. /// diff --git a/engine/baml-lib/schema-ast/src/ast/top.rs b/engine/baml-lib/schema-ast/src/ast/top.rs index a7f87af4b..76b56b6ee 100644 --- a/engine/baml-lib/schema-ast/src/ast/top.rs +++ b/engine/baml-lib/schema-ast/src/ast/top.rs @@ -4,7 +4,7 @@ use super::{ }; /// Enum for distinguishing between top-level entries -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone)] pub enum Top { /// An enum declaration. Enum(TypeExpressionBlock), diff --git a/engine/baml-lib/schema-ast/src/ast/type_expression_block.rs b/engine/baml-lib/schema-ast/src/ast/type_expression_block.rs index 40cfaa0d5..87d5147c9 100644 --- a/engine/baml-lib/schema-ast/src/ast/type_expression_block.rs +++ b/engine/baml-lib/schema-ast/src/ast/type_expression_block.rs @@ -23,7 +23,7 @@ impl std::ops::Index<FieldId> for TypeExpressionBlock { } } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone)] pub enum SubType { Enum, Class, @@ -31,7 +31,7 @@ pub enum SubType { } /// A class or enum declaration. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone)] pub struct TypeExpressionBlock { /// The name of the class or enum. /// diff --git a/engine/baml-lib/schema-ast/src/ast/value_expression_block.rs b/engine/baml-lib/schema-ast/src/ast/value_expression_block.rs index fecabca0a..d1108d876 100644 --- a/engine/baml-lib/schema-ast/src/ast/value_expression_block.rs +++ b/engine/baml-lib/schema-ast/src/ast/value_expression_block.rs @@ -43,7 +43,7 @@ impl std::ops::Index<ArgumentId> for BlockArg { } } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone)] pub struct BlockArg { /// The field's type. pub field_type: FieldType, @@ -57,7 +57,7 @@ impl BlockArg { self.field_type.name() } } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone)] pub struct BlockArgs { pub(crate) documentation: Option<Comment>, pub args: Vec<(Identifier, BlockArg)>, @@ -108,7 +108,7 @@ impl BlockArgs { /// A block declaration. /// A complete Function, Client, Generator, Test, or RetryPolicy. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone)] pub struct ValueExprBlock { /// The name of the block. /// diff --git a/engine/baml-lib/schema-ast/src/parser/parse_assignment.rs b/engine/baml-lib/schema-ast/src/parser/parse_assignment.rs index 8838c59e1..35298d315 100644 --- a/engine/baml-lib/schema-ast/src/parser/parse_assignment.rs +++ b/engine/baml-lib/schema-ast/src/parser/parse_assignment.rs @@ -70,7 +70,7 @@ mod tests { use internal_baml_diagnostics::{Diagnostics, SourceFile}; use pest::{consumes_to, fails_with, parses_to, Parser}; - fn parse_type_alias(input: &'static str) -> (Assignment, Diagnostics) { + fn parse_type_alias(input: &'static str) -> Assignment { let path = "test.baml"; let source = SourceFile::new_static(path.into(), input); @@ -84,7 +84,8 @@ mod tests { let assignment = super::parse_assignment(pairs, &mut diagnostics); - (assignment, diagnostics) + // (assignment, diagnostics) + assignment } #[test] @@ -122,33 +123,18 @@ mod tests { #[test] fn parse_union_type_alias() { - let (assignment, diagnostics) = parse_type_alias("type Test = int | string"); - - assert_eq!( - assignment, - Assignment { - identifier: Identifier::Local("Test".into(), diagnostics.span_from(5, 9)), - value: FieldType::Union( - FieldArity::Required, - vec![ - FieldType::Primitive( - FieldArity::Required, - TypeValue::Int, - diagnostics.span_from(12, 15), - Some(vec![]) - ), - FieldType::Primitive( - FieldArity::Required, - TypeValue::String, - diagnostics.span_from(18, 24), - Some(vec![]) - ) - ], - diagnostics.span_from(12, 24), - Some(vec![]) - ), - span: diagnostics.span_from(0, 24), - } - ) + let assignment = parse_type_alias("type Test = int | string"); + + assert_eq!(assignment.identifier.to_string(), "Test"); + + let FieldType::Union(_, elements, _, _) = assignment.value else { + panic!("Expected union type, got: {:?}", assignment.value); + }; + + let [FieldType::Primitive(_, TypeValue::Int, _, _), FieldType::Primitive(_, TypeValue::String, _, _)] = + elements.as_slice() + else { + panic!("Expected int | string union, got: {:?}", elements); + }; } } diff --git a/engine/baml-lib/schema-ast/src/parser/parse_schema.rs b/engine/baml-lib/schema-ast/src/parser/parse_schema.rs index 9fd471c8e..2d6205bad 100644 --- a/engine/baml-lib/schema-ast/src/parser/parse_schema.rs +++ b/engine/baml-lib/schema-ast/src/parser/parse_schema.rs @@ -262,47 +262,35 @@ mod tests { fn test_push_type_aliases() { let input = "type One = int\ntype Two = string | One"; - let root_path = "example_file.baml"; - let source = SourceFile::new_static(root_path.into(), input); + let path = "example_file.baml"; + let source = SourceFile::new_static(path.into(), input); + + let (ast, _) = parse_schema(&path.into(), &source).unwrap(); + + let [Top::TypeAlias(one), Top::TypeAlias(two)] = ast.tops.as_slice() else { + panic!( + "Expected two type aliases (type One, type Two), got: {:?}", + ast.tops + ); + }; + + assert_eq!(one.identifier.to_string(), "One"); + assert!(matches!( + one.value, + FieldType::Primitive(_, TypeValue::Int, _, _) + )); + + assert_eq!(two.identifier.to_string(), "Two"); + let FieldType::Union(_, elements, _, _) = &two.value else { + panic!("Expected union type (string | One), got: {:?}", two.value); + }; + + let [FieldType::Primitive(_, TypeValue::String, _, _), FieldType::Symbol(_, alias, _)] = + elements.as_slice() + else { + panic!("Expected union type (string | One), got: {:?}", two.value); + }; - let (ast, diagnostics) = parse_schema(&root_path.into(), &source).unwrap(); - - assert_eq!( - ast.tops, - vec![ - Top::TypeAlias(Assignment { - identifier: Identifier::Local("One".into(), diagnostics.span_from(5, 8)), - value: FieldType::Primitive( - FieldArity::Required, - TypeValue::Int, - diagnostics.span_from(11, 14), - None - ), - span: diagnostics.span_from(0, 14), - }), - Top::TypeAlias(Assignment { - identifier: Identifier::Local("Two".into(), diagnostics.span_from(20, 23)), - value: FieldType::Union( - FieldArity::Required, - vec![ - FieldType::Primitive( - FieldArity::Required, - TypeValue::String, - diagnostics.span_from(26, 32), - Some(vec![]), - ), - FieldType::Symbol( - FieldArity::Required, - Identifier::Local("One".into(), diagnostics.span_from(35, 38)), - Some(vec![]), - ) - ], - diagnostics.span_from(26, 38), - Some(vec![]), - ), - span: diagnostics.span_from(15, 38), - }) - ] - ) + assert_eq!(alias.to_string(), "One"); } } From ade4200c230c89fd4ff0f5334322baf548e0b5c6 Mon Sep 17 00:00:00 2001 From: Antonio Sarosi <sarosiantonio@gmail.com> Date: Thu, 14 Nov 2024 00:49:31 +0000 Subject: [PATCH 06/51] Revert `span_from` --- engine/baml-lib/diagnostics/src/collection.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/engine/baml-lib/diagnostics/src/collection.rs b/engine/baml-lib/diagnostics/src/collection.rs index 34e7081e0..5bdc03aa9 100644 --- a/engine/baml-lib/diagnostics/src/collection.rs +++ b/engine/baml-lib/diagnostics/src/collection.rs @@ -44,12 +44,8 @@ impl Diagnostics { } pub fn span(&self, p: pest::Span<'_>) -> Span { - self.span_from(p.start(), p.end()) - } - - pub fn span_from(&self, start: usize, end: usize) -> Span { match self.current_file { - Some(ref file) => Span::new(file.clone(), start, end), + Some(ref file) => Span::new(file.clone(), p.start(), p.end()), None => panic!("No current file set."), } } From ad299e612268557f9745cb7ef7d27e02fbfc4442 Mon Sep 17 00:00:00 2001 From: Antonio Sarosi <sarosiantonio@gmail.com> Date: Thu, 14 Nov 2024 01:35:35 +0000 Subject: [PATCH 07/51] Validate type aliases --- .../class/invalid_type_aliases.baml | 6 ++++ .../validation_files/class/type_aliases.baml | 31 ++++++++++++++++++- .../baml-lib/parser-database/src/names/mod.rs | 21 ++++++++++--- .../src/names/validate_reserved_names.rs | 4 +++ engine/baml-lib/schema-ast/src/ast.rs | 13 ++++---- .../baml-lib/schema-ast/src/ast/assignment.rs | 4 +-- engine/baml-lib/schema-ast/src/ast/top.rs | 2 +- 7 files changed, 66 insertions(+), 15 deletions(-) diff --git a/engine/baml-lib/baml/tests/validation_files/class/invalid_type_aliases.baml b/engine/baml-lib/baml/tests/validation_files/class/invalid_type_aliases.baml index e37b46e03..a8e4a7c3c 100644 --- a/engine/baml-lib/baml/tests/validation_files/class/invalid_type_aliases.baml +++ b/engine/baml-lib/baml/tests/validation_files/class/invalid_type_aliases.baml @@ -14,3 +14,9 @@ typpe Two = float // 8 | // Unexpected keyword. // 9 | typpe Two = float // | +// error: The type_alias "One" cannot be defined because a class with that name already exists. +// --> class/invalid_type_aliases.baml:6 +// | +// 5 | // Already existing name. +// 6 | type One = int +// | diff --git a/engine/baml-lib/baml/tests/validation_files/class/type_aliases.baml b/engine/baml-lib/baml/tests/validation_files/class/type_aliases.baml index c4c14570b..b2a37c0d2 100644 --- a/engine/baml-lib/baml/tests/validation_files/class/type_aliases.baml +++ b/engine/baml-lib/baml/tests/validation_files/class/type_aliases.baml @@ -4,4 +4,33 @@ type List = string[] type Graph = map<string, string[]> -type Combination = Primitive | List | Graph \ No newline at end of file +type Combination = Primitive | List | Graph + +function AliasPrimitive(p: Primitive) -> Primitive { + client "openai/gpt-4o" + prompt r#" + Return the given value back: {{ p }} + "# +} + +function MapAlias(m: Graph) -> Graph { + client "openai/gpt-4o" + prompt r#" + Return the given Graph back: + + {{ m }} + + {{ ctx.output_format }} + "# +} + +function NestedAlias(c: Combination) -> Combination { + client "openai/gpt-4o" + prompt r#" + Return the given value back: + + {{ c }} + + {{ ctx.output_format }} + "# +} diff --git a/engine/baml-lib/parser-database/src/names/mod.rs b/engine/baml-lib/parser-database/src/names/mod.rs index 0d211fa21..a1a5ef4e9 100644 --- a/engine/baml-lib/parser-database/src/names/mod.rs +++ b/engine/baml-lib/parser-database/src/names/mod.rs @@ -33,7 +33,7 @@ pub(super) struct Names { /// - Generators /// - Model fields for each model pub(super) fn resolve_names(ctx: &mut Context<'_>) { - let mut tmp_names: HashSet<&str> = HashSet::default(); // throwaway container for duplicate checking + let mut enum_value_names: HashSet<&str> = HashSet::default(); // throwaway container for duplicate checking let mut names = Names::default(); for (top_id, top) in ctx.ast.iter_tops() { @@ -41,7 +41,7 @@ pub(super) fn resolve_names(ctx: &mut Context<'_>) { let namespace = match (top_id, top) { (_, ast::Top::Enum(ast_enum)) => { - tmp_names.clear(); + enum_value_names.clear(); validate_enum_name(ast_enum, ctx.diagnostics); validate_attribute_identifiers(ast_enum, ctx); @@ -50,7 +50,7 @@ pub(super) fn resolve_names(ctx: &mut Context<'_>) { validate_attribute_identifiers(value, ctx); - if !tmp_names.insert(value.name()) { + if !enum_value_names.insert(value.name()) { ctx.push_error(DatamodelError::new_duplicate_enum_value_error( ast_enum.name.name(), value.name(), @@ -90,6 +90,17 @@ pub(super) fn resolve_names(ctx: &mut Context<'_>) { (_, ast::Top::Class(_)) => { unreachable!("Encountered impossible class declaration during parsing") } + + (ast::TopId::TypeAlias(_), ast::Top::TypeAlias(type_alias)) => { + validate_type_alias_name(type_alias, ctx.diagnostics); + + Some(either::Left(&mut names.tops)) + } + + (_, ast::Top::TypeAlias(_)) => { + unreachable!("Encountered impossible type alias declaration during parsing") + } + (ast::TopId::TemplateString(_), ast::Top::TemplateString(template_string)) => { validate_template_string_name(template_string, ctx.diagnostics); validate_attribute_identifiers(template_string, ctx); @@ -136,13 +147,13 @@ pub(super) fn resolve_names(ctx: &mut Context<'_>) { (_, ast::Top::Generator(generator)) => { validate_generator_name(generator, ctx.diagnostics); - check_for_duplicate_properties(top, generator.fields(), &mut tmp_names, ctx); + check_for_duplicate_properties(top, generator.fields(), &mut enum_value_names, ctx); Some(either::Left(&mut names.generators)) } (ast::TopId::TestCase(testcase_id), ast::Top::TestCase(testcase)) => { validate_test(testcase, ctx.diagnostics); - check_for_duplicate_properties(top, testcase.fields(), &mut tmp_names, ctx); + check_for_duplicate_properties(top, testcase.fields(), &mut enum_value_names, ctx); // TODO: I think we should do this later after all parsing, as duplication // would work best as a validation error with walkers. diff --git a/engine/baml-lib/parser-database/src/names/validate_reserved_names.rs b/engine/baml-lib/parser-database/src/names/validate_reserved_names.rs index 781a838fc..6389c32de 100644 --- a/engine/baml-lib/parser-database/src/names/validate_reserved_names.rs +++ b/engine/baml-lib/parser-database/src/names/validate_reserved_names.rs @@ -44,6 +44,10 @@ pub(crate) fn validate_class_name( validate_name("class", ast_class.identifier(), diagnostics, true); } +pub(crate) fn validate_type_alias_name(ast_class: &ast::Assignment, diagnostics: &mut Diagnostics) { + validate_name("type alias", ast_class.identifier(), diagnostics, true); +} + pub(crate) fn validate_class_field_name<T>( ast_class_field: &ast::Field<T>, diagnostics: &mut Diagnostics, diff --git a/engine/baml-lib/schema-ast/src/ast.rs b/engine/baml-lib/schema-ast/src/ast.rs index 7f0ec5e6b..82cd6128e 100644 --- a/engine/baml-lib/schema-ast/src/ast.rs +++ b/engine/baml-lib/schema-ast/src/ast.rs @@ -37,13 +37,14 @@ pub use value_expression_block::{BlockArg, BlockArgs, ValueExprBlock, ValueExprB /// AST representation of a prisma schema. /// -/// This module is used internally to represent an AST. The AST's nodes can be used -/// during validation of a schema, especially when implementing custom attributes. +/// This module is used internally to represent an AST. The AST's nodes can be +/// used during validation of a schema, especially when implementing custom +/// attributes. /// -/// The AST is not validated, also fields and attributes are not resolved. Every node is -/// annotated with its location in the text representation. -/// Basically, the AST is an object oriented representation of the datamodel's text. -/// Schema = Datamodel + Generators + Datasources +/// The AST is not validated, also fields and attributes are not resolved. Every +/// node is annotated with its location in the text representation. +/// Basically, the AST is an object oriented representation of the datamodel's +/// text. Schema = Datamodel + Generators + Datasources #[derive(Debug)] pub struct SchemaAst { /// All models, enums, composite types, datasources, generators and type aliases. diff --git a/engine/baml-lib/schema-ast/src/ast/assignment.rs b/engine/baml-lib/schema-ast/src/ast/assignment.rs index 646f4a701..bfa675645 100644 --- a/engine/baml-lib/schema-ast/src/ast/assignment.rs +++ b/engine/baml-lib/schema-ast/src/ast/assignment.rs @@ -32,8 +32,8 @@ impl WithSpan for Assignment { } } -// TODO: Right now the left side is always an identifier, but if ends up being -// an expression we'll have to refactor this somehow. +// TODO: Right now the left side is always an identifier, but if it ends up +// being an expression we'll have to refactor this somehow. impl WithIdentifier for Assignment { fn identifier(&self) -> &Identifier { &self.identifier diff --git a/engine/baml-lib/schema-ast/src/ast/top.rs b/engine/baml-lib/schema-ast/src/ast/top.rs index 76b56b6ee..f745f9ac3 100644 --- a/engine/baml-lib/schema-ast/src/ast/top.rs +++ b/engine/baml-lib/schema-ast/src/ast/top.rs @@ -36,7 +36,7 @@ impl Top { Top::Enum(_) => "enum", Top::Class(_) => "class", Top::Function(_) => "function", - Top::TypeAlias(_) => "type", + Top::TypeAlias(_) => "type_alias", Top::Client(_) => "client<llm>", Top::TemplateString(_) => "template_string", Top::Generator(_) => "generator", From 8786e3a62a43b995dc875529623400f567fbe167 Mon Sep 17 00:00:00 2001 From: Antonio Sarosi <sarosiantonio@gmail.com> Date: Thu, 14 Nov 2024 02:51:40 +0000 Subject: [PATCH 08/51] Add `TypeAlias` walker --- engine/baml-lib/baml-core/src/ir/repr.rs | 8 ++-- .../validation_pipeline/validations/cycle.rs | 5 +-- .../validations/functions.rs | 4 +- .../validations/template_strings.rs | 1 - engine/baml-lib/parser-database/src/lib.rs | 26 ++++++++---- .../baml-lib/parser-database/src/names/mod.rs | 10 ++--- .../parser-database/src/walkers/alias.rs | 5 +++ .../parser-database/src/walkers/class.rs | 24 +++++------ .../parser-database/src/walkers/function.rs | 17 ++++---- .../parser-database/src/walkers/mod.rs | 42 ++++++++++++------- 10 files changed, 81 insertions(+), 61 deletions(-) create mode 100644 engine/baml-lib/parser-database/src/walkers/alias.rs diff --git a/engine/baml-lib/baml-core/src/ir/repr.rs b/engine/baml-lib/baml-core/src/ir/repr.rs index 8d266626e..3867af340 100644 --- a/engine/baml-lib/baml-core/src/ir/repr.rs +++ b/engine/baml-lib/baml-core/src/ir/repr.rs @@ -2,14 +2,13 @@ use std::collections::HashSet; use anyhow::{anyhow, Result}; use baml_types::{Constraint, ConstraintLevel, FieldType}; -use either::Either; use indexmap::{IndexMap, IndexSet}; use internal_baml_parser_database::{ walkers::{ ClassWalker, ClientSpec as AstClientSpec, ClientWalker, ConfigurationWalker, EnumValueWalker, EnumWalker, FieldWalker, FunctionWalker, TemplateStringWalker, }, - Attributes, ParserDatabase, PromptAst, RetryPolicyStrategy, + Attributes, ParserDatabase, PromptAst, RetryPolicyStrategy, TypeWalker, }; use internal_baml_schema_ast::ast::SubType; @@ -413,7 +412,7 @@ impl WithRepr<FieldType> for ast::FieldType { } ast::FieldType::Symbol(arity, idn, ..) => type_with_arity( match db.find_type(idn) { - Some(Either::Left(class_walker)) => { + Some(TypeWalker::Class(class_walker)) => { let base_class = FieldType::Class(class_walker.name().to_string()); let maybe_constraints = class_walker.get_constraints(SubType::Class); match maybe_constraints { @@ -424,7 +423,7 @@ impl WithRepr<FieldType> for ast::FieldType { _ => base_class, } } - Some(Either::Right(enum_walker)) => { + Some(TypeWalker::Enum(enum_walker)) => { let base_type = FieldType::Enum(enum_walker.name().to_string()); let maybe_constraints = enum_walker.get_constraints(SubType::Enum); match maybe_constraints { @@ -435,6 +434,7 @@ impl WithRepr<FieldType> for ast::FieldType { _ => base_type, } } + Some(TypeWalker::TypeAlias(type_alias_walker)) => todo!(), None => return Err(anyhow!("Field type uses unresolvable local identifier")), }, arity, diff --git a/engine/baml-lib/baml-core/src/validate/validation_pipeline/validations/cycle.rs b/engine/baml-lib/baml-core/src/validate/validation_pipeline/validations/cycle.rs index c177d1f8e..25d872143 100644 --- a/engine/baml-lib/baml-core/src/validate/validation_pipeline/validations/cycle.rs +++ b/engine/baml-lib/baml-core/src/validate/validation_pipeline/validations/cycle.rs @@ -1,8 +1,7 @@ use std::collections::{HashMap, HashSet}; -use either::Either; use internal_baml_diagnostics::DatamodelError; -use internal_baml_parser_database::Tarjan; +use internal_baml_parser_database::{Tarjan, TypeWalker}; use internal_baml_schema_ast::ast::{FieldType, TypeExpId, WithName, WithSpan}; use crate::validate::validation_pipeline::context::Context; @@ -67,7 +66,7 @@ fn insert_required_deps( ) { match field { FieldType::Symbol(arity, ident, _) if arity.is_required() => { - if let Some(Either::Left(class)) = ctx.db.find_type_by_str(ident.name()) { + if let Some(TypeWalker::Class(class)) = ctx.db.find_type_by_str(ident.name()) { deps.insert(class.id); } } diff --git a/engine/baml-lib/baml-core/src/validate/validation_pipeline/validations/functions.rs b/engine/baml-lib/baml-core/src/validate/validation_pipeline/validations/functions.rs index 2296e4191..a966beeba 100644 --- a/engine/baml-lib/baml-core/src/validate/validation_pipeline/validations/functions.rs +++ b/engine/baml-lib/baml-core/src/validate/validation_pipeline/validations/functions.rs @@ -2,9 +2,9 @@ use std::collections::HashSet; use crate::validate::validation_pipeline::context::Context; -use either::Either; use internal_baml_diagnostics::{DatamodelError, DatamodelWarning, Span}; +use internal_baml_parser_database::TypeWalker; use internal_baml_schema_ast::ast::{FieldType, TypeExpId, WithIdentifier, WithName, WithSpan}; use super::types::validate_type; @@ -246,7 +246,7 @@ impl<'c> NestedChecks<'c> { match field_type { FieldType::Symbol(_, id, ..) => match self.ctx.db.find_type(id) { - Some(Either::Left(class_walker)) => { + Some(TypeWalker::Class(class_walker)) => { // Stop recursion when dealing with recursive types. if !self.visited.insert(class_walker.id) { return false; diff --git a/engine/baml-lib/baml-core/src/validate/validation_pipeline/validations/template_strings.rs b/engine/baml-lib/baml-core/src/validate/validation_pipeline/validations/template_strings.rs index d8a33485c..68b6d252f 100644 --- a/engine/baml-lib/baml-core/src/validate/validation_pipeline/validations/template_strings.rs +++ b/engine/baml-lib/baml-core/src/validate/validation_pipeline/validations/template_strings.rs @@ -2,7 +2,6 @@ use std::collections::HashSet; use crate::validate::validation_pipeline::context::Context; -use either::Either; use internal_baml_diagnostics::{DatamodelError, DatamodelWarning, Span}; use internal_baml_schema_ast::ast::{FieldType, TypeExpId, WithIdentifier, WithName, WithSpan}; diff --git a/engine/baml-lib/parser-database/src/lib.rs b/engine/baml-lib/parser-database/src/lib.rs index bb3998589..f4fe8cf1a 100644 --- a/engine/baml-lib/parser-database/src/lib.rs +++ b/engine/baml-lib/parser-database/src/lib.rs @@ -39,7 +39,6 @@ mod types; use std::collections::{HashMap, HashSet}; pub use coerce_expression::{coerce, coerce_array, coerce_opt}; -use either::Either; pub use internal_baml_schema_ast::ast; use internal_baml_schema_ast::ast::SchemaAst; pub use tarjan::Tarjan; @@ -47,6 +46,7 @@ pub use types::{ Attributes, ContantDelayStrategy, ExponentialBackoffStrategy, PrinterType, PromptAst, PromptVariable, RetryPolicy, RetryPolicyStrategy, StaticType, }; +pub use walkers::TypeWalker; use self::{context::Context, interner::StringId, types::Types}; use internal_baml_diagnostics::{DatamodelError, Diagnostics}; @@ -157,8 +157,10 @@ impl ParserDatabase { let deps = HashSet::from_iter(deps.iter().filter_map( |dep| match self.find_type_by_str(dep) { - Some(Either::Left(cls)) => Some(cls.id), - Some(Either::Right(_)) => None, + Some(TypeWalker::Class(cls)) => Some(cls.id), + Some(TypeWalker::Enum(_)) => None, + // TODO: Does this interfere with recursive types? + Some(TypeWalker::TypeAlias(_)) => todo!(), None => panic!("Unknown class `{dep}`"), }, )); @@ -173,8 +175,8 @@ impl ParserDatabase { .map(|cycle| cycle.into_iter().collect()) .collect(); - // Additionally ensure the same thing for functions, but since we've already handled classes, - // this should be trivial. + // Additionally ensure the same thing for functions, but since we've + // already handled classes, this should be trivial. let extends = self .types .function @@ -184,8 +186,11 @@ impl ParserDatabase { let input_deps = input .iter() .filter_map(|f| match self.find_type_by_str(f) { - Some(Either::Left(walker)) => Some(walker.dependencies().iter().cloned()), - Some(Either::Right(_)) => None, + Some(TypeWalker::Class(walker)) => { + Some(walker.dependencies().iter().cloned()) + } + Some(TypeWalker::Enum(_)) => None, + Some(TypeWalker::TypeAlias(_)) => None, _ => panic!("Unknown class `{}`", f), }) .flatten() @@ -194,8 +199,11 @@ impl ParserDatabase { let output_deps = output .iter() .filter_map(|f| match self.find_type_by_str(f) { - Some(Either::Left(walker)) => Some(walker.dependencies().iter().cloned()), - Some(Either::Right(_)) => None, + Some(TypeWalker::Class(walker)) => { + Some(walker.dependencies().iter().cloned()) + } + Some(TypeWalker::Enum(_)) => None, + Some(TypeWalker::TypeAlias(_)) => todo!(), _ => panic!("Unknown class `{}`", f), }) .flatten() diff --git a/engine/baml-lib/parser-database/src/names/mod.rs b/engine/baml-lib/parser-database/src/names/mod.rs index a1a5ef4e9..ada725b22 100644 --- a/engine/baml-lib/parser-database/src/names/mod.rs +++ b/engine/baml-lib/parser-database/src/names/mod.rs @@ -33,7 +33,7 @@ pub(super) struct Names { /// - Generators /// - Model fields for each model pub(super) fn resolve_names(ctx: &mut Context<'_>) { - let mut enum_value_names: HashSet<&str> = HashSet::default(); // throwaway container for duplicate checking + let mut tmp_names: HashSet<&str> = HashSet::default(); // throwaway container for duplicate checking let mut names = Names::default(); for (top_id, top) in ctx.ast.iter_tops() { @@ -41,7 +41,7 @@ pub(super) fn resolve_names(ctx: &mut Context<'_>) { let namespace = match (top_id, top) { (_, ast::Top::Enum(ast_enum)) => { - enum_value_names.clear(); + tmp_names.clear(); validate_enum_name(ast_enum, ctx.diagnostics); validate_attribute_identifiers(ast_enum, ctx); @@ -50,7 +50,7 @@ pub(super) fn resolve_names(ctx: &mut Context<'_>) { validate_attribute_identifiers(value, ctx); - if !enum_value_names.insert(value.name()) { + if !tmp_names.insert(value.name()) { ctx.push_error(DatamodelError::new_duplicate_enum_value_error( ast_enum.name.name(), value.name(), @@ -147,13 +147,13 @@ pub(super) fn resolve_names(ctx: &mut Context<'_>) { (_, ast::Top::Generator(generator)) => { validate_generator_name(generator, ctx.diagnostics); - check_for_duplicate_properties(top, generator.fields(), &mut enum_value_names, ctx); + check_for_duplicate_properties(top, generator.fields(), &mut tmp_names, ctx); Some(either::Left(&mut names.generators)) } (ast::TopId::TestCase(testcase_id), ast::Top::TestCase(testcase)) => { validate_test(testcase, ctx.diagnostics); - check_for_duplicate_properties(top, testcase.fields(), &mut enum_value_names, ctx); + check_for_duplicate_properties(top, testcase.fields(), &mut tmp_names, ctx); // TODO: I think we should do this later after all parsing, as duplication // would work best as a validation error with walkers. diff --git a/engine/baml-lib/parser-database/src/walkers/alias.rs b/engine/baml-lib/parser-database/src/walkers/alias.rs new file mode 100644 index 000000000..832862335 --- /dev/null +++ b/engine/baml-lib/parser-database/src/walkers/alias.rs @@ -0,0 +1,5 @@ +use super::TypeWalker; +use internal_baml_schema_ast::ast::{self, Identifier}; + +/// A `class` declaration in the Prisma schema. +pub type TypeAliasWalker<'db> = super::Walker<'db, ast::TypeExpId>; diff --git a/engine/baml-lib/parser-database/src/walkers/class.rs b/engine/baml-lib/parser-database/src/walkers/class.rs index 5dd2500b9..b9f707aa3 100644 --- a/engine/baml-lib/parser-database/src/walkers/class.rs +++ b/engine/baml-lib/parser-database/src/walkers/class.rs @@ -1,5 +1,6 @@ use std::collections::HashSet; +use super::TypeWalker; use super::{field::FieldWalker, EnumWalker}; use crate::types::Attributes; use baml_types::Constraint; @@ -42,9 +43,8 @@ impl<'db> ClassWalker<'db> { self.db.types.class_dependencies[&self.class_id()] .iter() .filter_map(|f| match self.db.find_type_by_str(f) { - Some(Either::Left(_cls)) => None, - Some(Either::Right(walker)) => Some(walker), - None => None, + Some(TypeWalker::Enum(walker)) => Some(walker), + _ => None, }) } @@ -53,9 +53,8 @@ impl<'db> ClassWalker<'db> { self.db.types.class_dependencies[&self.class_id()] .iter() .filter_map(|f| match self.db.find_type_by_str(f) { - Some(Either::Left(walker)) => Some(walker), - Some(Either::Right(_enm)) => None, - None => None, + Some(TypeWalker::Class(walker)) => Some(walker), + _ => None, }) } @@ -92,7 +91,8 @@ impl<'db> ClassWalker<'db> { /// Get the constraints of a class or an enum. pub fn get_constraints(&self, sub_type: SubType) -> Option<Vec<Constraint>> { - self.get_default_attributes(sub_type).map(|attrs| attrs.constraints.clone()) + self.get_default_attributes(sub_type) + .map(|attrs| attrs.constraints.clone()) } /// Arguments of the function. @@ -166,9 +166,8 @@ impl<'db> ArgWalker<'db> { input .iter() .filter_map(|f| match self.db.find_type_by_str(f) { - Some(Either::Left(_cls)) => None, - Some(Either::Right(walker)) => Some(walker), - None => None, + Some(TypeWalker::Enum(walker)) => Some(walker), + _ => None, }) } @@ -178,9 +177,8 @@ impl<'db> ArgWalker<'db> { input .iter() .filter_map(|f| match self.db.find_type_by_str(f) { - Some(Either::Left(walker)) => Some(walker), - Some(Either::Right(_enm)) => None, - None => None, + Some(TypeWalker::Class(walker)) => Some(walker), + _ => None, }) } } diff --git a/engine/baml-lib/parser-database/src/walkers/function.rs b/engine/baml-lib/parser-database/src/walkers/function.rs index 1627cfc81..eb4b96978 100644 --- a/engine/baml-lib/parser-database/src/walkers/function.rs +++ b/engine/baml-lib/parser-database/src/walkers/function.rs @@ -7,7 +7,7 @@ use crate::{ types::FunctionType, }; -use super::{ClassWalker, ConfigurationWalker, EnumWalker, Walker}; +use super::{ClassWalker, ConfigurationWalker, EnumWalker, TypeWalker, Walker}; use std::iter::ExactSizeIterator; @@ -143,7 +143,10 @@ impl<'db> FunctionWalker<'db> { match client.0.split_once("/") { // TODO: do this in a more robust way // actually validate which clients are and aren't allowed - Some((provider, model)) => Ok(ClientSpec::Shorthand(provider.to_string(), model.to_string())), + Some((provider, model)) => Ok(ClientSpec::Shorthand( + provider.to_string(), + model.to_string(), + )), None => match self.db.find_client(client.0.as_str()) { Some(client) => Ok(ClientSpec::Named(client.name().to_string())), None => { @@ -217,9 +220,8 @@ impl<'db> ArgWalker<'db> { if self.id.1 { input } else { output } .iter() .filter_map(|f| match self.db.find_type_by_str(f) { - Some(Either::Left(_cls)) => None, - Some(Either::Right(walker)) => Some(walker), - None => None, + Some(TypeWalker::Enum(walker)) => Some(walker), + _ => None, }) } @@ -229,9 +231,8 @@ impl<'db> ArgWalker<'db> { if self.id.1 { input } else { output } .iter() .filter_map(|f| match self.db.find_type_by_str(f) { - Some(Either::Left(walker)) => Some(walker), - Some(Either::Right(_enm)) => None, - None => None, + Some(TypeWalker::Class(walker)) => Some(walker), + _ => None, }) } } diff --git a/engine/baml-lib/parser-database/src/walkers/mod.rs b/engine/baml-lib/parser-database/src/walkers/mod.rs index 9d1fd2a92..6f9d5abff 100644 --- a/engine/baml-lib/parser-database/src/walkers/mod.rs +++ b/engine/baml-lib/parser-database/src/walkers/mod.rs @@ -6,6 +6,7 @@ //! - Know about relations. //! - Do not know anything about connectors, they are generic. +mod alias; mod r#class; mod client; mod configuration; @@ -14,16 +15,17 @@ mod field; mod function; mod template_string; +use alias::TypeAliasWalker; use baml_types::TypeValue; pub use client::*; pub use configuration::*; use either::Either; pub use field::*; -pub use function::{FunctionWalker, ClientSpec}; -pub use template_string::TemplateStringWalker; +pub use function::{ClientSpec, FunctionWalker}; use internal_baml_schema_ast::ast::{FieldType, Identifier, TopId, TypeExpId, WithName}; pub use r#class::*; pub use r#enum::*; +pub use template_string::TemplateStringWalker; /// A generic walker. Only walkers intantiated with a concrete ID type (`I`) are useful. #[derive(Clone, Copy)] @@ -50,11 +52,21 @@ where } } +/// Walker kind. +pub enum TypeWalker<'db> { + /// Class walker. + Class(ClassWalker<'db>), + /// Enum walker. + Enum(EnumWalker<'db>), + /// Type alias walker. + TypeAlias(TypeAliasWalker<'db>), +} + impl<'db> crate::ParserDatabase { /// Find an enum by name. pub fn find_enum(&'db self, idn: &Identifier) -> Option<EnumWalker<'db>> { self.find_type(idn).and_then(|either| match either { - Either::Right(class) => Some(class), + TypeWalker::Enum(enm) => Some(enm), _ => None, }) } @@ -66,22 +78,19 @@ impl<'db> crate::ParserDatabase { } /// Find a type by name. - pub fn find_type_by_str( - &'db self, - name: &str, - ) -> Option<Either<ClassWalker<'db>, EnumWalker<'db>>> { + pub fn find_type_by_str(&'db self, name: &str) -> Option<TypeWalker<'db>> { self.find_top_by_str(name).and_then(|top_id| match top_id { - TopId::Class(class_id) => Some(Either::Left(self.walk(*class_id))), - TopId::Enum(enum_id) => Some(Either::Right(self.walk(*enum_id))), + TopId::Class(class_id) => Some(TypeWalker::Class(self.walk(*class_id))), + TopId::Enum(enum_id) => Some(TypeWalker::Enum(self.walk(*enum_id))), + TopId::TypeAlias(type_alias_id) => { + Some(TypeWalker::TypeAlias(self.walk(*type_alias_id))) + } _ => None, }) } /// Find a type by name. - pub fn find_type( - &'db self, - idn: &Identifier, - ) -> Option<Either<ClassWalker<'db>, EnumWalker<'db>>> { + pub fn find_type(&'db self, idn: &Identifier) -> Option<TypeWalker<'db>> { match idn { Identifier::Local(local, _) => self.find_type_by_str(local), _ => None, @@ -91,7 +100,7 @@ impl<'db> crate::ParserDatabase { /// Find a model by name. pub fn find_class(&'db self, idn: &Identifier) -> Option<ClassWalker<'db>> { self.find_type(idn).and_then(|either| match either { - Either::Left(class) => Some(class), + TypeWalker::Class(class) => Some(class), _ => None, }) } @@ -255,8 +264,9 @@ impl<'db> crate::ParserDatabase { FieldType::Symbol(arity, idn, ..) => { let mut t = match self.find_type(idn) { None => Type::Undefined, - Some(Either::Left(_)) => Type::ClassRef(idn.to_string()), - Some(Either::Right(_)) => Type::String, + Some(TypeWalker::Class(_)) => Type::ClassRef(idn.to_string()), + Some(TypeWalker::Enum(_)) => Type::String, + Some(TypeWalker::TypeAlias(_)) => Type::String, }; if arity.is_optional() { t = Type::None | t; From 1f1d7771585f2a1a666a08f5c8bdbc63f9f9cedb Mon Sep 17 00:00:00 2001 From: Antonio Sarosi <sarosiantonio@gmail.com> Date: Wed, 20 Nov 2024 00:55:22 +0000 Subject: [PATCH 09/51] Resolve type aliases to final type --- .../src/ir/ir_helpers/to_baml_arg.rs | 1 + .../baml-lib/baml-core/src/ir/json_schema.rs | 1 + .../baml-lib/baml-types/src/field_type/mod.rs | 162 +++++++++--------- .../jinja-runtime/src/output_format/types.rs | 2 + .../src/deserializer/coercer/field_type.rs | 5 +- engine/baml-lib/jsonish/src/tests/mod.rs | 2 + engine/baml-lib/parser-database/src/lib.rs | 35 +++- .../baml-lib/parser-database/src/names/mod.rs | 3 + .../baml-lib/parser-database/src/types/mod.rs | 80 ++++++++- .../src/ast/value_expression_block.rs | 2 +- .../prompt_renderer/render_output_format.rs | 1 + engine/language_client_codegen/src/openapi.rs | 61 ++++--- .../src/python/generate_types.rs | 2 + .../language_client_codegen/src/python/mod.rs | 2 + .../src/ruby/field_type.rs | 18 +- .../src/ruby/generate_types.rs | 22 +-- .../src/typescript/mod.rs | 17 +- 17 files changed, 272 insertions(+), 144 deletions(-) diff --git a/engine/baml-lib/baml-core/src/ir/ir_helpers/to_baml_arg.rs b/engine/baml-lib/baml-core/src/ir/ir_helpers/to_baml_arg.rs index 65128b65e..af203f867 100644 --- a/engine/baml-lib/baml-core/src/ir/ir_helpers/to_baml_arg.rs +++ b/engine/baml-lib/baml-core/src/ir/ir_helpers/to_baml_arg.rs @@ -263,6 +263,7 @@ impl ArgCoercer { Err(()) } }, + (FieldType::Alias(name, target), _) => todo!(), (FieldType::List(item), _) => match value { BamlValue::List(arr) => { let mut items = Vec::new(); diff --git a/engine/baml-lib/baml-core/src/ir/json_schema.rs b/engine/baml-lib/baml-core/src/ir/json_schema.rs index 6d218651b..ae878eff6 100644 --- a/engine/baml-lib/baml-core/src/ir/json_schema.rs +++ b/engine/baml-lib/baml-core/src/ir/json_schema.rs @@ -159,6 +159,7 @@ impl<'db> WithJsonSchema for FieldType { FieldType::Class(name) | FieldType::Enum(name) => json!({ "$ref": format!("#/definitions/{}", name), }), + FieldType::Alias(_, target) => todo!(), FieldType::Literal(v) => json!({ "const": v.to_string(), }), diff --git a/engine/baml-lib/baml-types/src/field_type/mod.rs b/engine/baml-lib/baml-types/src/field_type/mod.rs index 97e578ff0..c3fa79953 100644 --- a/engine/baml-lib/baml-types/src/field_type/mod.rs +++ b/engine/baml-lib/baml-types/src/field_type/mod.rs @@ -82,6 +82,7 @@ pub enum FieldType { Union(Vec<FieldType>), Tuple(Vec<FieldType>), Optional(Box<FieldType>), + Alias(String, Box<FieldType>), Constrained { base: Box<FieldType>, constraints: Vec<Constraint>, @@ -92,11 +93,10 @@ pub enum FieldType { impl std::fmt::Display for FieldType { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - FieldType::Enum(name) | FieldType::Class(name) => { - write!(f, "{}", name) - } - FieldType::Primitive(t) => write!(f, "{}", t), - FieldType::Literal(v) => write!(f, "{}", v), + FieldType::Enum(name) | FieldType::Class(name) => write!(f, "{name}"), + FieldType::Alias(name, _) => write!(f, "{name}"), + FieldType::Primitive(t) => write!(f, "{t}"), + FieldType::Literal(v) => write!(f, "{v}"), FieldType::Union(choices) => { write!( f, @@ -167,83 +167,85 @@ impl FieldType { /// Consider renaming this to `is_assignable_to`. pub fn is_subtype_of(&self, other: &FieldType) -> bool { if self == other { - true - } else { - if let FieldType::Union(items) = other { - if items.iter().any(|item| self.is_subtype_of(item)) { - return true; - } + return true; + } + + if let FieldType::Union(items) = other { + if items.iter().any(|item| self.is_subtype_of(item)) { + return true; + } + } + + match (self, other) { + (FieldType::Primitive(TypeValue::Null), FieldType::Optional(_)) => true, + (FieldType::Optional(self_item), FieldType::Optional(other_item)) => { + self_item.is_subtype_of(other_item) + } + (_, FieldType::Optional(t)) => self.is_subtype_of(t), + (FieldType::Optional(_), _) => false, + + // Handle types that nest other types. + (FieldType::List(self_item), FieldType::List(other_item)) => { + self_item.is_subtype_of(other_item) + } + (FieldType::List(_), _) => false, + + (FieldType::Map(self_k, self_v), FieldType::Map(other_k, other_v)) => { + other_k.is_subtype_of(self_k) && (**self_v).is_subtype_of(other_v) } - match (self, other) { - (FieldType::Primitive(TypeValue::Null), FieldType::Optional(_)) => true, - (FieldType::Optional(self_item), FieldType::Optional(other_item)) => { - self_item.is_subtype_of(other_item) - } - (_, FieldType::Optional(t)) => self.is_subtype_of(t), - (FieldType::Optional(_), _) => false, - - // Handle types that nest other types. - (FieldType::List(self_item), FieldType::List(other_item)) => { - self_item.is_subtype_of(other_item) - } - (FieldType::List(_), _) => false, - - (FieldType::Map(self_k, self_v), FieldType::Map(other_k, other_v)) => { - other_k.is_subtype_of(self_k) && (**self_v).is_subtype_of(other_v) - } - (FieldType::Map(_, _), _) => false, - - ( - FieldType::Constrained { - base: self_base, - constraints: self_cs, - }, - FieldType::Constrained { - base: other_base, - constraints: other_cs, - }, - ) => self_base.is_subtype_of(other_base) && self_cs == other_cs, - (FieldType::Constrained { base, .. }, _) => base.is_subtype_of(other), - (_, FieldType::Constrained { base, .. }) => self.is_subtype_of(base), - ( - FieldType::Literal(LiteralValue::Bool(_)), - FieldType::Primitive(TypeValue::Bool), - ) => true, - (FieldType::Literal(LiteralValue::Bool(_)), _) => { - self.is_subtype_of(&FieldType::Primitive(TypeValue::Bool)) - } - ( - FieldType::Literal(LiteralValue::Int(_)), - FieldType::Primitive(TypeValue::Int), - ) => true, - (FieldType::Literal(LiteralValue::Int(_)), _) => { - self.is_subtype_of(&FieldType::Primitive(TypeValue::Int)) - } - ( - FieldType::Literal(LiteralValue::String(_)), - FieldType::Primitive(TypeValue::String), - ) => true, - (FieldType::Literal(LiteralValue::String(_)), _) => { - self.is_subtype_of(&FieldType::Primitive(TypeValue::String)) - } - - (FieldType::Union(self_items), _) => self_items - .iter() - .all(|self_item| self_item.is_subtype_of(other)), - - (FieldType::Tuple(self_items), FieldType::Tuple(other_items)) => { - self_items.len() == other_items.len() - && self_items - .iter() - .zip(other_items) - .all(|(self_item, other_item)| self_item.is_subtype_of(other_item)) - } - (FieldType::Tuple(_), _) => false, - - (FieldType::Primitive(_), _) => false, - (FieldType::Enum(_), _) => false, - (FieldType::Class(_), _) => false, + (FieldType::Map(_, _), _) => false, + + ( + FieldType::Constrained { + base: self_base, + constraints: self_cs, + }, + FieldType::Constrained { + base: other_base, + constraints: other_cs, + }, + ) => self_base.is_subtype_of(other_base) && self_cs == other_cs, + (FieldType::Constrained { base, .. }, _) => base.is_subtype_of(other), + (_, FieldType::Constrained { base, .. }) => self.is_subtype_of(base), + (FieldType::Literal(LiteralValue::Bool(_)), FieldType::Primitive(TypeValue::Bool)) => { + true + } + (FieldType::Literal(LiteralValue::Bool(_)), _) => { + self.is_subtype_of(&FieldType::Primitive(TypeValue::Bool)) + } + (FieldType::Literal(LiteralValue::Int(_)), FieldType::Primitive(TypeValue::Int)) => { + true + } + (FieldType::Literal(LiteralValue::Int(_)), _) => { + self.is_subtype_of(&FieldType::Primitive(TypeValue::Int)) + } + ( + FieldType::Literal(LiteralValue::String(_)), + FieldType::Primitive(TypeValue::String), + ) => true, + (FieldType::Literal(LiteralValue::String(_)), _) => { + self.is_subtype_of(&FieldType::Primitive(TypeValue::String)) + } + + (FieldType::Union(self_items), _) => self_items + .iter() + .all(|self_item| self_item.is_subtype_of(other)), + + (FieldType::Tuple(self_items), FieldType::Tuple(other_items)) => { + self_items.len() == other_items.len() + && self_items + .iter() + .zip(other_items) + .all(|(self_item, other_item)| self_item.is_subtype_of(other_item)) } + // TODO: Can this cause infinite recursion? + // Should the final resolved type (following all the aliases) be + // included in the variant so that we skip recursion? + (FieldType::Alias(_, target), _) => target.is_subtype_of(other), + (FieldType::Tuple(_), _) => false, + (FieldType::Primitive(_), _) => false, + (FieldType::Enum(_), _) => false, + (FieldType::Class(_), _) => false, } } } diff --git a/engine/baml-lib/jinja-runtime/src/output_format/types.rs b/engine/baml-lib/jinja-runtime/src/output_format/types.rs index 23f1d50ae..575cf8d30 100644 --- a/engine/baml-lib/jinja-runtime/src/output_format/types.rs +++ b/engine/baml-lib/jinja-runtime/src/output_format/types.rs @@ -333,6 +333,7 @@ impl OutputFormatContent { Some(format!("Answer in JSON using this {type_prefix}:{end}")) } + FieldType::Alias(_, _) => todo!(), FieldType::List(_) => Some(String::from( "Answer with a JSON Array using this schema:\n", )), @@ -481,6 +482,7 @@ impl OutputFormatContent { } .to_string() } + FieldType::Alias(_, _) => todo!(), FieldType::List(inner) => { let is_recursive = match inner.as_ref() { FieldType::Class(nested_class) => self.recursive_classes.contains(nested_class), diff --git a/engine/baml-lib/jsonish/src/deserializer/coercer/field_type.rs b/engine/baml-lib/jsonish/src/deserializer/coercer/field_type.rs index 9de18218b..a9fbb8f27 100644 --- a/engine/baml-lib/jsonish/src/deserializer/coercer/field_type.rs +++ b/engine/baml-lib/jsonish/src/deserializer/coercer/field_type.rs @@ -79,6 +79,7 @@ impl TypeCoercer for FieldType { FieldType::Enum(e) => IrRef::Enum(e).coerce(ctx, target, value), FieldType::Literal(l) => l.coerce(ctx, target, value), FieldType::Class(c) => IrRef::Class(c).coerce(ctx, target, value), + FieldType::Alias(_, _) => todo!(), FieldType::List(_) => coerce_array(ctx, self, value), FieldType::Union(_) => coerce_union(ctx, self, value), FieldType::Optional(_) => coerce_optional(ctx, self, value), @@ -139,7 +140,8 @@ pub fn validate_asserts(constraints: &Vec<(Constraint, bool)>) -> Result<(), Par expr.0 ), scope: vec![], - }).collect::<Vec<_>>(); + }) + .collect::<Vec<_>>(); if causes.len() > 0 { Err(ParsingError { causes: vec![], @@ -163,6 +165,7 @@ impl DefaultValue for FieldType { FieldType::Enum(e) => None, FieldType::Literal(_) => None, FieldType::Class(_) => None, + FieldType::Alias(_, _) => todo!(), FieldType::List(_) => Some(BamlValueWithFlags::List(get_flags(), Vec::new())), FieldType::Union(items) => items.iter().find_map(|i| i.default_value(error)), FieldType::Primitive(TypeValue::Null) | FieldType::Optional(_) => { diff --git a/engine/baml-lib/jsonish/src/tests/mod.rs b/engine/baml-lib/jsonish/src/tests/mod.rs index ce568b4d4..3ecc3451e 100644 --- a/engine/baml-lib/jsonish/src/tests/mod.rs +++ b/engine/baml-lib/jsonish/src/tests/mod.rs @@ -24,6 +24,7 @@ use std::{ use baml_types::BamlValue; use internal_baml_core::{ + ast::Field, internal_baml_diagnostics::SourceFile, ir::{repr::IntermediateRepr, ClassWalker, EnumWalker, FieldType, IRHelper, TypeValue}, validate, @@ -231,6 +232,7 @@ fn relevant_data_models<'a>( }); } } + (FieldType::Alias(_, _), _) => todo!(), (FieldType::Literal(_), _) => {} (FieldType::Primitive(_), _constraints) => {} (FieldType::Constrained { .. }, _) => { diff --git a/engine/baml-lib/parser-database/src/lib.rs b/engine/baml-lib/parser-database/src/lib.rs index f4fe8cf1a..5e5f3fe47 100644 --- a/engine/baml-lib/parser-database/src/lib.rs +++ b/engine/baml-lib/parser-database/src/lib.rs @@ -36,7 +36,7 @@ mod names; mod tarjan; mod types; -use std::collections::{HashMap, HashSet}; +use std::collections::{HashMap, HashSet, VecDeque}; pub use coerce_expression::{coerce, coerce_array, coerce_opt}; pub use internal_baml_schema_ast::ast; @@ -131,6 +131,35 @@ impl ParserDatabase { } fn finalize_dependencies(&mut self, diag: &mut Diagnostics) { + // Fully resolve type aliases. + for (id, targets) in self.types.type_aliases.iter() { + let mut resolved = HashSet::new(); + let mut queue = VecDeque::from_iter(targets.iter()); + + while let Some(target) = queue.pop_front() { + match self.find_type_by_str(target) { + Some(TypeWalker::Class(_) | TypeWalker::Enum(_)) => { + resolved.insert(target.to_owned()); + } + // TODO: Cycles and recursive stuff. + Some(TypeWalker::TypeAlias(alias)) => { + let alias_id = alias.id; + + if let Some(already_resolved) = + self.types.resolved_type_aliases.get_mut(&alias_id) + { + resolved.extend(already_resolved.iter().cloned()); + } else { + queue.extend(&self.types.type_aliases[&alias_id]) + } + } + None => panic!("Type alias pointing to invalid type `{target}`"), + }; + } + + self.types.resolved_type_aliases.insert(*id, resolved); + } + // NOTE: Class dependency cycles are already checked at // baml-lib/baml-core/src/validate/validation_pipeline/validations/cycle.rs // @@ -203,7 +232,9 @@ impl ParserDatabase { Some(walker.dependencies().iter().cloned()) } Some(TypeWalker::Enum(_)) => None, - Some(TypeWalker::TypeAlias(_)) => todo!(), + Some(TypeWalker::TypeAlias(walker)) => { + Some(self.types.resolved_type_aliases[&walker.id].iter().cloned()) + } _ => panic!("Unknown class `{}`", f), }) .flatten() diff --git a/engine/baml-lib/parser-database/src/names/mod.rs b/engine/baml-lib/parser-database/src/names/mod.rs index ada725b22..0eaf3a9b3 100644 --- a/engine/baml-lib/parser-database/src/names/mod.rs +++ b/engine/baml-lib/parser-database/src/names/mod.rs @@ -23,6 +23,7 @@ pub(super) struct Names { /// Tests have their own namespace. pub(super) tests: HashMap<StringId, HashMap<StringId, TopId>>, pub(super) model_fields: HashMap<(ast::TypeExpId, StringId), ast::FieldId>, + pub(super) type_aliases: HashMap<ast::TypeExpId, Option<StringId>>, // pub(super) composite_type_fields: HashMap<(ast::CompositeTypeId, StringId), ast::FieldId>, } @@ -94,6 +95,8 @@ pub(super) fn resolve_names(ctx: &mut Context<'_>) { (ast::TopId::TypeAlias(_), ast::Top::TypeAlias(type_alias)) => { validate_type_alias_name(type_alias, ctx.diagnostics); + let type_alias_id = ctx.interner.intern(type_alias.name()); + Some(either::Left(&mut names.tops)) } diff --git a/engine/baml-lib/parser-database/src/types/mod.rs b/engine/baml-lib/parser-database/src/types/mod.rs index ddb13b4ea..b817205cb 100644 --- a/engine/baml-lib/parser-database/src/types/mod.rs +++ b/engine/baml-lib/parser-database/src/types/mod.rs @@ -1,4 +1,4 @@ -use std::collections::{HashMap, HashSet}; +use std::collections::{HashMap, HashSet, VecDeque}; use std::hash::Hash; use crate::coerce; @@ -9,7 +9,7 @@ use indexmap::IndexMap; use internal_baml_diagnostics::Span; use internal_baml_prompt_parser::ast::{ChatBlock, PrinterBlock, Variable}; use internal_baml_schema_ast::ast::{ - self, Expression, FieldId, RawString, ValExpId, WithIdentifier, WithName, WithSpan, + self, Expression, FieldId, FieldType, RawString, ValExpId, WithIdentifier, WithName, WithSpan, }; mod configurations; @@ -32,6 +32,12 @@ pub(super) fn resolve_types(ctx: &mut Context<'_>) { visit_class(idx, model, ctx); } (_, ast::Top::Class(_)) => unreachable!("Class misconfigured"), + + (ast::TopId::TypeAlias(idx), ast::Top::TypeAlias(assignment)) => { + visit_type_alias(idx, assignment, ctx); + } + (_, ast::Top::TypeAlias(assignment)) => unreachable!("Type alias misconfigured"), + (ast::TopId::TemplateString(idx), ast::Top::TemplateString(template_string)) => { visit_template_string(idx, template_string, ctx) } @@ -225,6 +231,50 @@ pub(super) struct Types { pub(super) class_dependencies: HashMap<ast::TypeExpId, HashSet<String>>, pub(super) enum_dependencies: HashMap<ast::TypeExpId, HashSet<String>>, + /// Graph of type aliases. + /// + /// A type alias can point to one or many different types (with a union). + /// + /// Base case is primitives: + /// + /// ```ignore + /// type Alias = int + /// ``` + /// + /// In that case the type doesn't "point" to anything. + /// + /// Second case is classes, enums or other aliases: + /// + /// ```ignore + /// type ClassAlias = SomeClass + /// type EnumAlias = SomeEnum + /// type Alias = ClassAlias + /// ``` + /// + /// Third, an alias can point to many of the above through unions: + /// + /// ```ignore + /// type Alias = SomeClass | EnumAlias + /// ``` + /// + /// This graph stores the names of all the symbols that the type alias + /// points to. + pub(super) type_aliases: HashMap<ast::TypeExpId, HashSet<String>>, + + /// Same as [`Self::type_aliases`] but without intermediate edges in the + /// graph. + /// + /// Pointers here point directly to the resolved type. Example: + /// + /// ``` + /// type AliasOne = SomeClass + /// type AliasTwo = AliasOne + /// type AliasThree = AliasTwo + /// ``` + /// + /// Contents would be `AliasThree -> SomeClass`. + pub(super) resolved_type_aliases: HashMap<ast::TypeExpId, HashSet<String>>, + /// Strongly connected components of the dependency graph. /// /// Basically contains all the different cycles. This allows us to find a @@ -336,6 +386,32 @@ fn visit_class<'db>( }); } +fn visit_type_alias<'db>( + alias_id: ast::TypeExpId, + assignment: &'db ast::Assignment, + ctx: &mut Context<'db>, +) { + let targets = ctx + .types + .type_aliases + .entry(alias_id) + .or_insert(HashSet::new()); + + // Find all the symbols that the type alias points to. + let mut queue = VecDeque::from_iter([&assignment.value]); + while let Some(item) = queue.pop_front() { + match item { + FieldType::Symbol(..) => { + targets.insert(item.name()); + } + FieldType::Union(_, items, ..) | FieldType::Tuple(_, items, ..) => { + queue.extend(items.iter()); + } + _ => {} + } + } +} + fn visit_function<'db>(idx: ValExpId, function: &'db ast::ValueExprBlock, ctx: &mut Context<'db>) { let input_deps = function .input() diff --git a/engine/baml-lib/schema-ast/src/ast/value_expression_block.rs b/engine/baml-lib/schema-ast/src/ast/value_expression_block.rs index d1108d876..8c42ca3a0 100644 --- a/engine/baml-lib/schema-ast/src/ast/value_expression_block.rs +++ b/engine/baml-lib/schema-ast/src/ast/value_expression_block.rs @@ -64,7 +64,7 @@ pub struct BlockArgs { pub(crate) span: Span, } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone)] pub enum ValueExprBlockType { Function, Client, diff --git a/engine/baml-runtime/src/internal/prompt_renderer/render_output_format.rs b/engine/baml-runtime/src/internal/prompt_renderer/render_output_format.rs index 4d93fa0af..de663f65f 100644 --- a/engine/baml-runtime/src/internal/prompt_renderer/render_output_format.rs +++ b/engine/baml-runtime/src/internal/prompt_renderer/render_output_format.rs @@ -363,6 +363,7 @@ fn relevant_data_models<'a>( recursive_classes.insert(cls.to_owned()); } } + (FieldType::Alias(_, _), _) => todo!(), (FieldType::Literal(_), _) => {} (FieldType::Primitive(_), _) => {} (FieldType::Constrained { .. }, _) => { diff --git a/engine/language_client_codegen/src/openapi.rs b/engine/language_client_codegen/src/openapi.rs index e241a789d..84dd3ff3f 100644 --- a/engine/language_client_codegen/src/openapi.rs +++ b/engine/language_client_codegen/src/openapi.rs @@ -10,7 +10,10 @@ use internal_baml_core::ir::{ use serde::Serialize; use serde_json::json; -use crate::{dir_writer::{FileCollector, LanguageFeatures, RemoveDirBehavior}, field_type_attributes, TypeCheckAttributes}; +use crate::{ + dir_writer::{FileCollector, LanguageFeatures, RemoveDirBehavior}, + field_type_attributes, TypeCheckAttributes, +}; #[derive(Default)] pub(super) struct OpenApiLanguageFeatures {} @@ -347,7 +350,9 @@ impl<'ir> TryFrom<(&'ir IntermediateRepr, &'_ crate::GeneratorArgs)> for OpenApi fn check() -> TypeSpecWithMeta { TypeSpecWithMeta { meta: TypeMetadata::default(), - type_spec: TypeSpec::Ref{ r#ref: "#components/schemas/Check".to_string() }, + type_spec: TypeSpec::Ref { + r#ref: "#components/schemas/Check".to_string(), + }, } } @@ -357,13 +362,15 @@ fn check() -> TypeSpecWithMeta { fn type_def_for_checks(checks: TypeCheckAttributes) -> TypeSpecWithMeta { TypeSpecWithMeta { meta: TypeMetadata::default(), - type_spec: TypeSpec::Inline( - TypeDef::Class { - properties: checks.0.iter().map(|check_name| (check_name.clone(), check())).collect(), - required: checks.0.into_iter().collect(), - additional_properties: false, - } - ) + type_spec: TypeSpec::Inline(TypeDef::Class { + properties: checks + .0 + .iter() + .map(|check_name| (check_name.clone(), check())) + .collect(), + required: checks.0.into_iter().collect(), + additional_properties: false, + }), } } @@ -534,6 +541,7 @@ impl<'ir> ToTypeReferenceInTypeDefinition<'ir> for FieldType { r#ref: format!("#/components/schemas/{}", name), }, }, + FieldType::Alias(_, _) => todo!(), FieldType::Literal(v) => TypeSpecWithMeta { meta: TypeMetadata { title: None, @@ -631,26 +639,25 @@ impl<'ir> ToTypeReferenceInTypeDefinition<'ir> for FieldType { // something i saw suggested doing this type_spec } - FieldType::Constrained{base,..} => { - match field_type_attributes(self) { - Some(checks) => { - let base_type_ref = base.to_type_spec(ir)?; - let checks_type_spec = type_def_for_checks(checks); - TypeSpecWithMeta { - meta: TypeMetadata::default(), - type_spec: TypeSpec::Inline( - TypeDef::Class { - properties: vec![("value".to_string(), base_type_ref),("checks".to_string(), checks_type_spec)].into_iter().collect(), - required: vec!["value".to_string(), "checks".to_string()], - additional_properties: false, - } - ) - } - } - None => { - base.to_type_spec(ir)? + FieldType::Constrained { base, .. } => match field_type_attributes(self) { + Some(checks) => { + let base_type_ref = base.to_type_spec(ir)?; + let checks_type_spec = type_def_for_checks(checks); + TypeSpecWithMeta { + meta: TypeMetadata::default(), + type_spec: TypeSpec::Inline(TypeDef::Class { + properties: vec![ + ("value".to_string(), base_type_ref), + ("checks".to_string(), checks_type_spec), + ] + .into_iter() + .collect(), + required: vec!["value".to_string(), "checks".to_string()], + additional_properties: false, + }), } } + None => base.to_type_spec(ir)?, }, }) } diff --git a/engine/language_client_codegen/src/python/generate_types.rs b/engine/language_client_codegen/src/python/generate_types.rs index 4c2044770..85ad52c35 100644 --- a/engine/language_client_codegen/src/python/generate_types.rs +++ b/engine/language_client_codegen/src/python/generate_types.rs @@ -226,6 +226,7 @@ impl ToTypeReferenceInTypeDefinition for FieldType { format!("\"{name}\"") } } + FieldType::Alias(_, _) => todo!(), FieldType::Literal(value) => to_python_literal(value), FieldType::Class(name) => format!("\"{name}\""), FieldType::List(inner) => format!("List[{}]", inner.to_type_ref(ir)), @@ -281,6 +282,7 @@ impl ToTypeReferenceInTypeDefinition for FieldType { format!("Optional[types.{name}]") } } + FieldType::Alias(_, _) => todo!(), FieldType::Literal(value) => to_python_literal(value), FieldType::List(inner) => format!("List[{}]", inner.to_partial_type_ref(ir, true)), FieldType::Map(key, value) => { diff --git a/engine/language_client_codegen/src/python/mod.rs b/engine/language_client_codegen/src/python/mod.rs index fb1e6dc41..851bbf390 100644 --- a/engine/language_client_codegen/src/python/mod.rs +++ b/engine/language_client_codegen/src/python/mod.rs @@ -201,6 +201,7 @@ impl ToTypeReferenceInClientDefinition for FieldType { } } FieldType::Literal(value) => to_python_literal(value), + FieldType::Alias(_, _) => todo!(), FieldType::Class(name) => format!("types.{name}"), FieldType::List(inner) => format!("List[{}]", inner.to_type_ref(ir, with_checked)), FieldType::Map(key, value) => { @@ -255,6 +256,7 @@ impl ToTypeReferenceInClientDefinition for FieldType { } } FieldType::Class(name) => format!("partial_types.{name}"), + FieldType::Alias(_, _) => todo!(), FieldType::Literal(value) => to_python_literal(value), FieldType::List(inner) => { format!("List[{}]", inner.to_partial_type_ref(ir, with_checked)) diff --git a/engine/language_client_codegen/src/ruby/field_type.rs b/engine/language_client_codegen/src/ruby/field_type.rs index 91e2cbb83..2541659fb 100644 --- a/engine/language_client_codegen/src/ruby/field_type.rs +++ b/engine/language_client_codegen/src/ruby/field_type.rs @@ -1,4 +1,3 @@ - use baml_types::{BamlMediaType, FieldType, TypeValue}; use crate::field_type_attributes; @@ -10,6 +9,7 @@ impl ToRuby for FieldType { match self { FieldType::Class(name) => format!("Baml::Types::{}", name.clone()), FieldType::Enum(name) => format!("T.any(Baml::Types::{}, String)", name.clone()), + FieldType::Alias(_, _) => todo!(), // TODO: Temporary solution until we figure out Ruby literals. FieldType::Literal(value) => value.literal_base_type().to_ruby(), // https://sorbet.org/docs/stdlib-generics @@ -48,17 +48,13 @@ impl ToRuby for FieldType { .join(", ") ), FieldType::Optional(inner) => format!("T.nilable({})", inner.to_ruby()), - FieldType::Constrained{base,..} => { - match field_type_attributes(self) { - Some(_) => { - let base_type_ref = base.to_ruby(); - format!("Baml::Checked[{base_type_ref}]") - } - None => { - base.to_ruby() - } + FieldType::Constrained { base, .. } => match field_type_attributes(self) { + Some(_) => { + let base_type_ref = base.to_ruby(); + format!("Baml::Checked[{base_type_ref}]") } - } + None => base.to_ruby(), + }, } } } diff --git a/engine/language_client_codegen/src/ruby/generate_types.rs b/engine/language_client_codegen/src/ruby/generate_types.rs index 4f007f3e7..827136b58 100644 --- a/engine/language_client_codegen/src/ruby/generate_types.rs +++ b/engine/language_client_codegen/src/ruby/generate_types.rs @@ -85,7 +85,12 @@ impl<'ir> From<ClassWalker<'ir>> for RubyStruct<'ir> { .elem .static_fields .iter() - .map(|f| (Cow::Borrowed(f.elem.name.as_str()), f.elem.r#type.elem.to_type_ref())) + .map(|f| { + ( + Cow::Borrowed(f.elem.name.as_str()), + f.elem.r#type.elem.to_type_ref(), + ) + }) .collect(), } } @@ -136,6 +141,7 @@ impl ToTypeReferenceInTypeDefinition for FieldType { match self { FieldType::Class(name) => format!("Baml::PartialTypes::{}", name.clone()), FieldType::Enum(name) => format!("T.nilable(Baml::Types::{})", name.clone()), + FieldType::Alias(_, target) => todo!(), // TODO: Temporary solution until we figure out Ruby literals. FieldType::Literal(value) => value.literal_base_type().to_partial_type_ref(), // https://sorbet.org/docs/stdlib-generics @@ -167,16 +173,12 @@ impl ToTypeReferenceInTypeDefinition for FieldType { .join(", ") ), FieldType::Optional(inner) => inner.to_partial_type_ref(), - FieldType::Constrained{base,..} => { - match field_type_attributes(self) { - Some(checks) => { - let base_type_ref = base.to_partial_type_ref(); - format!("Baml::Checked[{base_type_ref}]") - } - None => { - base.to_partial_type_ref() - } + FieldType::Constrained { base, .. } => match field_type_attributes(self) { + Some(checks) => { + let base_type_ref = base.to_partial_type_ref(); + format!("Baml::Checked[{base_type_ref}]") } + None => base.to_partial_type_ref(), }, } } diff --git a/engine/language_client_codegen/src/typescript/mod.rs b/engine/language_client_codegen/src/typescript/mod.rs index c437fb03f..b4345b5e4 100644 --- a/engine/language_client_codegen/src/typescript/mod.rs +++ b/engine/language_client_codegen/src/typescript/mod.rs @@ -266,6 +266,7 @@ impl ToTypeReferenceInClientDefinition for FieldType { } } FieldType::Class(name) => format!("{name}"), + FieldType::Alias(_, target) => todo!(), FieldType::List(inner) => match inner.as_ref() { FieldType::Union(_) | FieldType::Optional(_) => { format!("({})[]", inner.to_type_ref(ir)) @@ -295,17 +296,13 @@ impl ToTypeReferenceInClientDefinition for FieldType { .join(", ") ), FieldType::Optional(inner) => format!("{} | null", inner.to_type_ref(ir)), - FieldType::Constrained{base,..} => { - match field_type_attributes(self) { - Some(checks) => { - let base_type_ref = base.to_type_ref(ir); - let checks_type_ref = type_name_for_checks(&checks); - format!("Checked<{base_type_ref},{checks_type_ref}>") - } - None => { - base.to_type_ref(ir) - } + FieldType::Constrained { base, .. } => match field_type_attributes(self) { + Some(checks) => { + let base_type_ref = base.to_type_ref(ir); + let checks_type_ref = type_name_for_checks(&checks); + format!("Checked<{base_type_ref},{checks_type_ref}>") } + None => base.to_type_ref(ir), }, } } From 0adebb0ea55f8669905376c80185ac072fc677f2 Mon Sep 17 00:00:00 2001 From: Antonio Sarosi <sarosiantonio@gmail.com> Date: Thu, 21 Nov 2024 01:18:24 +0000 Subject: [PATCH 10/51] Store type alias in IR --- engine/baml-lib/baml-core/src/ir/repr.rs | 40 ++++++++++++------- engine/baml-lib/parser-database/src/lib.rs | 6 +-- .../baml-lib/parser-database/src/types/mod.rs | 6 +-- .../parser-database/src/walkers/alias.rs | 36 +++++++++++++++-- .../parser-database/src/walkers/class.rs | 8 ++-- engine/baml-lib/schema-ast/src/ast.rs | 20 ++++++++-- engine/baml-lib/schema-ast/src/ast/top.rs | 7 ++++ 7 files changed, 93 insertions(+), 30 deletions(-) diff --git a/engine/baml-lib/baml-core/src/ir/repr.rs b/engine/baml-lib/baml-core/src/ir/repr.rs index b642b8890..646a861a9 100644 --- a/engine/baml-lib/baml-core/src/ir/repr.rs +++ b/engine/baml-lib/baml-core/src/ir/repr.rs @@ -414,8 +414,7 @@ impl WithRepr<FieldType> for ast::FieldType { match db.find_type(idn) { Some(TypeWalker::Class(class_walker)) => { let base_class = FieldType::Class(class_walker.name().to_string()); - let maybe_constraints = class_walker.get_constraints(SubType::Class); - match maybe_constraints { + match class_walker.get_constraints(SubType::Class) { Some(constraints) if constraints.len() > 0 => FieldType::Constrained { base: Box::new(base_class), constraints, @@ -425,8 +424,7 @@ impl WithRepr<FieldType> for ast::FieldType { } Some(TypeWalker::Enum(enum_walker)) => { let base_type = FieldType::Enum(enum_walker.name().to_string()); - let maybe_constraints = enum_walker.get_constraints(SubType::Enum); - match maybe_constraints { + match enum_walker.get_constraints(SubType::Enum) { Some(constraints) if constraints.len() > 0 => FieldType::Constrained { base: Box::new(base_type), constraints, @@ -434,7 +432,10 @@ impl WithRepr<FieldType> for ast::FieldType { _ => base_type, } } - Some(TypeWalker::TypeAlias(type_alias_walker)) => todo!(), + Some(TypeWalker::TypeAlias(alias_walker)) => FieldType::Alias( + alias_walker.name().to_owned(), + Box::new(alias_walker.ast_field_type().repr(db)?), + ), None => return Err(anyhow!("Field type uses unresolvable local identifier")), }, arity, @@ -676,8 +677,14 @@ impl WithRepr<Enum> for EnumWalker<'_> { fn repr(&self, db: &ParserDatabase) -> Result<Enum> { Ok(Enum { name: self.name().to_string(), - values: self.values().map(|w| (w.node(db).map(|v| (v, w.documentation().map(|s| Docstring(s.to_string())))))).collect::<Result<Vec<_>,_>>()?, - docstring: self.get_documentation().map(|s| Docstring(s)) + values: self + .values() + .map(|w| { + (w.node(db) + .map(|v| (v, w.documentation().map(|s| Docstring(s.to_string()))))) + }) + .collect::<Result<Vec<_>, _>>()?, + docstring: self.get_documentation().map(|s| Docstring(s)), }) } } @@ -722,7 +729,6 @@ impl WithRepr<Field> for FieldWalker<'_> { docstring: self.get_documentation().map(|s| Docstring(s)), }) } - } type ClassId = String; @@ -774,7 +780,7 @@ impl WithRepr<Class> for ClassWalker<'_> { .collect::<Result<Vec<_>>>()?, None => Vec::new(), }, - docstring: self.get_documentation().map(|s| Docstring(s)) + docstring: self.get_documentation().map(|s| Docstring(s)), }) } } @@ -1223,7 +1229,8 @@ mod tests { #[test] fn test_docstrings() { - let ir = make_test_ir(r#" + let ir = make_test_ir( + r#" /// Foo class. class Foo { /// Bar field. @@ -1243,7 +1250,9 @@ mod tests { THIRD } - "#).unwrap(); + "#, + ) + .unwrap(); // Test class docstrings let foo = ir.find_class("Foo").as_ref().unwrap().clone().elem(); @@ -1252,7 +1261,7 @@ mod tests { [field1, field2] => { assert_eq!(field1.elem.docstring.as_ref().unwrap().0, "Bar field."); assert_eq!(field2.elem.docstring.as_ref().unwrap().0, "Baz field."); - }, + } _ => { panic!("Expected 2 fields"); } @@ -1260,7 +1269,10 @@ mod tests { // Test enum docstrings let test_enum = ir.find_enum("TestEnum").as_ref().unwrap().clone().elem(); - assert_eq!(test_enum.docstring.as_ref().unwrap().0.as_str(), "Test enum."); + assert_eq!( + test_enum.docstring.as_ref().unwrap().0.as_str(), + "Test enum." + ); match test_enum.values.as_slice() { [val1, val2, val3] => { assert_eq!(val1.0.elem.0, "FIRST"); @@ -1269,7 +1281,7 @@ mod tests { assert_eq!(val2.1.as_ref().unwrap().0, "Second variant."); assert_eq!(val3.0.elem.0, "THIRD"); assert!(val3.1.is_none()); - }, + } _ => { panic!("Expected 3 enum values"); } diff --git a/engine/baml-lib/parser-database/src/lib.rs b/engine/baml-lib/parser-database/src/lib.rs index 5e5f3fe47..7739e068b 100644 --- a/engine/baml-lib/parser-database/src/lib.rs +++ b/engine/baml-lib/parser-database/src/lib.rs @@ -143,14 +143,12 @@ impl ParserDatabase { } // TODO: Cycles and recursive stuff. Some(TypeWalker::TypeAlias(alias)) => { - let alias_id = alias.id; - if let Some(already_resolved) = - self.types.resolved_type_aliases.get_mut(&alias_id) + self.types.resolved_type_aliases.get(&alias.id) { resolved.extend(already_resolved.iter().cloned()); } else { - queue.extend(&self.types.type_aliases[&alias_id]) + queue.extend(&self.types.type_aliases[&alias.id]) } } None => panic!("Type alias pointing to invalid type `{target}`"), diff --git a/engine/baml-lib/parser-database/src/types/mod.rs b/engine/baml-lib/parser-database/src/types/mod.rs index b817205cb..e32abfccf 100644 --- a/engine/baml-lib/parser-database/src/types/mod.rs +++ b/engine/baml-lib/parser-database/src/types/mod.rs @@ -259,7 +259,7 @@ pub(super) struct Types { /// /// This graph stores the names of all the symbols that the type alias /// points to. - pub(super) type_aliases: HashMap<ast::TypeExpId, HashSet<String>>, + pub(super) type_aliases: HashMap<ast::TypeAliasId, HashSet<String>>, /// Same as [`Self::type_aliases`] but without intermediate edges in the /// graph. @@ -273,7 +273,7 @@ pub(super) struct Types { /// ``` /// /// Contents would be `AliasThree -> SomeClass`. - pub(super) resolved_type_aliases: HashMap<ast::TypeExpId, HashSet<String>>, + pub(super) resolved_type_aliases: HashMap<ast::TypeAliasId, HashSet<String>>, /// Strongly connected components of the dependency graph. /// @@ -387,7 +387,7 @@ fn visit_class<'db>( } fn visit_type_alias<'db>( - alias_id: ast::TypeExpId, + alias_id: ast::TypeAliasId, assignment: &'db ast::Assignment, ctx: &mut Context<'db>, ) { diff --git a/engine/baml-lib/parser-database/src/walkers/alias.rs b/engine/baml-lib/parser-database/src/walkers/alias.rs index 832862335..ccd540f35 100644 --- a/engine/baml-lib/parser-database/src/walkers/alias.rs +++ b/engine/baml-lib/parser-database/src/walkers/alias.rs @@ -1,5 +1,35 @@ +use std::collections::HashSet; + use super::TypeWalker; -use internal_baml_schema_ast::ast::{self, Identifier}; +use internal_baml_schema_ast::ast::{self, FieldType, Identifier, WithName}; + +pub type TypeAliasWalker<'db> = super::Walker<'db, ast::TypeAliasId>; + +impl<'db> TypeAliasWalker<'db> { + /// Name of the type alias. + pub fn name(&self) -> &str { + &self.db.ast[self.id].identifier.name() + } + + /// Returns a set containing the names of the types aliased by `self`. + /// + /// Note that the parser DB must be populated for this method to work. + pub fn targets(&self) -> &'db HashSet<String> { + &self.db.types.type_aliases[&self.id] + } + + /// Returns a set containing the final resolution of the type alias. + /// + /// If the set is empty it just means that the type resolves to primitives + /// instead of symbols. + /// + /// Parser DB must be populated before calling this method. + pub fn resolved(&self) -> &'db HashSet<String> { + &self.db.types.resolved_type_aliases[&self.id] + } -/// A `class` declaration in the Prisma schema. -pub type TypeAliasWalker<'db> = super::Walker<'db, ast::TypeExpId>; + /// Returns the field type of the aliased type. + pub fn ast_field_type(&self) -> &'db FieldType { + &self.db.ast[self.id].value + } +} diff --git a/engine/baml-lib/parser-database/src/walkers/class.rs b/engine/baml-lib/parser-database/src/walkers/class.rs index 579d61078..30131559f 100644 --- a/engine/baml-lib/parser-database/src/walkers/class.rs +++ b/engine/baml-lib/parser-database/src/walkers/class.rs @@ -10,7 +10,7 @@ use internal_baml_schema_ast::ast::SubType; use internal_baml_schema_ast::ast::{self, ArgumentId, WithIdentifier, WithName, WithSpan}; use std::collections::HashMap; -/// A `class` declaration in the Prisma schema. +/// Class walker with some helper methods to extract info from the parser DB. pub type ClassWalker<'db> = super::Walker<'db, ast::TypeExpId>; impl<'db> ClassWalker<'db> { @@ -58,10 +58,12 @@ impl<'db> ClassWalker<'db> { }) } - /// Class docstring. pub fn get_documentation(&self) -> Option<String> { - self.ast_type_block().documentation.as_ref().map(|c| c.text.clone()) + self.ast_type_block() + .documentation + .as_ref() + .map(|c| c.text.clone()) } /// The name of the template string. diff --git a/engine/baml-lib/schema-ast/src/ast.rs b/engine/baml-lib/schema-ast/src/ast.rs index 82cd6128e..24b0b8a4f 100644 --- a/engine/baml-lib/schema-ast/src/ast.rs +++ b/engine/baml-lib/schema-ast/src/ast.rs @@ -102,6 +102,20 @@ impl std::ops::Index<TypeExpId> for SchemaAst { } } +/// An opaque identifier for a type alias in a schema AST. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct TypeAliasId(u32); + +impl std::ops::Index<TypeAliasId> for SchemaAst { + type Output = Assignment; + + fn index(&self, index: TypeAliasId) -> &Self::Output { + self.tops[index.0 as usize] + .as_type_alias_assignment() + .expect("expected type expression") + } +} + /// An opaque identifier for a model in a schema AST. Use the /// `schema[model_id]` syntax to resolve the id to an `ast::Model`. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -143,7 +157,7 @@ pub enum TopId { Function(ValExpId), /// A type alias declaration. - TypeAlias(TypeExpId), + TypeAlias(TypeAliasId), /// A client declaration. Client(ValExpId), @@ -221,7 +235,7 @@ impl std::ops::Index<TopId> for SchemaAst { let idx = match index { TopId::Enum(TypeExpId(idx)) => idx, TopId::Class(TypeExpId(idx)) => idx, - TopId::TypeAlias(TypeExpId(idx)) => idx, + TopId::TypeAlias(TypeAliasId(idx)) => idx, TopId::Function(ValExpId(idx)) => idx, TopId::TemplateString(TemplateStringId(idx)) => idx, TopId::Client(ValExpId(idx)) => idx, @@ -239,7 +253,7 @@ fn top_idx_to_top_id(top_idx: usize, top: &Top) -> TopId { Top::Enum(_) => TopId::Enum(TypeExpId(top_idx as u32)), Top::Class(_) => TopId::Class(TypeExpId(top_idx as u32)), Top::Function(_) => TopId::Function(ValExpId(top_idx as u32)), - Top::TypeAlias(_) => TopId::TypeAlias(TypeExpId(top_idx as u32)), + Top::TypeAlias(_) => TopId::TypeAlias(TypeAliasId(top_idx as u32)), Top::Client(_) => TopId::Client(ValExpId(top_idx as u32)), Top::TemplateString(_) => TopId::TemplateString(TemplateStringId(top_idx as u32)), Top::Generator(_) => TopId::Generator(ValExpId(top_idx as u32)), diff --git a/engine/baml-lib/schema-ast/src/ast/top.rs b/engine/baml-lib/schema-ast/src/ast/top.rs index f745f9ac3..dfc67285b 100644 --- a/engine/baml-lib/schema-ast/src/ast/top.rs +++ b/engine/baml-lib/schema-ast/src/ast/top.rs @@ -65,6 +65,13 @@ impl Top { } } + pub fn as_type_alias_assignment(&self) -> Option<&Assignment> { + match self { + Top::TypeAlias(assignment) => Some(assignment), + _ => None, + } + } + pub fn as_template_string(&self) -> Option<&TemplateString> { match self { Top::TemplateString(t) => Some(t), From 90c3654831818731c5ab38176291edc220d72acf Mon Sep 17 00:00:00 2001 From: Antonio Sarosi <sarosiantonio@gmail.com> Date: Thu, 21 Nov 2024 02:21:37 +0000 Subject: [PATCH 11/51] Refactor IR `FieldType::Alias` --- .../baml-core/src/ir/ir_helpers/to_baml_arg.rs | 2 +- engine/baml-lib/baml-core/src/ir/json_schema.rs | 2 +- engine/baml-lib/baml-core/src/ir/repr.rs | 9 +++++---- engine/baml-lib/baml-types/src/field_type/mod.rs | 13 ++++++++++--- engine/baml-lib/parser-database/src/types/mod.rs | 6 ++++++ .../prompt_renderer/render_output_format.rs | 2 +- 6 files changed, 24 insertions(+), 10 deletions(-) diff --git a/engine/baml-lib/baml-core/src/ir/ir_helpers/to_baml_arg.rs b/engine/baml-lib/baml-core/src/ir/ir_helpers/to_baml_arg.rs index af203f867..ecb684e0d 100644 --- a/engine/baml-lib/baml-core/src/ir/ir_helpers/to_baml_arg.rs +++ b/engine/baml-lib/baml-core/src/ir/ir_helpers/to_baml_arg.rs @@ -263,7 +263,7 @@ impl ArgCoercer { Err(()) } }, - (FieldType::Alias(name, target), _) => todo!(), + (FieldType::Alias { .. }, _) => todo!(), (FieldType::List(item), _) => match value { BamlValue::List(arr) => { let mut items = Vec::new(); diff --git a/engine/baml-lib/baml-core/src/ir/json_schema.rs b/engine/baml-lib/baml-core/src/ir/json_schema.rs index bbe00e59d..776aadd27 100644 --- a/engine/baml-lib/baml-core/src/ir/json_schema.rs +++ b/engine/baml-lib/baml-core/src/ir/json_schema.rs @@ -159,7 +159,7 @@ impl<'db> WithJsonSchema for FieldType { FieldType::Class(name) | FieldType::Enum(name) => json!({ "$ref": format!("#/definitions/{}", name), }), - FieldType::Alias(_, target) => todo!(), + FieldType::Alias { .. } => todo!(), FieldType::Literal(v) => json!({ "const": v.to_string(), }), diff --git a/engine/baml-lib/baml-core/src/ir/repr.rs b/engine/baml-lib/baml-core/src/ir/repr.rs index 646a861a9..09a49e8d5 100644 --- a/engine/baml-lib/baml-core/src/ir/repr.rs +++ b/engine/baml-lib/baml-core/src/ir/repr.rs @@ -432,10 +432,11 @@ impl WithRepr<FieldType> for ast::FieldType { _ => base_type, } } - Some(TypeWalker::TypeAlias(alias_walker)) => FieldType::Alias( - alias_walker.name().to_owned(), - Box::new(alias_walker.ast_field_type().repr(db)?), - ), + Some(TypeWalker::TypeAlias(alias_walker)) => FieldType::Alias { + name: alias_walker.name().to_owned(), + target: Box::new(alias_walker.ast_field_type().repr(db)?), + resolution: Box::new(FieldType::int()), // TODO + }, None => return Err(anyhow!("Field type uses unresolvable local identifier")), }, arity, diff --git a/engine/baml-lib/baml-types/src/field_type/mod.rs b/engine/baml-lib/baml-types/src/field_type/mod.rs index c3fa79953..479256fe9 100644 --- a/engine/baml-lib/baml-types/src/field_type/mod.rs +++ b/engine/baml-lib/baml-types/src/field_type/mod.rs @@ -82,7 +82,14 @@ pub enum FieldType { Union(Vec<FieldType>), Tuple(Vec<FieldType>), Optional(Box<FieldType>), - Alias(String, Box<FieldType>), + Alias { + /// Name of the alias. + name: String, + /// Type that the alias points to. + target: Box<FieldType>, + /// Final resolved type (an alias can point to other aliases). + resolution: Box<FieldType>, + }, Constrained { base: Box<FieldType>, constraints: Vec<Constraint>, @@ -94,7 +101,7 @@ impl std::fmt::Display for FieldType { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { FieldType::Enum(name) | FieldType::Class(name) => write!(f, "{name}"), - FieldType::Alias(name, _) => write!(f, "{name}"), + FieldType::Alias { name, .. } => write!(f, "{name}"), FieldType::Primitive(t) => write!(f, "{t}"), FieldType::Literal(v) => write!(f, "{v}"), FieldType::Union(choices) => { @@ -241,7 +248,7 @@ impl FieldType { // TODO: Can this cause infinite recursion? // Should the final resolved type (following all the aliases) be // included in the variant so that we skip recursion? - (FieldType::Alias(_, target), _) => target.is_subtype_of(other), + (FieldType::Alias { target, .. }, _) => target.is_subtype_of(other), (FieldType::Tuple(_), _) => false, (FieldType::Primitive(_), _) => false, (FieldType::Enum(_), _) => false, diff --git a/engine/baml-lib/parser-database/src/types/mod.rs b/engine/baml-lib/parser-database/src/types/mod.rs index e32abfccf..940303780 100644 --- a/engine/baml-lib/parser-database/src/types/mod.rs +++ b/engine/baml-lib/parser-database/src/types/mod.rs @@ -259,6 +259,12 @@ pub(super) struct Types { /// /// This graph stores the names of all the symbols that the type alias /// points to. + /// + /// + /// TODO @antonio: Remove this and just store IDs. Then get field type from + /// AST and resolve final type and store that in resolved_aliases below. + /// Change the type from hash set to field type because resolution requires + /// creating a type that does not even exist in the AST. pub(super) type_aliases: HashMap<ast::TypeAliasId, HashSet<String>>, /// Same as [`Self::type_aliases`] but without intermediate edges in the diff --git a/engine/baml-runtime/src/internal/prompt_renderer/render_output_format.rs b/engine/baml-runtime/src/internal/prompt_renderer/render_output_format.rs index de663f65f..77bc89fcb 100644 --- a/engine/baml-runtime/src/internal/prompt_renderer/render_output_format.rs +++ b/engine/baml-runtime/src/internal/prompt_renderer/render_output_format.rs @@ -363,7 +363,7 @@ fn relevant_data_models<'a>( recursive_classes.insert(cls.to_owned()); } } - (FieldType::Alias(_, _), _) => todo!(), + (FieldType::Alias { .. }, _) => todo!(), (FieldType::Literal(_), _) => {} (FieldType::Primitive(_), _) => {} (FieldType::Constrained { .. }, _) => { From cdb0bb1838052568da2ed01f6a0fdc07eef65b9c Mon Sep 17 00:00:00 2001 From: Antonio Sarosi <sarosiantonio@gmail.com> Date: Fri, 22 Nov 2024 15:20:04 +0000 Subject: [PATCH 12/51] Add `FieldType::Alias` to match cases --- engine/baml-lib/baml-core/src/ir/repr.rs | 2 +- .../jinja-runtime/src/output_format/types.rs | 8 +- .../src/deserializer/coercer/field_type.rs | 4 +- engine/baml-lib/jsonish/src/tests/mod.rs | 2 +- engine/baml-lib/parser-database/src/lib.rs | 41 +---- .../baml-lib/parser-database/src/types/mod.rs | 158 +++++++++++------- .../parser-database/src/walkers/alias.rs | 23 +-- engine/language_client_codegen/src/openapi.rs | 2 +- .../src/python/generate_types.rs | 18 +- .../language_client_codegen/src/python/mod.rs | 4 +- .../src/ruby/field_type.rs | 2 +- .../src/ruby/generate_types.rs | 2 +- .../src/typescript/mod.rs | 2 +- 13 files changed, 140 insertions(+), 128 deletions(-) diff --git a/engine/baml-lib/baml-core/src/ir/repr.rs b/engine/baml-lib/baml-core/src/ir/repr.rs index 09a49e8d5..298861d8e 100644 --- a/engine/baml-lib/baml-core/src/ir/repr.rs +++ b/engine/baml-lib/baml-core/src/ir/repr.rs @@ -434,7 +434,7 @@ impl WithRepr<FieldType> for ast::FieldType { } Some(TypeWalker::TypeAlias(alias_walker)) => FieldType::Alias { name: alias_walker.name().to_owned(), - target: Box::new(alias_walker.ast_field_type().repr(db)?), + target: Box::new(alias_walker.target().repr(db)?), resolution: Box::new(FieldType::int()), // TODO }, None => return Err(anyhow!("Field type uses unresolvable local identifier")), diff --git a/engine/baml-lib/jinja-runtime/src/output_format/types.rs b/engine/baml-lib/jinja-runtime/src/output_format/types.rs index 575cf8d30..5ba899af0 100644 --- a/engine/baml-lib/jinja-runtime/src/output_format/types.rs +++ b/engine/baml-lib/jinja-runtime/src/output_format/types.rs @@ -333,7 +333,9 @@ impl OutputFormatContent { Some(format!("Answer in JSON using this {type_prefix}:{end}")) } - FieldType::Alias(_, _) => todo!(), + FieldType::Alias { resolution, .. } => { + auto_prefix(&resolution, options, output_format_content) + } FieldType::List(_) => Some(String::from( "Answer with a JSON Array using this schema:\n", )), @@ -482,7 +484,9 @@ impl OutputFormatContent { } .to_string() } - FieldType::Alias(_, _) => todo!(), + FieldType::Alias { resolution, .. } => { + self.inner_type_render(options, &resolution, render_state, group_hoisted_literals)? + } FieldType::List(inner) => { let is_recursive = match inner.as_ref() { FieldType::Class(nested_class) => self.recursive_classes.contains(nested_class), diff --git a/engine/baml-lib/jsonish/src/deserializer/coercer/field_type.rs b/engine/baml-lib/jsonish/src/deserializer/coercer/field_type.rs index a9fbb8f27..1da3ddf39 100644 --- a/engine/baml-lib/jsonish/src/deserializer/coercer/field_type.rs +++ b/engine/baml-lib/jsonish/src/deserializer/coercer/field_type.rs @@ -79,7 +79,7 @@ impl TypeCoercer for FieldType { FieldType::Enum(e) => IrRef::Enum(e).coerce(ctx, target, value), FieldType::Literal(l) => l.coerce(ctx, target, value), FieldType::Class(c) => IrRef::Class(c).coerce(ctx, target, value), - FieldType::Alias(_, _) => todo!(), + FieldType::Alias { resolution, .. } => resolution.coerce(ctx, target, value), FieldType::List(_) => coerce_array(ctx, self, value), FieldType::Union(_) => coerce_union(ctx, self, value), FieldType::Optional(_) => coerce_optional(ctx, self, value), @@ -165,7 +165,7 @@ impl DefaultValue for FieldType { FieldType::Enum(e) => None, FieldType::Literal(_) => None, FieldType::Class(_) => None, - FieldType::Alias(_, _) => todo!(), + FieldType::Alias { resolution, .. } => resolution.default_value(error), FieldType::List(_) => Some(BamlValueWithFlags::List(get_flags(), Vec::new())), FieldType::Union(items) => items.iter().find_map(|i| i.default_value(error)), FieldType::Primitive(TypeValue::Null) | FieldType::Optional(_) => { diff --git a/engine/baml-lib/jsonish/src/tests/mod.rs b/engine/baml-lib/jsonish/src/tests/mod.rs index 3ecc3451e..aa44f8a86 100644 --- a/engine/baml-lib/jsonish/src/tests/mod.rs +++ b/engine/baml-lib/jsonish/src/tests/mod.rs @@ -232,7 +232,7 @@ fn relevant_data_models<'a>( }); } } - (FieldType::Alias(_, _), _) => todo!(), + (FieldType::Alias { resolution, .. }, _) => start.push(*resolution.to_owned()), (FieldType::Literal(_), _) => {} (FieldType::Primitive(_), _constraints) => {} (FieldType::Constrained { .. }, _) => { diff --git a/engine/baml-lib/parser-database/src/lib.rs b/engine/baml-lib/parser-database/src/lib.rs index 7739e068b..481f8a673 100644 --- a/engine/baml-lib/parser-database/src/lib.rs +++ b/engine/baml-lib/parser-database/src/lib.rs @@ -113,8 +113,6 @@ impl ParserDatabase { // First pass: resolve names. names::resolve_names(&mut ctx); - // Return early on name resolution errors. - // Second pass: resolve top-level items and field types. types::resolve_types(&mut ctx); @@ -131,33 +129,6 @@ impl ParserDatabase { } fn finalize_dependencies(&mut self, diag: &mut Diagnostics) { - // Fully resolve type aliases. - for (id, targets) in self.types.type_aliases.iter() { - let mut resolved = HashSet::new(); - let mut queue = VecDeque::from_iter(targets.iter()); - - while let Some(target) = queue.pop_front() { - match self.find_type_by_str(target) { - Some(TypeWalker::Class(_) | TypeWalker::Enum(_)) => { - resolved.insert(target.to_owned()); - } - // TODO: Cycles and recursive stuff. - Some(TypeWalker::TypeAlias(alias)) => { - if let Some(already_resolved) = - self.types.resolved_type_aliases.get(&alias.id) - { - resolved.extend(already_resolved.iter().cloned()); - } else { - queue.extend(&self.types.type_aliases[&alias.id]) - } - } - None => panic!("Type alias pointing to invalid type `{target}`"), - }; - } - - self.types.resolved_type_aliases.insert(*id, resolved); - } - // NOTE: Class dependency cycles are already checked at // baml-lib/baml-core/src/validate/validation_pipeline/validations/cycle.rs // @@ -217,8 +188,10 @@ impl ParserDatabase { Some(walker.dependencies().iter().cloned()) } Some(TypeWalker::Enum(_)) => None, - Some(TypeWalker::TypeAlias(_)) => None, - _ => panic!("Unknown class `{}`", f), + // TODO: Alias can point to classes enums, unions... + // what do we do here? + Some(TypeWalker::TypeAlias(walker)) => None, + _ => panic!("Unknown class `{f}`"), }) .flatten() .collect::<HashSet<_>>(); @@ -230,9 +203,9 @@ impl ParserDatabase { Some(walker.dependencies().iter().cloned()) } Some(TypeWalker::Enum(_)) => None, - Some(TypeWalker::TypeAlias(walker)) => { - Some(self.types.resolved_type_aliases[&walker.id].iter().cloned()) - } + // TODO: Alias can point to classes enums, unions... + // what do we do here? + Some(TypeWalker::TypeAlias(walker)) => None, _ => panic!("Unknown class `{}`", f), }) .flatten() diff --git a/engine/baml-lib/parser-database/src/types/mod.rs b/engine/baml-lib/parser-database/src/types/mod.rs index 940303780..624f0f452 100644 --- a/engine/baml-lib/parser-database/src/types/mod.rs +++ b/engine/baml-lib/parser-database/src/types/mod.rs @@ -231,55 +231,23 @@ pub(super) struct Types { pub(super) class_dependencies: HashMap<ast::TypeExpId, HashSet<String>>, pub(super) enum_dependencies: HashMap<ast::TypeExpId, HashSet<String>>, - /// Graph of type aliases. + /// Fully resolved type aliases. /// - /// A type alias can point to one or many different types (with a union). - /// - /// Base case is primitives: - /// - /// ```ignore - /// type Alias = int - /// ``` - /// - /// In that case the type doesn't "point" to anything. - /// - /// Second case is classes, enums or other aliases: - /// - /// ```ignore - /// type ClassAlias = SomeClass - /// type EnumAlias = SomeEnum - /// type Alias = ClassAlias - /// ``` - /// - /// Third, an alias can point to many of the above through unions: - /// - /// ```ignore - /// type Alias = SomeClass | EnumAlias - /// ``` - /// - /// This graph stores the names of all the symbols that the type alias - /// points to. - /// - /// - /// TODO @antonio: Remove this and just store IDs. Then get field type from - /// AST and resolve final type and store that in resolved_aliases below. - /// Change the type from hash set to field type because resolution requires - /// creating a type that does not even exist in the AST. - pub(super) type_aliases: HashMap<ast::TypeAliasId, HashSet<String>>, - - /// Same as [`Self::type_aliases`] but without intermediate edges in the - /// graph. - /// - /// Pointers here point directly to the resolved type. Example: + /// A type alias con point to one or many other type aliases. /// /// ``` /// type AliasOne = SomeClass - /// type AliasTwo = AliasOne + /// type AliasTwo = AnotherClass /// type AliasThree = AliasTwo + /// type AliasFour = AliasOne | AliasTwo /// ``` /// + /// In the above example, `AliasFour` would be resolved to the type + /// `SomeClass | AnotherClass`, which does not even exist in the AST. That's + /// why we need to store the resolution here. + /// /// Contents would be `AliasThree -> SomeClass`. - pub(super) resolved_type_aliases: HashMap<ast::TypeAliasId, HashSet<String>>, + pub(super) resolved_type_aliases: HashMap<ast::TypeAliasId, FieldType>, /// Strongly connected components of the dependency graph. /// @@ -392,30 +360,102 @@ fn visit_class<'db>( }); } +/// Returns a "virtual" type that represents the fully resolved alias. +/// +/// We call it "virtual" because it might not exist in the AST. Basic example: +/// +/// ```ignore +/// type AliasOne = SomeClass +/// type AliasTwo = AnotherClass +/// type AliasThree = AliasOne | AliasTwo | int +/// ``` +/// +/// The type would resolve to `SomeClass | AnotherClass | int`, which is not +/// stored in the AST. +fn resolve_type_alias(field_type: &FieldType, ctx: &mut Context<'_>) -> FieldType { + match field_type { + // For symbols we need to check if we're dealing with aliases. + FieldType::Symbol(arity, ident, span) => { + let Some(string_id) = ctx.interner.lookup(ident.name()) else { + unreachable!( + "Attempting to resolve alias `{ident}` that does not exist in the interner" + ); + }; + + let Some(top_id) = ctx.names.tops.get(&string_id) else { + unreachable!("Alias name `{ident}` is not registered in the context"); + }; + + match top_id { + ast::TopId::TypeAlias(alias_id) => { + // Check if we can avoid deeper recursion. + if let Some(resolved) = ctx.types.resolved_type_aliases.get(alias_id) { + return resolved.to_owned(); + } + + // Recurse... TODO: Recursive types and infinite cycles :( + let resolved = resolve_type_alias(&ctx.ast[*alias_id].value, ctx); + + // Sync arity. Basically stuff like: + // + // type AliasOne = SomeClass? + // type AliasTwo = AliasOne + // + // AliasTwo resolves to an "optional" type. + if resolved.is_optional() || arity.is_optional() { + resolved.to_nullable() + } else { + resolved + } + } + + // Class or enum. Already "resolved", pop off the stack. + _ => field_type.to_owned(), + } + } + + // Recurse and resolve each type individually. + FieldType::Union(arity, items, span, attrs) + | FieldType::Tuple(arity, items, span, attrs) => { + let resolved = items + .iter() + .map(|item| resolve_type_alias(item, ctx)) + .collect(); + + match field_type { + FieldType::Union(..) => { + FieldType::Union(*arity, resolved, span.clone(), attrs.clone()) + } + FieldType::Tuple(..) => { + FieldType::Tuple(*arity, resolved, span.clone(), attrs.clone()) + } + _ => unreachable!("should only match tuples and unions"), + } + } + + // Base case, primitives or other types that are not aliases. No more + // "pointers" and graphs here. + _ => field_type.to_owned(), + } +} + fn visit_type_alias<'db>( alias_id: ast::TypeAliasId, assignment: &'db ast::Assignment, ctx: &mut Context<'db>, ) { - let targets = ctx - .types - .type_aliases - .entry(alias_id) - .or_insert(HashSet::new()); - - // Find all the symbols that the type alias points to. - let mut queue = VecDeque::from_iter([&assignment.value]); - while let Some(item) = queue.pop_front() { - match item { - FieldType::Symbol(..) => { - targets.insert(item.name()); - } - FieldType::Union(_, items, ..) | FieldType::Tuple(_, items, ..) => { - queue.extend(items.iter()); - } - _ => {} - } + // Maybe this can't even happen since we iterate over the vec of tops and + // just get IDs sequentially, but anyway check just in case. + if ctx.types.resolved_type_aliases.contains_key(&alias_id) { + return; } + + // Now resolve the type. + let resolved = resolve_type_alias(&assignment.value, ctx); + + // TODO: Can we add types to the map recursively while solving them at + // the same time? It might speed up very long chains of aliases. + ctx.types.resolved_type_aliases.insert(alias_id, resolved); } fn visit_function<'db>(idx: ValExpId, function: &'db ast::ValueExprBlock, ctx: &mut Context<'db>) { diff --git a/engine/baml-lib/parser-database/src/walkers/alias.rs b/engine/baml-lib/parser-database/src/walkers/alias.rs index ccd540f35..8ba968dfc 100644 --- a/engine/baml-lib/parser-database/src/walkers/alias.rs +++ b/engine/baml-lib/parser-database/src/walkers/alias.rs @@ -11,25 +11,16 @@ impl<'db> TypeAliasWalker<'db> { &self.db.ast[self.id].identifier.name() } - /// Returns a set containing the names of the types aliased by `self`. - /// - /// Note that the parser DB must be populated for this method to work. - pub fn targets(&self) -> &'db HashSet<String> { - &self.db.types.type_aliases[&self.id] + /// Returns the field type that the alias points to. + pub fn target(&self) -> &'db FieldType { + &self.db.ast[self.id].value } - /// Returns a set containing the final resolution of the type alias. - /// - /// If the set is empty it just means that the type resolves to primitives - /// instead of symbols. + /// Returns a "virtual" type that represents the fully resolved alias. /// - /// Parser DB must be populated before calling this method. - pub fn resolved(&self) -> &'db HashSet<String> { + /// Since an alias can point to other aliases we might have to create a + /// type that does not exist in the AST. + pub fn resolved(&self) -> &'db FieldType { &self.db.types.resolved_type_aliases[&self.id] } - - /// Returns the field type of the aliased type. - pub fn ast_field_type(&self) -> &'db FieldType { - &self.db.ast[self.id].value - } } diff --git a/engine/language_client_codegen/src/openapi.rs b/engine/language_client_codegen/src/openapi.rs index c7d2f1d04..ca8e507fd 100644 --- a/engine/language_client_codegen/src/openapi.rs +++ b/engine/language_client_codegen/src/openapi.rs @@ -541,7 +541,7 @@ impl<'ir> ToTypeReferenceInTypeDefinition<'ir> for FieldType { r#ref: format!("#/components/schemas/{}", name), }, }, - FieldType::Alias(_, _) => todo!(), + FieldType::Alias { resolution, .. } => resolution.to_type_spec(ir)?, FieldType::Literal(v) => TypeSpecWithMeta { meta: TypeMetadata { title: None, diff --git a/engine/language_client_codegen/src/python/generate_types.rs b/engine/language_client_codegen/src/python/generate_types.rs index 59a5b4f24..1b8cc5d31 100644 --- a/engine/language_client_codegen/src/python/generate_types.rs +++ b/engine/language_client_codegen/src/python/generate_types.rs @@ -7,7 +7,8 @@ use crate::{field_type_attributes, type_check_attributes, TypeCheckAttributes}; use super::python_language_features::ToPython; use internal_baml_core::ir::{ - repr::{Docstring, IntermediateRepr}, ClassWalker, EnumWalker, FieldType, IRHelper, + repr::{Docstring, IntermediateRepr}, + ClassWalker, EnumWalker, FieldType, IRHelper, }; #[derive(askama::Template)] @@ -40,7 +41,6 @@ struct PythonClass<'ir> { dynamic: bool, } - #[derive(askama::Template)] #[template(path = "partial_types.py.j2", escape = "none")] pub(crate) struct PythonStreamTypes<'ir> { @@ -93,9 +93,14 @@ impl<'ir> From<EnumWalker<'ir>> for PythonEnum<'ir> { .elem .values .iter() - .map(|v| (v.0.elem.0.as_str(), v.1.as_ref().map(|d| render_docstring(d)))) + .map(|v| { + ( + v.0.elem.0.as_str(), + v.1.as_ref().map(|d| render_docstring(d)), + ) + }) .collect(), - docstring: e.item.elem.docstring.as_ref().map(|s| render_docstring(s)) + docstring: e.item.elem.docstring.as_ref().map(|s| render_docstring(s)), } } } @@ -184,7 +189,6 @@ pub fn type_name_for_checks(checks: &TypeCheckAttributes) -> String { format!["Literal[{check_names}]"] } - /// Returns the Python `Literal` representation of `self`. pub fn to_python_literal(literal: &LiteralValue) -> String { // Python bools are a little special... @@ -220,7 +224,7 @@ impl ToTypeReferenceInTypeDefinition for FieldType { format!("\"{name}\"") } } - FieldType::Alias(_, _) => todo!(), + FieldType::Alias { resolution, .. } => resolution.to_type_ref(ir), FieldType::Literal(value) => to_python_literal(value), FieldType::Class(name) => format!("\"{name}\""), FieldType::List(inner) => format!("List[{}]", inner.to_type_ref(ir)), @@ -276,7 +280,7 @@ impl ToTypeReferenceInTypeDefinition for FieldType { format!("Optional[types.{name}]") } } - FieldType::Alias(_, _) => todo!(), + FieldType::Alias { resolution, .. } => resolution.to_partial_type_ref(ir, wrapped), FieldType::Literal(value) => to_python_literal(value), FieldType::List(inner) => format!("List[{}]", inner.to_partial_type_ref(ir, true)), FieldType::Map(key, value) => { diff --git a/engine/language_client_codegen/src/python/mod.rs b/engine/language_client_codegen/src/python/mod.rs index 851bbf390..65eb962fc 100644 --- a/engine/language_client_codegen/src/python/mod.rs +++ b/engine/language_client_codegen/src/python/mod.rs @@ -201,7 +201,7 @@ impl ToTypeReferenceInClientDefinition for FieldType { } } FieldType::Literal(value) => to_python_literal(value), - FieldType::Alias(_, _) => todo!(), + FieldType::Alias { resolution, .. } => resolution.to_type_ref(ir, with_checked), FieldType::Class(name) => format!("types.{name}"), FieldType::List(inner) => format!("List[{}]", inner.to_type_ref(ir, with_checked)), FieldType::Map(key, value) => { @@ -256,7 +256,7 @@ impl ToTypeReferenceInClientDefinition for FieldType { } } FieldType::Class(name) => format!("partial_types.{name}"), - FieldType::Alias(_, _) => todo!(), + FieldType::Alias { resolution, .. } => resolution.to_partial_type_ref(ir, with_checked), FieldType::Literal(value) => to_python_literal(value), FieldType::List(inner) => { format!("List[{}]", inner.to_partial_type_ref(ir, with_checked)) diff --git a/engine/language_client_codegen/src/ruby/field_type.rs b/engine/language_client_codegen/src/ruby/field_type.rs index 7674f0fcc..c17c7e538 100644 --- a/engine/language_client_codegen/src/ruby/field_type.rs +++ b/engine/language_client_codegen/src/ruby/field_type.rs @@ -9,7 +9,7 @@ impl ToRuby for FieldType { match self { FieldType::Class(name) => format!("Baml::Types::{}", name.clone()), FieldType::Enum(name) => format!("T.any(Baml::Types::{}, String)", name.clone()), - FieldType::Alias(_, _) => todo!(), + FieldType::Alias { resolution, .. } => resolution.to_ruby(), // TODO: Temporary solution until we figure out Ruby literals. FieldType::Literal(value) => value.literal_base_type().to_ruby(), // https://sorbet.org/docs/stdlib-generics diff --git a/engine/language_client_codegen/src/ruby/generate_types.rs b/engine/language_client_codegen/src/ruby/generate_types.rs index 58dcd8fc7..b69a44359 100644 --- a/engine/language_client_codegen/src/ruby/generate_types.rs +++ b/engine/language_client_codegen/src/ruby/generate_types.rs @@ -168,7 +168,7 @@ impl ToTypeReferenceInTypeDefinition for FieldType { match self { FieldType::Class(name) => format!("Baml::PartialTypes::{}", name.clone()), FieldType::Enum(name) => format!("T.nilable(Baml::Types::{})", name.clone()), - FieldType::Alias(_, target) => todo!(), + FieldType::Alias { resolution, .. } => resolution.to_partial_type_ref(), // TODO: Temporary solution until we figure out Ruby literals. FieldType::Literal(value) => value.literal_base_type().to_partial_type_ref(), // https://sorbet.org/docs/stdlib-generics diff --git a/engine/language_client_codegen/src/typescript/mod.rs b/engine/language_client_codegen/src/typescript/mod.rs index 396efa5ef..412c3b75d 100644 --- a/engine/language_client_codegen/src/typescript/mod.rs +++ b/engine/language_client_codegen/src/typescript/mod.rs @@ -267,7 +267,7 @@ impl ToTypeReferenceInClientDefinition for FieldType { } } FieldType::Class(name) => format!("{name}"), - FieldType::Alias(_, target) => todo!(), + FieldType::Alias { resolution, .. } => resolution.to_type_ref(ir), FieldType::List(inner) => match inner.as_ref() { FieldType::Union(_) | FieldType::Optional(_) => { format!("({})[]", inner.to_type_ref(ir)) From 28907de78ff7e1cee92535a111068e7dca409a0b Mon Sep 17 00:00:00 2001 From: Antonio Sarosi <sarosiantonio@gmail.com> Date: Fri, 22 Nov 2024 15:30:14 +0000 Subject: [PATCH 13/51] Fix build --- engine/baml-lib/parser-database/src/types/mod.rs | 2 +- engine/baml-schema-wasm/src/runtime_wasm/mod.rs | 10 ++++------ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/engine/baml-lib/parser-database/src/types/mod.rs b/engine/baml-lib/parser-database/src/types/mod.rs index 624f0f452..fef174786 100644 --- a/engine/baml-lib/parser-database/src/types/mod.rs +++ b/engine/baml-lib/parser-database/src/types/mod.rs @@ -235,7 +235,7 @@ pub(super) struct Types { /// /// A type alias con point to one or many other type aliases. /// - /// ``` + /// ```ignore /// type AliasOne = SomeClass /// type AliasTwo = AnotherClass /// type AliasThree = AliasTwo diff --git a/engine/baml-schema-wasm/src/runtime_wasm/mod.rs b/engine/baml-schema-wasm/src/runtime_wasm/mod.rs index de6f8678c..7fdce8613 100644 --- a/engine/baml-schema-wasm/src/runtime_wasm/mod.rs +++ b/engine/baml-schema-wasm/src/runtime_wasm/mod.rs @@ -511,12 +511,7 @@ fn flatten_checks(value: &BamlValueWithFlags) -> (serde_json::Value, usize) { .flat_map(|f| match f { Flag::ConstraintResults(c) => c .iter() - .map(|(label, _expr, b)| { - ( - label.clone(), - *b, - ) - }) + .map(|(label, _expr, b)| (label.clone(), *b)) .collect::<Vec<_>>(), _ => vec![], }) @@ -816,6 +811,9 @@ fn get_dummy_value( baml_runtime::FieldType::Literal(_) => None, baml_runtime::FieldType::Enum(_) => None, baml_runtime::FieldType::Class(_) => None, + baml_runtime::FieldType::Alias { resolution, .. } => { + get_dummy_value(indent, allow_multiline, &resolution) + } baml_runtime::FieldType::List(item) => { let dummy = get_dummy_value(indent + 1, allow_multiline, item); // Repeat it 2 times From 29bed6b8663528fd817fa71d0a5b34135587091e Mon Sep 17 00:00:00 2001 From: Antonio Sarosi <sarosiantonio@gmail.com> Date: Fri, 22 Nov 2024 19:07:36 +0000 Subject: [PATCH 14/51] Codegen works! (probably not) --- .../src/ir/ir_helpers/to_baml_arg.rs | 4 +- engine/baml-lib/baml-core/src/ir/repr.rs | 2 +- .../validation_files/class/type_aliases.baml | 2 +- engine/baml-lib/jsonish/src/tests/mod.rs | 3 +- .../baml-lib/parser-database/src/names/mod.rs | 3 +- .../prompt_renderer/render_output_format.rs | 4 +- .../src/runtime_wasm/runtime_prompt.rs | 6 +- .../functions/output/type-aliases.baml | 36 ++++ .../python/baml_client/async_client.py | 159 ++++++++++++++ integ-tests/python/baml_client/inlinedbaml.py | 1 + integ-tests/python/baml_client/sync_client.py | 159 ++++++++++++++ integ-tests/ruby/baml_client/client.rb | 201 ++++++++++++++++++ integ-tests/ruby/baml_client/inlined.rb | 1 + .../typescript/baml_client/async_client.ts | 174 +++++++++++++++ .../typescript/baml_client/inlinedbaml.ts | 1 + .../typescript/baml_client/sync_client.ts | 75 +++++++ 16 files changed, 818 insertions(+), 13 deletions(-) create mode 100644 integ-tests/baml_src/test-files/functions/output/type-aliases.baml diff --git a/engine/baml-lib/baml-core/src/ir/ir_helpers/to_baml_arg.rs b/engine/baml-lib/baml-core/src/ir/ir_helpers/to_baml_arg.rs index ecb684e0d..2e5fa3efd 100644 --- a/engine/baml-lib/baml-core/src/ir/ir_helpers/to_baml_arg.rs +++ b/engine/baml-lib/baml-core/src/ir/ir_helpers/to_baml_arg.rs @@ -263,7 +263,9 @@ impl ArgCoercer { Err(()) } }, - (FieldType::Alias { .. }, _) => todo!(), + (FieldType::Alias { resolution, .. }, _) => { + self.coerce_arg(ir, &resolution, value, scope) + } (FieldType::List(item), _) => match value { BamlValue::List(arr) => { let mut items = Vec::new(); diff --git a/engine/baml-lib/baml-core/src/ir/repr.rs b/engine/baml-lib/baml-core/src/ir/repr.rs index 298861d8e..15377fa28 100644 --- a/engine/baml-lib/baml-core/src/ir/repr.rs +++ b/engine/baml-lib/baml-core/src/ir/repr.rs @@ -435,7 +435,7 @@ impl WithRepr<FieldType> for ast::FieldType { Some(TypeWalker::TypeAlias(alias_walker)) => FieldType::Alias { name: alias_walker.name().to_owned(), target: Box::new(alias_walker.target().repr(db)?), - resolution: Box::new(FieldType::int()), // TODO + resolution: Box::new(alias_walker.resolved().repr(db)?), }, None => return Err(anyhow!("Field type uses unresolvable local identifier")), }, diff --git a/engine/baml-lib/baml/tests/validation_files/class/type_aliases.baml b/engine/baml-lib/baml/tests/validation_files/class/type_aliases.baml index b2a37c0d2..94d2cb6ac 100644 --- a/engine/baml-lib/baml/tests/validation_files/class/type_aliases.baml +++ b/engine/baml-lib/baml/tests/validation_files/class/type_aliases.baml @@ -6,7 +6,7 @@ type Graph = map<string, string[]> type Combination = Primitive | List | Graph -function AliasPrimitive(p: Primitive) -> Primitive { +function PrimitiveAlias(p: Primitive) -> Primitive { client "openai/gpt-4o" prompt r#" Return the given value back: {{ p }} diff --git a/engine/baml-lib/jsonish/src/tests/mod.rs b/engine/baml-lib/jsonish/src/tests/mod.rs index aa44f8a86..d70d4df99 100644 --- a/engine/baml-lib/jsonish/src/tests/mod.rs +++ b/engine/baml-lib/jsonish/src/tests/mod.rs @@ -133,8 +133,7 @@ fn relevant_data_models<'a>( let mut recursive_classes = IndexSet::new(); let mut start: Vec<baml_types::FieldType> = vec![output.clone()]; - while !start.is_empty() { - let output = start.pop().unwrap(); + while let Some(output) = start.pop() { match ir.distribute_constraints(&output) { (FieldType::Enum(enm), constraints) => { if checked_types.insert(output.to_string()) { diff --git a/engine/baml-lib/parser-database/src/names/mod.rs b/engine/baml-lib/parser-database/src/names/mod.rs index 0eaf3a9b3..5e29ab4c5 100644 --- a/engine/baml-lib/parser-database/src/names/mod.rs +++ b/engine/baml-lib/parser-database/src/names/mod.rs @@ -23,7 +23,6 @@ pub(super) struct Names { /// Tests have their own namespace. pub(super) tests: HashMap<StringId, HashMap<StringId, TopId>>, pub(super) model_fields: HashMap<(ast::TypeExpId, StringId), ast::FieldId>, - pub(super) type_aliases: HashMap<ast::TypeExpId, Option<StringId>>, // pub(super) composite_type_fields: HashMap<(ast::CompositeTypeId, StringId), ast::FieldId>, } @@ -95,7 +94,7 @@ pub(super) fn resolve_names(ctx: &mut Context<'_>) { (ast::TopId::TypeAlias(_), ast::Top::TypeAlias(type_alias)) => { validate_type_alias_name(type_alias, ctx.diagnostics); - let type_alias_id = ctx.interner.intern(type_alias.name()); + ctx.interner.intern(type_alias.name()); Some(either::Left(&mut names.tops)) } diff --git a/engine/baml-runtime/src/internal/prompt_renderer/render_output_format.rs b/engine/baml-runtime/src/internal/prompt_renderer/render_output_format.rs index 77bc89fcb..a66823b73 100644 --- a/engine/baml-runtime/src/internal/prompt_renderer/render_output_format.rs +++ b/engine/baml-runtime/src/internal/prompt_renderer/render_output_format.rs @@ -363,7 +363,9 @@ fn relevant_data_models<'a>( recursive_classes.insert(cls.to_owned()); } } - (FieldType::Alias { .. }, _) => todo!(), + (FieldType::Alias { resolution, .. }, _) => { + start.push(*resolution.clone()); + } (FieldType::Literal(_), _) => {} (FieldType::Primitive(_), _) => {} (FieldType::Constrained { .. }, _) => { diff --git a/engine/baml-schema-wasm/src/runtime_wasm/runtime_prompt.rs b/engine/baml-schema-wasm/src/runtime_wasm/runtime_prompt.rs index b0cd13e68..aee711bea 100644 --- a/engine/baml-schema-wasm/src/runtime_wasm/runtime_prompt.rs +++ b/engine/baml-schema-wasm/src/runtime_wasm/runtime_prompt.rs @@ -7,16 +7,12 @@ use baml_runtime::{ }, ChatMessagePart, RenderedPrompt, }; -use serde::Serialize; use serde_json::json; -use crate::runtime_wasm::ToJsValue; -use baml_types::{BamlMedia, BamlMediaContent, BamlMediaType, MediaBase64}; +use baml_types::{BamlMediaContent, BamlMediaType, MediaBase64}; use serde_wasm_bindgen::to_value; use wasm_bindgen::prelude::*; -use super::WasmFunction; - #[wasm_bindgen(getter_with_clone)] pub struct WasmScope { scope: OrchestrationScope, diff --git a/integ-tests/baml_src/test-files/functions/output/type-aliases.baml b/integ-tests/baml_src/test-files/functions/output/type-aliases.baml new file mode 100644 index 000000000..94d2cb6ac --- /dev/null +++ b/integ-tests/baml_src/test-files/functions/output/type-aliases.baml @@ -0,0 +1,36 @@ +type Primitive = int | string | bool | float + +type List = string[] + +type Graph = map<string, string[]> + +type Combination = Primitive | List | Graph + +function PrimitiveAlias(p: Primitive) -> Primitive { + client "openai/gpt-4o" + prompt r#" + Return the given value back: {{ p }} + "# +} + +function MapAlias(m: Graph) -> Graph { + client "openai/gpt-4o" + prompt r#" + Return the given Graph back: + + {{ m }} + + {{ ctx.output_format }} + "# +} + +function NestedAlias(c: Combination) -> Combination { + client "openai/gpt-4o" + prompt r#" + Return the given value back: + + {{ c }} + + {{ ctx.output_format }} + "# +} diff --git a/integ-tests/python/baml_client/async_client.py b/integ-tests/python/baml_client/async_client.py index ae4f20aa7..3ba79e240 100644 --- a/integ-tests/python/baml_client/async_client.py +++ b/integ-tests/python/baml_client/async_client.py @@ -1453,6 +1453,29 @@ async def MakeNestedBlockConstraint( ) return cast(types.NestedBlockConstraint, raw.cast_to(types, types)) + async def MapAlias( + self, + m: Dict[str, List[str]], + baml_options: BamlCallOptions = {}, + ) -> Dict[str, List[str]]: + __tb__ = baml_options.get("tb", None) + if __tb__ is not None: + tb = __tb__._tb # type: ignore (we know how to use this private attribute) + else: + tb = None + __cr__ = baml_options.get("client_registry", None) + + raw = await self.__runtime.call_function( + "MapAlias", + { + "m": m, + }, + self.__ctx_manager.get(), + tb, + __cr__, + ) + return cast(Dict[str, List[str]], raw.cast_to(types, types)) + async def MyFunc( self, input: str, @@ -1476,6 +1499,29 @@ async def MyFunc( ) return cast(types.DynamicOutput, raw.cast_to(types, types)) + async def NestedAlias( + self, + c: Union[Union[int, str, bool, float], List[str], Dict[str, List[str]]], + baml_options: BamlCallOptions = {}, + ) -> Union[Union[int, str, bool, float], List[str], Dict[str, List[str]]]: + __tb__ = baml_options.get("tb", None) + if __tb__ is not None: + tb = __tb__._tb # type: ignore (we know how to use this private attribute) + else: + tb = None + __cr__ = baml_options.get("client_registry", None) + + raw = await self.__runtime.call_function( + "NestedAlias", + { + "c": c, + }, + self.__ctx_manager.get(), + tb, + __cr__, + ) + return cast(Union[Union[int, str, bool, float], List[str], Dict[str, List[str]]], raw.cast_to(types, types)) + async def OptionalTest_Function( self, input: str, @@ -1545,6 +1591,29 @@ async def PredictAgeBare( ) return cast(Checked[int,types.Literal["too_big"]], raw.cast_to(types, types)) + async def PrimitiveAlias( + self, + p: Union[int, str, bool, float], + baml_options: BamlCallOptions = {}, + ) -> Union[int, str, bool, float]: + __tb__ = baml_options.get("tb", None) + if __tb__ is not None: + tb = __tb__._tb # type: ignore (we know how to use this private attribute) + else: + tb = None + __cr__ = baml_options.get("client_registry", None) + + raw = await self.__runtime.call_function( + "PrimitiveAlias", + { + "p": p, + }, + self.__ctx_manager.get(), + tb, + __cr__, + ) + return cast(Union[int, str, bool, float], raw.cast_to(types, types)) + async def PromptTestClaude( self, input: str, @@ -4610,6 +4679,36 @@ def MakeNestedBlockConstraint( self.__ctx_manager.get(), ) + def MapAlias( + self, + m: Dict[str, List[str]], + baml_options: BamlCallOptions = {}, + ) -> baml_py.BamlStream[Dict[str, List[Optional[str]]], Dict[str, List[str]]]: + __tb__ = baml_options.get("tb", None) + if __tb__ is not None: + tb = __tb__._tb # type: ignore (we know how to use this private attribute) + else: + tb = None + __cr__ = baml_options.get("client_registry", None) + + raw = self.__runtime.stream_function( + "MapAlias", + { + "m": m, + }, + None, + self.__ctx_manager.get(), + tb, + __cr__, + ) + + return baml_py.BamlStream[Dict[str, List[Optional[str]]], Dict[str, List[str]]]( + raw, + lambda x: cast(Dict[str, List[Optional[str]]], x.cast_to(types, partial_types)), + lambda x: cast(Dict[str, List[str]], x.cast_to(types, types)), + self.__ctx_manager.get(), + ) + def MyFunc( self, input: str, @@ -4640,6 +4739,36 @@ def MyFunc( self.__ctx_manager.get(), ) + def NestedAlias( + self, + c: Union[Union[int, str, bool, float], List[str], Dict[str, List[str]]], + baml_options: BamlCallOptions = {}, + ) -> baml_py.BamlStream[Optional[Union[Optional[Union[Optional[int], Optional[str], Optional[bool], Optional[float]]], List[Optional[str]], Dict[str, List[Optional[str]]]]], Union[Union[int, str, bool, float], List[str], Dict[str, List[str]]]]: + __tb__ = baml_options.get("tb", None) + if __tb__ is not None: + tb = __tb__._tb # type: ignore (we know how to use this private attribute) + else: + tb = None + __cr__ = baml_options.get("client_registry", None) + + raw = self.__runtime.stream_function( + "NestedAlias", + { + "c": c, + }, + None, + self.__ctx_manager.get(), + tb, + __cr__, + ) + + return baml_py.BamlStream[Optional[Union[Optional[Union[Optional[int], Optional[str], Optional[bool], Optional[float]]], List[Optional[str]], Dict[str, List[Optional[str]]]]], Union[Union[int, str, bool, float], List[str], Dict[str, List[str]]]]( + raw, + lambda x: cast(Optional[Union[Optional[Union[Optional[int], Optional[str], Optional[bool], Optional[float]]], List[Optional[str]], Dict[str, List[Optional[str]]]]], x.cast_to(types, partial_types)), + lambda x: cast(Union[Union[int, str, bool, float], List[str], Dict[str, List[str]]], x.cast_to(types, types)), + self.__ctx_manager.get(), + ) + def OptionalTest_Function( self, input: str, @@ -4730,6 +4859,36 @@ def PredictAgeBare( self.__ctx_manager.get(), ) + def PrimitiveAlias( + self, + p: Union[int, str, bool, float], + baml_options: BamlCallOptions = {}, + ) -> baml_py.BamlStream[Optional[Union[Optional[int], Optional[str], Optional[bool], Optional[float]]], Union[int, str, bool, float]]: + __tb__ = baml_options.get("tb", None) + if __tb__ is not None: + tb = __tb__._tb # type: ignore (we know how to use this private attribute) + else: + tb = None + __cr__ = baml_options.get("client_registry", None) + + raw = self.__runtime.stream_function( + "PrimitiveAlias", + { + "p": p, + }, + None, + self.__ctx_manager.get(), + tb, + __cr__, + ) + + return baml_py.BamlStream[Optional[Union[Optional[int], Optional[str], Optional[bool], Optional[float]]], Union[int, str, bool, float]]( + raw, + lambda x: cast(Optional[Union[Optional[int], Optional[str], Optional[bool], Optional[float]]], x.cast_to(types, partial_types)), + lambda x: cast(Union[int, str, bool, float], x.cast_to(types, types)), + self.__ctx_manager.get(), + ) + def PromptTestClaude( self, input: str, diff --git a/integ-tests/python/baml_client/inlinedbaml.py b/integ-tests/python/baml_client/inlinedbaml.py index 5b61d4c61..6fc52ba62 100644 --- a/integ-tests/python/baml_client/inlinedbaml.py +++ b/integ-tests/python/baml_client/inlinedbaml.py @@ -82,6 +82,7 @@ "test-files/functions/output/recursive-class.baml": "class Node {\n data int\n next Node?\n}\n\nclass LinkedList {\n head Node?\n len int\n}\n\nclient<llm> O1 {\n provider \"openai\"\n options {\n model \"o1-mini\"\n default_role \"user\"\n }\n}\n\nfunction BuildLinkedList(input: int[]) -> LinkedList {\n client O1\n prompt #\"\n Build a linked list from the input array of integers.\n\n INPUT:\n {{ input }}\n\n {{ ctx.output_format }} \n \"#\n}\n\ntest TestLinkedList {\n functions [BuildLinkedList]\n args {\n input [1, 2, 3, 4, 5]\n }\n}\n", "test-files/functions/output/serialization-error.baml": "class DummyOutput {\n nonce string\n nonce2 string\n @@dynamic\n}\n\nfunction DummyOutputFunction(input: string) -> DummyOutput {\n client GPT35\n prompt #\"\n Say \"hello there\".\n \"#\n}", "test-files/functions/output/string-list.baml": "function FnOutputStringList(input: string) -> string[] {\n client GPT35\n prompt #\"\n Return a list of strings in json format like [\"string1\", \"string2\", \"string3\"].\n\n JSON:\n \"#\n}\n\ntest FnOutputStringList {\n functions [FnOutputStringList]\n args {\n input \"example input\"\n }\n}\n", + "test-files/functions/output/type-aliases.baml": "type Primitive = int | string | bool | float\n\ntype List = string[]\n\ntype Graph = map<string, string[]>\n\ntype Combination = Primitive | List | Graph\n\nfunction PrimitiveAlias(p: Primitive) -> Primitive {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given value back: {{ p }}\n \"#\n}\n\nfunction MapAlias(m: Graph) -> Graph {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given Graph back:\n\n {{ m }}\n\n {{ ctx.output_format }}\n \"#\n}\n\nfunction NestedAlias(c: Combination) -> Combination {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given value back:\n\n {{ c }}\n\n {{ ctx.output_format }}\n \"#\n}\n", "test-files/functions/output/unions.baml": "class UnionTest_ReturnType {\n prop1 string | bool\n prop2 (float | bool)[]\n prop3 (bool[] | int[])\n}\n\nfunction UnionTest_Function(input: string | bool) -> UnionTest_ReturnType {\n client GPT35\n prompt #\"\n Return a JSON blob with this schema: \n {{ctx.output_format}}\n\n JSON:\n \"#\n}\n\ntest UnionTest_Function {\n functions [UnionTest_Function]\n args {\n input \"example input\"\n }\n}\n", "test-files/functions/prompts/no-chat-messages.baml": "\n\nfunction PromptTestClaude(input: string) -> string {\n client Sonnet\n prompt #\"\n Tell me a haiku about {{ input }}\n \"#\n}\n\n\nfunction PromptTestStreaming(input: string) -> string {\n client GPT35\n prompt #\"\n Tell me a short story about {{ input }}\n \"#\n}\n\ntest TestName {\n functions [PromptTestStreaming]\n args {\n input #\"\n hello world\n \"#\n }\n}\n", "test-files/functions/prompts/with-chat-messages.baml": "\nfunction PromptTestOpenAIChat(input: string) -> string {\n client GPT35\n prompt #\"\n {{ _.role(\"system\") }}\n You are an assistant that always responds in a very excited way with emojis and also outputs this word 4 times after giving a response: {{ input }}\n \n {{ _.role(\"user\") }}\n Tell me a haiku about {{ input }}\n \"#\n}\n\nfunction PromptTestOpenAIChatNoSystem(input: string) -> string {\n client GPT35\n prompt #\"\n You are an assistant that always responds in a very excited way with emojis and also outputs this word 4 times after giving a response: {{ input }}\n \n {{ _.role(\"user\") }}\n Tell me a haiku about {{ input }}\n \"#\n}\n\nfunction PromptTestClaudeChat(input: string) -> string {\n client Claude\n prompt #\"\n {{ _.role(\"system\") }}\n You are an assistant that always responds in a very excited way with emojis and also outputs this word 4 times after giving a response: {{ input }}\n \n {{ _.role(\"user\") }}\n Tell me a haiku about {{ input }}\n \"#\n}\n\nfunction PromptTestClaudeChatNoSystem(input: string) -> string {\n client Claude\n prompt #\"\n You are an assistant that always responds in a very excited way with emojis and also outputs this word 4 times after giving a response: {{ input }}\n \n {{ _.role(\"user\") }}\n Tell me a haiku about {{ input }}\n \"#\n}\n\ntest TestSystemAndNonSystemChat1 {\n functions [PromptTestClaude, PromptTestOpenAI, PromptTestOpenAIChat, PromptTestOpenAIChatNoSystem, PromptTestClaudeChat, PromptTestClaudeChatNoSystem]\n args {\n input \"cats\"\n }\n}\n\ntest TestSystemAndNonSystemChat2 {\n functions [PromptTestClaude, PromptTestOpenAI, PromptTestOpenAIChat, PromptTestOpenAIChatNoSystem, PromptTestClaudeChat, PromptTestClaudeChatNoSystem]\n args {\n input \"lion\"\n }\n}", diff --git a/integ-tests/python/baml_client/sync_client.py b/integ-tests/python/baml_client/sync_client.py index d1f667c70..696442e3c 100644 --- a/integ-tests/python/baml_client/sync_client.py +++ b/integ-tests/python/baml_client/sync_client.py @@ -1450,6 +1450,29 @@ def MakeNestedBlockConstraint( ) return cast(types.NestedBlockConstraint, raw.cast_to(types, types)) + def MapAlias( + self, + m: Dict[str, List[str]], + baml_options: BamlCallOptions = {}, + ) -> Dict[str, List[str]]: + __tb__ = baml_options.get("tb", None) + if __tb__ is not None: + tb = __tb__._tb # type: ignore (we know how to use this private attribute) + else: + tb = None + __cr__ = baml_options.get("client_registry", None) + + raw = self.__runtime.call_function_sync( + "MapAlias", + { + "m": m, + }, + self.__ctx_manager.get(), + tb, + __cr__, + ) + return cast(Dict[str, List[str]], raw.cast_to(types, types)) + def MyFunc( self, input: str, @@ -1473,6 +1496,29 @@ def MyFunc( ) return cast(types.DynamicOutput, raw.cast_to(types, types)) + def NestedAlias( + self, + c: Union[Union[int, str, bool, float], List[str], Dict[str, List[str]]], + baml_options: BamlCallOptions = {}, + ) -> Union[Union[int, str, bool, float], List[str], Dict[str, List[str]]]: + __tb__ = baml_options.get("tb", None) + if __tb__ is not None: + tb = __tb__._tb # type: ignore (we know how to use this private attribute) + else: + tb = None + __cr__ = baml_options.get("client_registry", None) + + raw = self.__runtime.call_function_sync( + "NestedAlias", + { + "c": c, + }, + self.__ctx_manager.get(), + tb, + __cr__, + ) + return cast(Union[Union[int, str, bool, float], List[str], Dict[str, List[str]]], raw.cast_to(types, types)) + def OptionalTest_Function( self, input: str, @@ -1542,6 +1588,29 @@ def PredictAgeBare( ) return cast(Checked[int,types.Literal["too_big"]], raw.cast_to(types, types)) + def PrimitiveAlias( + self, + p: Union[int, str, bool, float], + baml_options: BamlCallOptions = {}, + ) -> Union[int, str, bool, float]: + __tb__ = baml_options.get("tb", None) + if __tb__ is not None: + tb = __tb__._tb # type: ignore (we know how to use this private attribute) + else: + tb = None + __cr__ = baml_options.get("client_registry", None) + + raw = self.__runtime.call_function_sync( + "PrimitiveAlias", + { + "p": p, + }, + self.__ctx_manager.get(), + tb, + __cr__, + ) + return cast(Union[int, str, bool, float], raw.cast_to(types, types)) + def PromptTestClaude( self, input: str, @@ -4608,6 +4677,36 @@ def MakeNestedBlockConstraint( self.__ctx_manager.get(), ) + def MapAlias( + self, + m: Dict[str, List[str]], + baml_options: BamlCallOptions = {}, + ) -> baml_py.BamlSyncStream[Dict[str, List[Optional[str]]], Dict[str, List[str]]]: + __tb__ = baml_options.get("tb", None) + if __tb__ is not None: + tb = __tb__._tb # type: ignore (we know how to use this private attribute) + else: + tb = None + __cr__ = baml_options.get("client_registry", None) + + raw = self.__runtime.stream_function_sync( + "MapAlias", + { + "m": m, + }, + None, + self.__ctx_manager.get(), + tb, + __cr__, + ) + + return baml_py.BamlSyncStream[Dict[str, List[Optional[str]]], Dict[str, List[str]]]( + raw, + lambda x: cast(Dict[str, List[Optional[str]]], x.cast_to(types, partial_types)), + lambda x: cast(Dict[str, List[str]], x.cast_to(types, types)), + self.__ctx_manager.get(), + ) + def MyFunc( self, input: str, @@ -4638,6 +4737,36 @@ def MyFunc( self.__ctx_manager.get(), ) + def NestedAlias( + self, + c: Union[Union[int, str, bool, float], List[str], Dict[str, List[str]]], + baml_options: BamlCallOptions = {}, + ) -> baml_py.BamlSyncStream[Optional[Union[Optional[Union[Optional[int], Optional[str], Optional[bool], Optional[float]]], List[Optional[str]], Dict[str, List[Optional[str]]]]], Union[Union[int, str, bool, float], List[str], Dict[str, List[str]]]]: + __tb__ = baml_options.get("tb", None) + if __tb__ is not None: + tb = __tb__._tb # type: ignore (we know how to use this private attribute) + else: + tb = None + __cr__ = baml_options.get("client_registry", None) + + raw = self.__runtime.stream_function_sync( + "NestedAlias", + { + "c": c, + }, + None, + self.__ctx_manager.get(), + tb, + __cr__, + ) + + return baml_py.BamlSyncStream[Optional[Union[Optional[Union[Optional[int], Optional[str], Optional[bool], Optional[float]]], List[Optional[str]], Dict[str, List[Optional[str]]]]], Union[Union[int, str, bool, float], List[str], Dict[str, List[str]]]]( + raw, + lambda x: cast(Optional[Union[Optional[Union[Optional[int], Optional[str], Optional[bool], Optional[float]]], List[Optional[str]], Dict[str, List[Optional[str]]]]], x.cast_to(types, partial_types)), + lambda x: cast(Union[Union[int, str, bool, float], List[str], Dict[str, List[str]]], x.cast_to(types, types)), + self.__ctx_manager.get(), + ) + def OptionalTest_Function( self, input: str, @@ -4728,6 +4857,36 @@ def PredictAgeBare( self.__ctx_manager.get(), ) + def PrimitiveAlias( + self, + p: Union[int, str, bool, float], + baml_options: BamlCallOptions = {}, + ) -> baml_py.BamlSyncStream[Optional[Union[Optional[int], Optional[str], Optional[bool], Optional[float]]], Union[int, str, bool, float]]: + __tb__ = baml_options.get("tb", None) + if __tb__ is not None: + tb = __tb__._tb # type: ignore (we know how to use this private attribute) + else: + tb = None + __cr__ = baml_options.get("client_registry", None) + + raw = self.__runtime.stream_function_sync( + "PrimitiveAlias", + { + "p": p, + }, + None, + self.__ctx_manager.get(), + tb, + __cr__, + ) + + return baml_py.BamlSyncStream[Optional[Union[Optional[int], Optional[str], Optional[bool], Optional[float]]], Union[int, str, bool, float]]( + raw, + lambda x: cast(Optional[Union[Optional[int], Optional[str], Optional[bool], Optional[float]]], x.cast_to(types, partial_types)), + lambda x: cast(Union[int, str, bool, float], x.cast_to(types, types)), + self.__ctx_manager.get(), + ) + def PromptTestClaude( self, input: str, diff --git a/integ-tests/ruby/baml_client/client.rb b/integ-tests/ruby/baml_client/client.rb index 0bf846671..9bc876faf 100644 --- a/integ-tests/ruby/baml_client/client.rb +++ b/integ-tests/ruby/baml_client/client.rb @@ -2002,6 +2002,38 @@ def MakeNestedBlockConstraint( (raw.parsed_using_types(Baml::Types)) end + sig { + params( + varargs: T.untyped, + m: T::Hash[String, T::Array[String]], + baml_options: T::Hash[Symbol, T.any(Baml::TypeBuilder, Baml::ClientRegistry)] + ).returns(T::Hash[String, T::Array[String]]) + } + def MapAlias( + *varargs, + m:, + baml_options: {} + ) + if varargs.any? + + raise ArgumentError.new("MapAlias may only be called with keyword arguments") + end + if (baml_options.keys - [:client_registry, :tb]).any? + raise ArgumentError.new("Received unknown keys in baml_options (valid keys: :client_registry, :tb): #{baml_options.keys - [:client_registry, :tb]}") + end + + raw = @runtime.call_function( + "MapAlias", + { + m: m, + }, + @ctx_manager, + baml_options[:tb]&.instance_variable_get(:@registry), + baml_options[:client_registry], + ) + (raw.parsed_using_types(Baml::Types)) + end + sig { params( varargs: T.untyped, @@ -2034,6 +2066,38 @@ def MyFunc( (raw.parsed_using_types(Baml::Types)) end + sig { + params( + varargs: T.untyped, + c: T.any(T.any(Integer, String, T::Boolean, Float), T::Array[String], T::Hash[String, T::Array[String]]), + baml_options: T::Hash[Symbol, T.any(Baml::TypeBuilder, Baml::ClientRegistry)] + ).returns(T.any(T.any(Integer, String, T::Boolean, Float), T::Array[String], T::Hash[String, T::Array[String]])) + } + def NestedAlias( + *varargs, + c:, + baml_options: {} + ) + if varargs.any? + + raise ArgumentError.new("NestedAlias may only be called with keyword arguments") + end + if (baml_options.keys - [:client_registry, :tb]).any? + raise ArgumentError.new("Received unknown keys in baml_options (valid keys: :client_registry, :tb): #{baml_options.keys - [:client_registry, :tb]}") + end + + raw = @runtime.call_function( + "NestedAlias", + { + c: c, + }, + @ctx_manager, + baml_options[:tb]&.instance_variable_get(:@registry), + baml_options[:client_registry], + ) + (raw.parsed_using_types(Baml::Types)) + end + sig { params( varargs: T.untyped, @@ -2130,6 +2194,38 @@ def PredictAgeBare( (raw.parsed_using_types(Baml::Types)) end + sig { + params( + varargs: T.untyped, + p: T.any(Integer, String, T::Boolean, Float), + baml_options: T::Hash[Symbol, T.any(Baml::TypeBuilder, Baml::ClientRegistry)] + ).returns(T.any(Integer, String, T::Boolean, Float)) + } + def PrimitiveAlias( + *varargs, + p:, + baml_options: {} + ) + if varargs.any? + + raise ArgumentError.new("PrimitiveAlias may only be called with keyword arguments") + end + if (baml_options.keys - [:client_registry, :tb]).any? + raise ArgumentError.new("Received unknown keys in baml_options (valid keys: :client_registry, :tb): #{baml_options.keys - [:client_registry, :tb]}") + end + + raw = @runtime.call_function( + "PrimitiveAlias", + { + p: p, + }, + @ctx_manager, + baml_options[:tb]&.instance_variable_get(:@registry), + baml_options[:client_registry], + ) + (raw.parsed_using_types(Baml::Types)) + end + sig { params( varargs: T.untyped, @@ -5974,6 +6070,41 @@ def MakeNestedBlockConstraint( ) end + sig { + params( + varargs: T.untyped, + m: T::Hash[String, T::Array[String]], + baml_options: T::Hash[Symbol, T.any(Baml::TypeBuilder, Baml::ClientRegistry)] + ).returns(Baml::BamlStream[T::Hash[String, T::Array[String]]]) + } + def MapAlias( + *varargs, + m:, + baml_options: {} + ) + if varargs.any? + + raise ArgumentError.new("MapAlias may only be called with keyword arguments") + end + if (baml_options.keys - [:client_registry, :tb]).any? + raise ArgumentError.new("Received unknown keys in baml_options (valid keys: :client_registry, :tb): #{baml_options.keys - [:client_registry, :tb]}") + end + + raw = @runtime.stream_function( + "MapAlias", + { + m: m, + }, + @ctx_manager, + baml_options[:tb]&.instance_variable_get(:@registry), + baml_options[:client_registry], + ) + Baml::BamlStream[T::Hash[String, T::Array[T.nilable(String)]], T::Hash[String, T::Array[String]]].new( + ffi_stream: raw, + ctx_manager: @ctx_manager + ) + end + sig { params( varargs: T.untyped, @@ -6009,6 +6140,41 @@ def MyFunc( ) end + sig { + params( + varargs: T.untyped, + c: T.any(T.any(Integer, String, T::Boolean, Float), T::Array[String], T::Hash[String, T::Array[String]]), + baml_options: T::Hash[Symbol, T.any(Baml::TypeBuilder, Baml::ClientRegistry)] + ).returns(Baml::BamlStream[T.any(T.any(Integer, String, T::Boolean, Float), T::Array[String], T::Hash[String, T::Array[String]])]) + } + def NestedAlias( + *varargs, + c:, + baml_options: {} + ) + if varargs.any? + + raise ArgumentError.new("NestedAlias may only be called with keyword arguments") + end + if (baml_options.keys - [:client_registry, :tb]).any? + raise ArgumentError.new("Received unknown keys in baml_options (valid keys: :client_registry, :tb): #{baml_options.keys - [:client_registry, :tb]}") + end + + raw = @runtime.stream_function( + "NestedAlias", + { + c: c, + }, + @ctx_manager, + baml_options[:tb]&.instance_variable_get(:@registry), + baml_options[:client_registry], + ) + Baml::BamlStream[T.nilable(T.any(T.nilable(T.any(T.nilable(Integer), T.nilable(String), T.nilable(T::Boolean), T.nilable(Float))), T::Array[T.nilable(String)], T::Hash[String, T::Array[T.nilable(String)]])), T.any(T.any(Integer, String, T::Boolean, Float), T::Array[String], T::Hash[String, T::Array[String]])].new( + ffi_stream: raw, + ctx_manager: @ctx_manager + ) + end + sig { params( varargs: T.untyped, @@ -6114,6 +6280,41 @@ def PredictAgeBare( ) end + sig { + params( + varargs: T.untyped, + p: T.any(Integer, String, T::Boolean, Float), + baml_options: T::Hash[Symbol, T.any(Baml::TypeBuilder, Baml::ClientRegistry)] + ).returns(Baml::BamlStream[T.any(Integer, String, T::Boolean, Float)]) + } + def PrimitiveAlias( + *varargs, + p:, + baml_options: {} + ) + if varargs.any? + + raise ArgumentError.new("PrimitiveAlias may only be called with keyword arguments") + end + if (baml_options.keys - [:client_registry, :tb]).any? + raise ArgumentError.new("Received unknown keys in baml_options (valid keys: :client_registry, :tb): #{baml_options.keys - [:client_registry, :tb]}") + end + + raw = @runtime.stream_function( + "PrimitiveAlias", + { + p: p, + }, + @ctx_manager, + baml_options[:tb]&.instance_variable_get(:@registry), + baml_options[:client_registry], + ) + Baml::BamlStream[T.nilable(T.any(T.nilable(Integer), T.nilable(String), T.nilable(T::Boolean), T.nilable(Float))), T.any(Integer, String, T::Boolean, Float)].new( + ffi_stream: raw, + ctx_manager: @ctx_manager + ) + end + sig { params( varargs: T.untyped, diff --git a/integ-tests/ruby/baml_client/inlined.rb b/integ-tests/ruby/baml_client/inlined.rb index 05f152c23..d1720315f 100644 --- a/integ-tests/ruby/baml_client/inlined.rb +++ b/integ-tests/ruby/baml_client/inlined.rb @@ -82,6 +82,7 @@ module Inlined "test-files/functions/output/recursive-class.baml" => "class Node {\n data int\n next Node?\n}\n\nclass LinkedList {\n head Node?\n len int\n}\n\nclient<llm> O1 {\n provider \"openai\"\n options {\n model \"o1-mini\"\n default_role \"user\"\n }\n}\n\nfunction BuildLinkedList(input: int[]) -> LinkedList {\n client O1\n prompt #\"\n Build a linked list from the input array of integers.\n\n INPUT:\n {{ input }}\n\n {{ ctx.output_format }} \n \"#\n}\n\ntest TestLinkedList {\n functions [BuildLinkedList]\n args {\n input [1, 2, 3, 4, 5]\n }\n}\n", "test-files/functions/output/serialization-error.baml" => "class DummyOutput {\n nonce string\n nonce2 string\n @@dynamic\n}\n\nfunction DummyOutputFunction(input: string) -> DummyOutput {\n client GPT35\n prompt #\"\n Say \"hello there\".\n \"#\n}", "test-files/functions/output/string-list.baml" => "function FnOutputStringList(input: string) -> string[] {\n client GPT35\n prompt #\"\n Return a list of strings in json format like [\"string1\", \"string2\", \"string3\"].\n\n JSON:\n \"#\n}\n\ntest FnOutputStringList {\n functions [FnOutputStringList]\n args {\n input \"example input\"\n }\n}\n", + "test-files/functions/output/type-aliases.baml" => "type Primitive = int | string | bool | float\n\ntype List = string[]\n\ntype Graph = map<string, string[]>\n\ntype Combination = Primitive | List | Graph\n\nfunction PrimitiveAlias(p: Primitive) -> Primitive {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given value back: {{ p }}\n \"#\n}\n\nfunction MapAlias(m: Graph) -> Graph {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given Graph back:\n\n {{ m }}\n\n {{ ctx.output_format }}\n \"#\n}\n\nfunction NestedAlias(c: Combination) -> Combination {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given value back:\n\n {{ c }}\n\n {{ ctx.output_format }}\n \"#\n}\n", "test-files/functions/output/unions.baml" => "class UnionTest_ReturnType {\n prop1 string | bool\n prop2 (float | bool)[]\n prop3 (bool[] | int[])\n}\n\nfunction UnionTest_Function(input: string | bool) -> UnionTest_ReturnType {\n client GPT35\n prompt #\"\n Return a JSON blob with this schema: \n {{ctx.output_format}}\n\n JSON:\n \"#\n}\n\ntest UnionTest_Function {\n functions [UnionTest_Function]\n args {\n input \"example input\"\n }\n}\n", "test-files/functions/prompts/no-chat-messages.baml" => "\n\nfunction PromptTestClaude(input: string) -> string {\n client Sonnet\n prompt #\"\n Tell me a haiku about {{ input }}\n \"#\n}\n\n\nfunction PromptTestStreaming(input: string) -> string {\n client GPT35\n prompt #\"\n Tell me a short story about {{ input }}\n \"#\n}\n\ntest TestName {\n functions [PromptTestStreaming]\n args {\n input #\"\n hello world\n \"#\n }\n}\n", "test-files/functions/prompts/with-chat-messages.baml" => "\nfunction PromptTestOpenAIChat(input: string) -> string {\n client GPT35\n prompt #\"\n {{ _.role(\"system\") }}\n You are an assistant that always responds in a very excited way with emojis and also outputs this word 4 times after giving a response: {{ input }}\n \n {{ _.role(\"user\") }}\n Tell me a haiku about {{ input }}\n \"#\n}\n\nfunction PromptTestOpenAIChatNoSystem(input: string) -> string {\n client GPT35\n prompt #\"\n You are an assistant that always responds in a very excited way with emojis and also outputs this word 4 times after giving a response: {{ input }}\n \n {{ _.role(\"user\") }}\n Tell me a haiku about {{ input }}\n \"#\n}\n\nfunction PromptTestClaudeChat(input: string) -> string {\n client Claude\n prompt #\"\n {{ _.role(\"system\") }}\n You are an assistant that always responds in a very excited way with emojis and also outputs this word 4 times after giving a response: {{ input }}\n \n {{ _.role(\"user\") }}\n Tell me a haiku about {{ input }}\n \"#\n}\n\nfunction PromptTestClaudeChatNoSystem(input: string) -> string {\n client Claude\n prompt #\"\n You are an assistant that always responds in a very excited way with emojis and also outputs this word 4 times after giving a response: {{ input }}\n \n {{ _.role(\"user\") }}\n Tell me a haiku about {{ input }}\n \"#\n}\n\ntest TestSystemAndNonSystemChat1 {\n functions [PromptTestClaude, PromptTestOpenAI, PromptTestOpenAIChat, PromptTestOpenAIChatNoSystem, PromptTestClaudeChat, PromptTestClaudeChatNoSystem]\n args {\n input \"cats\"\n }\n}\n\ntest TestSystemAndNonSystemChat2 {\n functions [PromptTestClaude, PromptTestOpenAI, PromptTestOpenAIChat, PromptTestOpenAIChatNoSystem, PromptTestClaudeChat, PromptTestClaudeChatNoSystem]\n args {\n input \"lion\"\n }\n}", diff --git a/integ-tests/typescript/baml_client/async_client.ts b/integ-tests/typescript/baml_client/async_client.ts index 7c910ffae..65c6d49d5 100644 --- a/integ-tests/typescript/baml_client/async_client.ts +++ b/integ-tests/typescript/baml_client/async_client.ts @@ -1568,6 +1568,31 @@ export class BamlAsyncClient { } } + async MapAlias( + m: Record<string, string[]>, + __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } + ): Promise<Record<string, string[]>> { + try { + const raw = await this.runtime.callFunction( + "MapAlias", + { + "m": m + }, + this.ctx_manager.cloneContext(), + __baml_options__?.tb?.__tb(), + __baml_options__?.clientRegistry, + ) + return raw.parsed() as Record<string, string[]> + } catch (error: any) { + const bamlError = createBamlValidationError(error); + if (bamlError instanceof BamlValidationError) { + throw bamlError; + } else { + throw error; + } + } + } + async MyFunc( input: string, __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } @@ -1593,6 +1618,31 @@ export class BamlAsyncClient { } } + async NestedAlias( + c: number | string | boolean | number | string[] | Record<string, string[]>, + __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } + ): Promise<number | string | boolean | number | string[] | Record<string, string[]>> { + try { + const raw = await this.runtime.callFunction( + "NestedAlias", + { + "c": c + }, + this.ctx_manager.cloneContext(), + __baml_options__?.tb?.__tb(), + __baml_options__?.clientRegistry, + ) + return raw.parsed() as number | string | boolean | number | string[] | Record<string, string[]> + } catch (error: any) { + const bamlError = createBamlValidationError(error); + if (bamlError instanceof BamlValidationError) { + throw bamlError; + } else { + throw error; + } + } + } + async OptionalTest_Function( input: string, __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } @@ -1668,6 +1718,31 @@ export class BamlAsyncClient { } } + async PrimitiveAlias( + p: number | string | boolean | number, + __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } + ): Promise<number | string | boolean | number> { + try { + const raw = await this.runtime.callFunction( + "PrimitiveAlias", + { + "p": p + }, + this.ctx_manager.cloneContext(), + __baml_options__?.tb?.__tb(), + __baml_options__?.clientRegistry, + ) + return raw.parsed() as number | string | boolean | number + } catch (error: any) { + const bamlError = createBamlValidationError(error); + if (bamlError instanceof BamlValidationError) { + throw bamlError; + } else { + throw error; + } + } + } + async PromptTestClaude( input: string, __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } @@ -5012,6 +5087,39 @@ class BamlStreamClient { } } + MapAlias( + m: Record<string, string[]>, + __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } + ): BamlStream<RecursivePartialNull<Record<string, string[]>>, Record<string, string[]>> { + try { + const raw = this.runtime.streamFunction( + "MapAlias", + { + "m": m + }, + undefined, + this.ctx_manager.cloneContext(), + __baml_options__?.tb?.__tb(), + __baml_options__?.clientRegistry, + ) + return new BamlStream<RecursivePartialNull<Record<string, string[]>>, Record<string, string[]>>( + raw, + (a): a is RecursivePartialNull<Record<string, string[]>> => a, + (a): a is Record<string, string[]> => a, + this.ctx_manager.cloneContext(), + __baml_options__?.tb?.__tb(), + ) + } catch (error) { + if (error instanceof Error) { + const bamlError = createBamlValidationError(error); + if (bamlError instanceof BamlValidationError) { + throw bamlError; + } + } + throw error; + } + } + MyFunc( input: string, __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } @@ -5045,6 +5153,39 @@ class BamlStreamClient { } } + NestedAlias( + c: number | string | boolean | number | string[] | Record<string, string[]>, + __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } + ): BamlStream<RecursivePartialNull<number | string | boolean | number | string[] | Record<string, string[]>>, number | string | boolean | number | string[] | Record<string, string[]>> { + try { + const raw = this.runtime.streamFunction( + "NestedAlias", + { + "c": c + }, + undefined, + this.ctx_manager.cloneContext(), + __baml_options__?.tb?.__tb(), + __baml_options__?.clientRegistry, + ) + return new BamlStream<RecursivePartialNull<number | string | boolean | number | string[] | Record<string, string[]>>, number | string | boolean | number | string[] | Record<string, string[]>>( + raw, + (a): a is RecursivePartialNull<number | string | boolean | number | string[] | Record<string, string[]>> => a, + (a): a is number | string | boolean | number | string[] | Record<string, string[]> => a, + this.ctx_manager.cloneContext(), + __baml_options__?.tb?.__tb(), + ) + } catch (error) { + if (error instanceof Error) { + const bamlError = createBamlValidationError(error); + if (bamlError instanceof BamlValidationError) { + throw bamlError; + } + } + throw error; + } + } + OptionalTest_Function( input: string, __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } @@ -5144,6 +5285,39 @@ class BamlStreamClient { } } + PrimitiveAlias( + p: number | string | boolean | number, + __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } + ): BamlStream<RecursivePartialNull<number | string | boolean | number>, number | string | boolean | number> { + try { + const raw = this.runtime.streamFunction( + "PrimitiveAlias", + { + "p": p + }, + undefined, + this.ctx_manager.cloneContext(), + __baml_options__?.tb?.__tb(), + __baml_options__?.clientRegistry, + ) + return new BamlStream<RecursivePartialNull<number | string | boolean | number>, number | string | boolean | number>( + raw, + (a): a is RecursivePartialNull<number | string | boolean | number> => a, + (a): a is number | string | boolean | number => a, + this.ctx_manager.cloneContext(), + __baml_options__?.tb?.__tb(), + ) + } catch (error) { + if (error instanceof Error) { + const bamlError = createBamlValidationError(error); + if (bamlError instanceof BamlValidationError) { + throw bamlError; + } + } + throw error; + } + } + PromptTestClaude( input: string, __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } diff --git a/integ-tests/typescript/baml_client/inlinedbaml.ts b/integ-tests/typescript/baml_client/inlinedbaml.ts index 57db73d28..1535ba826 100644 --- a/integ-tests/typescript/baml_client/inlinedbaml.ts +++ b/integ-tests/typescript/baml_client/inlinedbaml.ts @@ -83,6 +83,7 @@ const fileMap = { "test-files/functions/output/recursive-class.baml": "class Node {\n data int\n next Node?\n}\n\nclass LinkedList {\n head Node?\n len int\n}\n\nclient<llm> O1 {\n provider \"openai\"\n options {\n model \"o1-mini\"\n default_role \"user\"\n }\n}\n\nfunction BuildLinkedList(input: int[]) -> LinkedList {\n client O1\n prompt #\"\n Build a linked list from the input array of integers.\n\n INPUT:\n {{ input }}\n\n {{ ctx.output_format }} \n \"#\n}\n\ntest TestLinkedList {\n functions [BuildLinkedList]\n args {\n input [1, 2, 3, 4, 5]\n }\n}\n", "test-files/functions/output/serialization-error.baml": "class DummyOutput {\n nonce string\n nonce2 string\n @@dynamic\n}\n\nfunction DummyOutputFunction(input: string) -> DummyOutput {\n client GPT35\n prompt #\"\n Say \"hello there\".\n \"#\n}", "test-files/functions/output/string-list.baml": "function FnOutputStringList(input: string) -> string[] {\n client GPT35\n prompt #\"\n Return a list of strings in json format like [\"string1\", \"string2\", \"string3\"].\n\n JSON:\n \"#\n}\n\ntest FnOutputStringList {\n functions [FnOutputStringList]\n args {\n input \"example input\"\n }\n}\n", + "test-files/functions/output/type-aliases.baml": "type Primitive = int | string | bool | float\n\ntype List = string[]\n\ntype Graph = map<string, string[]>\n\ntype Combination = Primitive | List | Graph\n\nfunction PrimitiveAlias(p: Primitive) -> Primitive {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given value back: {{ p }}\n \"#\n}\n\nfunction MapAlias(m: Graph) -> Graph {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given Graph back:\n\n {{ m }}\n\n {{ ctx.output_format }}\n \"#\n}\n\nfunction NestedAlias(c: Combination) -> Combination {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given value back:\n\n {{ c }}\n\n {{ ctx.output_format }}\n \"#\n}\n", "test-files/functions/output/unions.baml": "class UnionTest_ReturnType {\n prop1 string | bool\n prop2 (float | bool)[]\n prop3 (bool[] | int[])\n}\n\nfunction UnionTest_Function(input: string | bool) -> UnionTest_ReturnType {\n client GPT35\n prompt #\"\n Return a JSON blob with this schema: \n {{ctx.output_format}}\n\n JSON:\n \"#\n}\n\ntest UnionTest_Function {\n functions [UnionTest_Function]\n args {\n input \"example input\"\n }\n}\n", "test-files/functions/prompts/no-chat-messages.baml": "\n\nfunction PromptTestClaude(input: string) -> string {\n client Sonnet\n prompt #\"\n Tell me a haiku about {{ input }}\n \"#\n}\n\n\nfunction PromptTestStreaming(input: string) -> string {\n client GPT35\n prompt #\"\n Tell me a short story about {{ input }}\n \"#\n}\n\ntest TestName {\n functions [PromptTestStreaming]\n args {\n input #\"\n hello world\n \"#\n }\n}\n", "test-files/functions/prompts/with-chat-messages.baml": "\nfunction PromptTestOpenAIChat(input: string) -> string {\n client GPT35\n prompt #\"\n {{ _.role(\"system\") }}\n You are an assistant that always responds in a very excited way with emojis and also outputs this word 4 times after giving a response: {{ input }}\n \n {{ _.role(\"user\") }}\n Tell me a haiku about {{ input }}\n \"#\n}\n\nfunction PromptTestOpenAIChatNoSystem(input: string) -> string {\n client GPT35\n prompt #\"\n You are an assistant that always responds in a very excited way with emojis and also outputs this word 4 times after giving a response: {{ input }}\n \n {{ _.role(\"user\") }}\n Tell me a haiku about {{ input }}\n \"#\n}\n\nfunction PromptTestClaudeChat(input: string) -> string {\n client Claude\n prompt #\"\n {{ _.role(\"system\") }}\n You are an assistant that always responds in a very excited way with emojis and also outputs this word 4 times after giving a response: {{ input }}\n \n {{ _.role(\"user\") }}\n Tell me a haiku about {{ input }}\n \"#\n}\n\nfunction PromptTestClaudeChatNoSystem(input: string) -> string {\n client Claude\n prompt #\"\n You are an assistant that always responds in a very excited way with emojis and also outputs this word 4 times after giving a response: {{ input }}\n \n {{ _.role(\"user\") }}\n Tell me a haiku about {{ input }}\n \"#\n}\n\ntest TestSystemAndNonSystemChat1 {\n functions [PromptTestClaude, PromptTestOpenAI, PromptTestOpenAIChat, PromptTestOpenAIChatNoSystem, PromptTestClaudeChat, PromptTestClaudeChatNoSystem]\n args {\n input \"cats\"\n }\n}\n\ntest TestSystemAndNonSystemChat2 {\n functions [PromptTestClaude, PromptTestOpenAI, PromptTestOpenAIChat, PromptTestOpenAIChatNoSystem, PromptTestClaudeChat, PromptTestClaudeChatNoSystem]\n args {\n input \"lion\"\n }\n}", diff --git a/integ-tests/typescript/baml_client/sync_client.ts b/integ-tests/typescript/baml_client/sync_client.ts index f57137669..dc46c958b 100644 --- a/integ-tests/typescript/baml_client/sync_client.ts +++ b/integ-tests/typescript/baml_client/sync_client.ts @@ -1568,6 +1568,31 @@ export class BamlSyncClient { } } + MapAlias( + m: Record<string, string[]>, + __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } + ): Record<string, string[]> { + try { + const raw = this.runtime.callFunctionSync( + "MapAlias", + { + "m": m + }, + this.ctx_manager.cloneContext(), + __baml_options__?.tb?.__tb(), + __baml_options__?.clientRegistry, + ) + return raw.parsed() as Record<string, string[]> + } catch (error: any) { + const bamlError = createBamlValidationError(error); + if (bamlError instanceof BamlValidationError) { + throw bamlError; + } else { + throw error; + } + } + } + MyFunc( input: string, __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } @@ -1593,6 +1618,31 @@ export class BamlSyncClient { } } + NestedAlias( + c: number | string | boolean | number | string[] | Record<string, string[]>, + __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } + ): number | string | boolean | number | string[] | Record<string, string[]> { + try { + const raw = this.runtime.callFunctionSync( + "NestedAlias", + { + "c": c + }, + this.ctx_manager.cloneContext(), + __baml_options__?.tb?.__tb(), + __baml_options__?.clientRegistry, + ) + return raw.parsed() as number | string | boolean | number | string[] | Record<string, string[]> + } catch (error: any) { + const bamlError = createBamlValidationError(error); + if (bamlError instanceof BamlValidationError) { + throw bamlError; + } else { + throw error; + } + } + } + OptionalTest_Function( input: string, __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } @@ -1668,6 +1718,31 @@ export class BamlSyncClient { } } + PrimitiveAlias( + p: number | string | boolean | number, + __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } + ): number | string | boolean | number { + try { + const raw = this.runtime.callFunctionSync( + "PrimitiveAlias", + { + "p": p + }, + this.ctx_manager.cloneContext(), + __baml_options__?.tb?.__tb(), + __baml_options__?.clientRegistry, + ) + return raw.parsed() as number | string | boolean | number + } catch (error: any) { + const bamlError = createBamlValidationError(error); + if (bamlError instanceof BamlValidationError) { + throw bamlError; + } else { + throw error; + } + } + } + PromptTestClaude( input: string, __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } From 273f13d9245ca6e3f5b0d7c700ecde328fe9508a Mon Sep 17 00:00:00 2001 From: Antonio Sarosi <sarosiantonio@gmail.com> Date: Wed, 27 Nov 2024 02:00:09 +0000 Subject: [PATCH 15/51] Fix recursive aliases --- .../validation_pipeline/validations/cycle.rs | 46 ++++++++++++- .../class/recursive_type_aliases.baml | 49 +++++++++++++ engine/baml-lib/parser-database/src/lib.rs | 18 +++++ engine/baml-lib/parser-database/src/tarjan.rs | 23 ++++--- .../baml-lib/parser-database/src/types/mod.rs | 68 ++++++++++++++----- 5 files changed, 176 insertions(+), 28 deletions(-) create mode 100644 engine/baml-lib/baml/tests/validation_files/class/recursive_type_aliases.baml diff --git a/engine/baml-lib/baml-core/src/validate/validation_pipeline/validations/cycle.rs b/engine/baml-lib/baml-core/src/validate/validation_pipeline/validations/cycle.rs index 25d872143..b9248ef84 100644 --- a/engine/baml-lib/baml-core/src/validate/validation_pipeline/validations/cycle.rs +++ b/engine/baml-lib/baml-core/src/validate/validation_pipeline/validations/cycle.rs @@ -51,6 +51,23 @@ pub(super) fn validate(ctx: &mut Context<'_>) { ctx.db.ast()[component[0]].span().clone(), )); } + + // TODO: Extract this into some generic function. + eprintln!("Type aliases: {:?}", ctx.db.type_aliases()); + for component in Tarjan::components(&ctx.db.type_aliases()) { + let cycle = component + .iter() + .map(|id| ctx.db.ast()[*id].name().to_string()) + .collect::<Vec<_>>() + .join(" -> "); + + // TODO: We can push an error for every sinlge class here (that's what + // Rust does), for now it's an error for every cycle found. + ctx.push_error(DatamodelError::new_validation_error( + &format!("These aliases form a dependency cycle: {}", cycle), + ctx.db.ast()[component[0]].span().clone(), + )); + } } /// Inserts all the required dependencies of a field into the given set. @@ -66,8 +83,33 @@ fn insert_required_deps( ) { match field { FieldType::Symbol(arity, ident, _) if arity.is_required() => { - if let Some(TypeWalker::Class(class)) = ctx.db.find_type_by_str(ident.name()) { - deps.insert(class.id); + match ctx.db.find_type_by_str(ident.name()) { + Some(TypeWalker::Class(class)) => { + deps.insert(class.id); + } + Some(TypeWalker::TypeAlias(alias)) => { + // TODO: By the time this code runs we would ideally want + // type aliases to be resolved but we can't do that because + // type alias cycles are not validated yet, we have to + // do that in this file. Take a look at the `validate` + // function at `baml-lib/baml-core/src/lib.rs`. + // + // First we run the `ParserDatabase::validate` function + // which creates the alias graph by visiting all aliases. + // Then we run the `validate::validate` which ends up + // running this code here. Finally we run the + // `ParserDatabase::finalize` which is the place where we + // can resolve type aliases since we've already validated + // that there are no cycles so we won't run into infinite + // recursion. Ideally we want this: + // + // insert_required_deps(id, alias.resolved(), ctx, deps); + + // But we'll run this instead which will follow all the + // alias pointers again until it finds the resolved type. + insert_required_deps(id, alias.target(), ctx, deps); + } + _ => {} } } diff --git a/engine/baml-lib/baml/tests/validation_files/class/recursive_type_aliases.baml b/engine/baml-lib/baml/tests/validation_files/class/recursive_type_aliases.baml new file mode 100644 index 000000000..7bedddda8 --- /dev/null +++ b/engine/baml-lib/baml/tests/validation_files/class/recursive_type_aliases.baml @@ -0,0 +1,49 @@ +// Simple alias that points to recursive type. +class Node { + value int + next Node? +} + +type LinkedList = Node + +// Mutual recursion. There is no "type" here at all. +type One = Two + +type Two = One + +// Cycle. Same as above but longer. +type A = B + +type B = C + +type C = A + +// Recursive class with alias pointing to itself. +class Recursive { + value int + ptr RecAlias +} + +type RecAlias = Recursive + +// error: Error validating: These classes form a dependency cycle: Recursive +// --> class/recursive_type_aliases.baml:22 +// | +// 21 | // Recursive class with alias pointing to itself. +// 22 | class Recursive { +// 23 | value int +// 24 | ptr RecAlias +// 25 | } +// | +// error: Error validating: These aliases form a dependency cycle: One -> Two +// --> class/recursive_type_aliases.baml:10 +// | +// 9 | // Mutual recursion. There is no "type" here at all. +// 10 | type One = Two +// | +// error: Error validating: These aliases form a dependency cycle: A -> B -> C +// --> class/recursive_type_aliases.baml:15 +// | +// 14 | // Cycle. Same as above but longer. +// 15 | type A = B +// | diff --git a/engine/baml-lib/parser-database/src/lib.rs b/engine/baml-lib/parser-database/src/lib.rs index 481f8a673..8fe9dfd68 100644 --- a/engine/baml-lib/parser-database/src/lib.rs +++ b/engine/baml-lib/parser-database/src/lib.rs @@ -42,6 +42,7 @@ pub use coerce_expression::{coerce, coerce_array, coerce_opt}; pub use internal_baml_schema_ast::ast; use internal_baml_schema_ast::ast::SchemaAst; pub use tarjan::Tarjan; +use types::resolve_type_alias; pub use types::{ Attributes, ContantDelayStrategy, ExponentialBackoffStrategy, PrinterType, PromptAst, PromptVariable, RetryPolicy, RetryPolicyStrategy, StaticType, @@ -173,6 +174,14 @@ impl ParserDatabase { .map(|cycle| cycle.into_iter().collect()) .collect(); + // Resolve type aliases. + // Cycles are already validated so this should not stack overflow and + // it should find the final type. + for alias_id in self.types.type_aliases.keys() { + let resolved = resolve_type_alias(&self.ast[*alias_id].value, &self); + self.types.resolved_type_aliases.insert(*alias_id, resolved); + } + // Additionally ensure the same thing for functions, but since we've // already handled classes, this should be trivial. let extends = self @@ -226,6 +235,15 @@ impl ParserDatabase { pub fn ast(&self) -> &ast::SchemaAst { &self.ast } + + /// Returns the graph of type aliases. + /// + /// Each vertex is a type alias and each edge is a reference to another type + /// alias. + pub fn type_aliases(&self) -> &HashMap<ast::TypeAliasId, HashSet<ast::TypeAliasId>> { + &self.types.type_aliases + } + /// The total number of enums in the schema. This is O(1). pub fn enums_count(&self) -> usize { self.types.enum_attributes.len() diff --git a/engine/baml-lib/parser-database/src/tarjan.rs b/engine/baml-lib/parser-database/src/tarjan.rs index 935969e95..4cc8e5356 100644 --- a/engine/baml-lib/parser-database/src/tarjan.rs +++ b/engine/baml-lib/parser-database/src/tarjan.rs @@ -6,12 +6,13 @@ use std::{ cmp, collections::{HashMap, HashSet}, + hash::Hash, }; use internal_baml_schema_ast::ast::TypeExpId; /// Dependency graph represented as an adjacency list. -type Graph = HashMap<TypeExpId, HashSet<TypeExpId>>; +type Graph<V> = HashMap<V, HashSet<V>>; /// State of each node for Tarjan's algorithm. #[derive(Clone, Copy)] @@ -35,20 +36,24 @@ struct NodeState { /// This struct is simply bookkeeping for the algorithm, it can be implemented /// with just function calls but the recursive one would need 6 parameters which /// is pretty ugly. -pub struct Tarjan<'g> { +pub struct Tarjan<'g, V> { /// Ref to the depdenency graph. - graph: &'g Graph, + graph: &'g Graph<V>, /// Node number counter. index: usize, /// Nodes are placed on a stack in the order in which they are visited. - stack: Vec<TypeExpId>, + stack: Vec<V>, /// State of each node. - state: HashMap<TypeExpId, NodeState>, + state: HashMap<V, NodeState>, /// Strongly connected components. - components: Vec<Vec<TypeExpId>>, + components: Vec<Vec<V>>, } -impl<'g> Tarjan<'g> { +// V is Copy because we mostly use opaque identifiers for class or alias IDs. +// In practice T ends up being a u32, but if for some reason this needs to +// be used with strings then we can make V Clone instead of Copy and refactor +// the code below. +impl<'g, V: Eq + Ord + Hash + Copy> Tarjan<'g, V> { /// Unvisited node marker. /// /// Technically we should use [`Option<usize>`] and [`None`] for @@ -63,7 +68,7 @@ impl<'g> Tarjan<'g> { /// Loops through all the nodes in the graph and visits them if they haven't /// been visited already. When the algorithm is done, [`Self::components`] /// will contain all the cycles in the graph. - pub fn components(graph: &'g Graph) -> Vec<Vec<TypeExpId>> { + pub fn components(graph: &'g Graph<V>) -> Vec<Vec<V>> { let mut tarjans = Self { graph, index: 0, @@ -105,7 +110,7 @@ impl<'g> Tarjan<'g> { /// /// This is where the "algorithm" runs. Could be implemented iteratively if /// needed at some point. - fn strong_connect(&mut self, node_id: TypeExpId) { + fn strong_connect(&mut self, node_id: V) { // Initialize node state. This node has not yet been visited so we don't // have to grab the state from the hash map. And if we did, then we'd // have to fight the borrow checker by taking mut refs and read-only diff --git a/engine/baml-lib/parser-database/src/types/mod.rs b/engine/baml-lib/parser-database/src/types/mod.rs index 6abb3d607..eec91c928 100644 --- a/engine/baml-lib/parser-database/src/types/mod.rs +++ b/engine/baml-lib/parser-database/src/types/mod.rs @@ -1,8 +1,8 @@ use std::collections::{HashMap, HashSet, VecDeque}; use std::hash::Hash; -use crate::coerce; use crate::types::configurations::visit_test_case; +use crate::{coerce, ParserDatabase}; use crate::{context::Context, DatamodelError}; use baml_types::Constraint; @@ -233,6 +233,11 @@ pub(super) struct Types { pub(super) class_dependencies: HashMap<ast::TypeExpId, HashSet<String>>, pub(super) enum_dependencies: HashMap<ast::TypeExpId, HashSet<String>>, + /// Graph of type aliases. + /// + /// This graph is only used to detect infinite cycles in type aliases. + pub(crate) type_aliases: HashMap<ast::TypeAliasId, HashSet<ast::TypeAliasId>>, + /// Fully resolved type aliases. /// /// A type alias con point to one or many other type aliases. @@ -374,29 +379,29 @@ fn visit_class<'db>( /// /// The type would resolve to `SomeClass | AnotherClass | int`, which is not /// stored in the AST. -fn resolve_type_alias(field_type: &FieldType, ctx: &mut Context<'_>) -> FieldType { +pub fn resolve_type_alias(field_type: &FieldType, db: &ParserDatabase) -> FieldType { match field_type { // For symbols we need to check if we're dealing with aliases. FieldType::Symbol(arity, ident, span) => { - let Some(string_id) = ctx.interner.lookup(ident.name()) else { + let Some(string_id) = db.interner.lookup(ident.name()) else { unreachable!( "Attempting to resolve alias `{ident}` that does not exist in the interner" ); }; - let Some(top_id) = ctx.names.tops.get(&string_id) else { + let Some(top_id) = db.names.tops.get(&string_id) else { unreachable!("Alias name `{ident}` is not registered in the context"); }; match top_id { ast::TopId::TypeAlias(alias_id) => { // Check if we can avoid deeper recursion. - if let Some(resolved) = ctx.types.resolved_type_aliases.get(alias_id) { + if let Some(resolved) = db.types.resolved_type_aliases.get(alias_id) { return resolved.to_owned(); } // Recurse... TODO: Recursive types and infinite cycles :( - let resolved = resolve_type_alias(&ctx.ast[*alias_id].value, ctx); + let resolved = resolve_type_alias(&db.ast[*alias_id].value, db); // Sync arity. Basically stuff like: // @@ -421,7 +426,7 @@ fn resolve_type_alias(field_type: &FieldType, ctx: &mut Context<'_>) -> FieldTyp | FieldType::Tuple(arity, items, span, attrs) => { let resolved = items .iter() - .map(|item| resolve_type_alias(item, ctx)) + .map(|item| resolve_type_alias(item, db)) .collect(); match field_type { @@ -446,18 +451,47 @@ fn visit_type_alias<'db>( assignment: &'db ast::Assignment, ctx: &mut Context<'db>, ) { - // Maybe this can't even happen since we iterate over the vec of tops and - // just get IDs sequentially, but anyway check just in case. - if ctx.types.resolved_type_aliases.contains_key(&alias_id) { - return; - } + // Insert the entry as soon as we get here then if we find something we'll + // add edges to the graph. Otherwise no edges but we still need the Vertex + // in order for the cycles algorithm to work. + let alias_refs = ctx.types.type_aliases.entry(alias_id).or_default(); + + let mut stack = vec![&assignment.value]; + + while let Some(item) = stack.pop() { + match item { + FieldType::Symbol(_, ident, _) => { + let Some(string_id) = ctx.interner.lookup(ident.name()) else { + unreachable!("Visiting alias `{ident}` that does not exist in the interner"); + }; - // Now resolve the type. - let resolved = resolve_type_alias(&assignment.value, ctx); + let Some(top_id) = ctx.names.tops.get(&string_id) else { + unreachable!("Alias name `{ident}` is not registered in the context"); + }; - // TODO: Can we add types to the map recursively while solving them at - // the same time? It might speed up very long chains of aliases. - ctx.types.resolved_type_aliases.insert(alias_id, resolved); + // Add alias to the graph. + if let ast::TopId::TypeAlias(nested_alias_id) = top_id { + alias_refs.insert(*nested_alias_id); + } + } + + FieldType::Union(_, items, ..) | FieldType::Tuple(_, items, ..) => { + stack.extend(items.iter()); + } + + FieldType::List(_, nested, ..) => { + stack.push(nested); + } + + FieldType::Map(_, nested, ..) => { + let (key, value) = nested.as_ref(); + stack.push(key); + stack.push(value); + } + + _ => {} + } + } } fn visit_function<'db>(idx: ValExpId, function: &'db ast::ValueExprBlock, ctx: &mut Context<'db>) { From dc8d994bdd12a35a2185bf29717f3ed0b73d7cc6 Mon Sep 17 00:00:00 2001 From: Antonio Sarosi <sarosiantonio@gmail.com> Date: Wed, 27 Nov 2024 02:28:06 +0000 Subject: [PATCH 16/51] Extract infinite cycle finding into generic function --- .../validation_pipeline/validations/cycle.rs | 56 ++++++++++++------- engine/baml-lib/parser-database/src/lib.rs | 6 +- engine/baml-lib/parser-database/src/tarjan.rs | 2 +- .../baml-lib/parser-database/src/types/mod.rs | 8 ++- 4 files changed, 46 insertions(+), 26 deletions(-) diff --git a/engine/baml-lib/baml-core/src/validate/validation_pipeline/validations/cycle.rs b/engine/baml-lib/baml-core/src/validate/validation_pipeline/validations/cycle.rs index b9248ef84..2661ee2cd 100644 --- a/engine/baml-lib/baml-core/src/validate/validation_pipeline/validations/cycle.rs +++ b/engine/baml-lib/baml-core/src/validate/validation_pipeline/validations/cycle.rs @@ -1,8 +1,12 @@ -use std::collections::{HashMap, HashSet}; +use std::{ + collections::{HashMap, HashSet}, + hash::Hash, + ops::Index, +}; use internal_baml_diagnostics::DatamodelError; use internal_baml_parser_database::{Tarjan, TypeWalker}; -use internal_baml_schema_ast::ast::{FieldType, TypeExpId, WithName, WithSpan}; +use internal_baml_schema_ast::ast::{FieldType, SchemaAst, TypeExpId, WithName, WithSpan}; use crate::validate::validation_pipeline::context::Context; @@ -37,24 +41,36 @@ pub(super) fn validate(ctx: &mut Context<'_>) { (class.id, dependencies) })); - for component in Tarjan::components(&dependency_graph) { - let cycle = component - .iter() - .map(|id| ctx.db.ast()[*id].name().to_string()) - .collect::<Vec<_>>() - .join(" -> "); - - // TODO: We can push an error for every sinlge class here (that's what - // Rust does), for now it's an error for every cycle found. - ctx.push_error(DatamodelError::new_validation_error( - &format!("These classes form a dependency cycle: {}", cycle), - ctx.db.ast()[component[0]].span().clone(), - )); - } + report_infinite_cycles( + &dependency_graph, + ctx, + "These classes form a dependency cycle", + ); + + // The graph for aliases is already built when visiting each alias. Maybe + // we can use the same logic for the required dependencies graph above. + report_infinite_cycles( + &ctx.db.type_alias_dependencies(), + ctx, + "These aliases form a dependency cycle", + ); +} - // TODO: Extract this into some generic function. - eprintln!("Type aliases: {:?}", ctx.db.type_aliases()); - for component in Tarjan::components(&ctx.db.type_aliases()) { +/// Finds and reports all the infinite cycles in the given graph. +/// +/// It prints errors like this: +/// +/// "Error validating: These classes form a dependency cycle: A -> B -> C" +fn report_infinite_cycles<V: Ord + Eq + Hash + Copy>( + graph: &HashMap<V, HashSet<V>>, + ctx: &mut Context<'_>, + message: &str, +) where + SchemaAst: Index<V>, + <SchemaAst as Index<V>>::Output: WithName, + <SchemaAst as Index<V>>::Output: WithSpan, +{ + for component in Tarjan::components(graph) { let cycle = component .iter() .map(|id| ctx.db.ast()[*id].name().to_string()) @@ -64,7 +80,7 @@ pub(super) fn validate(ctx: &mut Context<'_>) { // TODO: We can push an error for every sinlge class here (that's what // Rust does), for now it's an error for every cycle found. ctx.push_error(DatamodelError::new_validation_error( - &format!("These aliases form a dependency cycle: {}", cycle), + &format!("{message}: {cycle}"), ctx.db.ast()[component[0]].span().clone(), )); } diff --git a/engine/baml-lib/parser-database/src/lib.rs b/engine/baml-lib/parser-database/src/lib.rs index 8fe9dfd68..d23f18d47 100644 --- a/engine/baml-lib/parser-database/src/lib.rs +++ b/engine/baml-lib/parser-database/src/lib.rs @@ -177,7 +177,7 @@ impl ParserDatabase { // Resolve type aliases. // Cycles are already validated so this should not stack overflow and // it should find the final type. - for alias_id in self.types.type_aliases.keys() { + for alias_id in self.types.type_alias_dependencies.keys() { let resolved = resolve_type_alias(&self.ast[*alias_id].value, &self); self.types.resolved_type_aliases.insert(*alias_id, resolved); } @@ -240,8 +240,8 @@ impl ParserDatabase { /// /// Each vertex is a type alias and each edge is a reference to another type /// alias. - pub fn type_aliases(&self) -> &HashMap<ast::TypeAliasId, HashSet<ast::TypeAliasId>> { - &self.types.type_aliases + pub fn type_alias_dependencies(&self) -> &HashMap<ast::TypeAliasId, HashSet<ast::TypeAliasId>> { + &self.types.type_alias_dependencies } /// The total number of enums in the schema. This is O(1). diff --git a/engine/baml-lib/parser-database/src/tarjan.rs b/engine/baml-lib/parser-database/src/tarjan.rs index 4cc8e5356..55d5af18f 100644 --- a/engine/baml-lib/parser-database/src/tarjan.rs +++ b/engine/baml-lib/parser-database/src/tarjan.rs @@ -50,7 +50,7 @@ pub struct Tarjan<'g, V> { } // V is Copy because we mostly use opaque identifiers for class or alias IDs. -// In practice T ends up being a u32, but if for some reason this needs to +// In practice V ends up being a u32, but if for some reason this needs to // be used with strings then we can make V Clone instead of Copy and refactor // the code below. impl<'g, V: Eq + Ord + Hash + Copy> Tarjan<'g, V> { diff --git a/engine/baml-lib/parser-database/src/types/mod.rs b/engine/baml-lib/parser-database/src/types/mod.rs index eec91c928..f49c678d8 100644 --- a/engine/baml-lib/parser-database/src/types/mod.rs +++ b/engine/baml-lib/parser-database/src/types/mod.rs @@ -236,7 +236,7 @@ pub(super) struct Types { /// Graph of type aliases. /// /// This graph is only used to detect infinite cycles in type aliases. - pub(crate) type_aliases: HashMap<ast::TypeAliasId, HashSet<ast::TypeAliasId>>, + pub(crate) type_alias_dependencies: HashMap<ast::TypeAliasId, HashSet<ast::TypeAliasId>>, /// Fully resolved type aliases. /// @@ -454,7 +454,11 @@ fn visit_type_alias<'db>( // Insert the entry as soon as we get here then if we find something we'll // add edges to the graph. Otherwise no edges but we still need the Vertex // in order for the cycles algorithm to work. - let alias_refs = ctx.types.type_aliases.entry(alias_id).or_default(); + let alias_refs = ctx + .types + .type_alias_dependencies + .entry(alias_id) + .or_default(); let mut stack = vec![&assignment.value]; From 362c9f53957b733e6f3220ecea4446a4373755b4 Mon Sep 17 00:00:00 2001 From: Antonio Sarosi <sarosiantonio@gmail.com> Date: Wed, 27 Nov 2024 02:51:01 +0000 Subject: [PATCH 17/51] Fix recursion when class points to infinite alias cycle --- .../validation_pipeline/validations/cycle.rs | 48 +++++++++++++------ .../class/recursive_type_aliases.baml | 34 +++++++++---- 2 files changed, 59 insertions(+), 23 deletions(-) diff --git a/engine/baml-lib/baml-core/src/validate/validation_pipeline/validations/cycle.rs b/engine/baml-lib/baml-core/src/validate/validation_pipeline/validations/cycle.rs index 2661ee2cd..89e0abb40 100644 --- a/engine/baml-lib/baml-core/src/validate/validation_pipeline/validations/cycle.rs +++ b/engine/baml-lib/baml-core/src/validate/validation_pipeline/validations/cycle.rs @@ -6,12 +6,22 @@ use std::{ use internal_baml_diagnostics::DatamodelError; use internal_baml_parser_database::{Tarjan, TypeWalker}; -use internal_baml_schema_ast::ast::{FieldType, SchemaAst, TypeExpId, WithName, WithSpan}; +use internal_baml_schema_ast::ast::{ + FieldType, SchemaAst, TypeAliasId, TypeExpId, WithName, WithSpan, +}; use crate::validate::validation_pipeline::context::Context; /// Validates if the dependency graph contains one or more infinite cycles. pub(super) fn validate(ctx: &mut Context<'_>) { + // Solve cycles first. We need that information in case a class points to + // an unresolveble type alias. + let alias_cycles = report_infinite_cycles( + &ctx.db.type_alias_dependencies(), + ctx, + "These aliases form a dependency cycle", + ); + // First, build a graph of all the "required" dependencies represented as an // adjacency list. We're only going to consider type dependencies that can // actually cause infinite recursion. Unions and optionals can stop the @@ -34,7 +44,13 @@ pub(super) fn validate(ctx: &mut Context<'_>) { for field in &expr_block.fields { if let Some(field_type) = &field.expr { - insert_required_deps(class.id, field_type, ctx, &mut dependencies); + insert_required_deps( + class.id, + field_type, + ctx, + &mut dependencies, + &alias_cycles.iter().flatten().copied().collect(), + ); } } @@ -46,14 +62,6 @@ pub(super) fn validate(ctx: &mut Context<'_>) { ctx, "These classes form a dependency cycle", ); - - // The graph for aliases is already built when visiting each alias. Maybe - // we can use the same logic for the required dependencies graph above. - report_infinite_cycles( - &ctx.db.type_alias_dependencies(), - ctx, - "These aliases form a dependency cycle", - ); } /// Finds and reports all the infinite cycles in the given graph. @@ -65,12 +73,15 @@ fn report_infinite_cycles<V: Ord + Eq + Hash + Copy>( graph: &HashMap<V, HashSet<V>>, ctx: &mut Context<'_>, message: &str, -) where +) -> Vec<Vec<V>> +where SchemaAst: Index<V>, <SchemaAst as Index<V>>::Output: WithName, <SchemaAst as Index<V>>::Output: WithSpan, { - for component in Tarjan::components(graph) { + let components = Tarjan::components(graph); + + for component in &components { let cycle = component .iter() .map(|id| ctx.db.ast()[*id].name().to_string()) @@ -84,6 +95,8 @@ fn report_infinite_cycles<V: Ord + Eq + Hash + Copy>( ctx.db.ast()[component[0]].span().clone(), )); } + + components } /// Inserts all the required dependencies of a field into the given set. @@ -91,11 +104,14 @@ fn report_infinite_cycles<V: Ord + Eq + Hash + Copy>( /// Recursively deals with unions of unions. Can be implemented iteratively with /// a while loop and a stack/queue if this ends up being slow / inefficient or /// it reaches stack overflows with large inputs. +/// +/// TODO: Use a struct to keep all this state. Too many parameters already. fn insert_required_deps( id: TypeExpId, field: &FieldType, ctx: &Context<'_>, deps: &mut HashSet<TypeExpId>, + alias_cycles: &HashSet<TypeAliasId>, ) { match field { FieldType::Symbol(arity, ident, _) if arity.is_required() => { @@ -123,7 +139,11 @@ fn insert_required_deps( // But we'll run this instead which will follow all the // alias pointers again until it finds the resolved type. - insert_required_deps(id, alias.target(), ctx, deps); + // We also have to stop recursion if we know the alias is + // part of a cycle. + if !alias_cycles.contains(&alias.id) { + insert_required_deps(id, alias.target(), ctx, deps, alias_cycles) + } } _ => {} } @@ -139,7 +159,7 @@ fn insert_required_deps( let mut nested_deps = HashSet::new(); for f in field_types { - insert_required_deps(id, f, ctx, &mut nested_deps); + insert_required_deps(id, f, ctx, &mut nested_deps, alias_cycles); // No nested deps found on this component, this makes the // union finite, so no need to go deeper. diff --git a/engine/baml-lib/baml/tests/validation_files/class/recursive_type_aliases.baml b/engine/baml-lib/baml/tests/validation_files/class/recursive_type_aliases.baml index 7bedddda8..66bda4f61 100644 --- a/engine/baml-lib/baml/tests/validation_files/class/recursive_type_aliases.baml +++ b/engine/baml-lib/baml/tests/validation_files/class/recursive_type_aliases.baml @@ -26,15 +26,16 @@ class Recursive { type RecAlias = Recursive -// error: Error validating: These classes form a dependency cycle: Recursive -// --> class/recursive_type_aliases.baml:22 -// | -// 21 | // Recursive class with alias pointing to itself. -// 22 | class Recursive { -// 23 | value int -// 24 | ptr RecAlias -// 25 | } -// | +// Class that points to alias which enters infinite cycle. +class InfiniteCycle { + value int + ptr EnterCycle +} + +type EnterCycle = NoStop + +type NoStop = EnterCycle + // error: Error validating: These aliases form a dependency cycle: One -> Two // --> class/recursive_type_aliases.baml:10 // | @@ -47,3 +48,18 @@ type RecAlias = Recursive // 14 | // Cycle. Same as above but longer. // 15 | type A = B // | +// error: Error validating: These aliases form a dependency cycle: EnterCycle -> NoStop +// --> class/recursive_type_aliases.baml:35 +// | +// 34 | +// 35 | type EnterCycle = NoStop +// | +// error: Error validating: These classes form a dependency cycle: Recursive +// --> class/recursive_type_aliases.baml:22 +// | +// 21 | // Recursive class with alias pointing to itself. +// 22 | class Recursive { +// 23 | value int +// 24 | ptr RecAlias +// 25 | } +// | From f9b058551d2d57e755dfd4141883eb133e586d23 Mon Sep 17 00:00:00 2001 From: Antonio Sarosi <sarosiantonio@gmail.com> Date: Wed, 27 Nov 2024 19:12:48 +0000 Subject: [PATCH 18/51] Fix func inputs and outputs when type alias --- engine/baml-lib/parser-database/src/lib.rs | 22 ++++++++++++------- .../parser-database/src/walkers/alias.rs | 16 ++++++++++++++ 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/engine/baml-lib/parser-database/src/lib.rs b/engine/baml-lib/parser-database/src/lib.rs index d23f18d47..5070da9c3 100644 --- a/engine/baml-lib/parser-database/src/lib.rs +++ b/engine/baml-lib/parser-database/src/lib.rs @@ -40,7 +40,7 @@ use std::collections::{HashMap, HashSet, VecDeque}; pub use coerce_expression::{coerce, coerce_array, coerce_opt}; pub use internal_baml_schema_ast::ast; -use internal_baml_schema_ast::ast::SchemaAst; +use internal_baml_schema_ast::ast::{FieldType, SchemaAst, WithName}; pub use tarjan::Tarjan; use types::resolve_type_alias; pub use types::{ @@ -197,9 +197,12 @@ impl ParserDatabase { Some(walker.dependencies().iter().cloned()) } Some(TypeWalker::Enum(_)) => None, - // TODO: Alias can point to classes enums, unions... - // what do we do here? - Some(TypeWalker::TypeAlias(walker)) => None, + Some(TypeWalker::TypeAlias(walker)) => match walker.resolved_as_walker() { + Some(TypeWalker::Class(inner_walker)) => { + Some(inner_walker.dependencies().iter().cloned()) + } + _ => None, + }, _ => panic!("Unknown class `{f}`"), }) .flatten() @@ -212,10 +215,13 @@ impl ParserDatabase { Some(walker.dependencies().iter().cloned()) } Some(TypeWalker::Enum(_)) => None, - // TODO: Alias can point to classes enums, unions... - // what do we do here? - Some(TypeWalker::TypeAlias(walker)) => None, - _ => panic!("Unknown class `{}`", f), + Some(TypeWalker::TypeAlias(walker)) => match walker.resolved_as_walker() { + Some(TypeWalker::Class(inner_walker)) => { + Some(inner_walker.dependencies().iter().cloned()) + } + _ => None, + }, + _ => panic!("Unknown class `{f}`"), }) .flatten() .collect::<HashSet<_>>(); diff --git a/engine/baml-lib/parser-database/src/walkers/alias.rs b/engine/baml-lib/parser-database/src/walkers/alias.rs index 8ba968dfc..cd0fbc469 100644 --- a/engine/baml-lib/parser-database/src/walkers/alias.rs +++ b/engine/baml-lib/parser-database/src/walkers/alias.rs @@ -23,4 +23,20 @@ impl<'db> TypeAliasWalker<'db> { pub fn resolved(&self) -> &'db FieldType { &self.db.types.resolved_type_aliases[&self.id] } + + /// Returns a [`TypeWalker`] over the resolved type if it's a symbol. + /// + /// # Panics + /// + /// Panics if the resolved type is a symbol but the symbol is not found. + pub fn resolved_as_walker(&self) -> Option<TypeWalker<'db>> { + match self.resolved() { + FieldType::Symbol(_, ident, _) => match self.db.find_type_by_str(ident.name()) { + Some(walker) => Some(walker), + _ => panic!("Unknown class or enum `{ident}`"), + }, + + _ => None, + } + } } From 7eae431fd5634cee5e345c6f1c3386b26f254c2c Mon Sep 17 00:00:00 2001 From: Antonio Sarosi <sarosiantonio@gmail.com> Date: Wed, 27 Nov 2024 20:10:56 +0000 Subject: [PATCH 19/51] Fix json schema `todo!()` --- .../baml-lib/baml-core/src/ir/json_schema.rs | 2 +- engine/baml-lib/parser-database/src/lib.rs | 23 +++++++++---------- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/engine/baml-lib/baml-core/src/ir/json_schema.rs b/engine/baml-lib/baml-core/src/ir/json_schema.rs index 776aadd27..cfc860e57 100644 --- a/engine/baml-lib/baml-core/src/ir/json_schema.rs +++ b/engine/baml-lib/baml-core/src/ir/json_schema.rs @@ -159,7 +159,7 @@ impl<'db> WithJsonSchema for FieldType { FieldType::Class(name) | FieldType::Enum(name) => json!({ "$ref": format!("#/definitions/{}", name), }), - FieldType::Alias { .. } => todo!(), + FieldType::Alias { resolution, .. } => resolution.json_schema(), FieldType::Literal(v) => json!({ "const": v.to_string(), }), diff --git a/engine/baml-lib/parser-database/src/lib.rs b/engine/baml-lib/parser-database/src/lib.rs index 5070da9c3..c1cb7ada6 100644 --- a/engine/baml-lib/parser-database/src/lib.rs +++ b/engine/baml-lib/parser-database/src/lib.rs @@ -130,6 +130,14 @@ impl ParserDatabase { } fn finalize_dependencies(&mut self, diag: &mut Diagnostics) { + // Resolve type aliases. + // Cycles are already validated so this should not stack overflow and + // it should find the final type. + for alias_id in self.types.type_alias_dependencies.keys() { + let resolved = resolve_type_alias(&self.ast[*alias_id].value, &self); + self.types.resolved_type_aliases.insert(*alias_id, resolved); + } + // NOTE: Class dependency cycles are already checked at // baml-lib/baml-core/src/validate/validation_pipeline/validations/cycle.rs // @@ -151,15 +159,14 @@ impl ParserDatabase { // instead of strings (class names). That requires less conversions when // working with the graph. Once the work is done, IDs can be converted // to names where needed. - let cycles = Tarjan::components(&HashMap::from_iter( + let finite_cycles = Tarjan::components(&HashMap::from_iter( self.types.class_dependencies.iter().map(|(id, deps)| { let deps = HashSet::from_iter(deps.iter().filter_map( |dep| match self.find_type_by_str(dep) { Some(TypeWalker::Class(cls)) => Some(cls.id), Some(TypeWalker::Enum(_)) => None, - // TODO: Does this interfere with recursive types? - Some(TypeWalker::TypeAlias(_)) => todo!(), + Some(TypeWalker::TypeAlias(_)) => None, None => panic!("Unknown class `{dep}`"), }, )); @@ -169,19 +176,11 @@ impl ParserDatabase { // Inject finite cycles into parser DB. This will then be passed into // the IR and then into the Jinja output format. - self.types.finite_recursive_cycles = cycles + self.types.finite_recursive_cycles = finite_cycles .into_iter() .map(|cycle| cycle.into_iter().collect()) .collect(); - // Resolve type aliases. - // Cycles are already validated so this should not stack overflow and - // it should find the final type. - for alias_id in self.types.type_alias_dependencies.keys() { - let resolved = resolve_type_alias(&self.ast[*alias_id].value, &self); - self.types.resolved_type_aliases.insert(*alias_id, resolved); - } - // Additionally ensure the same thing for functions, but since we've // already handled classes, this should be trivial. let extends = self From 3d8e944efc6becfe51d6f34922610482b6e80ea5 Mon Sep 17 00:00:00 2001 From: Antonio Sarosi <sarosiantonio@gmail.com> Date: Wed, 27 Nov 2024 22:23:03 +0000 Subject: [PATCH 20/51] Refactor function dependency tree builder --- engine/baml-lib/parser-database/src/lib.rs | 94 +++++++++++++--------- 1 file changed, 54 insertions(+), 40 deletions(-) diff --git a/engine/baml-lib/parser-database/src/lib.rs b/engine/baml-lib/parser-database/src/lib.rs index c1cb7ada6..2d5f487bb 100644 --- a/engine/baml-lib/parser-database/src/lib.rs +++ b/engine/baml-lib/parser-database/src/lib.rs @@ -124,7 +124,7 @@ impl ParserDatabase { ctx.diagnostics.to_result() } - /// Updates the prompt + /// Last changes after validation. pub fn finalize(&mut self, diag: &mut Diagnostics) { self.finalize_dependencies(diag); } @@ -181,51 +181,17 @@ impl ParserDatabase { .map(|cycle| cycle.into_iter().collect()) .collect(); - // Additionally ensure the same thing for functions, but since we've - // already handled classes, this should be trivial. + // Fully resolve function dependencies. let extends = self .types .function .iter() - .map(|(&k, func)| { + .map(|(&id, func)| { let (input, output) = &func.dependencies; - let input_deps = input - .iter() - .filter_map(|f| match self.find_type_by_str(f) { - Some(TypeWalker::Class(walker)) => { - Some(walker.dependencies().iter().cloned()) - } - Some(TypeWalker::Enum(_)) => None, - Some(TypeWalker::TypeAlias(walker)) => match walker.resolved_as_walker() { - Some(TypeWalker::Class(inner_walker)) => { - Some(inner_walker.dependencies().iter().cloned()) - } - _ => None, - }, - _ => panic!("Unknown class `{f}`"), - }) - .flatten() - .collect::<HashSet<_>>(); - - let output_deps = output - .iter() - .filter_map(|f| match self.find_type_by_str(f) { - Some(TypeWalker::Class(walker)) => { - Some(walker.dependencies().iter().cloned()) - } - Some(TypeWalker::Enum(_)) => None, - Some(TypeWalker::TypeAlias(walker)) => match walker.resolved_as_walker() { - Some(TypeWalker::Class(inner_walker)) => { - Some(inner_walker.dependencies().iter().cloned()) - } - _ => None, - }, - _ => panic!("Unknown class `{f}`"), - }) - .flatten() - .collect::<HashSet<_>>(); + let input_deps = self.collect_dependency_tree(input); + let output_deps = self.collect_dependency_tree(output); - (k, (input_deps, output_deps)) + (id, (input_deps, output_deps)) }) .collect::<Vec<_>>(); @@ -236,6 +202,54 @@ impl ParserDatabase { } } + /// Resolve the entire tree of dependencies for functions. + /// + /// Initial passes through the AST can only resolve one level of + /// dependencies for functions. This method will go through that first level + /// and collect all the dependencies of the dependencies. + fn collect_dependency_tree(&self, deps: &HashSet<String>) -> HashSet<String> { + let mut collected_deps = HashSet::new(); + let mut stack = Vec::from_iter(deps.iter().map(|dep| dep.as_str())); + + while let Some(dep) = stack.pop() { + match self.find_type_by_str(dep) { + // Add all the dependencies of the class. + Some(TypeWalker::Class(walker)) => { + for nested_dep in walker.dependencies() { + if collected_deps.insert(nested_dep.to_owned()) { + // Recurse if not already visited. + stack.push(nested_dep); + } + } + } + + // For aliases just get the resolved identifiers and + // push them into the stack. If we find resolved classes we'll + // add their dependencies as well. Note that this is not + // "recursive" per se because type aliases can never "resolve" + // to other type aliases. + Some(TypeWalker::TypeAlias(walker)) => { + stack.extend(walker.resolved().flat_idns().iter().map(|ident| { + // Add the resolved name itself to the deps. + collected_deps.insert(ident.name().to_owned()); + // Push the resolved name into the stack in case + // it's a class, we'll have to add its deps as + // well. + ident.name() + })) + } + + // Skip enums. + Some(TypeWalker::Enum(_)) => {} + + // This should not happen. + _ => panic!("Unknown class `{dep}`"), + } + } + + collected_deps + } + /// The parsed AST. pub fn ast(&self) -> &ast::SchemaAst { &self.ast From c0e9d084ff90d59564ea3d346a93c6c769ff7ef7 Mon Sep 17 00:00:00 2001 From: Antonio Sarosi <sarosiantonio@gmail.com> Date: Wed, 27 Nov 2024 22:40:03 +0000 Subject: [PATCH 21/51] Resolve some TODOs --- engine/baml-lib/baml-types/src/field_type/mod.rs | 5 +---- engine/baml-lib/parser-database/src/types/mod.rs | 5 ++++- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/engine/baml-lib/baml-types/src/field_type/mod.rs b/engine/baml-lib/baml-types/src/field_type/mod.rs index 479256fe9..eb153252b 100644 --- a/engine/baml-lib/baml-types/src/field_type/mod.rs +++ b/engine/baml-lib/baml-types/src/field_type/mod.rs @@ -245,10 +245,7 @@ impl FieldType { .zip(other_items) .all(|(self_item, other_item)| self_item.is_subtype_of(other_item)) } - // TODO: Can this cause infinite recursion? - // Should the final resolved type (following all the aliases) be - // included in the variant so that we skip recursion? - (FieldType::Alias { target, .. }, _) => target.is_subtype_of(other), + (FieldType::Alias { resolution, .. }, _) => resolution.is_subtype_of(other), (FieldType::Tuple(_), _) => false, (FieldType::Primitive(_), _) => false, (FieldType::Enum(_), _) => false, diff --git a/engine/baml-lib/parser-database/src/types/mod.rs b/engine/baml-lib/parser-database/src/types/mod.rs index f49c678d8..b7a2f348d 100644 --- a/engine/baml-lib/parser-database/src/types/mod.rs +++ b/engine/baml-lib/parser-database/src/types/mod.rs @@ -379,6 +379,9 @@ fn visit_class<'db>( /// /// The type would resolve to `SomeClass | AnotherClass | int`, which is not /// stored in the AST. +/// +/// **Important**: This function can only be called once infinite cycles have +/// been detected! Otherwise it'll stack overflow. pub fn resolve_type_alias(field_type: &FieldType, db: &ParserDatabase) -> FieldType { match field_type { // For symbols we need to check if we're dealing with aliases. @@ -400,7 +403,7 @@ pub fn resolve_type_alias(field_type: &FieldType, db: &ParserDatabase) -> FieldT return resolved.to_owned(); } - // Recurse... TODO: Recursive types and infinite cycles :( + // Recurse... let resolved = resolve_type_alias(&db.ast[*alias_id].value, db); // Sync arity. Basically stuff like: From cde729a79d68f54ce86de4616bc3ce9f6f2e3664 Mon Sep 17 00:00:00 2001 From: Antonio Sarosi <sarosiantonio@gmail.com> Date: Wed, 27 Nov 2024 23:51:22 +0000 Subject: [PATCH 22/51] Add syntax change to vscode ext --- .../packages/syntaxes/baml.tmLanguage.json | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/typescript/vscode-ext/packages/syntaxes/baml.tmLanguage.json b/typescript/vscode-ext/packages/syntaxes/baml.tmLanguage.json index 6bcb9f63a..66b8fd9ff 100644 --- a/typescript/vscode-ext/packages/syntaxes/baml.tmLanguage.json +++ b/typescript/vscode-ext/packages/syntaxes/baml.tmLanguage.json @@ -720,12 +720,23 @@ "name": "constant.numeric" }, "type_alias": { - "begin": "(type)\\s+(\\w+)", + "begin": "(type)\\s+(\\w+)\\s*(=)", "beginCaptures": { "1": { "name": "storage.type.declaration" }, - "2": { "name": "entity.name.type" } + "2": { "name": "entity.name.type" }, + "3": { "name": "keyword.operator.assignment" } }, - "patterns": [{ "include": "#comment" }] + "end": "(?=$|\\n)", + "patterns": [ + { "include": "#comment" }, + { + "begin": "(?<=\\=)\\s*", + "end": "(?=$|\\n)", + "patterns": [ + { "include": "#type_definition" } + ] + } + ] }, "invalid_assignment": { "name": "invalid.illegal", From b9de7ed1ef8bb12fe86d0a1bb9b8dd32ecc87e42 Mon Sep 17 00:00:00 2001 From: Antonio Sarosi <sarosiantonio@gmail.com> Date: Thu, 28 Nov 2024 22:38:04 +0000 Subject: [PATCH 23/51] Fix subtype bug with aliases and add tests --- .../baml-core/src/ir/ir_helpers/mod.rs | 8 +- .../baml-lib/baml-types/src/field_type/mod.rs | 3 +- integ-tests/python/tests/test_functions.py | 18 + integ-tests/ruby/test_functions.rb | 12 + integ-tests/typescript/test-report.html | 1881 +---------------- .../typescript/tests/integ-tests.test.ts | 18 + 6 files changed, 111 insertions(+), 1829 deletions(-) diff --git a/engine/baml-lib/baml-core/src/ir/ir_helpers/mod.rs b/engine/baml-lib/baml-core/src/ir/ir_helpers/mod.rs index 50907b5d7..87768c9f1 100644 --- a/engine/baml-lib/baml-core/src/ir/ir_helpers/mod.rs +++ b/engine/baml-lib/baml-core/src/ir/ir_helpers/mod.rs @@ -290,8 +290,9 @@ impl IRHelper for IntermediateRepr { if !map_type.is_subtype_of(&field_type) { anyhow::bail!("Could not unify {:?} with {:?}", map_type, field_type); - } else { - let mapped_fields: BamlMap<String, BamlValueWithMeta<FieldType>> = + } + + let mapped_fields: BamlMap<String, BamlValueWithMeta<FieldType>> = pairs .into_iter() .map(|(key, val)| { @@ -299,8 +300,7 @@ impl IRHelper for IntermediateRepr { Ok((key, sub_value)) }) .collect::<anyhow::Result<BamlMap<String,BamlValueWithMeta<FieldType>>>>()?; - Ok(BamlValueWithMeta::Map(mapped_fields, field_type)) - } + Ok(BamlValueWithMeta::Map(mapped_fields, field_type)) } None => Ok(BamlValueWithMeta::Map(BamlMap::new(), field_type)), } diff --git a/engine/baml-lib/baml-types/src/field_type/mod.rs b/engine/baml-lib/baml-types/src/field_type/mod.rs index eb153252b..2cd4d4d03 100644 --- a/engine/baml-lib/baml-types/src/field_type/mod.rs +++ b/engine/baml-lib/baml-types/src/field_type/mod.rs @@ -184,6 +184,8 @@ impl FieldType { } match (self, other) { + (FieldType::Alias { resolution, .. }, _) => resolution.is_subtype_of(other), + (_, FieldType::Alias { resolution, .. }) => self.is_subtype_of(resolution), (FieldType::Primitive(TypeValue::Null), FieldType::Optional(_)) => true, (FieldType::Optional(self_item), FieldType::Optional(other_item)) => { self_item.is_subtype_of(other_item) @@ -245,7 +247,6 @@ impl FieldType { .zip(other_items) .all(|(self_item, other_item)| self_item.is_subtype_of(other_item)) } - (FieldType::Alias { resolution, .. }, _) => resolution.is_subtype_of(other), (FieldType::Tuple(_), _) => false, (FieldType::Primitive(_), _) => false, (FieldType::Enum(_), _) => false, diff --git a/integ-tests/python/tests/test_functions.py b/integ-tests/python/tests/test_functions.py index 961f897b9..fdc5c2c32 100644 --- a/integ-tests/python/tests/test_functions.py +++ b/integ-tests/python/tests/test_functions.py @@ -249,6 +249,24 @@ async def test_single_literal_string_key_in_map(self): res = await b.InOutSingleLiteralStringMapKey({"key": "1"}) assert res["key"] == "1" + @pytest.mark.asyncio + async def test_primitive_union_alias(self): + res = await b.PrimitiveAlias("test") + assert res == "test" + + @pytest.mark.asyncio + async def test_map_alias(self): + res = await b.MapAlias({"A": ["B", "C"], "B": [], "C": []}) + assert res == {"A": ["B", "C"], "B": [], "C": []} + + @pytest.mark.asyncio + async def test_alias_union(self): + res = await b.NestedAlias("test") + assert res == "test" + + res = await b.NestedAlias({"A": ["B", "C"], "B": [], "C": []}) + assert res == {"A": ["B", "C"], "B": [], "C": []} + class MyCustomClass(NamedArgsSingleClass): date: datetime.datetime diff --git a/integ-tests/ruby/test_functions.rb b/integ-tests/ruby/test_functions.rb index 01a4f4995..22b0b5a41 100644 --- a/integ-tests/ruby/test_functions.rb +++ b/integ-tests/ruby/test_functions.rb @@ -78,6 +78,18 @@ res = b.InOutSingleLiteralStringMapKey(m: {"key" => "1"}) assert_equal res['key'], "1" + + res = b.PrimitiveAlias(p: "test") + assert_equal res, "test" + + res = b.MapAlias(m: {"A" => ["B", "C"], "B" => [], "C" => []}) + assert_equal res, {"A" => ["B", "C"], "B" => [], "C" => []} + + res = b.NestedAlias(c: "test") + assert_equal res, "test" + + res = b.NestedAlias(c: {"A" => ["B", "C"], "B" => [], "C" => []}) + assert_equal res, {"A" => ["B", "C"], "B" => [], "C" => []} end it "accepts subclass of baml type" do diff --git a/integ-tests/typescript/test-report.html b/integ-tests/typescript/test-report.html index d62ea4f2c..1d6bb9a4b 100644 --- a/integ-tests/typescript/test-report.html +++ b/integ-tests/typescript/test-report.html @@ -257,1831 +257,64 @@ font-size: 1rem; padding: 0 0.5rem; } -</style></head><body><div class="jesthtml-content"><header><h1 id="title">Test Report</h1></header><div id="metadata-container"><div id="timestamp">Started: 2024-11-26 08:53:34</div><div id="summary"><div id="suite-summary"><div class="summary-total">Suites (1)</div><div class="summary-passed summary-empty">0 passed</div><div class="summary-failed ">1 failed</div><div class="summary-pending summary-empty">0 pending</div></div><div id="test-summary"><div class="summary-total">Tests (67)</div><div class="summary-passed ">66 passed</div><div class="summary-failed ">1 failed</div><div class="summary-pending summary-empty">0 pending</div></div></div></div><div id="suite-1" class="suite-container"><input id="collapsible-0" type="checkbox" class="toggle" checked="checked"/><label for="collapsible-0"><div class="suite-info"><div class="suite-path">/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts</div><div class="suite-time warn">126.655s</div></div></label><div class="suite-tests"><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single bool</div><div class="test-status">passed</div><div class="test-duration">0.715s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single string list</div><div class="test-status">passed</div><div class="test-duration">0.382s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">return literal union</div><div class="test-status">passed</div><div class="test-duration">0.748s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single class</div><div class="test-status">passed</div><div class="test-duration">0.505s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">multiple classes</div><div class="test-status">passed</div><div class="test-duration">0.46s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single enum list</div><div class="test-status">passed</div><div class="test-duration">0.336s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single float</div><div class="test-status">passed</div><div class="test-duration">0.402s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single int</div><div class="test-status">passed</div><div class="test-duration">0.337s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single literal int</div><div class="test-status">passed</div><div class="test-duration">0.632s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single literal bool</div><div class="test-status">passed</div><div class="test-duration">0.388s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single literal string</div><div class="test-status">passed</div><div class="test-duration">0.299s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single class with literal prop</div><div class="test-status">passed</div><div class="test-duration">0.597s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single class with literal union prop</div><div class="test-status">passed</div><div class="test-duration">0.675s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single optional string</div><div class="test-status">passed</div><div class="test-duration">0.365s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single map string to string</div><div class="test-status">passed</div><div class="test-duration">0.551s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single map string to class</div><div class="test-status">passed</div><div class="test-duration">2.455s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single map string to map</div><div class="test-status">passed</div><div class="test-duration">0.51s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">enum key in map</div><div class="test-status">passed</div><div class="test-duration">0.897s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">literal string union key in map</div><div class="test-status">passed</div><div class="test-duration">0.717s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single literal string key in map</div><div class="test-status">passed</div><div class="test-duration">0.629s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work for all outputs</div><div class="test-status">passed</div><div class="test-duration">5.004s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">works with retries1</div><div class="test-status">passed</div><div class="test-duration">1.089s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">works with retries2</div><div class="test-status">passed</div><div class="test-duration">2.107s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">works with fallbacks</div><div class="test-status">passed</div><div class="test-duration">1.748s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with image from url</div><div class="test-status">passed</div><div class="test-duration">1.61s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with image from base 64</div><div class="test-status">passed</div><div class="test-duration">1.27s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with audio base 64</div><div class="test-status">passed</div><div class="test-duration">0.991s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with audio from url</div><div class="test-status">passed</div><div class="test-duration">1.083s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support streaming in OpenAI</div><div class="test-status">passed</div><div class="test-duration">2.228s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support streaming in Gemini</div><div class="test-status">passed</div><div class="test-duration">9.793s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support AWS</div><div class="test-status">passed</div><div class="test-duration">1.833s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support streaming in AWS</div><div class="test-status">passed</div><div class="test-duration">1.618s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should allow overriding the region</div><div class="test-status">passed</div><div class="test-duration">0.056s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support OpenAI shorthand</div><div class="test-status">passed</div><div class="test-duration">11.457s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support OpenAI shorthand streaming</div><div class="test-status">passed</div><div class="test-duration">17.055s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support anthropic shorthand</div><div class="test-status">passed</div><div class="test-duration">2.906s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support anthropic shorthand streaming</div><div class="test-status">passed</div><div class="test-duration">2.369s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support streaming without iterating</div><div class="test-status">passed</div><div class="test-duration">1.626s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support streaming in Claude</div><div class="test-status">passed</div><div class="test-duration">1.515s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support vertex</div><div class="test-status">passed</div><div class="test-duration">9.006s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">supports tracing sync</div><div class="test-status">passed</div><div class="test-duration">0.009s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">supports tracing async</div><div class="test-status">passed</div><div class="test-duration">4.163s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic types single</div><div class="test-status">passed</div><div class="test-duration">1.418s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic types enum</div><div class="test-status">passed</div><div class="test-duration">0.909s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic literals</div><div class="test-status">passed</div><div class="test-duration">1.001s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic types class</div><div class="test-status">passed</div><div class="test-duration">1.114s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic inputs class</div><div class="test-status">passed</div><div class="test-duration">0.523s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic inputs list</div><div class="test-status">passed</div><div class="test-duration">0.643s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic output map</div><div class="test-status">passed</div><div class="test-duration">0.703s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic output union</div><div class="test-status">passed</div><div class="test-duration">1.71s</div></div></div><div class="test-result failed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with nested classes</div><div class="test-status">failed</div><div class="test-duration">10.822s</div></div><div class="failureMessages"> <pre class="failureMsg">Error: expect(received).toEqual(expected) // deep equality +</style></head><body><div class="jesthtml-content"><header><h1 id="title">Test Report</h1></header><div id="metadata-container"><div id="timestamp">Started: 2024-11-28 22:38:45</div><div id="summary"><div id="suite-summary"><div class="summary-total">Suites (1)</div><div class="summary-passed summary-empty">0 passed</div><div class="summary-failed ">1 failed</div><div class="summary-pending summary-empty">0 pending</div></div><div id="test-summary"><div class="summary-total">Tests (70)</div><div class="summary-passed ">67 passed</div><div class="summary-failed ">3 failed</div><div class="summary-pending summary-empty">0 pending</div></div></div></div><div id="suite-1" class="suite-container"><input id="collapsible-0" type="checkbox" class="toggle" checked="checked"/><label for="collapsible-0"><div class="suite-info"><div class="suite-path">/workspaces/baml/integ-tests/typescript/tests/integ-tests.test.ts</div><div class="suite-time warn">98.002s</div></div></label><div class="suite-tests"><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single bool</div><div class="test-status">passed</div><div class="test-duration">0.442s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single string list</div><div class="test-status">passed</div><div class="test-duration">0.606s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">return literal union</div><div class="test-status">passed</div><div class="test-duration">0.382s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single class</div><div class="test-status">passed</div><div class="test-duration">0.448s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">multiple classes</div><div class="test-status">passed</div><div class="test-duration">0.444s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single enum list</div><div class="test-status">passed</div><div class="test-duration">0.425s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single float</div><div class="test-status">passed</div><div class="test-duration">0.863s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single int</div><div class="test-status">passed</div><div class="test-duration">0.338s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single literal int</div><div class="test-status">passed</div><div class="test-duration">0.38s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single literal bool</div><div class="test-status">passed</div><div class="test-duration">0.408s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single literal string</div><div class="test-status">passed</div><div class="test-duration">0.409s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single class with literal prop</div><div class="test-status">passed</div><div class="test-duration">0.547s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single class with literal union prop</div><div class="test-status">passed</div><div class="test-duration">0.679s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single optional string</div><div class="test-status">passed</div><div class="test-duration">0.328s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single map string to string</div><div class="test-status">passed</div><div class="test-duration">0.492s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single map string to class</div><div class="test-status">passed</div><div class="test-duration">0.514s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single map string to map</div><div class="test-status">passed</div><div class="test-duration">0.518s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">enum key in map</div><div class="test-status">passed</div><div class="test-duration">0.808s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">literal string union key in map</div><div class="test-status">passed</div><div class="test-duration">0.553s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single literal string key in map</div><div class="test-status">passed</div><div class="test-duration">0.882s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">primitive union alias</div><div class="test-status">passed</div><div class="test-duration">0.823s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">map alias</div><div class="test-status">passed</div><div class="test-duration">0.822s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">alias union</div><div class="test-status">passed</div><div class="test-duration">1.463s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work for all outputs</div><div class="test-status">passed</div><div class="test-duration">4.158s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">works with retries1</div><div class="test-status">passed</div><div class="test-duration">1.164s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">works with retries2</div><div class="test-status">passed</div><div class="test-duration">2.411s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">works with fallbacks</div><div class="test-status">passed</div><div class="test-duration">2.378s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with image from url</div><div class="test-status">passed</div><div class="test-duration">1.432s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with image from base 64</div><div class="test-status">passed</div><div class="test-duration">1.528s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with audio base 64</div><div class="test-status">passed</div><div class="test-duration">1.555s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with audio from url</div><div class="test-status">passed</div><div class="test-duration">1.619s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support streaming in OpenAI</div><div class="test-status">passed</div><div class="test-duration">2.023s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support streaming in Gemini</div><div class="test-status">passed</div><div class="test-duration">9.831s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support AWS</div><div class="test-status">passed</div><div class="test-duration">1.651s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support streaming in AWS</div><div class="test-status">passed</div><div class="test-duration">1.516s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should allow overriding the region</div><div class="test-status">passed</div><div class="test-duration">0.02s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support OpenAI shorthand</div><div class="test-status">passed</div><div class="test-duration">6.471s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support OpenAI shorthand streaming</div><div class="test-status">passed</div><div class="test-duration">9.687s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support anthropic shorthand</div><div class="test-status">passed</div><div class="test-duration">3.816s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support anthropic shorthand streaming</div><div class="test-status">passed</div><div class="test-duration">2.662s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support streaming without iterating</div><div class="test-status">passed</div><div class="test-duration">1.73s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support streaming in Claude</div><div class="test-status">passed</div><div class="test-duration">1.42s</div></div></div><div class="test-result failed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support vertex</div><div class="test-status">failed</div><div class="test-duration">0.001s</div></div><div class="failureMessages"> <pre class="failureMsg">Error: BamlError: Failed to read service account file: -- Expected - 1 -+ Received + 1 +Caused by: + No such file or directory (os error 2)</pre></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">supports tracing sync</div><div class="test-status">passed</div><div class="test-duration">0.014s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">supports tracing async</div><div class="test-status">passed</div><div class="test-duration">2.783s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic types single</div><div class="test-status">passed</div><div class="test-duration">1.115s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic types enum</div><div class="test-status">passed</div><div class="test-duration">0.924s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic literals</div><div class="test-status">passed</div><div class="test-duration">1.019s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic types class</div><div class="test-status">passed</div><div class="test-duration">0.84s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic inputs class</div><div class="test-status">passed</div><div class="test-duration">0.495s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic inputs list</div><div class="test-status">passed</div><div class="test-duration">0.508s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic output map</div><div class="test-status">passed</div><div class="test-duration">0.715s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic output union</div><div class="test-status">passed</div><div class="test-duration">1.478s</div></div></div><div class="test-result failed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with nested classes</div><div class="test-status">failed</div><div class="test-duration">0.108s</div></div><div class="failureMessages"> <pre class="failureMsg">Error: BamlError: BamlClientError: Something went wrong with the LLM client: reqwest::Error { kind: Request, url: Url { scheme: "http", cannot_be_a_base: false, username: "", password: None, host: Some(Domain("localhost")), port: Some(11434), path: "/v1/chat/completions", query: None, fragment: None }, source: hyper_util::client::legacy::Error(Connect, ConnectError("tcp connect error", Os { code: 111, kind: ConnectionRefused, message: "Connection refused" })) } + at BamlStream.parsed [as getFinalResponse] (/workspaces/baml/engine/language_client_typescript/stream.js:58:39) + at Object.<anonymous> (/workspaces/baml/integ-tests/typescript/tests/integ-tests.test.ts:620:19)</pre></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic client</div><div class="test-status">passed</div><div class="test-duration">0.358s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with 'onLogEvent'</div><div class="test-status">passed</div><div class="test-duration">1.923s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with a sync client</div><div class="test-status">passed</div><div class="test-duration">0.471s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should raise an error when appropriate</div><div class="test-status">passed</div><div class="test-duration">0.817s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should raise a BAMLValidationError</div><div class="test-status">passed</div><div class="test-duration">0.47s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should reset environment variables correctly</div><div class="test-status">passed</div><div class="test-duration">2.557s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should use aliases when serializing input objects - classes</div><div class="test-status">passed</div><div class="test-duration">1.517s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should use aliases when serializing, but still have original keys in jinja</div><div class="test-status">passed</div><div class="test-duration">0.837s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should use aliases when serializing input objects - enums</div><div class="test-status">passed</div><div class="test-duration">0.445s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should use aliases when serializing input objects - lists</div><div class="test-status">passed</div><div class="test-duration">0.481s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">constraints: should handle checks in return types</div><div class="test-status">passed</div><div class="test-duration">0.613s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">constraints: should handle checks in returned unions</div><div class="test-status">passed</div><div class="test-duration">0.829s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">constraints: should handle block-level checks</div><div class="test-status">passed</div><div class="test-duration">0.509s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">constraints: should handle nested-block-level checks</div><div class="test-status">passed</div><div class="test-duration">0.709s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">simple recursive type</div><div class="test-status">passed</div><div class="test-duration">2.458s</div></div></div><div class="test-result failed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">mutually recursive type</div><div class="test-status">failed</div><div class="test-duration">1.686s</div></div><div class="failureMessages"> <pre class="failureMsg">Error: expect(received).toEqual(expected) // deep equality - Object { - "prop1": "Hello", - "prop2": Object { - "inner": Object { - "prop2": 42, -- "prop3": 3.14, -+ "prop3": null, - }, - "prop1": "World", - "prop2": "Javascript", +- Expected - 32 ++ Received + 0 + +@@ -4,49 +4,17 @@ + Object { + "children": Object { + "trees": Array [ + Object { + "children": Object { +- "trees": Array [ +- Object { +- "children": Object { +- "trees": Array [], +- }, +- "data": 2, +- }, +- ], +- }, +- "data": 1, +- }, +- Object { +- "children": Object { + "trees": Array [], + }, + "data": 4, + }, + ], + }, + "data": 3, +- }, +- Object { +- "children": Object { +- "trees": Array [ +- Object { +- "children": Object { +- "trees": Array [], +- }, +- "data": 6, +- }, +- Object { +- "children": Object { +- "trees": Array [], +- }, +- "data": 8, +- }, +- ], +- }, +- "data": 7, + }, + ], }, + "data": 5, } - at Object.toEqual (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:604:25)</pre></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic client</div><div class="test-status">passed</div><div class="test-duration">0.446s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with 'onLogEvent'</div><div class="test-status">passed</div><div class="test-duration">1.604s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with a sync client</div><div class="test-status">passed</div><div class="test-duration">0.513s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should raise an error when appropriate</div><div class="test-status">passed</div><div class="test-duration">0.676s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should raise a BAMLValidationError</div><div class="test-status">passed</div><div class="test-duration">0.324s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should reset environment variables correctly</div><div class="test-status">passed</div><div class="test-duration">1.013s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should use aliases when serializing input objects - classes</div><div class="test-status">passed</div><div class="test-duration">1.046s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should use aliases when serializing, but still have original keys in jinja</div><div class="test-status">passed</div><div class="test-duration">0.78s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should use aliases when serializing input objects - enums</div><div class="test-status">passed</div><div class="test-duration">0.362s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should use aliases when serializing input objects - lists</div><div class="test-status">passed</div><div class="test-duration">0.301s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">constraints: should handle checks in return types</div><div class="test-status">passed</div><div class="test-duration">0.773s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">constraints: should handle checks in returned unions</div><div class="test-status">passed</div><div class="test-duration">0.695s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">constraints: should handle block-level checks</div><div class="test-status">passed</div><div class="test-duration">0.447s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">constraints: should handle nested-block-level checks</div><div class="test-status">passed</div><div class="test-duration">0.56s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">simple recursive type</div><div class="test-status">passed</div><div class="test-duration">2.252s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">mutually recursive type</div><div class="test-status">passed</div><div class="test-duration">2.226s</div></div></div></div><div class="suite-consolelog"><div class="suite-consolelog-header">Console Log</div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:48:15) - at Promise.then.completed (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/utils.js:298:28) - at new Promise (<anonymous>) - at callAsyncCircusFn (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/utils.js:231:10) - at _callCircusTest (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/run.js:316:40) - at _runTest (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/run.js:252:3) - at _runTestsForDescribeBlock (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/run.js:126:9) - at _runTestsForDescribeBlock (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/run.js:121:9) - at _runTestsForDescribeBlock (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/run.js:121:9) - at run (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/run.js:71:3) - at runAndTransformResultsToJestFormat (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/legacy-code-todo-rewrite/jestAdapterInit.js:122:21) - at jestAdapter (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/legacy-code-todo-rewrite/jestAdapter.js:79:19) - at runTestInternal (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-runner@29.7.0/node_modules/jest-runner/build/runTest.js:367:16) - at runTest (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-runner@29.7.0/node_modules/jest-runner/build/runTest.js:444:34)</pre><pre class="suite-consolelog-item-message">calling with class</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:54:15)</pre><pre class="suite-consolelog-item-message">got response key -true -52</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:194:15)</pre><pre class="suite-consolelog-item-message">Expected error Error: BamlError: BamlClientError: BamlClientHttpError: LLM call failed: LLMErrorResponse { client: "RetryClientConstant", model: None, prompt: Chat([RenderedChatMessage { role: "user", allow_duplicate_role: false, parts: [Text("Say a haiku")] }]), request_options: {"model": String("gpt-3.5-turbo")}, start_time: SystemTime { tv_sec: 1732640033, tv_nsec: 802692000 }, latency: 163.428167ms, message: "Request failed: https://api.openai.com/v1/chat/completions\n{\n \"error\": {\n \"message\": \"Incorrect API key provided: blah. You can find your API key at https://platform.openai.com/account/api-keys.\",\n \"type\": \"invalid_request_error\",\n \"param\": null,\n \"code\": \"invalid_api_key\"\n }\n}\n", code: InvalidAuthentication } - at BamlAsyncClient.parsed [as TestRetryConstant] (/Users/vbv/repos/gloo-lang/integ-tests/typescript/baml_client/async_client.ts:2810:18) - at Object.<anonymous> (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:191:7) { - code: 'GenericFailure' -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:203:15)</pre><pre class="suite-consolelog-item-message">Expected error Error: BamlError: BamlClientError: BamlClientHttpError: LLM call failed: LLMErrorResponse { client: "RetryClientExponential", model: None, prompt: Chat([RenderedChatMessage { role: "user", allow_duplicate_role: false, parts: [Text("Say a haiku")] }]), request_options: {"model": String("gpt-3.5-turbo")}, start_time: SystemTime { tv_sec: 1732640035, tv_nsec: 927579000 }, latency: 166.684125ms, message: "Request failed: https://api.openai.com/v1/chat/completions\n{\n \"error\": {\n \"message\": \"Incorrect API key provided: blahh. You can find your API key at https://platform.openai.com/account/api-keys.\",\n \"type\": \"invalid_request_error\",\n \"param\": null,\n \"code\": \"invalid_api_key\"\n }\n}\n", code: InvalidAuthentication } - at BamlAsyncClient.parsed [as TestRetryExponential] (/Users/vbv/repos/gloo-lang/integ-tests/typescript/baml_client/async_client.ts:2835:18) - at Object.<anonymous> (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:200:7) { - code: 'GenericFailure' -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:362:15) - at func (/Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:83:38) - at AsyncLocalStorage.run (node:async_hooks:338:14) - at run (/Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:81:22) - at Object.<anonymous> (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:371:5) - at Promise.then.completed (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/utils.js:298:28) - at new Promise (<anonymous>) - at callAsyncCircusFn (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/utils.js:231:10) - at _callCircusTest (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/run.js:316:40) - at _runTest (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/run.js:252:3) - at _runTestsForDescribeBlock (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/run.js:126:9) - at _runTestsForDescribeBlock (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/run.js:121:9) - at run (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/run.js:71:3) - at runAndTransformResultsToJestFormat (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/legacy-code-todo-rewrite/jestAdapterInit.js:122:21) - at jestAdapter (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/legacy-code-todo-rewrite/jestAdapter.js:79:19) - at runTestInternal (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-runner@29.7.0/node_modules/jest-runner/build/runTest.js:367:16) - at runTest (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-runner@29.7.0/node_modules/jest-runner/build/runTest.js:444:34)</pre><pre class="suite-consolelog-item-message">hello world</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:365:15) - at func (/Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:83:38) - at AsyncLocalStorage.run (node:async_hooks:338:14) - at run (/Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:81:22) - at Object.<anonymous> (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:371:5) - at Promise.then.completed (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/utils.js:298:28) - at new Promise (<anonymous>) - at callAsyncCircusFn (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/utils.js:231:10) - at _callCircusTest (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/run.js:316:40) - at _runTest (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/run.js:252:3) - at _runTestsForDescribeBlock (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/run.js:126:9) - at _runTestsForDescribeBlock (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/run.js:121:9) - at run (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/run.js:71:3) - at runAndTransformResultsToJestFormat (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/legacy-code-todo-rewrite/jestAdapterInit.js:122:21) - at jestAdapter (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/legacy-code-todo-rewrite/jestAdapter.js:79:19) - at runTestInternal (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-runner@29.7.0/node_modules/jest-runner/build/runTest.js:367:16) - at runTest (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-runner@29.7.0/node_modules/jest-runner/build/runTest.js:444:34)</pre><pre class="suite-consolelog-item-message">dummyFunc returned</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:368:15) - at func (/Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:83:38) - at AsyncLocalStorage.run (node:async_hooks:338:14) - at run (/Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:81:22) - at Object.<anonymous> (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:371:5) - at Promise.then.completed (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/utils.js:298:28) - at new Promise (<anonymous>) - at callAsyncCircusFn (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/utils.js:231:10) - at _callCircusTest (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/run.js:316:40) - at _runTest (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/run.js:252:3) - at _runTestsForDescribeBlock (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/run.js:126:9) - at _runTestsForDescribeBlock (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/run.js:121:9) - at run (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/run.js:71:3) - at runAndTransformResultsToJestFormat (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/legacy-code-todo-rewrite/jestAdapterInit.js:122:21) - at jestAdapter (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/legacy-code-todo-rewrite/jestAdapter.js:79:19) - at runTestInternal (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-runner@29.7.0/node_modules/jest-runner/build/runTest.js:367:16) - at runTest (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-runner@29.7.0/node_modules/jest-runner/build/runTest.js:444:34)</pre><pre class="suite-consolelog-item-message">dummyFunc2 returned</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:383:15) - at runNextTicks (node:internal/process/task_queues:60:5) - at listOnTimeout (node:internal/timers:538:9) - at processTimers (node:internal/timers:512:7) - at /Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:104:38 - at /Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:102:13 - at async Promise.all (index 0) - at dummyFn (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:389:22) - at /Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:104:38 - at /Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:102:13 - at async Promise.all (index 0) - at Object.<anonymous> (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:401:5)</pre><pre class="suite-consolelog-item-message">samDummyNested nested1</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:383:15) - at runNextTicks (node:internal/process/task_queues:60:5) - at listOnTimeout (node:internal/timers:538:9) - at processTimers (node:internal/timers:512:7) - at /Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:104:38 - at /Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:102:13 - at async Promise.all (index 1) - at dummyFn (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:389:22) - at /Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:104:38 - at /Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:102:13 - at async Promise.all (index 0) - at Object.<anonymous> (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:401:5)</pre><pre class="suite-consolelog-item-message">samDummyNested nested2</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:383:15) - at runNextTicks (node:internal/process/task_queues:60:5) - at listOnTimeout (node:internal/timers:538:9) - at processTimers (node:internal/timers:512:7) - at /Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:104:38 - at /Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:102:13 - at async Promise.all (index 2) - at dummyFn (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:389:22) - at /Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:104:38 - at /Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:102:13 - at async Promise.all (index 0) - at Object.<anonymous> (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:401:5)</pre><pre class="suite-consolelog-item-message">samDummyNested nested3</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:394:15) - at runNextTicks (node:internal/process/task_queues:60:5) - at listOnTimeout (node:internal/timers:538:9) - at processTimers (node:internal/timers:512:7) - at /Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:104:38 - at /Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:102:13 - at async Promise.all (index 0) - at Object.<anonymous> (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:401:5)</pre><pre class="suite-consolelog-item-message">dummy hi1</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:383:15) - at runNextTicks (node:internal/process/task_queues:60:5) - at listOnTimeout (node:internal/timers:538:9) - at processTimers (node:internal/timers:512:7) - at /Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:104:38 - at /Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:102:13 - at async Promise.all (index 0) - at dummyFn (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:389:22) - at /Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:104:38 - at /Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:102:13 - at async Promise.all (index 1) - at Object.<anonymous> (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:401:5)</pre><pre class="suite-consolelog-item-message">samDummyNested nested1</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:383:15) - at runNextTicks (node:internal/process/task_queues:60:5) - at listOnTimeout (node:internal/timers:538:9) - at processTimers (node:internal/timers:512:7) - at /Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:104:38 - at /Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:102:13 - at async Promise.all (index 1) - at dummyFn (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:389:22) - at /Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:104:38 - at /Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:102:13 - at async Promise.all (index 1) - at Object.<anonymous> (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:401:5)</pre><pre class="suite-consolelog-item-message">samDummyNested nested2</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:383:15) - at runNextTicks (node:internal/process/task_queues:60:5) - at listOnTimeout (node:internal/timers:538:9) - at processTimers (node:internal/timers:512:7) - at /Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:104:38 - at /Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:102:13 - at async Promise.all (index 2) - at dummyFn (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:389:22) - at /Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:104:38 - at /Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:102:13 - at async Promise.all (index 1) - at Object.<anonymous> (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:401:5)</pre><pre class="suite-consolelog-item-message">samDummyNested nested3</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:394:15) - at runNextTicks (node:internal/process/task_queues:60:5) - at listOnTimeout (node:internal/timers:538:9) - at processTimers (node:internal/timers:512:7) - at /Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:104:38 - at /Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:102:13 - at async Promise.all (index 1) - at Object.<anonymous> (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:401:5)</pre><pre class="suite-consolelog-item-message">dummy hi2</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:383:15) - at runNextTicks (node:internal/process/task_queues:60:5) - at listOnTimeout (node:internal/timers:538:9) - at processTimers (node:internal/timers:512:7) - at /Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:104:38 - at /Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:102:13 - at async Promise.all (index 0) - at dummyFn (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:389:22) - at /Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:104:38 - at /Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:102:13 - at async Promise.all (index 2) - at Object.<anonymous> (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:401:5)</pre><pre class="suite-consolelog-item-message">samDummyNested nested1</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:383:15) - at runNextTicks (node:internal/process/task_queues:60:5) - at listOnTimeout (node:internal/timers:538:9) - at processTimers (node:internal/timers:512:7) - at /Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:104:38 - at /Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:102:13 - at async Promise.all (index 1) - at dummyFn (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:389:22) - at /Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:104:38 - at /Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:102:13 - at async Promise.all (index 2) - at Object.<anonymous> (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:401:5)</pre><pre class="suite-consolelog-item-message">samDummyNested nested2</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:383:15) - at /Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:104:38 - at /Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:102:13 - at async Promise.all (index 2) - at dummyFn (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:389:22) - at /Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:104:38 - at /Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:102:13 - at async Promise.all (index 2) - at Object.<anonymous> (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:401:5)</pre><pre class="suite-consolelog-item-message">samDummyNested nested3</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:394:15) - at /Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:104:38 - at /Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:102:13 - at async Promise.all (index 2) - at Object.<anonymous> (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:401:5)</pre><pre class="suite-consolelog-item-message">dummy hi3</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:408:15) - at func (/Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:104:44) - at AsyncLocalStorage.run (node:async_hooks:338:14) - at run (/Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:102:28) - at Object.<anonymous> (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:424:5)</pre><pre class="suite-consolelog-item-message">hello world</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:383:15) - at runNextTicks (node:internal/process/task_queues:60:5) - at listOnTimeout (node:internal/timers:538:9) - at processTimers (node:internal/timers:512:7) - at /Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:104:38 - at /Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:102:13 - at async Promise.all (index 0) - at dummyFn (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:389:22)</pre><pre class="suite-consolelog-item-message">samDummyNested nested1</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:383:15) - at runNextTicks (node:internal/process/task_queues:60:5) - at listOnTimeout (node:internal/timers:538:9) - at processTimers (node:internal/timers:512:7) - at /Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:104:38 - at /Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:102:13 - at async Promise.all (index 1) - at dummyFn (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:389:22)</pre><pre class="suite-consolelog-item-message">samDummyNested nested2</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:383:15) - at runNextTicks (node:internal/process/task_queues:60:5) - at listOnTimeout (node:internal/timers:538:9) - at processTimers (node:internal/timers:512:7) - at /Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:104:38 - at /Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:102:13 - at async Promise.all (index 2) - at dummyFn (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:389:22)</pre><pre class="suite-consolelog-item-message">samDummyNested nested3</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:394:15) - at runNextTicks (node:internal/process/task_queues:60:5) - at listOnTimeout (node:internal/timers:538:9) - at processTimers (node:internal/timers:512:7)</pre><pre class="suite-consolelog-item-message">dummy firstDummyFuncArg</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:383:15) - at runNextTicks (node:internal/process/task_queues:60:5) - at listOnTimeout (node:internal/timers:538:9) - at processTimers (node:internal/timers:512:7) - at /Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:104:38 - at /Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:102:13 - at async Promise.all (index 0) - at dummyFn (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:389:22) - at /Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:104:38 - at /Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:102:13 - at /Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:413:20 - at /Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:104:38 - at /Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:102:13 - at Object.<anonymous> (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:407:17)</pre><pre class="suite-consolelog-item-message">samDummyNested nested1</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:383:15) - at runNextTicks (node:internal/process/task_queues:60:5) - at listOnTimeout (node:internal/timers:538:9) - at processTimers (node:internal/timers:512:7) - at /Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:104:38 - at /Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:102:13 - at async Promise.all (index 1) - at dummyFn (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:389:22) - at /Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:104:38 - at /Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:102:13 - at /Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:413:20 - at /Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:104:38 - at /Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:102:13 - at Object.<anonymous> (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:407:17)</pre><pre class="suite-consolelog-item-message">samDummyNested nested2</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:383:15) - at /Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:104:38 - at /Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:102:13 - at async Promise.all (index 2) - at dummyFn (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:389:22) - at /Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:104:38 - at /Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:102:13 - at /Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:413:20 - at /Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:104:38 - at /Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:102:13 - at Object.<anonymous> (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:407:17)</pre><pre class="suite-consolelog-item-message">samDummyNested nested3</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:394:15) - at /Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:104:38 - at /Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:102:13 - at /Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:413:20 - at /Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:104:38 - at /Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:102:13 - at Object.<anonymous> (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:407:17)</pre><pre class="suite-consolelog-item-message">dummy secondDummyFuncArg</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:383:15) - at runNextTicks (node:internal/process/task_queues:60:5) - at listOnTimeout (node:internal/timers:538:9) - at processTimers (node:internal/timers:512:7) - at /Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:104:38 - at /Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:102:13 - at async Promise.all (index 0) - at dummyFn (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:389:22) - at /Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:104:38 - at /Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:102:13 - at /Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:421:20 - at /Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:104:38 - at /Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:102:13 - at Object.<anonymous> (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:407:17)</pre><pre class="suite-consolelog-item-message">samDummyNested nested1</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:383:15) - at runNextTicks (node:internal/process/task_queues:60:5) - at listOnTimeout (node:internal/timers:538:9) - at processTimers (node:internal/timers:512:7) - at /Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:104:38 - at /Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:102:13 - at async Promise.all (index 1) - at dummyFn (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:389:22) - at /Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:104:38 - at /Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:102:13 - at /Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:421:20 - at /Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:104:38 - at /Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:102:13 - at Object.<anonymous> (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:407:17)</pre><pre class="suite-consolelog-item-message">samDummyNested nested2</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:383:15) - at runNextTicks (node:internal/process/task_queues:60:5) - at listOnTimeout (node:internal/timers:538:9) - at processTimers (node:internal/timers:512:7) - at /Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:104:38 - at /Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:102:13 - at async Promise.all (index 2) - at dummyFn (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:389:22) - at /Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:104:38 - at /Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:102:13 - at /Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:421:20 - at /Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:104:38 - at /Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:102:13 - at Object.<anonymous> (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:407:17)</pre><pre class="suite-consolelog-item-message">samDummyNested nested3</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:394:15) - at runNextTicks (node:internal/process/task_queues:60:5) - at listOnTimeout (node:internal/timers:538:9) - at processTimers (node:internal/timers:512:7) - at /Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:104:38 - at /Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:102:13 - at /Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:421:20 - at /Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:104:38 - at /Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:102:13 - at Object.<anonymous> (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:407:17)</pre><pre class="suite-consolelog-item-message">dummy thirdDummyFuncArg</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:427:15) - at func (/Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:104:44) - at AsyncLocalStorage.run (node:async_hooks:338:14) - at run (/Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:102:28) - at Object.<anonymous> (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:433:5) - at runNextTicks (node:internal/process/task_queues:60:5) - at listOnTimeout (node:internal/timers:538:9) - at processTimers (node:internal/timers:512:7)</pre><pre class="suite-consolelog-item-message">hello world</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:437:13) - at runNextTicks (node:internal/process/task_queues:60:5) - at listOnTimeout (node:internal/timers:538:9) - at processTimers (node:internal/timers:512:7)</pre><pre class="suite-consolelog-item-message">stats {"failed":0,"started":30,"finalized":30,"submitted":30,"sent":30,"done":30}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:461:13)</pre><pre class="suite-consolelog-item-message">[ - { - name: 'Harrison', - hair_color: 'BLACK', - last_name: null, - height: 1.83, - hobbies: [ 'SPORTS' ] - } -]</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:530:13) - at Promise.then.completed (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/utils.js:298:28) - at new Promise (<anonymous>) - at callAsyncCircusFn (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/utils.js:231:10) - at _callCircusTest (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/run.js:316:40) - at _runTest (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/run.js:252:3) - at _runTestsForDescribeBlock (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/run.js:126:9) - at _runTestsForDescribeBlock (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/run.js:121:9) - at run (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/run.js:71:3) - at runAndTransformResultsToJestFormat (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/legacy-code-todo-rewrite/jestAdapterInit.js:122:21) - at jestAdapter (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/legacy-code-todo-rewrite/jestAdapter.js:79:19) - at runTestInternal (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-runner@29.7.0/node_modules/jest-runner/build/runTest.js:367:16) - at runTest (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-runner@29.7.0/node_modules/jest-runner/build/runTest.js:444:34)</pre><pre class="suite-consolelog-item-message">[ - [ - 'hair_color', - ClassPropertyBuilder { bldr: ClassPropertyBuilder {} } - ], - [ - 'attributes', - ClassPropertyBuilder { bldr: ClassPropertyBuilder {} } - ] -]</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:532:15) - at Promise.then.completed (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/utils.js:298:28) - at new Promise (<anonymous>) - at callAsyncCircusFn (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/utils.js:231:10) - at _callCircusTest (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/run.js:316:40) - at _runTest (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/run.js:252:3) - at _runTestsForDescribeBlock (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/run.js:126:9) - at _runTestsForDescribeBlock (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/run.js:121:9) - at run (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/run.js:71:3) - at runAndTransformResultsToJestFormat (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/legacy-code-todo-rewrite/jestAdapterInit.js:122:21) - at jestAdapter (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/legacy-code-todo-rewrite/jestAdapter.js:79:19) - at runTestInternal (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-runner@29.7.0/node_modules/jest-runner/build/runTest.js:367:16) - at runTest (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-runner@29.7.0/node_modules/jest-runner/build/runTest.js:444:34)</pre><pre class="suite-consolelog-item-message">Property: hair_color</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:532:15) - at Promise.then.completed (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/utils.js:298:28) - at new Promise (<anonymous>) - at callAsyncCircusFn (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/utils.js:231:10) - at _callCircusTest (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/run.js:316:40) - at _runTest (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/run.js:252:3) - at _runTestsForDescribeBlock (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/run.js:126:9) - at _runTestsForDescribeBlock (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/run.js:121:9) - at run (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/run.js:71:3) - at runAndTransformResultsToJestFormat (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/legacy-code-todo-rewrite/jestAdapterInit.js:122:21) - at jestAdapter (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/legacy-code-todo-rewrite/jestAdapter.js:79:19) - at runTestInternal (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-runner@29.7.0/node_modules/jest-runner/build/runTest.js:367:16) - at runTest (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-runner@29.7.0/node_modules/jest-runner/build/runTest.js:444:34)</pre><pre class="suite-consolelog-item-message">Property: attributes</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:540:13)</pre><pre class="suite-consolelog-item-message">final { - hair_color: 'black', - attributes: { height: '6 feet', eye_color: 'blue', facial_hair: 'beard' } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:564:13) - at Promise.then.completed (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/utils.js:298:28) - at new Promise (<anonymous>) - at callAsyncCircusFn (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/utils.js:231:10) - at _callCircusTest (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/run.js:316:40) - at _runTest (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/run.js:252:3) - at _runTestsForDescribeBlock (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/run.js:126:9) - at _runTestsForDescribeBlock (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/run.js:121:9) - at run (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/run.js:71:3) - at runAndTransformResultsToJestFormat (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/legacy-code-todo-rewrite/jestAdapterInit.js:122:21) - at jestAdapter (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/legacy-code-todo-rewrite/jestAdapter.js:79:19) - at runTestInternal (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-runner@29.7.0/node_modules/jest-runner/build/runTest.js:367:16) - at runTest (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-runner@29.7.0/node_modules/jest-runner/build/runTest.js:444:34)</pre><pre class="suite-consolelog-item-message">[ - [ - 'hair_color', - ClassPropertyBuilder { bldr: ClassPropertyBuilder {} } - ], - [ - 'attributes', - ClassPropertyBuilder { bldr: ClassPropertyBuilder {} } - ], - [ 'height', ClassPropertyBuilder { bldr: ClassPropertyBuilder {} } ] -]</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:566:15) - at Promise.then.completed (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/utils.js:298:28) - at new Promise (<anonymous>) - at callAsyncCircusFn (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/utils.js:231:10) - at _callCircusTest (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/run.js:316:40) - at _runTest (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/run.js:252:3) - at _runTestsForDescribeBlock (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/run.js:126:9) - at _runTestsForDescribeBlock (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/run.js:121:9) - at run (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/run.js:71:3) - at runAndTransformResultsToJestFormat (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/legacy-code-todo-rewrite/jestAdapterInit.js:122:21) - at jestAdapter (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/legacy-code-todo-rewrite/jestAdapter.js:79:19) - at runTestInternal (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-runner@29.7.0/node_modules/jest-runner/build/runTest.js:367:16) - at runTest (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-runner@29.7.0/node_modules/jest-runner/build/runTest.js:444:34)</pre><pre class="suite-consolelog-item-message">Property: hair_color</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:566:15) - at Promise.then.completed (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/utils.js:298:28) - at new Promise (<anonymous>) - at callAsyncCircusFn (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/utils.js:231:10) - at _callCircusTest (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/run.js:316:40) - at _runTest (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/run.js:252:3) - at _runTestsForDescribeBlock (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/run.js:126:9) - at _runTestsForDescribeBlock (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/run.js:121:9) - at run (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/run.js:71:3) - at runAndTransformResultsToJestFormat (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/legacy-code-todo-rewrite/jestAdapterInit.js:122:21) - at jestAdapter (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/legacy-code-todo-rewrite/jestAdapter.js:79:19) - at runTestInternal (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-runner@29.7.0/node_modules/jest-runner/build/runTest.js:367:16) - at runTest (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-runner@29.7.0/node_modules/jest-runner/build/runTest.js:444:34)</pre><pre class="suite-consolelog-item-message">Property: attributes</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:566:15) - at Promise.then.completed (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/utils.js:298:28) - at new Promise (<anonymous>) - at callAsyncCircusFn (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/utils.js:231:10) - at _callCircusTest (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/run.js:316:40) - at _runTest (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/run.js:252:3) - at _runTestsForDescribeBlock (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/run.js:126:9) - at _runTestsForDescribeBlock (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/run.js:121:9) - at run (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/run.js:71:3) - at runAndTransformResultsToJestFormat (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/legacy-code-todo-rewrite/jestAdapterInit.js:122:21) - at jestAdapter (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-circus@29.7.0/node_modules/jest-circus/build/legacy-code-todo-rewrite/jestAdapter.js:79:19) - at runTestInternal (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-runner@29.7.0/node_modules/jest-runner/build/runTest.js:367:16) - at runTest (/Users/vbv/repos/gloo-lang/integ-tests/typescript/node_modules/.pnpm/jest-runner@29.7.0/node_modules/jest-runner/build/runTest.js:444:34)</pre><pre class="suite-consolelog-item-message">Property: height</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:574:13)</pre><pre class="suite-consolelog-item-message">final { - hair_color: 'black', - attributes: { eye_color: 'blue', facial_hair: 'beard' }, - height: { feet: 6, inches: null } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:585:13)</pre><pre class="suite-consolelog-item-message">final { - hair_color: 'black', - attributes: { eye_color: 'blue', facial_hair: 'beard' }, - height: { meters: 1.8 } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { prop1: null, prop2: null }</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { prop1: null, prop2: null }</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { prop1: null, prop2: null }</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { prop1: null, prop2: null }</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { prop1: null, prop2: null }</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { prop1: null, prop2: null }</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { prop1: null, prop2: null }</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { prop1: null, prop2: null }</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { prop1: null, prop2: null }</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { prop1: null, prop2: null }</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { prop1: null, prop2: null }</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { prop1: null, prop2: null }</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { prop1: null, prop2: null }</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { prop1: null, prop2: null }</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { prop1: null, prop2: null }</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { prop1: null, prop2: null }</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { prop1: null, prop2: null }</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { prop1: null, prop2: null }</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { prop1: null, prop2: null }</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { prop1: null, prop2: null }</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { prop1: null, prop2: null }</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { prop1: null, prop2: null }</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { prop1: null, prop2: null }</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { prop1: null, prop2: null }</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { prop1: null, prop2: null }</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { prop1: null, prop2: null }</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { prop1: '', prop2: null }</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { prop1: 'Hello', prop2: null }</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { prop1: 'Hello', prop2: null }</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { prop1: 'Hello', prop2: null }</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { prop1: 'Hello', prop2: null }</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { prop1: 'Hello', prop2: null }</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { prop1: 'Hello', prop2: null }</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { prop1: 'Hello', prop2: null }</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { prop1: 'Hello', prop2: null }</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { prop1: 'Hello', prop2: { prop1: null, prop2: null, inner: null } }</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { prop1: 'Hello', prop2: { prop1: null, prop2: null, inner: null } }</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { prop1: 'Hello', prop2: { prop1: null, prop2: null, inner: null } }</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { prop1: 'Hello', prop2: { prop1: null, prop2: null, inner: null } }</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { prop1: 'Hello', prop2: { prop1: null, prop2: null, inner: null } }</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { prop1: 'Hello', prop2: { prop1: null, prop2: null, inner: null } }</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { prop1: 'Hello', prop2: { prop1: null, prop2: null, inner: null } }</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { prop1: 'Hello', prop2: { prop1: '', prop2: null, inner: null } }</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { prop1: 'Hello', prop2: { prop1: 'World', prop2: null, inner: null } }</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { prop1: 'Hello', prop2: { prop1: 'World', prop2: null, inner: null } }</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { prop1: 'Hello', prop2: { prop1: 'World', prop2: null, inner: null } }</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { prop1: 'Hello', prop2: { prop1: 'World', prop2: null, inner: null } }</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { prop1: 'Hello', prop2: { prop1: 'World', prop2: null, inner: null } }</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { prop1: 'Hello', prop2: { prop1: 'World', prop2: null, inner: null } }</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { prop1: 'Hello', prop2: { prop1: 'World', prop2: null, inner: null } }</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { prop1: 'Hello', prop2: { prop1: 'World', prop2: null, inner: null } }</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { prop1: 'Hello', prop2: { prop1: 'World', prop2: '', inner: null } }</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { prop1: 'Hello', prop2: { prop1: 'World', prop2: 'J', inner: null } }</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { prop1: 'World', prop2: 'Javascript', inner: null } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { prop1: 'World', prop2: 'Javascript', inner: null } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { prop1: 'World', prop2: 'Javascript', inner: null } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { prop1: 'World', prop2: 'Javascript', inner: null } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { prop1: 'World', prop2: 'Javascript', inner: null } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { prop1: 'World', prop2: 'Javascript', inner: null } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { prop1: 'World', prop2: 'Javascript', inner: null } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: null, prop3: null } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: null, prop3: null } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: null, prop3: null } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: null, prop3: null } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: null, prop3: null } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: null, prop3: null } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: null, prop3: null } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: null, prop3: null } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: null, prop3: null } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: null, prop3: null } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: null } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: null } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: null } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: null } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: null } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: null, prop3: null } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: null } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: null } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: null } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: null } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: null } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: null } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: null } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: null } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: null } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: null } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: null } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: null } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: null } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15) - at runNextTicks (node:internal/process/task_queues:60:5) - at processTimers (node:internal/timers:509:9)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15) - at runNextTicks (node:internal/process/task_queues:60:5) - at processTimers (node:internal/timers:509:9)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15) - at runNextTicks (node:internal/process/task_queues:60:5) - at processTimers (node:internal/timers:509:9)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15) - at runNextTicks (node:internal/process/task_queues:60:5) - at processTimers (node:internal/timers:509:9)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: null } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15) - at runNextTicks (node:internal/process/task_queues:60:5) - at processTimers (node:internal/timers:509:9)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15) - at runNextTicks (node:internal/process/task_queues:60:5) - at processTimers (node:internal/timers:509:9)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15) - at runNextTicks (node:internal/process/task_queues:60:5) - at processTimers (node:internal/timers:509:9)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15) - at runNextTicks (node:internal/process/task_queues:60:5) - at listOnTimeout (node:internal/timers:538:9) - at processTimers (node:internal/timers:512:7)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15) - at runNextTicks (node:internal/process/task_queues:60:5) - at listOnTimeout (node:internal/timers:538:9) - at processTimers (node:internal/timers:512:7)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15) - at runNextTicks (node:internal/process/task_queues:60:5) - at listOnTimeout (node:internal/timers:538:9) - at processTimers (node:internal/timers:512:7)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15) - at runNextTicks (node:internal/process/task_queues:60:5) - at listOnTimeout (node:internal/timers:538:9) - at processTimers (node:internal/timers:512:7)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15) - at runNextTicks (node:internal/process/task_queues:60:5) - at listOnTimeout (node:internal/timers:538:9) - at processTimers (node:internal/timers:512:7)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15) - at runNextTicks (node:internal/process/task_queues:60:5) - at listOnTimeout (node:internal/timers:538:9) - at processTimers (node:internal/timers:512:7)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: null } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: null } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: null } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: null } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: null } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: null } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: null } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: null } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: null } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: null } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: null } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: null } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: null } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: null } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: 3.14 } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: null } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:598:15)</pre><pre class="suite-consolelog-item-message">msg { - prop1: 'Hello', - prop2: { - prop1: 'World', - prop2: 'Javascript', - inner: { prop2: 42, prop3: null } - } -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:623:15) - at callback (/Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:70:17)</pre><pre class="suite-consolelog-item-message">onLogEvent { - metadata: { - eventId: '66813c1f-6af6-457c-8cbe-3d59263196ff', - rootEventId: '66813c1f-6af6-457c-8cbe-3d59263196ff' - }, - prompt: '[\n' + - ' {\n' + - ' "role": "user",\n' + - ' "content": [\n' + - ' {\n' + - ' "text": "Return this value back to me: [\\"a\\", \\"b\\", \\"c\\"]"\n' + - ' }\n' + - ' ]\n' + - ' }\n' + - ']', - rawOutput: '["a", "b", "c"]', - parsedOutput: '["a", "b", "c"]', - startTime: '2024-11-26T16:55:28.057Z' -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:623:15) - at callback (/Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:70:17)</pre><pre class="suite-consolelog-item-message">onLogEvent { - metadata: { - eventId: 'a4564200-6e8c-47be-9880-d153a020d11c', - rootEventId: 'a4564200-6e8c-47be-9880-d153a020d11c' - }, - prompt: '[\n' + - ' {\n' + - ' "role": "user",\n' + - ' "content": [\n' + - ' {\n' + - ' "text": "Return this value back to me: [\\"d\\", \\"e\\", \\"f\\"]"\n' + - ' }\n' + - ' ]\n' + - ' }\n' + - ']', - rawOutput: '["d", "e", "f"]', - parsedOutput: '["d", "e", "f"]', - startTime: '2024-11-26T16:55:28.438Z' -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:657:15)</pre><pre class="suite-consolelog-item-message">Error: Error: BamlError: BamlClientError: BamlClientHttpError: LLM call failed: LLMErrorResponse { client: "MyClient", model: None, prompt: Chat([RenderedChatMessage { role: "user", allow_duplicate_role: false, parts: [Text("Given a string, extract info using the schema:\n\nMy name is Harrison. My hair is black and I'm 6 feet tall.\n\nAnswer in JSON using this schema:\n{\n}")] }]), request_options: {"model": String("gpt-4o-mini")}, start_time: SystemTime { tv_sec: 1732640129, tv_nsec: 988540000 }, latency: 139.890458ms, message: "Request failed: https://api.openai.com/v1/chat/completions\n{\n \"error\": {\n \"message\": \"Incorrect API key provided: INVALID_KEY. You can find your API key at https://platform.openai.com/account/api-keys.\",\n \"type\": \"invalid_request_error\",\n \"param\": null,\n \"code\": \"invalid_api_key\"\n }\n}\n", code: InvalidAuthentication } - at BamlAsyncClient.parsed (/Users/vbv/repos/gloo-lang/integ-tests/typescript/baml_client/async_client.ts:1585:18) - at Object.<anonymous> (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:654:7) { - code: 'GenericFailure' -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:665:17) - at Object.<anonymous> (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:661:5)</pre><pre class="suite-consolelog-item-message">BamlValidationError: BamlValidationError: Failed to parse LLM response: Failed to coerce value: <root>: Failed while parsing required fields: missing=2, unparsed=0 - - <root>: Missing required field: nonce - - <root>: Missing required field: nonce2 - at Function.from (/Users/vbv/repos/gloo-lang/engine/language_client_typescript/index.js:33:28) - at from (/Users/vbv/repos/gloo-lang/engine/language_client_typescript/index.js:58:32) - at BamlAsyncClient.DummyOutputFunction (/Users/vbv/repos/gloo-lang/integ-tests/typescript/baml_client/async_client.ts:562:50) - at /Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:663:9 - at Object.<anonymous> (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:661:5) { - prompt: '[\x1B[2mchat\x1B[0m] \x1B[43muser: \x1B[0mSay "hello there".\n', - raw_output: 'Hello there!' -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:677:17)</pre><pre class="suite-consolelog-item-message">error BamlValidationError: BamlValidationError: Failed to parse LLM response: Failed to coerce value: <root>: Failed while parsing required fields: missing=2, unparsed=0 - - <root>: Missing required field: nonce - - <root>: Missing required field: nonce2 - at Function.from (/Users/vbv/repos/gloo-lang/engine/language_client_typescript/index.js:33:28) - at from (/Users/vbv/repos/gloo-lang/engine/language_client_typescript/index.js:58:32) - at BamlAsyncClient.DummyOutputFunction (/Users/vbv/repos/gloo-lang/integ-tests/typescript/baml_client/async_client.ts:562:50) - at Object.<anonymous> (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:673:7) { - prompt: '[\x1B[2mchat\x1B[0m] \x1B[43muser: \x1B[0mSay "hello there".\n', - raw_output: 'Hello there!' -}</pre></div><div class="suite-consolelog-item"><pre class="suite-consolelog-item-origin"> at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:777:13)</pre><pre class="suite-consolelog-item-message">{"nbc":{"value":{"foo":1,"bar":"hello"},"checks":{"cross_field":{"name":"cross_field","expression":"this.bar|length > this.foo","status":"succeeded"}}}}</pre></div></div></div></div></body></html> \ No newline at end of file + at Object.toEqual (/workspaces/baml/integ-tests/typescript/tests/integ-tests.test.ts:856:17)</pre></div></div></div></div></div></body></html> \ No newline at end of file diff --git a/integ-tests/typescript/tests/integ-tests.test.ts b/integ-tests/typescript/tests/integ-tests.test.ts index 3ad7066d1..301edc4cb 100644 --- a/integ-tests/typescript/tests/integ-tests.test.ts +++ b/integ-tests/typescript/tests/integ-tests.test.ts @@ -148,6 +148,24 @@ describe('Integ tests', () => { const res = await b.InOutSingleLiteralStringMapKey({ key: '1' }) expect(res).toHaveProperty('key', '1') }) + + it('primitive union alias', async () => { + const res = await b.PrimitiveAlias('test') + expect(res).toEqual('test') + }) + + it('map alias', async () => { + const res = await b.MapAlias({ A: ['B', 'C'], B: [], C: [] }) + expect(res).toEqual({ A: ['B', 'C'], B: [], C: [] }) + }) + + it('alias union', async () => { + let res = await b.NestedAlias('test') + expect(res).toEqual('test') + + res = await b.NestedAlias({ A: ['B', 'C'], B: [], C: [] }) + expect(res).toEqual({ A: ['B', 'C'], B: [], C: [] }) + }) }) it('should work for all outputs', async () => { From d2381621c61696ace631e1cc43a0e0b61449103b Mon Sep 17 00:00:00 2001 From: Antonio Sarosi <sarosiantonio@gmail.com> Date: Thu, 28 Nov 2024 22:47:57 +0000 Subject: [PATCH 24/51] Fix literal unions test prompt --- .../functions/output/literal-unions.baml | 2 +- integ-tests/python/baml_client/inlinedbaml.py | 2 +- integ-tests/ruby/baml_client/inlined.rb | 2 +- .../typescript/baml_client/inlinedbaml.ts | 2 +- integ-tests/typescript/test-report.html | 61 +------------------ 5 files changed, 7 insertions(+), 62 deletions(-) diff --git a/integ-tests/baml_src/test-files/functions/output/literal-unions.baml b/integ-tests/baml_src/test-files/functions/output/literal-unions.baml index 0712b6f83..a3dbd0d0c 100644 --- a/integ-tests/baml_src/test-files/functions/output/literal-unions.baml +++ b/integ-tests/baml_src/test-files/functions/output/literal-unions.baml @@ -1,7 +1,7 @@ function LiteralUnionsTest(input: string) -> 1 | true | "string output" { client GPT35 prompt #" - Return one of these values: + Return one of these values without any additional context: {{ctx.output_format}} "# } diff --git a/integ-tests/python/baml_client/inlinedbaml.py b/integ-tests/python/baml_client/inlinedbaml.py index 899763780..224074c15 100644 --- a/integ-tests/python/baml_client/inlinedbaml.py +++ b/integ-tests/python/baml_client/inlinedbaml.py @@ -73,7 +73,7 @@ "test-files/functions/output/literal-boolean.baml": "function FnOutputLiteralBool(input: string) -> false {\n client GPT35\n prompt #\"\n Return a false: {{ ctx.output_format}}\n \"#\n}\n\ntest FnOutputLiteralBool {\n functions [FnOutputLiteralBool]\n args {\n input \"example input\"\n }\n}\n", "test-files/functions/output/literal-int.baml": "function FnOutputLiteralInt(input: string) -> 5 {\n client GPT35\n prompt #\"\n Return an integer: {{ ctx.output_format}}\n \"#\n}\n\ntest FnOutputLiteralInt {\n functions [FnOutputLiteralInt]\n args {\n input \"example input\"\n }\n}\n", "test-files/functions/output/literal-string.baml": "function FnOutputLiteralString(input: string) -> \"example output\" {\n client GPT35\n prompt #\"\n Return a string: {{ ctx.output_format}}\n \"#\n}\n\ntest FnOutputLiteralString {\n functions [FnOutputLiteralString]\n args {\n input \"example input\"\n }\n}\n", - "test-files/functions/output/literal-unions.baml": "function LiteralUnionsTest(input: string) -> 1 | true | \"string output\" {\n client GPT35\n prompt #\"\n Return one of these values: \n {{ctx.output_format}}\n \"#\n}\n\ntest LiteralUnionsTest {\n functions [LiteralUnionsTest]\n args {\n input \"example input\"\n }\n}\n", + "test-files/functions/output/literal-unions.baml": "function LiteralUnionsTest(input: string) -> 1 | true | \"string output\" {\n client GPT35\n prompt #\"\n Return one of these values without any additional context: \n {{ctx.output_format}}\n \"#\n}\n\ntest LiteralUnionsTest {\n functions [LiteralUnionsTest]\n args {\n input \"example input\"\n }\n}\n", "test-files/functions/output/map-enum-key.baml": "enum MapKey {\n A\n B\n C\n}\n\nfunction InOutEnumMapKey(i1: map<MapKey, string>, i2: map<MapKey, string>) -> map<MapKey, string> {\n client \"openai/gpt-4o\"\n prompt #\"\n Merge these: {{i1}} {{i2}}\n\n {{ ctx.output_format }}\n \"#\n}\n", "test-files/functions/output/map-literal-union-key.baml": "function InOutLiteralStringUnionMapKey(\n i1: map<\"one\" | \"two\" | (\"three\" | \"four\"), string>, \n i2: map<\"one\" | \"two\" | (\"three\" | \"four\"), string>\n) -> map<\"one\" | \"two\" | (\"three\" | \"four\"), string> {\n client \"openai/gpt-4o\"\n prompt #\"\n Merge these:\n \n {{i1}}\n \n {{i2}}\n\n {{ ctx.output_format }}\n \"#\n}\n\nfunction InOutSingleLiteralStringMapKey(m: map<\"key\", string>) -> map<\"key\", string> {\n client \"openai/gpt-4o\"\n prompt #\"\n Return the same map you were given:\n \n {{m}}\n\n {{ ctx.output_format }}\n \"#\n}\n", "test-files/functions/output/mutually-recursive-classes.baml": "class Tree {\n data int\n children Forest\n}\n\nclass Forest {\n trees Tree[]\n}\n\nclass BinaryNode {\n data int\n left BinaryNode?\n right BinaryNode?\n}\n\nfunction BuildTree(input: BinaryNode) -> Tree {\n client GPT35\n prompt #\"\n Given the input binary tree, transform it into a generic tree using the given schema.\n\n INPUT:\n {{ input }}\n\n {{ ctx.output_format }} \n \"#\n}\n\ntest TestTree {\n functions [BuildTree]\n args {\n input {\n data 2\n left {\n data 1\n left null\n right null\n }\n right {\n data 3\n left null\n right null\n }\n }\n }\n}", diff --git a/integ-tests/ruby/baml_client/inlined.rb b/integ-tests/ruby/baml_client/inlined.rb index cd1e25d72..f6201e07f 100644 --- a/integ-tests/ruby/baml_client/inlined.rb +++ b/integ-tests/ruby/baml_client/inlined.rb @@ -73,7 +73,7 @@ module Inlined "test-files/functions/output/literal-boolean.baml" => "function FnOutputLiteralBool(input: string) -> false {\n client GPT35\n prompt #\"\n Return a false: {{ ctx.output_format}}\n \"#\n}\n\ntest FnOutputLiteralBool {\n functions [FnOutputLiteralBool]\n args {\n input \"example input\"\n }\n}\n", "test-files/functions/output/literal-int.baml" => "function FnOutputLiteralInt(input: string) -> 5 {\n client GPT35\n prompt #\"\n Return an integer: {{ ctx.output_format}}\n \"#\n}\n\ntest FnOutputLiteralInt {\n functions [FnOutputLiteralInt]\n args {\n input \"example input\"\n }\n}\n", "test-files/functions/output/literal-string.baml" => "function FnOutputLiteralString(input: string) -> \"example output\" {\n client GPT35\n prompt #\"\n Return a string: {{ ctx.output_format}}\n \"#\n}\n\ntest FnOutputLiteralString {\n functions [FnOutputLiteralString]\n args {\n input \"example input\"\n }\n}\n", - "test-files/functions/output/literal-unions.baml" => "function LiteralUnionsTest(input: string) -> 1 | true | \"string output\" {\n client GPT35\n prompt #\"\n Return one of these values: \n {{ctx.output_format}}\n \"#\n}\n\ntest LiteralUnionsTest {\n functions [LiteralUnionsTest]\n args {\n input \"example input\"\n }\n}\n", + "test-files/functions/output/literal-unions.baml" => "function LiteralUnionsTest(input: string) -> 1 | true | \"string output\" {\n client GPT35\n prompt #\"\n Return one of these values without any additional context: \n {{ctx.output_format}}\n \"#\n}\n\ntest LiteralUnionsTest {\n functions [LiteralUnionsTest]\n args {\n input \"example input\"\n }\n}\n", "test-files/functions/output/map-enum-key.baml" => "enum MapKey {\n A\n B\n C\n}\n\nfunction InOutEnumMapKey(i1: map<MapKey, string>, i2: map<MapKey, string>) -> map<MapKey, string> {\n client \"openai/gpt-4o\"\n prompt #\"\n Merge these: {{i1}} {{i2}}\n\n {{ ctx.output_format }}\n \"#\n}\n", "test-files/functions/output/map-literal-union-key.baml" => "function InOutLiteralStringUnionMapKey(\n i1: map<\"one\" | \"two\" | (\"three\" | \"four\"), string>, \n i2: map<\"one\" | \"two\" | (\"three\" | \"four\"), string>\n) -> map<\"one\" | \"two\" | (\"three\" | \"four\"), string> {\n client \"openai/gpt-4o\"\n prompt #\"\n Merge these:\n \n {{i1}}\n \n {{i2}}\n\n {{ ctx.output_format }}\n \"#\n}\n\nfunction InOutSingleLiteralStringMapKey(m: map<\"key\", string>) -> map<\"key\", string> {\n client \"openai/gpt-4o\"\n prompt #\"\n Return the same map you were given:\n \n {{m}}\n\n {{ ctx.output_format }}\n \"#\n}\n", "test-files/functions/output/mutually-recursive-classes.baml" => "class Tree {\n data int\n children Forest\n}\n\nclass Forest {\n trees Tree[]\n}\n\nclass BinaryNode {\n data int\n left BinaryNode?\n right BinaryNode?\n}\n\nfunction BuildTree(input: BinaryNode) -> Tree {\n client GPT35\n prompt #\"\n Given the input binary tree, transform it into a generic tree using the given schema.\n\n INPUT:\n {{ input }}\n\n {{ ctx.output_format }} \n \"#\n}\n\ntest TestTree {\n functions [BuildTree]\n args {\n input {\n data 2\n left {\n data 1\n left null\n right null\n }\n right {\n data 3\n left null\n right null\n }\n }\n }\n}", diff --git a/integ-tests/typescript/baml_client/inlinedbaml.ts b/integ-tests/typescript/baml_client/inlinedbaml.ts index 43fbc8d22..232c36118 100644 --- a/integ-tests/typescript/baml_client/inlinedbaml.ts +++ b/integ-tests/typescript/baml_client/inlinedbaml.ts @@ -74,7 +74,7 @@ const fileMap = { "test-files/functions/output/literal-boolean.baml": "function FnOutputLiteralBool(input: string) -> false {\n client GPT35\n prompt #\"\n Return a false: {{ ctx.output_format}}\n \"#\n}\n\ntest FnOutputLiteralBool {\n functions [FnOutputLiteralBool]\n args {\n input \"example input\"\n }\n}\n", "test-files/functions/output/literal-int.baml": "function FnOutputLiteralInt(input: string) -> 5 {\n client GPT35\n prompt #\"\n Return an integer: {{ ctx.output_format}}\n \"#\n}\n\ntest FnOutputLiteralInt {\n functions [FnOutputLiteralInt]\n args {\n input \"example input\"\n }\n}\n", "test-files/functions/output/literal-string.baml": "function FnOutputLiteralString(input: string) -> \"example output\" {\n client GPT35\n prompt #\"\n Return a string: {{ ctx.output_format}}\n \"#\n}\n\ntest FnOutputLiteralString {\n functions [FnOutputLiteralString]\n args {\n input \"example input\"\n }\n}\n", - "test-files/functions/output/literal-unions.baml": "function LiteralUnionsTest(input: string) -> 1 | true | \"string output\" {\n client GPT35\n prompt #\"\n Return one of these values: \n {{ctx.output_format}}\n \"#\n}\n\ntest LiteralUnionsTest {\n functions [LiteralUnionsTest]\n args {\n input \"example input\"\n }\n}\n", + "test-files/functions/output/literal-unions.baml": "function LiteralUnionsTest(input: string) -> 1 | true | \"string output\" {\n client GPT35\n prompt #\"\n Return one of these values without any additional context: \n {{ctx.output_format}}\n \"#\n}\n\ntest LiteralUnionsTest {\n functions [LiteralUnionsTest]\n args {\n input \"example input\"\n }\n}\n", "test-files/functions/output/map-enum-key.baml": "enum MapKey {\n A\n B\n C\n}\n\nfunction InOutEnumMapKey(i1: map<MapKey, string>, i2: map<MapKey, string>) -> map<MapKey, string> {\n client \"openai/gpt-4o\"\n prompt #\"\n Merge these: {{i1}} {{i2}}\n\n {{ ctx.output_format }}\n \"#\n}\n", "test-files/functions/output/map-literal-union-key.baml": "function InOutLiteralStringUnionMapKey(\n i1: map<\"one\" | \"two\" | (\"three\" | \"four\"), string>, \n i2: map<\"one\" | \"two\" | (\"three\" | \"four\"), string>\n) -> map<\"one\" | \"two\" | (\"three\" | \"four\"), string> {\n client \"openai/gpt-4o\"\n prompt #\"\n Merge these:\n \n {{i1}}\n \n {{i2}}\n\n {{ ctx.output_format }}\n \"#\n}\n\nfunction InOutSingleLiteralStringMapKey(m: map<\"key\", string>) -> map<\"key\", string> {\n client \"openai/gpt-4o\"\n prompt #\"\n Return the same map you were given:\n \n {{m}}\n\n {{ ctx.output_format }}\n \"#\n}\n", "test-files/functions/output/mutually-recursive-classes.baml": "class Tree {\n data int\n children Forest\n}\n\nclass Forest {\n trees Tree[]\n}\n\nclass BinaryNode {\n data int\n left BinaryNode?\n right BinaryNode?\n}\n\nfunction BuildTree(input: BinaryNode) -> Tree {\n client GPT35\n prompt #\"\n Given the input binary tree, transform it into a generic tree using the given schema.\n\n INPUT:\n {{ input }}\n\n {{ ctx.output_format }} \n \"#\n}\n\ntest TestTree {\n functions [BuildTree]\n args {\n input {\n data 2\n left {\n data 1\n left null\n right null\n }\n right {\n data 3\n left null\n right null\n }\n }\n }\n}", diff --git a/integ-tests/typescript/test-report.html b/integ-tests/typescript/test-report.html index 1d6bb9a4b..249e518b0 100644 --- a/integ-tests/typescript/test-report.html +++ b/integ-tests/typescript/test-report.html @@ -257,64 +257,9 @@ font-size: 1rem; padding: 0 0.5rem; } -</style></head><body><div class="jesthtml-content"><header><h1 id="title">Test Report</h1></header><div id="metadata-container"><div id="timestamp">Started: 2024-11-28 22:38:45</div><div id="summary"><div id="suite-summary"><div class="summary-total">Suites (1)</div><div class="summary-passed summary-empty">0 passed</div><div class="summary-failed ">1 failed</div><div class="summary-pending summary-empty">0 pending</div></div><div id="test-summary"><div class="summary-total">Tests (70)</div><div class="summary-passed ">67 passed</div><div class="summary-failed ">3 failed</div><div class="summary-pending summary-empty">0 pending</div></div></div></div><div id="suite-1" class="suite-container"><input id="collapsible-0" type="checkbox" class="toggle" checked="checked"/><label for="collapsible-0"><div class="suite-info"><div class="suite-path">/workspaces/baml/integ-tests/typescript/tests/integ-tests.test.ts</div><div class="suite-time warn">98.002s</div></div></label><div class="suite-tests"><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single bool</div><div class="test-status">passed</div><div class="test-duration">0.442s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single string list</div><div class="test-status">passed</div><div class="test-duration">0.606s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">return literal union</div><div class="test-status">passed</div><div class="test-duration">0.382s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single class</div><div class="test-status">passed</div><div class="test-duration">0.448s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">multiple classes</div><div class="test-status">passed</div><div class="test-duration">0.444s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single enum list</div><div class="test-status">passed</div><div class="test-duration">0.425s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single float</div><div class="test-status">passed</div><div class="test-duration">0.863s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single int</div><div class="test-status">passed</div><div class="test-duration">0.338s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single literal int</div><div class="test-status">passed</div><div class="test-duration">0.38s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single literal bool</div><div class="test-status">passed</div><div class="test-duration">0.408s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single literal string</div><div class="test-status">passed</div><div class="test-duration">0.409s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single class with literal prop</div><div class="test-status">passed</div><div class="test-duration">0.547s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single class with literal union prop</div><div class="test-status">passed</div><div class="test-duration">0.679s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single optional string</div><div class="test-status">passed</div><div class="test-duration">0.328s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single map string to string</div><div class="test-status">passed</div><div class="test-duration">0.492s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single map string to class</div><div class="test-status">passed</div><div class="test-duration">0.514s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single map string to map</div><div class="test-status">passed</div><div class="test-duration">0.518s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">enum key in map</div><div class="test-status">passed</div><div class="test-duration">0.808s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">literal string union key in map</div><div class="test-status">passed</div><div class="test-duration">0.553s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single literal string key in map</div><div class="test-status">passed</div><div class="test-duration">0.882s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">primitive union alias</div><div class="test-status">passed</div><div class="test-duration">0.823s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">map alias</div><div class="test-status">passed</div><div class="test-duration">0.822s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">alias union</div><div class="test-status">passed</div><div class="test-duration">1.463s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work for all outputs</div><div class="test-status">passed</div><div class="test-duration">4.158s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">works with retries1</div><div class="test-status">passed</div><div class="test-duration">1.164s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">works with retries2</div><div class="test-status">passed</div><div class="test-duration">2.411s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">works with fallbacks</div><div class="test-status">passed</div><div class="test-duration">2.378s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with image from url</div><div class="test-status">passed</div><div class="test-duration">1.432s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with image from base 64</div><div class="test-status">passed</div><div class="test-duration">1.528s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with audio base 64</div><div class="test-status">passed</div><div class="test-duration">1.555s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with audio from url</div><div class="test-status">passed</div><div class="test-duration">1.619s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support streaming in OpenAI</div><div class="test-status">passed</div><div class="test-duration">2.023s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support streaming in Gemini</div><div class="test-status">passed</div><div class="test-duration">9.831s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support AWS</div><div class="test-status">passed</div><div class="test-duration">1.651s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support streaming in AWS</div><div class="test-status">passed</div><div class="test-duration">1.516s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should allow overriding the region</div><div class="test-status">passed</div><div class="test-duration">0.02s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support OpenAI shorthand</div><div class="test-status">passed</div><div class="test-duration">6.471s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support OpenAI shorthand streaming</div><div class="test-status">passed</div><div class="test-duration">9.687s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support anthropic shorthand</div><div class="test-status">passed</div><div class="test-duration">3.816s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support anthropic shorthand streaming</div><div class="test-status">passed</div><div class="test-duration">2.662s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support streaming without iterating</div><div class="test-status">passed</div><div class="test-duration">1.73s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support streaming in Claude</div><div class="test-status">passed</div><div class="test-duration">1.42s</div></div></div><div class="test-result failed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support vertex</div><div class="test-status">failed</div><div class="test-duration">0.001s</div></div><div class="failureMessages"> <pre class="failureMsg">Error: BamlError: Failed to read service account file: +</style></head><body><div class="jesthtml-content"><header><h1 id="title">Test Report</h1></header><div id="metadata-container"><div id="timestamp">Started: 2024-11-28 22:45:21</div><div id="summary"><div id="suite-summary"><div class="summary-total">Suites (1)</div><div class="summary-passed summary-empty">0 passed</div><div class="summary-failed ">1 failed</div><div class="summary-pending summary-empty">0 pending</div></div><div id="test-summary"><div class="summary-total">Tests (70)</div><div class="summary-passed ">68 passed</div><div class="summary-failed ">2 failed</div><div class="summary-pending summary-empty">0 pending</div></div></div></div><div id="suite-1" class="suite-container"><input id="collapsible-0" type="checkbox" class="toggle" checked="checked"/><label for="collapsible-0"><div class="suite-info"><div class="suite-path">/workspaces/baml/integ-tests/typescript/tests/integ-tests.test.ts</div><div class="suite-time warn">105.957s</div></div></label><div class="suite-tests"><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single bool</div><div class="test-status">passed</div><div class="test-duration">0.428s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single string list</div><div class="test-status">passed</div><div class="test-duration">0.385s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">return literal union</div><div class="test-status">passed</div><div class="test-duration">0.312s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single class</div><div class="test-status">passed</div><div class="test-duration">0.405s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">multiple classes</div><div class="test-status">passed</div><div class="test-duration">0.717s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single enum list</div><div class="test-status">passed</div><div class="test-duration">0.407s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single float</div><div class="test-status">passed</div><div class="test-duration">0.304s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single int</div><div class="test-status">passed</div><div class="test-duration">0.523s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single literal int</div><div class="test-status">passed</div><div class="test-duration">0.39s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single literal bool</div><div class="test-status">passed</div><div class="test-duration">0.281s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single literal string</div><div class="test-status">passed</div><div class="test-duration">0.343s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single class with literal prop</div><div class="test-status">passed</div><div class="test-duration">0.615s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single class with literal union prop</div><div class="test-status">passed</div><div class="test-duration">0.616s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single optional string</div><div class="test-status">passed</div><div class="test-duration">0.609s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single map string to string</div><div class="test-status">passed</div><div class="test-duration">0.641s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single map string to class</div><div class="test-status">passed</div><div class="test-duration">0.587s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single map string to map</div><div class="test-status">passed</div><div class="test-duration">0.427s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">enum key in map</div><div class="test-status">passed</div><div class="test-duration">0.64s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">literal string union key in map</div><div class="test-status">passed</div><div class="test-duration">0.777s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single literal string key in map</div><div class="test-status">passed</div><div class="test-duration">0.743s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">primitive union alias</div><div class="test-status">passed</div><div class="test-duration">0.46s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">map alias</div><div class="test-status">passed</div><div class="test-duration">0.845s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">alias union</div><div class="test-status">passed</div><div class="test-duration">1.529s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work for all outputs</div><div class="test-status">passed</div><div class="test-duration">4.278s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">works with retries1</div><div class="test-status">passed</div><div class="test-duration">1.074s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">works with retries2</div><div class="test-status">passed</div><div class="test-duration">2.274s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">works with fallbacks</div><div class="test-status">passed</div><div class="test-duration">2s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with image from url</div><div class="test-status">passed</div><div class="test-duration">1.742s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with image from base 64</div><div class="test-status">passed</div><div class="test-duration">1.229s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with audio base 64</div><div class="test-status">passed</div><div class="test-duration">1.536s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with audio from url</div><div class="test-status">passed</div><div class="test-duration">1.603s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support streaming in OpenAI</div><div class="test-status">passed</div><div class="test-duration">2.034s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support streaming in Gemini</div><div class="test-status">passed</div><div class="test-duration">10.13s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support AWS</div><div class="test-status">passed</div><div class="test-duration">1.694s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support streaming in AWS</div><div class="test-status">passed</div><div class="test-duration">1.526s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should allow overriding the region</div><div class="test-status">passed</div><div class="test-duration">0.011s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support OpenAI shorthand</div><div class="test-status">passed</div><div class="test-duration">14.026s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support OpenAI shorthand streaming</div><div class="test-status">passed</div><div class="test-duration">9.005s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support anthropic shorthand</div><div class="test-status">passed</div><div class="test-duration">3.033s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support anthropic shorthand streaming</div><div class="test-status">passed</div><div class="test-duration">3.136s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support streaming without iterating</div><div class="test-status">passed</div><div class="test-duration">1.992s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support streaming in Claude</div><div class="test-status">passed</div><div class="test-duration">1.027s</div></div></div><div class="test-result failed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support vertex</div><div class="test-status">failed</div><div class="test-duration">0.003s</div></div><div class="failureMessages"> <pre class="failureMsg">Error: BamlError: Failed to read service account file: Caused by: - No such file or directory (os error 2)</pre></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">supports tracing sync</div><div class="test-status">passed</div><div class="test-duration">0.014s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">supports tracing async</div><div class="test-status">passed</div><div class="test-duration">2.783s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic types single</div><div class="test-status">passed</div><div class="test-duration">1.115s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic types enum</div><div class="test-status">passed</div><div class="test-duration">0.924s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic literals</div><div class="test-status">passed</div><div class="test-duration">1.019s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic types class</div><div class="test-status">passed</div><div class="test-duration">0.84s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic inputs class</div><div class="test-status">passed</div><div class="test-duration">0.495s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic inputs list</div><div class="test-status">passed</div><div class="test-duration">0.508s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic output map</div><div class="test-status">passed</div><div class="test-duration">0.715s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic output union</div><div class="test-status">passed</div><div class="test-duration">1.478s</div></div></div><div class="test-result failed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with nested classes</div><div class="test-status">failed</div><div class="test-duration">0.108s</div></div><div class="failureMessages"> <pre class="failureMsg">Error: BamlError: BamlClientError: Something went wrong with the LLM client: reqwest::Error { kind: Request, url: Url { scheme: "http", cannot_be_a_base: false, username: "", password: None, host: Some(Domain("localhost")), port: Some(11434), path: "/v1/chat/completions", query: None, fragment: None }, source: hyper_util::client::legacy::Error(Connect, ConnectError("tcp connect error", Os { code: 111, kind: ConnectionRefused, message: "Connection refused" })) } + No such file or directory (os error 2)</pre></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">supports tracing sync</div><div class="test-status">passed</div><div class="test-duration">0.014s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">supports tracing async</div><div class="test-status">passed</div><div class="test-duration">1.783s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic types single</div><div class="test-status">passed</div><div class="test-duration">0.895s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic types enum</div><div class="test-status">passed</div><div class="test-duration">0.748s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic literals</div><div class="test-status">passed</div><div class="test-duration">0.785s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic types class</div><div class="test-status">passed</div><div class="test-duration">6.247s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic inputs class</div><div class="test-status">passed</div><div class="test-duration">0.508s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic inputs list</div><div class="test-status">passed</div><div class="test-duration">0.551s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic output map</div><div class="test-status">passed</div><div class="test-duration">0.685s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic output union</div><div class="test-status">passed</div><div class="test-duration">1.529s</div></div></div><div class="test-result failed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with nested classes</div><div class="test-status">failed</div><div class="test-duration">0.106s</div></div><div class="failureMessages"> <pre class="failureMsg">Error: BamlError: BamlClientError: Something went wrong with the LLM client: reqwest::Error { kind: Request, url: Url { scheme: "http", cannot_be_a_base: false, username: "", password: None, host: Some(Domain("localhost")), port: Some(11434), path: "/v1/chat/completions", query: None, fragment: None }, source: hyper_util::client::legacy::Error(Connect, ConnectError("tcp connect error", Os { code: 111, kind: ConnectionRefused, message: "Connection refused" })) } at BamlStream.parsed [as getFinalResponse] (/workspaces/baml/engine/language_client_typescript/stream.js:58:39) - at Object.<anonymous> (/workspaces/baml/integ-tests/typescript/tests/integ-tests.test.ts:620:19)</pre></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic client</div><div class="test-status">passed</div><div class="test-duration">0.358s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with 'onLogEvent'</div><div class="test-status">passed</div><div class="test-duration">1.923s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with a sync client</div><div class="test-status">passed</div><div class="test-duration">0.471s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should raise an error when appropriate</div><div class="test-status">passed</div><div class="test-duration">0.817s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should raise a BAMLValidationError</div><div class="test-status">passed</div><div class="test-duration">0.47s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should reset environment variables correctly</div><div class="test-status">passed</div><div class="test-duration">2.557s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should use aliases when serializing input objects - classes</div><div class="test-status">passed</div><div class="test-duration">1.517s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should use aliases when serializing, but still have original keys in jinja</div><div class="test-status">passed</div><div class="test-duration">0.837s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should use aliases when serializing input objects - enums</div><div class="test-status">passed</div><div class="test-duration">0.445s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should use aliases when serializing input objects - lists</div><div class="test-status">passed</div><div class="test-duration">0.481s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">constraints: should handle checks in return types</div><div class="test-status">passed</div><div class="test-duration">0.613s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">constraints: should handle checks in returned unions</div><div class="test-status">passed</div><div class="test-duration">0.829s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">constraints: should handle block-level checks</div><div class="test-status">passed</div><div class="test-duration">0.509s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">constraints: should handle nested-block-level checks</div><div class="test-status">passed</div><div class="test-duration">0.709s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">simple recursive type</div><div class="test-status">passed</div><div class="test-duration">2.458s</div></div></div><div class="test-result failed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">mutually recursive type</div><div class="test-status">failed</div><div class="test-duration">1.686s</div></div><div class="failureMessages"> <pre class="failureMsg">Error: expect(received).toEqual(expected) // deep equality - -- Expected - 32 -+ Received + 0 - -@@ -4,49 +4,17 @@ - Object { - "children": Object { - "trees": Array [ - Object { - "children": Object { -- "trees": Array [ -- Object { -- "children": Object { -- "trees": Array [], -- }, -- "data": 2, -- }, -- ], -- }, -- "data": 1, -- }, -- Object { -- "children": Object { - "trees": Array [], - }, - "data": 4, - }, - ], - }, - "data": 3, -- }, -- Object { -- "children": Object { -- "trees": Array [ -- Object { -- "children": Object { -- "trees": Array [], -- }, -- "data": 6, -- }, -- Object { -- "children": Object { -- "trees": Array [], -- }, -- "data": 8, -- }, -- ], -- }, -- "data": 7, - }, - ], - }, - "data": 5, - } - at Object.toEqual (/workspaces/baml/integ-tests/typescript/tests/integ-tests.test.ts:856:17)</pre></div></div></div></div></div></body></html> \ No newline at end of file + at Object.<anonymous> (/workspaces/baml/integ-tests/typescript/tests/integ-tests.test.ts:620:19)</pre></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic client</div><div class="test-status">passed</div><div class="test-duration">0.414s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with 'onLogEvent'</div><div class="test-status">passed</div><div class="test-duration">1.518s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with a sync client</div><div class="test-status">passed</div><div class="test-duration">0.621s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should raise an error when appropriate</div><div class="test-status">passed</div><div class="test-duration">1.07s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should raise a BAMLValidationError</div><div class="test-status">passed</div><div class="test-duration">0.369s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should reset environment variables correctly</div><div class="test-status">passed</div><div class="test-duration">1.53s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should use aliases when serializing input objects - classes</div><div class="test-status">passed</div><div class="test-duration">0.921s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should use aliases when serializing, but still have original keys in jinja</div><div class="test-status">passed</div><div class="test-duration">0.919s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should use aliases when serializing input objects - enums</div><div class="test-status">passed</div><div class="test-duration">0.808s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should use aliases when serializing input objects - lists</div><div class="test-status">passed</div><div class="test-duration">0.289s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">constraints: should handle checks in return types</div><div class="test-status">passed</div><div class="test-duration">0.651s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">constraints: should handle checks in returned unions</div><div class="test-status">passed</div><div class="test-duration">0.573s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">constraints: should handle block-level checks</div><div class="test-status">passed</div><div class="test-duration">0.648s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">constraints: should handle nested-block-level checks</div><div class="test-status">passed</div><div class="test-duration">0.543s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">simple recursive type</div><div class="test-status">passed</div><div class="test-duration">2.737s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">mutually recursive type</div><div class="test-status">passed</div><div class="test-duration">1.637s</div></div></div></div></div></div></body></html> \ No newline at end of file From 3e05de65e98af47b517b6acde8e4c5d11e429af1 Mon Sep 17 00:00:00 2001 From: Antonio Sarosi <sarosiantonio@gmail.com> Date: Thu, 28 Nov 2024 22:51:55 +0000 Subject: [PATCH 25/51] Test report --- integ-tests/typescript/test-report.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/integ-tests/typescript/test-report.html b/integ-tests/typescript/test-report.html index 249e518b0..93f836248 100644 --- a/integ-tests/typescript/test-report.html +++ b/integ-tests/typescript/test-report.html @@ -257,9 +257,9 @@ font-size: 1rem; padding: 0 0.5rem; } -</style></head><body><div class="jesthtml-content"><header><h1 id="title">Test Report</h1></header><div id="metadata-container"><div id="timestamp">Started: 2024-11-28 22:45:21</div><div id="summary"><div id="suite-summary"><div class="summary-total">Suites (1)</div><div class="summary-passed summary-empty">0 passed</div><div class="summary-failed ">1 failed</div><div class="summary-pending summary-empty">0 pending</div></div><div id="test-summary"><div class="summary-total">Tests (70)</div><div class="summary-passed ">68 passed</div><div class="summary-failed ">2 failed</div><div class="summary-pending summary-empty">0 pending</div></div></div></div><div id="suite-1" class="suite-container"><input id="collapsible-0" type="checkbox" class="toggle" checked="checked"/><label for="collapsible-0"><div class="suite-info"><div class="suite-path">/workspaces/baml/integ-tests/typescript/tests/integ-tests.test.ts</div><div class="suite-time warn">105.957s</div></div></label><div class="suite-tests"><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single bool</div><div class="test-status">passed</div><div class="test-duration">0.428s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single string list</div><div class="test-status">passed</div><div class="test-duration">0.385s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">return literal union</div><div class="test-status">passed</div><div class="test-duration">0.312s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single class</div><div class="test-status">passed</div><div class="test-duration">0.405s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">multiple classes</div><div class="test-status">passed</div><div class="test-duration">0.717s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single enum list</div><div class="test-status">passed</div><div class="test-duration">0.407s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single float</div><div class="test-status">passed</div><div class="test-duration">0.304s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single int</div><div class="test-status">passed</div><div class="test-duration">0.523s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single literal int</div><div class="test-status">passed</div><div class="test-duration">0.39s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single literal bool</div><div class="test-status">passed</div><div class="test-duration">0.281s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single literal string</div><div class="test-status">passed</div><div class="test-duration">0.343s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single class with literal prop</div><div class="test-status">passed</div><div class="test-duration">0.615s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single class with literal union prop</div><div class="test-status">passed</div><div class="test-duration">0.616s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single optional string</div><div class="test-status">passed</div><div class="test-duration">0.609s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single map string to string</div><div class="test-status">passed</div><div class="test-duration">0.641s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single map string to class</div><div class="test-status">passed</div><div class="test-duration">0.587s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single map string to map</div><div class="test-status">passed</div><div class="test-duration">0.427s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">enum key in map</div><div class="test-status">passed</div><div class="test-duration">0.64s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">literal string union key in map</div><div class="test-status">passed</div><div class="test-duration">0.777s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single literal string key in map</div><div class="test-status">passed</div><div class="test-duration">0.743s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">primitive union alias</div><div class="test-status">passed</div><div class="test-duration">0.46s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">map alias</div><div class="test-status">passed</div><div class="test-duration">0.845s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">alias union</div><div class="test-status">passed</div><div class="test-duration">1.529s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work for all outputs</div><div class="test-status">passed</div><div class="test-duration">4.278s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">works with retries1</div><div class="test-status">passed</div><div class="test-duration">1.074s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">works with retries2</div><div class="test-status">passed</div><div class="test-duration">2.274s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">works with fallbacks</div><div class="test-status">passed</div><div class="test-duration">2s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with image from url</div><div class="test-status">passed</div><div class="test-duration">1.742s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with image from base 64</div><div class="test-status">passed</div><div class="test-duration">1.229s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with audio base 64</div><div class="test-status">passed</div><div class="test-duration">1.536s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with audio from url</div><div class="test-status">passed</div><div class="test-duration">1.603s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support streaming in OpenAI</div><div class="test-status">passed</div><div class="test-duration">2.034s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support streaming in Gemini</div><div class="test-status">passed</div><div class="test-duration">10.13s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support AWS</div><div class="test-status">passed</div><div class="test-duration">1.694s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support streaming in AWS</div><div class="test-status">passed</div><div class="test-duration">1.526s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should allow overriding the region</div><div class="test-status">passed</div><div class="test-duration">0.011s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support OpenAI shorthand</div><div class="test-status">passed</div><div class="test-duration">14.026s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support OpenAI shorthand streaming</div><div class="test-status">passed</div><div class="test-duration">9.005s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support anthropic shorthand</div><div class="test-status">passed</div><div class="test-duration">3.033s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support anthropic shorthand streaming</div><div class="test-status">passed</div><div class="test-duration">3.136s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support streaming without iterating</div><div class="test-status">passed</div><div class="test-duration">1.992s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support streaming in Claude</div><div class="test-status">passed</div><div class="test-duration">1.027s</div></div></div><div class="test-result failed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support vertex</div><div class="test-status">failed</div><div class="test-duration">0.003s</div></div><div class="failureMessages"> <pre class="failureMsg">Error: BamlError: Failed to read service account file: +</style></head><body><div class="jesthtml-content"><header><h1 id="title">Test Report</h1></header><div id="metadata-container"><div id="timestamp">Started: 2024-11-28 22:49:40</div><div id="summary"><div id="suite-summary"><div class="summary-total">Suites (1)</div><div class="summary-passed summary-empty">0 passed</div><div class="summary-failed ">1 failed</div><div class="summary-pending summary-empty">0 pending</div></div><div id="test-summary"><div class="summary-total">Tests (70)</div><div class="summary-passed ">68 passed</div><div class="summary-failed ">2 failed</div><div class="summary-pending summary-empty">0 pending</div></div></div></div><div id="suite-1" class="suite-container"><input id="collapsible-0" type="checkbox" class="toggle" checked="checked"/><label for="collapsible-0"><div class="suite-info"><div class="suite-path">/workspaces/baml/integ-tests/typescript/tests/integ-tests.test.ts</div><div class="suite-time warn">106.322s</div></div></label><div class="suite-tests"><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single bool</div><div class="test-status">passed</div><div class="test-duration">0.336s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single string list</div><div class="test-status">passed</div><div class="test-duration">0.476s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">return literal union</div><div class="test-status">passed</div><div class="test-duration">0.354s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single class</div><div class="test-status">passed</div><div class="test-duration">0.363s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">multiple classes</div><div class="test-status">passed</div><div class="test-duration">0.437s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single enum list</div><div class="test-status">passed</div><div class="test-duration">0.482s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single float</div><div class="test-status">passed</div><div class="test-duration">0.388s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single int</div><div class="test-status">passed</div><div class="test-duration">0.327s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single literal int</div><div class="test-status">passed</div><div class="test-duration">0.842s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single literal bool</div><div class="test-status">passed</div><div class="test-duration">0.385s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single literal string</div><div class="test-status">passed</div><div class="test-duration">0.716s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single class with literal prop</div><div class="test-status">passed</div><div class="test-duration">0.443s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single class with literal union prop</div><div class="test-status">passed</div><div class="test-duration">0.522s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single optional string</div><div class="test-status">passed</div><div class="test-duration">0.276s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single map string to string</div><div class="test-status">passed</div><div class="test-duration">0.384s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single map string to class</div><div class="test-status">passed</div><div class="test-duration">0.631s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single map string to map</div><div class="test-status">passed</div><div class="test-duration">0.473s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">enum key in map</div><div class="test-status">passed</div><div class="test-duration">0.757s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">literal string union key in map</div><div class="test-status">passed</div><div class="test-duration">0.817s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single literal string key in map</div><div class="test-status">passed</div><div class="test-duration">0.612s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">primitive union alias</div><div class="test-status">passed</div><div class="test-duration">0.355s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">map alias</div><div class="test-status">passed</div><div class="test-duration">1.07s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">alias union</div><div class="test-status">passed</div><div class="test-duration">1.468s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work for all outputs</div><div class="test-status">passed</div><div class="test-duration">4.689s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">works with retries1</div><div class="test-status">passed</div><div class="test-duration">1.159s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">works with retries2</div><div class="test-status">passed</div><div class="test-duration">2.216s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">works with fallbacks</div><div class="test-status">passed</div><div class="test-duration">1.637s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with image from url</div><div class="test-status">passed</div><div class="test-duration">1.288s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with image from base 64</div><div class="test-status">passed</div><div class="test-duration">1.227s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with audio base 64</div><div class="test-status">passed</div><div class="test-duration">1.681s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with audio from url</div><div class="test-status">passed</div><div class="test-duration">1.7s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support streaming in OpenAI</div><div class="test-status">passed</div><div class="test-duration">1.834s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support streaming in Gemini</div><div class="test-status">passed</div><div class="test-duration">9.248s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support AWS</div><div class="test-status">passed</div><div class="test-duration">1.666s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support streaming in AWS</div><div class="test-status">passed</div><div class="test-duration">1.624s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should allow overriding the region</div><div class="test-status">passed</div><div class="test-duration">0.013s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support OpenAI shorthand</div><div class="test-status">passed</div><div class="test-duration">14.637s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support OpenAI shorthand streaming</div><div class="test-status">passed</div><div class="test-duration">13.789s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support anthropic shorthand</div><div class="test-status">passed</div><div class="test-duration">3.208s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support anthropic shorthand streaming</div><div class="test-status">passed</div><div class="test-duration">3.215s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support streaming without iterating</div><div class="test-status">passed</div><div class="test-duration">2.225s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support streaming in Claude</div><div class="test-status">passed</div><div class="test-duration">1.113s</div></div></div><div class="test-result failed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support vertex</div><div class="test-status">failed</div><div class="test-duration">0.002s</div></div><div class="failureMessages"> <pre class="failureMsg">Error: BamlError: Failed to read service account file: Caused by: - No such file or directory (os error 2)</pre></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">supports tracing sync</div><div class="test-status">passed</div><div class="test-duration">0.014s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">supports tracing async</div><div class="test-status">passed</div><div class="test-duration">1.783s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic types single</div><div class="test-status">passed</div><div class="test-duration">0.895s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic types enum</div><div class="test-status">passed</div><div class="test-duration">0.748s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic literals</div><div class="test-status">passed</div><div class="test-duration">0.785s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic types class</div><div class="test-status">passed</div><div class="test-duration">6.247s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic inputs class</div><div class="test-status">passed</div><div class="test-duration">0.508s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic inputs list</div><div class="test-status">passed</div><div class="test-duration">0.551s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic output map</div><div class="test-status">passed</div><div class="test-duration">0.685s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic output union</div><div class="test-status">passed</div><div class="test-duration">1.529s</div></div></div><div class="test-result failed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with nested classes</div><div class="test-status">failed</div><div class="test-duration">0.106s</div></div><div class="failureMessages"> <pre class="failureMsg">Error: BamlError: BamlClientError: Something went wrong with the LLM client: reqwest::Error { kind: Request, url: Url { scheme: "http", cannot_be_a_base: false, username: "", password: None, host: Some(Domain("localhost")), port: Some(11434), path: "/v1/chat/completions", query: None, fragment: None }, source: hyper_util::client::legacy::Error(Connect, ConnectError("tcp connect error", Os { code: 111, kind: ConnectionRefused, message: "Connection refused" })) } + No such file or directory (os error 2)</pre></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">supports tracing sync</div><div class="test-status">passed</div><div class="test-duration">0.011s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">supports tracing async</div><div class="test-status">passed</div><div class="test-duration">2.095s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic types single</div><div class="test-status">passed</div><div class="test-duration">0.966s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic types enum</div><div class="test-status">passed</div><div class="test-duration">1.019s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic literals</div><div class="test-status">passed</div><div class="test-duration">1.638s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic types class</div><div class="test-status">passed</div><div class="test-duration">0.822s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic inputs class</div><div class="test-status">passed</div><div class="test-duration">0.427s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic inputs list</div><div class="test-status">passed</div><div class="test-duration">0.45s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic output map</div><div class="test-status">passed</div><div class="test-duration">0.641s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic output union</div><div class="test-status">passed</div><div class="test-duration">1.652s</div></div></div><div class="test-result failed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with nested classes</div><div class="test-status">failed</div><div class="test-duration">0.113s</div></div><div class="failureMessages"> <pre class="failureMsg">Error: BamlError: BamlClientError: Something went wrong with the LLM client: reqwest::Error { kind: Request, url: Url { scheme: "http", cannot_be_a_base: false, username: "", password: None, host: Some(Domain("localhost")), port: Some(11434), path: "/v1/chat/completions", query: None, fragment: None }, source: hyper_util::client::legacy::Error(Connect, ConnectError("tcp connect error", Os { code: 111, kind: ConnectionRefused, message: "Connection refused" })) } at BamlStream.parsed [as getFinalResponse] (/workspaces/baml/engine/language_client_typescript/stream.js:58:39) - at Object.<anonymous> (/workspaces/baml/integ-tests/typescript/tests/integ-tests.test.ts:620:19)</pre></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic client</div><div class="test-status">passed</div><div class="test-duration">0.414s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with 'onLogEvent'</div><div class="test-status">passed</div><div class="test-duration">1.518s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with a sync client</div><div class="test-status">passed</div><div class="test-duration">0.621s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should raise an error when appropriate</div><div class="test-status">passed</div><div class="test-duration">1.07s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should raise a BAMLValidationError</div><div class="test-status">passed</div><div class="test-duration">0.369s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should reset environment variables correctly</div><div class="test-status">passed</div><div class="test-duration">1.53s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should use aliases when serializing input objects - classes</div><div class="test-status">passed</div><div class="test-duration">0.921s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should use aliases when serializing, but still have original keys in jinja</div><div class="test-status">passed</div><div class="test-duration">0.919s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should use aliases when serializing input objects - enums</div><div class="test-status">passed</div><div class="test-duration">0.808s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should use aliases when serializing input objects - lists</div><div class="test-status">passed</div><div class="test-duration">0.289s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">constraints: should handle checks in return types</div><div class="test-status">passed</div><div class="test-duration">0.651s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">constraints: should handle checks in returned unions</div><div class="test-status">passed</div><div class="test-duration">0.573s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">constraints: should handle block-level checks</div><div class="test-status">passed</div><div class="test-duration">0.648s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">constraints: should handle nested-block-level checks</div><div class="test-status">passed</div><div class="test-duration">0.543s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">simple recursive type</div><div class="test-status">passed</div><div class="test-duration">2.737s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">mutually recursive type</div><div class="test-status">passed</div><div class="test-duration">1.637s</div></div></div></div></div></div></body></html> \ No newline at end of file + at Object.<anonymous> (/workspaces/baml/integ-tests/typescript/tests/integ-tests.test.ts:620:19)</pre></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic client</div><div class="test-status">passed</div><div class="test-duration">0.602s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with 'onLogEvent'</div><div class="test-status">passed</div><div class="test-duration">1.941s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with a sync client</div><div class="test-status">passed</div><div class="test-duration">0.411s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should raise an error when appropriate</div><div class="test-status">passed</div><div class="test-duration">0.882s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should raise a BAMLValidationError</div><div class="test-status">passed</div><div class="test-duration">0.351s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should reset environment variables correctly</div><div class="test-status">passed</div><div class="test-duration">1.532s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should use aliases when serializing input objects - classes</div><div class="test-status">passed</div><div class="test-duration">0.92s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should use aliases when serializing, but still have original keys in jinja</div><div class="test-status">passed</div><div class="test-duration">0.718s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should use aliases when serializing input objects - enums</div><div class="test-status">passed</div><div class="test-duration">0.51s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should use aliases when serializing input objects - lists</div><div class="test-status">passed</div><div class="test-duration">0.307s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">constraints: should handle checks in return types</div><div class="test-status">passed</div><div class="test-duration">0.587s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">constraints: should handle checks in returned unions</div><div class="test-status">passed</div><div class="test-duration">0.65s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">constraints: should handle block-level checks</div><div class="test-status">passed</div><div class="test-duration">0.417s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">constraints: should handle nested-block-level checks</div><div class="test-status">passed</div><div class="test-duration">0.6s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">simple recursive type</div><div class="test-status">passed</div><div class="test-duration">2.764s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">mutually recursive type</div><div class="test-status">passed</div><div class="test-duration">1.541s</div></div></div></div></div></div></body></html> \ No newline at end of file From 384190bdd5e6ccc49846707e8919abbfe8600fa8 Mon Sep 17 00:00:00 2001 From: Antonio Sarosi <sarosiantonio@gmail.com> Date: Thu, 28 Nov 2024 23:19:50 +0000 Subject: [PATCH 26/51] Add more recursive tests --- .../class/recursive_type_aliases.baml | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/engine/baml-lib/baml/tests/validation_files/class/recursive_type_aliases.baml b/engine/baml-lib/baml/tests/validation_files/class/recursive_type_aliases.baml index 66bda4f61..67143ebe5 100644 --- a/engine/baml-lib/baml/tests/validation_files/class/recursive_type_aliases.baml +++ b/engine/baml-lib/baml/tests/validation_files/class/recursive_type_aliases.baml @@ -26,6 +26,22 @@ class Recursive { type RecAlias = Recursive +// Same but finite. +class FiniteRecursive { + value int + ptr FiniteRecAlias? +} + +type FiniteRecAlias = FiniteRecursive + +// Move the "finite" condition to the alias itself. Should still work. +class RecursiveWithOptionalAlias { + value int + ptr RecOptionalAlias +} + +type RecOptionalAlias = RecursiveWithOptionalAlias? + // Class that points to alias which enters infinite cycle. class InfiniteCycle { value int @@ -49,10 +65,10 @@ type NoStop = EnterCycle // 15 | type A = B // | // error: Error validating: These aliases form a dependency cycle: EnterCycle -> NoStop -// --> class/recursive_type_aliases.baml:35 +// --> class/recursive_type_aliases.baml:51 // | -// 34 | -// 35 | type EnterCycle = NoStop +// 50 | +// 51 | type EnterCycle = NoStop // | // error: Error validating: These classes form a dependency cycle: Recursive // --> class/recursive_type_aliases.baml:22 From 0577796928cc05ccadaf68b78ab76fd3231f85b5 Mon Sep 17 00:00:00 2001 From: Antonio Sarosi <sarosiantonio@gmail.com> Date: Fri, 29 Nov 2024 00:06:54 +0000 Subject: [PATCH 27/51] Add some tests for type resolution --- engine/baml-lib/parser-database/src/lib.rs | 62 ++++++++++++++++++- .../parser-database/src/walkers/mod.rs | 8 +++ 2 files changed, 67 insertions(+), 3 deletions(-) diff --git a/engine/baml-lib/parser-database/src/lib.rs b/engine/baml-lib/parser-database/src/lib.rs index 56b7dc10d..4666df2ca 100644 --- a/engine/baml-lib/parser-database/src/lib.rs +++ b/engine/baml-lib/parser-database/src/lib.rs @@ -44,8 +44,8 @@ use internal_baml_schema_ast::ast::{FieldType, SchemaAst, WithName}; pub use tarjan::Tarjan; use types::resolve_type_alias; pub use types::{ - Attributes, ContantDelayStrategy, ExponentialBackoffStrategy, PrinterType, PromptAst, - PromptVariable, RetryPolicy, RetryPolicyStrategy, StaticType, ClientProperties + Attributes, ClientProperties, ContantDelayStrategy, ExponentialBackoffStrategy, PrinterType, + PromptAst, PromptVariable, RetryPolicy, RetryPolicyStrategy, StaticType, }; pub use walkers::TypeWalker; @@ -288,10 +288,12 @@ mod test { use std::path::PathBuf; use super::*; + use ast::FieldArity; + use baml_types::TypeValue; use internal_baml_diagnostics::{Diagnostics, SourceFile}; use internal_baml_schema_ast::parse_schema; - fn assert_finite_cycles(baml: &'static str, expected: &[&[&str]]) -> Result<(), Diagnostics> { + fn parse(baml: &'static str) -> Result<ParserDatabase, Diagnostics> { let mut db = ParserDatabase::new(); let source = SourceFile::new_static(PathBuf::from("test.baml"), baml); let (ast, mut diag) = parse_schema(&source.path_buf(), &source)?; @@ -300,6 +302,14 @@ mod test { db.validate(&mut diag)?; db.finalize(&mut diag); + diag.to_result()?; + + Ok(db) + } + + fn assert_finite_cycles(baml: &'static str, expected: &[&[&str]]) -> Result<(), Diagnostics> { + let mut db = parse(baml)?; + assert_eq!( db.finite_recursive_cycles() .iter() @@ -526,4 +536,50 @@ mod test { &[&["RecMap"]], ) } + + #[test] + fn resolve_simple_alias() -> Result<(), Diagnostics> { + let db = parse("type Number = int")?; + + assert!(matches!( + db.resolved_type_alias_by_name("Number").unwrap(), + FieldType::Primitive(FieldArity::Required, TypeValue::Int, _, _) + )); + + Ok(()) + } + + #[test] + fn resolve_multiple_levels_of_aliases() -> Result<(), Diagnostics> { + #[rustfmt::skip] + let db = parse(r#" + type One = string + type Two = One + type Three = Two + type Four = Three + "#)?; + + assert!(matches!( + db.resolved_type_alias_by_name("Four").unwrap(), + FieldType::Primitive(FieldArity::Required, TypeValue::String, _, _) + )); + + Ok(()) + } + + #[test] + fn sync_alias_arity() -> Result<(), Diagnostics> { + #[rustfmt::skip] + let db = parse(r#" + type Required = float + type Optional = Required? + "#)?; + + assert!(matches!( + db.resolved_type_alias_by_name("Optional").unwrap(), + FieldType::Primitive(FieldArity::Optional, TypeValue::Float, _, _) + )); + + Ok(()) + } } diff --git a/engine/baml-lib/parser-database/src/walkers/mod.rs b/engine/baml-lib/parser-database/src/walkers/mod.rs index 61e84267f..ae705b111 100644 --- a/engine/baml-lib/parser-database/src/walkers/mod.rs +++ b/engine/baml-lib/parser-database/src/walkers/mod.rs @@ -142,6 +142,14 @@ impl<'db> crate::ParserDatabase { &self.types.finite_recursive_cycles } + /// Returns the resolved aliases map. + pub fn resolved_type_alias_by_name(&self, alias: &str) -> Option<&FieldType> { + match self.find_type_by_str(alias) { + Some(TypeWalker::TypeAlias(walker)) => Some(walker.resolved()), + _ => None, + } + } + /// Traverse a schema element by id. pub fn walk<I>(&self, id: I) -> Walker<'_, I> { Walker { db: self, id } From 3fc435cc1128f4a404d47d628db77bec377565cd Mon Sep 17 00:00:00 2001 From: Antonio Sarosi <sarosiantonio@gmail.com> Date: Sat, 30 Nov 2024 17:54:28 +0100 Subject: [PATCH 28/51] Fix arity syncing bug --- engine/baml-lib/parser-database/src/types/mod.rs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/engine/baml-lib/parser-database/src/types/mod.rs b/engine/baml-lib/parser-database/src/types/mod.rs index 6d5628084..4b9dd23f2 100644 --- a/engine/baml-lib/parser-database/src/types/mod.rs +++ b/engine/baml-lib/parser-database/src/types/mod.rs @@ -406,13 +406,12 @@ pub fn resolve_type_alias(field_type: &FieldType, db: &ParserDatabase) -> FieldT match top_id { ast::TopId::TypeAlias(alias_id) => { - // Check if we can avoid deeper recursion. - if let Some(resolved) = db.types.resolved_type_aliases.get(alias_id) { - return resolved.to_owned(); - } - - // Recurse... - let resolved = resolve_type_alias(&db.ast[*alias_id].value, db); + let resolved = match db.types.resolved_type_aliases.get(alias_id) { + // Check if we can avoid deeper recursion. + Some(already_resolved) => already_resolved.to_owned(), + // No luck, recurse. + None => resolve_type_alias(&db.ast[*alias_id].value, db), + }; // Sync arity. Basically stuff like: // From db25404e77ba075559e218bb5875af90cf557233 Mon Sep 17 00:00:00 2001 From: Antonio Sarosi <sarosiantonio@gmail.com> Date: Sat, 30 Nov 2024 18:44:16 +0100 Subject: [PATCH 29/51] Run integ tests TS --- integ-tests/typescript/test-report.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/integ-tests/typescript/test-report.html b/integ-tests/typescript/test-report.html index 93f836248..857e89bd6 100644 --- a/integ-tests/typescript/test-report.html +++ b/integ-tests/typescript/test-report.html @@ -257,9 +257,9 @@ font-size: 1rem; padding: 0 0.5rem; } -</style></head><body><div class="jesthtml-content"><header><h1 id="title">Test Report</h1></header><div id="metadata-container"><div id="timestamp">Started: 2024-11-28 22:49:40</div><div id="summary"><div id="suite-summary"><div class="summary-total">Suites (1)</div><div class="summary-passed summary-empty">0 passed</div><div class="summary-failed ">1 failed</div><div class="summary-pending summary-empty">0 pending</div></div><div id="test-summary"><div class="summary-total">Tests (70)</div><div class="summary-passed ">68 passed</div><div class="summary-failed ">2 failed</div><div class="summary-pending summary-empty">0 pending</div></div></div></div><div id="suite-1" class="suite-container"><input id="collapsible-0" type="checkbox" class="toggle" checked="checked"/><label for="collapsible-0"><div class="suite-info"><div class="suite-path">/workspaces/baml/integ-tests/typescript/tests/integ-tests.test.ts</div><div class="suite-time warn">106.322s</div></div></label><div class="suite-tests"><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single bool</div><div class="test-status">passed</div><div class="test-duration">0.336s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single string list</div><div class="test-status">passed</div><div class="test-duration">0.476s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">return literal union</div><div class="test-status">passed</div><div class="test-duration">0.354s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single class</div><div class="test-status">passed</div><div class="test-duration">0.363s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">multiple classes</div><div class="test-status">passed</div><div class="test-duration">0.437s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single enum list</div><div class="test-status">passed</div><div class="test-duration">0.482s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single float</div><div class="test-status">passed</div><div class="test-duration">0.388s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single int</div><div class="test-status">passed</div><div class="test-duration">0.327s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single literal int</div><div class="test-status">passed</div><div class="test-duration">0.842s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single literal bool</div><div class="test-status">passed</div><div class="test-duration">0.385s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single literal string</div><div class="test-status">passed</div><div class="test-duration">0.716s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single class with literal prop</div><div class="test-status">passed</div><div class="test-duration">0.443s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single class with literal union prop</div><div class="test-status">passed</div><div class="test-duration">0.522s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single optional string</div><div class="test-status">passed</div><div class="test-duration">0.276s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single map string to string</div><div class="test-status">passed</div><div class="test-duration">0.384s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single map string to class</div><div class="test-status">passed</div><div class="test-duration">0.631s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single map string to map</div><div class="test-status">passed</div><div class="test-duration">0.473s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">enum key in map</div><div class="test-status">passed</div><div class="test-duration">0.757s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">literal string union key in map</div><div class="test-status">passed</div><div class="test-duration">0.817s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single literal string key in map</div><div class="test-status">passed</div><div class="test-duration">0.612s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">primitive union alias</div><div class="test-status">passed</div><div class="test-duration">0.355s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">map alias</div><div class="test-status">passed</div><div class="test-duration">1.07s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">alias union</div><div class="test-status">passed</div><div class="test-duration">1.468s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work for all outputs</div><div class="test-status">passed</div><div class="test-duration">4.689s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">works with retries1</div><div class="test-status">passed</div><div class="test-duration">1.159s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">works with retries2</div><div class="test-status">passed</div><div class="test-duration">2.216s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">works with fallbacks</div><div class="test-status">passed</div><div class="test-duration">1.637s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with image from url</div><div class="test-status">passed</div><div class="test-duration">1.288s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with image from base 64</div><div class="test-status">passed</div><div class="test-duration">1.227s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with audio base 64</div><div class="test-status">passed</div><div class="test-duration">1.681s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with audio from url</div><div class="test-status">passed</div><div class="test-duration">1.7s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support streaming in OpenAI</div><div class="test-status">passed</div><div class="test-duration">1.834s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support streaming in Gemini</div><div class="test-status">passed</div><div class="test-duration">9.248s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support AWS</div><div class="test-status">passed</div><div class="test-duration">1.666s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support streaming in AWS</div><div class="test-status">passed</div><div class="test-duration">1.624s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should allow overriding the region</div><div class="test-status">passed</div><div class="test-duration">0.013s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support OpenAI shorthand</div><div class="test-status">passed</div><div class="test-duration">14.637s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support OpenAI shorthand streaming</div><div class="test-status">passed</div><div class="test-duration">13.789s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support anthropic shorthand</div><div class="test-status">passed</div><div class="test-duration">3.208s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support anthropic shorthand streaming</div><div class="test-status">passed</div><div class="test-duration">3.215s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support streaming without iterating</div><div class="test-status">passed</div><div class="test-duration">2.225s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support streaming in Claude</div><div class="test-status">passed</div><div class="test-duration">1.113s</div></div></div><div class="test-result failed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support vertex</div><div class="test-status">failed</div><div class="test-duration">0.002s</div></div><div class="failureMessages"> <pre class="failureMsg">Error: BamlError: Failed to read service account file: +</style></head><body><div class="jesthtml-content"><header><h1 id="title">Test Report</h1></header><div id="metadata-container"><div id="timestamp">Started: 2024-11-30 17:41:18</div><div id="summary"><div id="suite-summary"><div class="summary-total">Suites (1)</div><div class="summary-passed summary-empty">0 passed</div><div class="summary-failed ">1 failed</div><div class="summary-pending summary-empty">0 pending</div></div><div id="test-summary"><div class="summary-total">Tests (70)</div><div class="summary-passed ">68 passed</div><div class="summary-failed ">2 failed</div><div class="summary-pending summary-empty">0 pending</div></div></div></div><div id="suite-1" class="suite-container"><input id="collapsible-0" type="checkbox" class="toggle" checked="checked"/><label for="collapsible-0"><div class="suite-info"><div class="suite-path">/workspaces/baml/integ-tests/typescript/tests/integ-tests.test.ts</div><div class="suite-time warn">117.765s</div></div></label><div class="suite-tests"><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single bool</div><div class="test-status">passed</div><div class="test-duration">1.768s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single string list</div><div class="test-status">passed</div><div class="test-duration">0.466s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">return literal union</div><div class="test-status">passed</div><div class="test-duration">0.658s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single class</div><div class="test-status">passed</div><div class="test-duration">0.515s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">multiple classes</div><div class="test-status">passed</div><div class="test-duration">0.513s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single enum list</div><div class="test-status">passed</div><div class="test-duration">0.496s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single float</div><div class="test-status">passed</div><div class="test-duration">0.725s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single int</div><div class="test-status">passed</div><div class="test-duration">0.716s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single literal int</div><div class="test-status">passed</div><div class="test-duration">0.512s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single literal bool</div><div class="test-status">passed</div><div class="test-duration">0.405s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single literal string</div><div class="test-status">passed</div><div class="test-duration">0.483s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single class with literal prop</div><div class="test-status">passed</div><div class="test-duration">1.135s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single class with literal union prop</div><div class="test-status">passed</div><div class="test-duration">0.967s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single optional string</div><div class="test-status">passed</div><div class="test-duration">0.532s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single map string to string</div><div class="test-status">passed</div><div class="test-duration">0.773s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single map string to class</div><div class="test-status">passed</div><div class="test-duration">0.78s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single map string to map</div><div class="test-status">passed</div><div class="test-duration">0.63s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">enum key in map</div><div class="test-status">passed</div><div class="test-duration">0.758s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">literal string union key in map</div><div class="test-status">passed</div><div class="test-duration">0.7s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single literal string key in map</div><div class="test-status">passed</div><div class="test-duration">0.915s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">primitive union alias</div><div class="test-status">passed</div><div class="test-duration">0.621s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">map alias</div><div class="test-status">passed</div><div class="test-duration">0.821s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">alias union</div><div class="test-status">passed</div><div class="test-duration">1.688s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work for all outputs</div><div class="test-status">passed</div><div class="test-duration">5.447s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">works with retries1</div><div class="test-status">passed</div><div class="test-duration">1.967s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">works with retries2</div><div class="test-status">passed</div><div class="test-duration">2.874s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">works with fallbacks</div><div class="test-status">passed</div><div class="test-duration">2.354s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with image from url</div><div class="test-status">passed</div><div class="test-duration">1.435s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with image from base 64</div><div class="test-status">passed</div><div class="test-duration">1.742s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with audio base 64</div><div class="test-status">passed</div><div class="test-duration">1.839s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with audio from url</div><div class="test-status">passed</div><div class="test-duration">2.048s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support streaming in OpenAI</div><div class="test-status">passed</div><div class="test-duration">2.222s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support streaming in Gemini</div><div class="test-status">passed</div><div class="test-duration">9.431s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support AWS</div><div class="test-status">passed</div><div class="test-duration">1.86s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support streaming in AWS</div><div class="test-status">passed</div><div class="test-duration">1.821s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should allow overriding the region</div><div class="test-status">passed</div><div class="test-duration">0.104s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support OpenAI shorthand</div><div class="test-status">passed</div><div class="test-duration">17.882s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support OpenAI shorthand streaming</div><div class="test-status">passed</div><div class="test-duration">7.083s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support anthropic shorthand</div><div class="test-status">passed</div><div class="test-duration">2.549s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support anthropic shorthand streaming</div><div class="test-status">passed</div><div class="test-duration">2.43s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support streaming without iterating</div><div class="test-status">passed</div><div class="test-duration">2.498s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support streaming in Claude</div><div class="test-status">passed</div><div class="test-duration">1.023s</div></div></div><div class="test-result failed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support vertex</div><div class="test-status">failed</div><div class="test-duration">0.001s</div></div><div class="failureMessages"> <pre class="failureMsg">Error: BamlError: Failed to read service account file: Caused by: - No such file or directory (os error 2)</pre></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">supports tracing sync</div><div class="test-status">passed</div><div class="test-duration">0.011s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">supports tracing async</div><div class="test-status">passed</div><div class="test-duration">2.095s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic types single</div><div class="test-status">passed</div><div class="test-duration">0.966s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic types enum</div><div class="test-status">passed</div><div class="test-duration">1.019s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic literals</div><div class="test-status">passed</div><div class="test-duration">1.638s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic types class</div><div class="test-status">passed</div><div class="test-duration">0.822s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic inputs class</div><div class="test-status">passed</div><div class="test-duration">0.427s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic inputs list</div><div class="test-status">passed</div><div class="test-duration">0.45s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic output map</div><div class="test-status">passed</div><div class="test-duration">0.641s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic output union</div><div class="test-status">passed</div><div class="test-duration">1.652s</div></div></div><div class="test-result failed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with nested classes</div><div class="test-status">failed</div><div class="test-duration">0.113s</div></div><div class="failureMessages"> <pre class="failureMsg">Error: BamlError: BamlClientError: Something went wrong with the LLM client: reqwest::Error { kind: Request, url: Url { scheme: "http", cannot_be_a_base: false, username: "", password: None, host: Some(Domain("localhost")), port: Some(11434), path: "/v1/chat/completions", query: None, fragment: None }, source: hyper_util::client::legacy::Error(Connect, ConnectError("tcp connect error", Os { code: 111, kind: ConnectionRefused, message: "Connection refused" })) } + No such file or directory (os error 2)</pre></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">supports tracing sync</div><div class="test-status">passed</div><div class="test-duration">0.006s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">supports tracing async</div><div class="test-status">passed</div><div class="test-duration">3.518s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic types single</div><div class="test-status">passed</div><div class="test-duration">1.019s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic types enum</div><div class="test-status">passed</div><div class="test-duration">1.078s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic literals</div><div class="test-status">passed</div><div class="test-duration">0.969s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic types class</div><div class="test-status">passed</div><div class="test-duration">1.229s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic inputs class</div><div class="test-status">passed</div><div class="test-duration">0.608s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic inputs list</div><div class="test-status">passed</div><div class="test-duration">0.574s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic output map</div><div class="test-status">passed</div><div class="test-duration">0.76s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic output union</div><div class="test-status">passed</div><div class="test-duration">1.787s</div></div></div><div class="test-result failed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with nested classes</div><div class="test-status">failed</div><div class="test-duration">0.108s</div></div><div class="failureMessages"> <pre class="failureMsg">Error: BamlError: BamlClientError: Something went wrong with the LLM client: reqwest::Error { kind: Request, url: Url { scheme: "http", cannot_be_a_base: false, username: "", password: None, host: Some(Domain("localhost")), port: Some(11434), path: "/v1/chat/completions", query: None, fragment: None }, source: hyper_util::client::legacy::Error(Connect, ConnectError("tcp connect error", Os { code: 111, kind: ConnectionRefused, message: "Connection refused" })) } at BamlStream.parsed [as getFinalResponse] (/workspaces/baml/engine/language_client_typescript/stream.js:58:39) - at Object.<anonymous> (/workspaces/baml/integ-tests/typescript/tests/integ-tests.test.ts:620:19)</pre></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic client</div><div class="test-status">passed</div><div class="test-duration">0.602s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with 'onLogEvent'</div><div class="test-status">passed</div><div class="test-duration">1.941s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with a sync client</div><div class="test-status">passed</div><div class="test-duration">0.411s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should raise an error when appropriate</div><div class="test-status">passed</div><div class="test-duration">0.882s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should raise a BAMLValidationError</div><div class="test-status">passed</div><div class="test-duration">0.351s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should reset environment variables correctly</div><div class="test-status">passed</div><div class="test-duration">1.532s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should use aliases when serializing input objects - classes</div><div class="test-status">passed</div><div class="test-duration">0.92s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should use aliases when serializing, but still have original keys in jinja</div><div class="test-status">passed</div><div class="test-duration">0.718s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should use aliases when serializing input objects - enums</div><div class="test-status">passed</div><div class="test-duration">0.51s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should use aliases when serializing input objects - lists</div><div class="test-status">passed</div><div class="test-duration">0.307s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">constraints: should handle checks in return types</div><div class="test-status">passed</div><div class="test-duration">0.587s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">constraints: should handle checks in returned unions</div><div class="test-status">passed</div><div class="test-duration">0.65s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">constraints: should handle block-level checks</div><div class="test-status">passed</div><div class="test-duration">0.417s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">constraints: should handle nested-block-level checks</div><div class="test-status">passed</div><div class="test-duration">0.6s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">simple recursive type</div><div class="test-status">passed</div><div class="test-duration">2.764s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">mutually recursive type</div><div class="test-status">passed</div><div class="test-duration">1.541s</div></div></div></div></div></div></body></html> \ No newline at end of file + at Object.<anonymous> (/workspaces/baml/integ-tests/typescript/tests/integ-tests.test.ts:620:19)</pre></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic client</div><div class="test-status">passed</div><div class="test-duration">0.609s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with 'onLogEvent'</div><div class="test-status">passed</div><div class="test-duration">2.471s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with a sync client</div><div class="test-status">passed</div><div class="test-duration">0.551s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should raise an error when appropriate</div><div class="test-status">passed</div><div class="test-duration">1.458s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should raise a BAMLValidationError</div><div class="test-status">passed</div><div class="test-duration">0.509s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should reset environment variables correctly</div><div class="test-status">passed</div><div class="test-duration">2.135s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should use aliases when serializing input objects - classes</div><div class="test-status">passed</div><div class="test-duration">1.223s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should use aliases when serializing, but still have original keys in jinja</div><div class="test-status">passed</div><div class="test-duration">1.264s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should use aliases when serializing input objects - enums</div><div class="test-status">passed</div><div class="test-duration">0.853s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should use aliases when serializing input objects - lists</div><div class="test-status">passed</div><div class="test-duration">0.543s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">constraints: should handle checks in return types</div><div class="test-status">passed</div><div class="test-duration">0.72s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">constraints: should handle checks in returned unions</div><div class="test-status">passed</div><div class="test-duration">0.754s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">constraints: should handle block-level checks</div><div class="test-status">passed</div><div class="test-duration">0.673s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">constraints: should handle nested-block-level checks</div><div class="test-status">passed</div><div class="test-duration">0.623s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">simple recursive type</div><div class="test-status">passed</div><div class="test-duration">2.759s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">mutually recursive type</div><div class="test-status">passed</div><div class="test-duration">1.802s</div></div></div></div></div></div></body></html> \ No newline at end of file From fbd9309ca6ea5026079626339d40da93b511f25d Mon Sep 17 00:00:00 2001 From: Antonio Sarosi <sarosiantonio@gmail.com> Date: Sat, 30 Nov 2024 23:56:53 +0100 Subject: [PATCH 30/51] Add test for alias that points to recursive class --- .../class/recursive_type_aliases.baml | 9 + .../output/recursive-type-aliases.baml | 18 + .../python/baml_client/async_client.py | 53 + integ-tests/python/baml_client/inlinedbaml.py | 1 + .../python/baml_client/partial_types.py | 4 + integ-tests/python/baml_client/sync_client.py | 53 + .../python/baml_client/type_builder.py | 2 +- integ-tests/python/baml_client/types.py | 4 + integ-tests/python/tests/test_functions.py | 8 + integ-tests/ruby/baml_client/client.rb | 67 + integ-tests/ruby/baml_client/inlined.rb | 1 + integ-tests/ruby/baml_client/partial-types.rb | 15 + integ-tests/ruby/baml_client/type-registry.rb | 2 +- integ-tests/ruby/baml_client/types.rb | 15 + integ-tests/ruby/test_functions.rb | 3 + .../typescript/baml_client/async_client.ts | 60 +- .../typescript/baml_client/inlinedbaml.ts | 1 + .../typescript/baml_client/sync_client.ts | 27 +- .../typescript/baml_client/type_builder.ts | 2 +- integ-tests/typescript/baml_client/types.ts | 6 + integ-tests/typescript/test-report.html | 1180 ++++------------- .../typescript/tests/integ-tests.test.ts | 5 + 22 files changed, 611 insertions(+), 925 deletions(-) create mode 100644 integ-tests/baml_src/test-files/functions/output/recursive-type-aliases.baml diff --git a/engine/baml-lib/baml/tests/validation_files/class/recursive_type_aliases.baml b/engine/baml-lib/baml/tests/validation_files/class/recursive_type_aliases.baml index 67143ebe5..a200c3293 100644 --- a/engine/baml-lib/baml/tests/validation_files/class/recursive_type_aliases.baml +++ b/engine/baml-lib/baml/tests/validation_files/class/recursive_type_aliases.baml @@ -52,6 +52,9 @@ type EnterCycle = NoStop type NoStop = EnterCycle +// RecursiveMap +type Map = map<string, Map> + // error: Error validating: These aliases form a dependency cycle: One -> Two // --> class/recursive_type_aliases.baml:10 // | @@ -70,6 +73,12 @@ type NoStop = EnterCycle // 50 | // 51 | type EnterCycle = NoStop // | +// error: Error validating: These aliases form a dependency cycle: Map +// --> class/recursive_type_aliases.baml:56 +// | +// 55 | // RecursiveMap +// 56 | type Map = map<string, Map> +// | // error: Error validating: These classes form a dependency cycle: Recursive // --> class/recursive_type_aliases.baml:22 // | diff --git a/integ-tests/baml_src/test-files/functions/output/recursive-type-aliases.baml b/integ-tests/baml_src/test-files/functions/output/recursive-type-aliases.baml new file mode 100644 index 000000000..3446f6423 --- /dev/null +++ b/integ-tests/baml_src/test-files/functions/output/recursive-type-aliases.baml @@ -0,0 +1,18 @@ +// Simple alias that points to recursive type. +class LinkedListAliasNode { + value int + next LinkedListAliasNode? +} + +type LinkedListAlias = LinkedListAliasNode + +function AliasThatPointsToRecursiveType(list: LinkedListAlias) -> LinkedListAlias { + client "openai/gpt-4o" + prompt r#" + Return the given linked list back: + + {{ list }} + + {{ ctx.output_format }} + "# +} diff --git a/integ-tests/python/baml_client/async_client.py b/integ-tests/python/baml_client/async_client.py index 3ba79e240..4f542e68e 100644 --- a/integ-tests/python/baml_client/async_client.py +++ b/integ-tests/python/baml_client/async_client.py @@ -73,6 +73,29 @@ async def AaaSamOutputFormat( ) return cast(types.Recipe, raw.cast_to(types, types)) + async def AliasThatPointsToRecursiveType( + self, + list: types.LinkedListAliasNode, + baml_options: BamlCallOptions = {}, + ) -> types.LinkedListAliasNode: + __tb__ = baml_options.get("tb", None) + if __tb__ is not None: + tb = __tb__._tb # type: ignore (we know how to use this private attribute) + else: + tb = None + __cr__ = baml_options.get("client_registry", None) + + raw = await self.__runtime.call_function( + "AliasThatPointsToRecursiveType", + { + "list": list, + }, + self.__ctx_manager.get(), + tb, + __cr__, + ) + return cast(types.LinkedListAliasNode, raw.cast_to(types, types)) + async def AliasedInputClass( self, input: types.InputClass, @@ -2874,6 +2897,36 @@ def AaaSamOutputFormat( self.__ctx_manager.get(), ) + def AliasThatPointsToRecursiveType( + self, + list: types.LinkedListAliasNode, + baml_options: BamlCallOptions = {}, + ) -> baml_py.BamlStream[partial_types.LinkedListAliasNode, types.LinkedListAliasNode]: + __tb__ = baml_options.get("tb", None) + if __tb__ is not None: + tb = __tb__._tb # type: ignore (we know how to use this private attribute) + else: + tb = None + __cr__ = baml_options.get("client_registry", None) + + raw = self.__runtime.stream_function( + "AliasThatPointsToRecursiveType", + { + "list": list, + }, + None, + self.__ctx_manager.get(), + tb, + __cr__, + ) + + return baml_py.BamlStream[partial_types.LinkedListAliasNode, types.LinkedListAliasNode]( + raw, + lambda x: cast(partial_types.LinkedListAliasNode, x.cast_to(types, partial_types)), + lambda x: cast(types.LinkedListAliasNode, x.cast_to(types, types)), + self.__ctx_manager.get(), + ) + def AliasedInputClass( self, input: types.InputClass, diff --git a/integ-tests/python/baml_client/inlinedbaml.py b/integ-tests/python/baml_client/inlinedbaml.py index 224074c15..3f1663522 100644 --- a/integ-tests/python/baml_client/inlinedbaml.py +++ b/integ-tests/python/baml_client/inlinedbaml.py @@ -80,6 +80,7 @@ "test-files/functions/output/optional-class.baml": "class ClassOptionalOutput {\n prop1 string\n prop2 string\n}\n\nfunction FnClassOptionalOutput(input: string) -> ClassOptionalOutput? {\n client GPT35\n prompt #\"\n Return a json blob for the following input:\n {{input}}\n\n {{ctx.output_format}}\n\n JSON:\n \"#\n}\n\n\nclass Blah {\n prop4 string?\n}\n\nclass ClassOptionalOutput2 {\n prop1 string?\n prop2 string?\n prop3 Blah?\n}\n\nfunction FnClassOptionalOutput2(input: string) -> ClassOptionalOutput2? {\n client GPT35\n prompt #\"\n Return a json blob for the following input:\n {{input}}\n\n {{ctx.output_format}}\n\n JSON:\n \"#\n}\n\ntest FnClassOptionalOutput2 {\n functions [FnClassOptionalOutput2, FnClassOptionalOutput]\n args {\n input \"example input\"\n }\n}\n", "test-files/functions/output/optional.baml": "class OptionalTest_Prop1 {\n omega_a string\n omega_b int\n}\n\nenum OptionalTest_CategoryType {\n Aleph\n Beta\n Gamma\n}\n \nclass OptionalTest_ReturnType {\n omega_1 OptionalTest_Prop1?\n omega_2 string?\n omega_3 (OptionalTest_CategoryType?)[]\n} \n \nfunction OptionalTest_Function(input: string) -> (OptionalTest_ReturnType?)[]\n{ \n client GPT35\n prompt #\"\n Return a JSON blob with this schema: \n {{ctx.output_format}}\n\n JSON:\n \"#\n}\n\ntest OptionalTest_Function {\n functions [OptionalTest_Function]\n args {\n input \"example input\"\n }\n}\n", "test-files/functions/output/recursive-class.baml": "class Node {\n data int\n next Node?\n}\n\nclass LinkedList {\n head Node?\n len int\n}\n\nclient<llm> O1 {\n provider \"openai\"\n options {\n model \"o1-mini\"\n default_role \"user\"\n }\n}\n\nfunction BuildLinkedList(input: int[]) -> LinkedList {\n client O1\n prompt #\"\n Build a linked list from the input array of integers.\n\n INPUT:\n {{ input }}\n\n {{ ctx.output_format }} \n \"#\n}\n\ntest TestLinkedList {\n functions [BuildLinkedList]\n args {\n input [1, 2, 3, 4, 5]\n }\n}\n", + "test-files/functions/output/recursive-type-aliases.baml": "// Simple alias that points to recursive type.\nclass LinkedListAliasNode {\n value int\n next LinkedListAliasNode?\n}\n\ntype LinkedListAlias = LinkedListAliasNode\n\nfunction AliasThatPointsToRecursiveType(list: LinkedListAlias) -> LinkedListAlias {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given linked list back:\n \n {{ list }}\n \n {{ ctx.output_format }}\n \"#\n}\n", "test-files/functions/output/serialization-error.baml": "class DummyOutput {\n nonce string\n nonce2 string\n @@dynamic\n}\n\nfunction DummyOutputFunction(input: string) -> DummyOutput {\n client GPT35\n prompt #\"\n Say \"hello there\".\n \"#\n}", "test-files/functions/output/string-list.baml": "function FnOutputStringList(input: string) -> string[] {\n client GPT35\n prompt #\"\n Return a list of strings in json format like [\"string1\", \"string2\", \"string3\"].\n\n JSON:\n \"#\n}\n\ntest FnOutputStringList {\n functions [FnOutputStringList]\n args {\n input \"example input\"\n }\n}\n", "test-files/functions/output/type-aliases.baml": "type Primitive = int | string | bool | float\n\ntype List = string[]\n\ntype Graph = map<string, string[]>\n\ntype Combination = Primitive | List | Graph\n\nfunction PrimitiveAlias(p: Primitive) -> Primitive {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given value back: {{ p }}\n \"#\n}\n\nfunction MapAlias(m: Graph) -> Graph {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given Graph back:\n\n {{ m }}\n\n {{ ctx.output_format }}\n \"#\n}\n\nfunction NestedAlias(c: Combination) -> Combination {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given value back:\n\n {{ c }}\n\n {{ ctx.output_format }}\n \"#\n}\n", diff --git a/integ-tests/python/baml_client/partial_types.py b/integ-tests/python/baml_client/partial_types.py index d7c66c8f3..67465e54d 100644 --- a/integ-tests/python/baml_client/partial_types.py +++ b/integ-tests/python/baml_client/partial_types.py @@ -173,6 +173,10 @@ class LinkedList(BaseModel): head: Optional["Node"] = None len: Optional[int] = None +class LinkedListAliasNode(BaseModel): + value: Optional[int] = None + next: Optional["LinkedListAliasNode"] = None + class LiteralClassHello(BaseModel): prop: Literal["hello"] diff --git a/integ-tests/python/baml_client/sync_client.py b/integ-tests/python/baml_client/sync_client.py index 696442e3c..5313bae25 100644 --- a/integ-tests/python/baml_client/sync_client.py +++ b/integ-tests/python/baml_client/sync_client.py @@ -70,6 +70,29 @@ def AaaSamOutputFormat( ) return cast(types.Recipe, raw.cast_to(types, types)) + def AliasThatPointsToRecursiveType( + self, + list: types.LinkedListAliasNode, + baml_options: BamlCallOptions = {}, + ) -> types.LinkedListAliasNode: + __tb__ = baml_options.get("tb", None) + if __tb__ is not None: + tb = __tb__._tb # type: ignore (we know how to use this private attribute) + else: + tb = None + __cr__ = baml_options.get("client_registry", None) + + raw = self.__runtime.call_function_sync( + "AliasThatPointsToRecursiveType", + { + "list": list, + }, + self.__ctx_manager.get(), + tb, + __cr__, + ) + return cast(types.LinkedListAliasNode, raw.cast_to(types, types)) + def AliasedInputClass( self, input: types.InputClass, @@ -2872,6 +2895,36 @@ def AaaSamOutputFormat( self.__ctx_manager.get(), ) + def AliasThatPointsToRecursiveType( + self, + list: types.LinkedListAliasNode, + baml_options: BamlCallOptions = {}, + ) -> baml_py.BamlSyncStream[partial_types.LinkedListAliasNode, types.LinkedListAliasNode]: + __tb__ = baml_options.get("tb", None) + if __tb__ is not None: + tb = __tb__._tb # type: ignore (we know how to use this private attribute) + else: + tb = None + __cr__ = baml_options.get("client_registry", None) + + raw = self.__runtime.stream_function_sync( + "AliasThatPointsToRecursiveType", + { + "list": list, + }, + None, + self.__ctx_manager.get(), + tb, + __cr__, + ) + + return baml_py.BamlSyncStream[partial_types.LinkedListAliasNode, types.LinkedListAliasNode]( + raw, + lambda x: cast(partial_types.LinkedListAliasNode, x.cast_to(types, partial_types)), + lambda x: cast(types.LinkedListAliasNode, x.cast_to(types, types)), + self.__ctx_manager.get(), + ) + def AliasedInputClass( self, input: types.InputClass, diff --git a/integ-tests/python/baml_client/type_builder.py b/integ-tests/python/baml_client/type_builder.py index a8ed479ae..913d8e74d 100644 --- a/integ-tests/python/baml_client/type_builder.py +++ b/integ-tests/python/baml_client/type_builder.py @@ -20,7 +20,7 @@ class TypeBuilder(_TypeBuilder): def __init__(self): super().__init__(classes=set( - ["BigNumbers","BinaryNode","Blah","BlockConstraint","BlockConstraintForParam","BookOrder","ClassOptionalOutput","ClassOptionalOutput2","ClassWithImage","CompoundBigNumbers","ContactInfo","CustomTaskResult","DummyOutput","DynInputOutput","DynamicClassOne","DynamicClassTwo","DynamicOutput","Earthling","Education","Email","EmailAddress","Event","FakeImage","FlightConfirmation","FooAny","Forest","GroceryReceipt","InnerClass","InnerClass2","InputClass","InputClassNested","LinkedList","LiteralClassHello","LiteralClassOne","LiteralClassTwo","MalformedConstraints","MalformedConstraints2","Martian","NamedArgsSingleClass","Nested","Nested2","NestedBlockConstraint","NestedBlockConstraintForParam","Node","OptionalTest_Prop1","OptionalTest_ReturnType","OrderInfo","OriginalA","OriginalB","Person","PhoneNumber","Quantity","RaysData","ReceiptInfo","ReceiptItem","Recipe","Resume","Schema","SearchParams","SomeClassNestedDynamic","StringToClassEntry","TestClassAlias","TestClassNested","TestClassWithEnum","TestOutputClass","Tree","TwoStoriesOneTitle","UnionTest_ReturnType","WithReasoning",] + ["BigNumbers","BinaryNode","Blah","BlockConstraint","BlockConstraintForParam","BookOrder","ClassOptionalOutput","ClassOptionalOutput2","ClassWithImage","CompoundBigNumbers","ContactInfo","CustomTaskResult","DummyOutput","DynInputOutput","DynamicClassOne","DynamicClassTwo","DynamicOutput","Earthling","Education","Email","EmailAddress","Event","FakeImage","FlightConfirmation","FooAny","Forest","GroceryReceipt","InnerClass","InnerClass2","InputClass","InputClassNested","LinkedList","LinkedListAliasNode","LiteralClassHello","LiteralClassOne","LiteralClassTwo","MalformedConstraints","MalformedConstraints2","Martian","NamedArgsSingleClass","Nested","Nested2","NestedBlockConstraint","NestedBlockConstraintForParam","Node","OptionalTest_Prop1","OptionalTest_ReturnType","OrderInfo","OriginalA","OriginalB","Person","PhoneNumber","Quantity","RaysData","ReceiptInfo","ReceiptItem","Recipe","Resume","Schema","SearchParams","SomeClassNestedDynamic","StringToClassEntry","TestClassAlias","TestClassNested","TestClassWithEnum","TestOutputClass","Tree","TwoStoriesOneTitle","UnionTest_ReturnType","WithReasoning",] ), enums=set( ["AliasedEnum","Category","Category2","Category3","Color","DataType","DynEnumOne","DynEnumTwo","EnumInClass","EnumOutput","Hobby","MapKey","NamedArgsSingleEnum","NamedArgsSingleEnumList","OptionalTest_CategoryType","OrderStatus","Tag","TestEnum",] )) diff --git a/integ-tests/python/baml_client/types.py b/integ-tests/python/baml_client/types.py index ebe52a38f..1b65032e9 100644 --- a/integ-tests/python/baml_client/types.py +++ b/integ-tests/python/baml_client/types.py @@ -298,6 +298,10 @@ class LinkedList(BaseModel): head: Optional["Node"] = None len: int +class LinkedListAliasNode(BaseModel): + value: int + next: Optional["LinkedListAliasNode"] = None + class LiteralClassHello(BaseModel): prop: Literal["hello"] diff --git a/integ-tests/python/tests/test_functions.py b/integ-tests/python/tests/test_functions.py index fdc5c2c32..46c67ae3b 100644 --- a/integ-tests/python/tests/test_functions.py +++ b/integ-tests/python/tests/test_functions.py @@ -40,6 +40,7 @@ BlockConstraintForParam, NestedBlockConstraintForParam, MapKey, + LinkedListAliasNode, ) import baml_client.types as types from ..baml_client.tracing import trace, set_tags, flush, on_log_event @@ -267,6 +268,13 @@ async def test_alias_union(self): res = await b.NestedAlias({"A": ["B", "C"], "B": [], "C": []}) assert res == {"A": ["B", "C"], "B": [], "C": []} + @pytest.mark.asyncio + async def test_alias_pointing_to_recursive_class(self): + res = await b.AliasThatPointsToRecursiveType( + LinkedListAliasNode(value=1, next=None) + ) + assert res == LinkedListAliasNode(value=1, next=None) + class MyCustomClass(NamedArgsSingleClass): date: datetime.datetime diff --git a/integ-tests/ruby/baml_client/client.rb b/integ-tests/ruby/baml_client/client.rb index 9bc876faf..b56a6fce2 100644 --- a/integ-tests/ruby/baml_client/client.rb +++ b/integ-tests/ruby/baml_client/client.rb @@ -82,6 +82,38 @@ def AaaSamOutputFormat( (raw.parsed_using_types(Baml::Types)) end + sig { + params( + varargs: T.untyped, + list: Baml::Types::LinkedListAliasNode, + baml_options: T::Hash[Symbol, T.any(Baml::TypeBuilder, Baml::ClientRegistry)] + ).returns(Baml::Types::LinkedListAliasNode) + } + def AliasThatPointsToRecursiveType( + *varargs, + list:, + baml_options: {} + ) + if varargs.any? + + raise ArgumentError.new("AliasThatPointsToRecursiveType may only be called with keyword arguments") + end + if (baml_options.keys - [:client_registry, :tb]).any? + raise ArgumentError.new("Received unknown keys in baml_options (valid keys: :client_registry, :tb): #{baml_options.keys - [:client_registry, :tb]}") + end + + raw = @runtime.call_function( + "AliasThatPointsToRecursiveType", + { + list: list, + }, + @ctx_manager, + baml_options[:tb]&.instance_variable_get(:@registry), + baml_options[:client_registry], + ) + (raw.parsed_using_types(Baml::Types)) + end + sig { params( varargs: T.untyped, @@ -3970,6 +4002,41 @@ def AaaSamOutputFormat( ) end + sig { + params( + varargs: T.untyped, + list: Baml::Types::LinkedListAliasNode, + baml_options: T::Hash[Symbol, T.any(Baml::TypeBuilder, Baml::ClientRegistry)] + ).returns(Baml::BamlStream[Baml::Types::LinkedListAliasNode]) + } + def AliasThatPointsToRecursiveType( + *varargs, + list:, + baml_options: {} + ) + if varargs.any? + + raise ArgumentError.new("AliasThatPointsToRecursiveType may only be called with keyword arguments") + end + if (baml_options.keys - [:client_registry, :tb]).any? + raise ArgumentError.new("Received unknown keys in baml_options (valid keys: :client_registry, :tb): #{baml_options.keys - [:client_registry, :tb]}") + end + + raw = @runtime.stream_function( + "AliasThatPointsToRecursiveType", + { + list: list, + }, + @ctx_manager, + baml_options[:tb]&.instance_variable_get(:@registry), + baml_options[:client_registry], + ) + Baml::BamlStream[Baml::PartialTypes::LinkedListAliasNode, Baml::Types::LinkedListAliasNode].new( + ffi_stream: raw, + ctx_manager: @ctx_manager + ) + end + sig { params( varargs: T.untyped, diff --git a/integ-tests/ruby/baml_client/inlined.rb b/integ-tests/ruby/baml_client/inlined.rb index f6201e07f..e8f52b706 100644 --- a/integ-tests/ruby/baml_client/inlined.rb +++ b/integ-tests/ruby/baml_client/inlined.rb @@ -80,6 +80,7 @@ module Inlined "test-files/functions/output/optional-class.baml" => "class ClassOptionalOutput {\n prop1 string\n prop2 string\n}\n\nfunction FnClassOptionalOutput(input: string) -> ClassOptionalOutput? {\n client GPT35\n prompt #\"\n Return a json blob for the following input:\n {{input}}\n\n {{ctx.output_format}}\n\n JSON:\n \"#\n}\n\n\nclass Blah {\n prop4 string?\n}\n\nclass ClassOptionalOutput2 {\n prop1 string?\n prop2 string?\n prop3 Blah?\n}\n\nfunction FnClassOptionalOutput2(input: string) -> ClassOptionalOutput2? {\n client GPT35\n prompt #\"\n Return a json blob for the following input:\n {{input}}\n\n {{ctx.output_format}}\n\n JSON:\n \"#\n}\n\ntest FnClassOptionalOutput2 {\n functions [FnClassOptionalOutput2, FnClassOptionalOutput]\n args {\n input \"example input\"\n }\n}\n", "test-files/functions/output/optional.baml" => "class OptionalTest_Prop1 {\n omega_a string\n omega_b int\n}\n\nenum OptionalTest_CategoryType {\n Aleph\n Beta\n Gamma\n}\n \nclass OptionalTest_ReturnType {\n omega_1 OptionalTest_Prop1?\n omega_2 string?\n omega_3 (OptionalTest_CategoryType?)[]\n} \n \nfunction OptionalTest_Function(input: string) -> (OptionalTest_ReturnType?)[]\n{ \n client GPT35\n prompt #\"\n Return a JSON blob with this schema: \n {{ctx.output_format}}\n\n JSON:\n \"#\n}\n\ntest OptionalTest_Function {\n functions [OptionalTest_Function]\n args {\n input \"example input\"\n }\n}\n", "test-files/functions/output/recursive-class.baml" => "class Node {\n data int\n next Node?\n}\n\nclass LinkedList {\n head Node?\n len int\n}\n\nclient<llm> O1 {\n provider \"openai\"\n options {\n model \"o1-mini\"\n default_role \"user\"\n }\n}\n\nfunction BuildLinkedList(input: int[]) -> LinkedList {\n client O1\n prompt #\"\n Build a linked list from the input array of integers.\n\n INPUT:\n {{ input }}\n\n {{ ctx.output_format }} \n \"#\n}\n\ntest TestLinkedList {\n functions [BuildLinkedList]\n args {\n input [1, 2, 3, 4, 5]\n }\n}\n", + "test-files/functions/output/recursive-type-aliases.baml" => "// Simple alias that points to recursive type.\nclass LinkedListAliasNode {\n value int\n next LinkedListAliasNode?\n}\n\ntype LinkedListAlias = LinkedListAliasNode\n\nfunction AliasThatPointsToRecursiveType(list: LinkedListAlias) -> LinkedListAlias {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given linked list back:\n \n {{ list }}\n \n {{ ctx.output_format }}\n \"#\n}\n", "test-files/functions/output/serialization-error.baml" => "class DummyOutput {\n nonce string\n nonce2 string\n @@dynamic\n}\n\nfunction DummyOutputFunction(input: string) -> DummyOutput {\n client GPT35\n prompt #\"\n Say \"hello there\".\n \"#\n}", "test-files/functions/output/string-list.baml" => "function FnOutputStringList(input: string) -> string[] {\n client GPT35\n prompt #\"\n Return a list of strings in json format like [\"string1\", \"string2\", \"string3\"].\n\n JSON:\n \"#\n}\n\ntest FnOutputStringList {\n functions [FnOutputStringList]\n args {\n input \"example input\"\n }\n}\n", "test-files/functions/output/type-aliases.baml" => "type Primitive = int | string | bool | float\n\ntype List = string[]\n\ntype Graph = map<string, string[]>\n\ntype Combination = Primitive | List | Graph\n\nfunction PrimitiveAlias(p: Primitive) -> Primitive {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given value back: {{ p }}\n \"#\n}\n\nfunction MapAlias(m: Graph) -> Graph {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given Graph back:\n\n {{ m }}\n\n {{ ctx.output_format }}\n \"#\n}\n\nfunction NestedAlias(c: Combination) -> Combination {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given value back:\n\n {{ c }}\n\n {{ ctx.output_format }}\n \"#\n}\n", diff --git a/integ-tests/ruby/baml_client/partial-types.rb b/integ-tests/ruby/baml_client/partial-types.rb index 888e97382..7a37aaa67 100644 --- a/integ-tests/ruby/baml_client/partial-types.rb +++ b/integ-tests/ruby/baml_client/partial-types.rb @@ -52,6 +52,7 @@ class InnerClass2 < T::Struct; end class InputClass < T::Struct; end class InputClassNested < T::Struct; end class LinkedList < T::Struct; end + class LinkedListAliasNode < T::Struct; end class LiteralClassHello < T::Struct; end class LiteralClassOne < T::Struct; end class LiteralClassTwo < T::Struct; end @@ -559,6 +560,20 @@ def initialize(props) @props = props end end + class LinkedListAliasNode < T::Struct + include Baml::Sorbet::Struct + const :value, T.nilable(Integer) + const :next, Baml::PartialTypes::LinkedListAliasNode + + def initialize(props) + super( + value: props[:value], + next: props[:next], + ) + + @props = props + end + end class LiteralClassHello < T::Struct include Baml::Sorbet::Struct const :prop, T.nilable(String) diff --git a/integ-tests/ruby/baml_client/type-registry.rb b/integ-tests/ruby/baml_client/type-registry.rb index f2e87aa08..b9b66634a 100644 --- a/integ-tests/ruby/baml_client/type-registry.rb +++ b/integ-tests/ruby/baml_client/type-registry.rb @@ -18,7 +18,7 @@ module Baml class TypeBuilder def initialize @registry = Baml::Ffi::TypeBuilder.new - @classes = Set[ "BigNumbers", "BinaryNode", "Blah", "BlockConstraint", "BlockConstraintForParam", "BookOrder", "ClassOptionalOutput", "ClassOptionalOutput2", "ClassWithImage", "CompoundBigNumbers", "ContactInfo", "CustomTaskResult", "DummyOutput", "DynInputOutput", "DynamicClassOne", "DynamicClassTwo", "DynamicOutput", "Earthling", "Education", "Email", "EmailAddress", "Event", "FakeImage", "FlightConfirmation", "FooAny", "Forest", "GroceryReceipt", "InnerClass", "InnerClass2", "InputClass", "InputClassNested", "LinkedList", "LiteralClassHello", "LiteralClassOne", "LiteralClassTwo", "MalformedConstraints", "MalformedConstraints2", "Martian", "NamedArgsSingleClass", "Nested", "Nested2", "NestedBlockConstraint", "NestedBlockConstraintForParam", "Node", "OptionalTest_Prop1", "OptionalTest_ReturnType", "OrderInfo", "OriginalA", "OriginalB", "Person", "PhoneNumber", "Quantity", "RaysData", "ReceiptInfo", "ReceiptItem", "Recipe", "Resume", "Schema", "SearchParams", "SomeClassNestedDynamic", "StringToClassEntry", "TestClassAlias", "TestClassNested", "TestClassWithEnum", "TestOutputClass", "Tree", "TwoStoriesOneTitle", "UnionTest_ReturnType", "WithReasoning", ] + @classes = Set[ "BigNumbers", "BinaryNode", "Blah", "BlockConstraint", "BlockConstraintForParam", "BookOrder", "ClassOptionalOutput", "ClassOptionalOutput2", "ClassWithImage", "CompoundBigNumbers", "ContactInfo", "CustomTaskResult", "DummyOutput", "DynInputOutput", "DynamicClassOne", "DynamicClassTwo", "DynamicOutput", "Earthling", "Education", "Email", "EmailAddress", "Event", "FakeImage", "FlightConfirmation", "FooAny", "Forest", "GroceryReceipt", "InnerClass", "InnerClass2", "InputClass", "InputClassNested", "LinkedList", "LinkedListAliasNode", "LiteralClassHello", "LiteralClassOne", "LiteralClassTwo", "MalformedConstraints", "MalformedConstraints2", "Martian", "NamedArgsSingleClass", "Nested", "Nested2", "NestedBlockConstraint", "NestedBlockConstraintForParam", "Node", "OptionalTest_Prop1", "OptionalTest_ReturnType", "OrderInfo", "OriginalA", "OriginalB", "Person", "PhoneNumber", "Quantity", "RaysData", "ReceiptInfo", "ReceiptItem", "Recipe", "Resume", "Schema", "SearchParams", "SomeClassNestedDynamic", "StringToClassEntry", "TestClassAlias", "TestClassNested", "TestClassWithEnum", "TestOutputClass", "Tree", "TwoStoriesOneTitle", "UnionTest_ReturnType", "WithReasoning", ] @enums = Set[ "AliasedEnum", "Category", "Category2", "Category3", "Color", "DataType", "DynEnumOne", "DynEnumTwo", "EnumInClass", "EnumOutput", "Hobby", "MapKey", "NamedArgsSingleEnum", "NamedArgsSingleEnumList", "OptionalTest_CategoryType", "OrderStatus", "Tag", "TestEnum", ] end diff --git a/integ-tests/ruby/baml_client/types.rb b/integ-tests/ruby/baml_client/types.rb index 9c6d2c6d6..0b9eddbdd 100644 --- a/integ-tests/ruby/baml_client/types.rb +++ b/integ-tests/ruby/baml_client/types.rb @@ -177,6 +177,7 @@ class InnerClass2 < T::Struct; end class InputClass < T::Struct; end class InputClassNested < T::Struct; end class LinkedList < T::Struct; end + class LinkedListAliasNode < T::Struct; end class LiteralClassHello < T::Struct; end class LiteralClassOne < T::Struct; end class LiteralClassTwo < T::Struct; end @@ -684,6 +685,20 @@ def initialize(props) @props = props end end + class LinkedListAliasNode < T::Struct + include Baml::Sorbet::Struct + const :value, Integer + const :next, T.nilable(Baml::Types::LinkedListAliasNode) + + def initialize(props) + super( + value: props[:value], + next: props[:next], + ) + + @props = props + end + end class LiteralClassHello < T::Struct include Baml::Sorbet::Struct const :prop, String diff --git a/integ-tests/ruby/test_functions.rb b/integ-tests/ruby/test_functions.rb index 22b0b5a41..b28ef001e 100644 --- a/integ-tests/ruby/test_functions.rb +++ b/integ-tests/ruby/test_functions.rb @@ -90,6 +90,9 @@ res = b.NestedAlias(c: {"A" => ["B", "C"], "B" => [], "C" => []}) assert_equal res, {"A" => ["B", "C"], "B" => [], "C" => []} + + res = b.AliasThatPointsToRecursiveType(list: { "value" => 1, "next" => null }) + assert_equal res, { "value" => 1, "next" => null } end it "accepts subclass of baml type" do diff --git a/integ-tests/typescript/baml_client/async_client.ts b/integ-tests/typescript/baml_client/async_client.ts index 65c6d49d5..0e37e3503 100644 --- a/integ-tests/typescript/baml_client/async_client.ts +++ b/integ-tests/typescript/baml_client/async_client.ts @@ -17,7 +17,7 @@ $ pnpm add @boundaryml/baml // biome-ignore format: autogenerated code import { BamlRuntime, FunctionResult, BamlCtxManager, BamlStream, Image, ClientRegistry, BamlValidationError, createBamlValidationError } from "@boundaryml/baml" import { Checked, Check } from "./types" -import {BigNumbers, BinaryNode, Blah, BlockConstraint, BlockConstraintForParam, BookOrder, ClassOptionalOutput, ClassOptionalOutput2, ClassWithImage, CompoundBigNumbers, ContactInfo, CustomTaskResult, DummyOutput, DynInputOutput, DynamicClassOne, DynamicClassTwo, DynamicOutput, Earthling, Education, Email, EmailAddress, Event, FakeImage, FlightConfirmation, FooAny, Forest, GroceryReceipt, InnerClass, InnerClass2, InputClass, InputClassNested, LinkedList, LiteralClassHello, LiteralClassOne, LiteralClassTwo, MalformedConstraints, MalformedConstraints2, Martian, NamedArgsSingleClass, Nested, Nested2, NestedBlockConstraint, NestedBlockConstraintForParam, Node, OptionalTest_Prop1, OptionalTest_ReturnType, OrderInfo, OriginalA, OriginalB, Person, PhoneNumber, Quantity, RaysData, ReceiptInfo, ReceiptItem, Recipe, Resume, Schema, SearchParams, SomeClassNestedDynamic, StringToClassEntry, TestClassAlias, TestClassNested, TestClassWithEnum, TestOutputClass, Tree, TwoStoriesOneTitle, UnionTest_ReturnType, WithReasoning, AliasedEnum, Category, Category2, Category3, Color, DataType, DynEnumOne, DynEnumTwo, EnumInClass, EnumOutput, Hobby, MapKey, NamedArgsSingleEnum, NamedArgsSingleEnumList, OptionalTest_CategoryType, OrderStatus, Tag, TestEnum} from "./types" +import {BigNumbers, BinaryNode, Blah, BlockConstraint, BlockConstraintForParam, BookOrder, ClassOptionalOutput, ClassOptionalOutput2, ClassWithImage, CompoundBigNumbers, ContactInfo, CustomTaskResult, DummyOutput, DynInputOutput, DynamicClassOne, DynamicClassTwo, DynamicOutput, Earthling, Education, Email, EmailAddress, Event, FakeImage, FlightConfirmation, FooAny, Forest, GroceryReceipt, InnerClass, InnerClass2, InputClass, InputClassNested, LinkedList, LinkedListAliasNode, LiteralClassHello, LiteralClassOne, LiteralClassTwo, MalformedConstraints, MalformedConstraints2, Martian, NamedArgsSingleClass, Nested, Nested2, NestedBlockConstraint, NestedBlockConstraintForParam, Node, OptionalTest_Prop1, OptionalTest_ReturnType, OrderInfo, OriginalA, OriginalB, Person, PhoneNumber, Quantity, RaysData, ReceiptInfo, ReceiptItem, Recipe, Resume, Schema, SearchParams, SomeClassNestedDynamic, StringToClassEntry, TestClassAlias, TestClassNested, TestClassWithEnum, TestOutputClass, Tree, TwoStoriesOneTitle, UnionTest_ReturnType, WithReasoning, AliasedEnum, Category, Category2, Category3, Color, DataType, DynEnumOne, DynEnumTwo, EnumInClass, EnumOutput, Hobby, MapKey, NamedArgsSingleEnum, NamedArgsSingleEnumList, OptionalTest_CategoryType, OrderStatus, Tag, TestEnum} from "./types" import TypeBuilder from "./type_builder" import { DO_NOT_USE_DIRECTLY_UNLESS_YOU_KNOW_WHAT_YOURE_DOING_CTX, DO_NOT_USE_DIRECTLY_UNLESS_YOU_KNOW_WHAT_YOURE_DOING_RUNTIME } from "./globals" @@ -68,6 +68,31 @@ export class BamlAsyncClient { } } + async AliasThatPointsToRecursiveType( + list: LinkedListAliasNode, + __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } + ): Promise<LinkedListAliasNode> { + try { + const raw = await this.runtime.callFunction( + "AliasThatPointsToRecursiveType", + { + "list": list + }, + this.ctx_manager.cloneContext(), + __baml_options__?.tb?.__tb(), + __baml_options__?.clientRegistry, + ) + return raw.parsed() as LinkedListAliasNode + } catch (error: any) { + const bamlError = createBamlValidationError(error); + if (bamlError instanceof BamlValidationError) { + throw bamlError; + } else { + throw error; + } + } + } + async AliasedInputClass( input: InputClass, __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } @@ -3107,6 +3132,39 @@ class BamlStreamClient { } } + AliasThatPointsToRecursiveType( + list: LinkedListAliasNode, + __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } + ): BamlStream<RecursivePartialNull<LinkedListAliasNode>, LinkedListAliasNode> { + try { + const raw = this.runtime.streamFunction( + "AliasThatPointsToRecursiveType", + { + "list": list + }, + undefined, + this.ctx_manager.cloneContext(), + __baml_options__?.tb?.__tb(), + __baml_options__?.clientRegistry, + ) + return new BamlStream<RecursivePartialNull<LinkedListAliasNode>, LinkedListAliasNode>( + raw, + (a): a is RecursivePartialNull<LinkedListAliasNode> => a, + (a): a is LinkedListAliasNode => a, + this.ctx_manager.cloneContext(), + __baml_options__?.tb?.__tb(), + ) + } catch (error) { + if (error instanceof Error) { + const bamlError = createBamlValidationError(error); + if (bamlError instanceof BamlValidationError) { + throw bamlError; + } + } + throw error; + } + } + AliasedInputClass( input: InputClass, __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } diff --git a/integ-tests/typescript/baml_client/inlinedbaml.ts b/integ-tests/typescript/baml_client/inlinedbaml.ts index 232c36118..0a03bdd14 100644 --- a/integ-tests/typescript/baml_client/inlinedbaml.ts +++ b/integ-tests/typescript/baml_client/inlinedbaml.ts @@ -81,6 +81,7 @@ const fileMap = { "test-files/functions/output/optional-class.baml": "class ClassOptionalOutput {\n prop1 string\n prop2 string\n}\n\nfunction FnClassOptionalOutput(input: string) -> ClassOptionalOutput? {\n client GPT35\n prompt #\"\n Return a json blob for the following input:\n {{input}}\n\n {{ctx.output_format}}\n\n JSON:\n \"#\n}\n\n\nclass Blah {\n prop4 string?\n}\n\nclass ClassOptionalOutput2 {\n prop1 string?\n prop2 string?\n prop3 Blah?\n}\n\nfunction FnClassOptionalOutput2(input: string) -> ClassOptionalOutput2? {\n client GPT35\n prompt #\"\n Return a json blob for the following input:\n {{input}}\n\n {{ctx.output_format}}\n\n JSON:\n \"#\n}\n\ntest FnClassOptionalOutput2 {\n functions [FnClassOptionalOutput2, FnClassOptionalOutput]\n args {\n input \"example input\"\n }\n}\n", "test-files/functions/output/optional.baml": "class OptionalTest_Prop1 {\n omega_a string\n omega_b int\n}\n\nenum OptionalTest_CategoryType {\n Aleph\n Beta\n Gamma\n}\n \nclass OptionalTest_ReturnType {\n omega_1 OptionalTest_Prop1?\n omega_2 string?\n omega_3 (OptionalTest_CategoryType?)[]\n} \n \nfunction OptionalTest_Function(input: string) -> (OptionalTest_ReturnType?)[]\n{ \n client GPT35\n prompt #\"\n Return a JSON blob with this schema: \n {{ctx.output_format}}\n\n JSON:\n \"#\n}\n\ntest OptionalTest_Function {\n functions [OptionalTest_Function]\n args {\n input \"example input\"\n }\n}\n", "test-files/functions/output/recursive-class.baml": "class Node {\n data int\n next Node?\n}\n\nclass LinkedList {\n head Node?\n len int\n}\n\nclient<llm> O1 {\n provider \"openai\"\n options {\n model \"o1-mini\"\n default_role \"user\"\n }\n}\n\nfunction BuildLinkedList(input: int[]) -> LinkedList {\n client O1\n prompt #\"\n Build a linked list from the input array of integers.\n\n INPUT:\n {{ input }}\n\n {{ ctx.output_format }} \n \"#\n}\n\ntest TestLinkedList {\n functions [BuildLinkedList]\n args {\n input [1, 2, 3, 4, 5]\n }\n}\n", + "test-files/functions/output/recursive-type-aliases.baml": "// Simple alias that points to recursive type.\nclass LinkedListAliasNode {\n value int\n next LinkedListAliasNode?\n}\n\ntype LinkedListAlias = LinkedListAliasNode\n\nfunction AliasThatPointsToRecursiveType(list: LinkedListAlias) -> LinkedListAlias {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given linked list back:\n \n {{ list }}\n \n {{ ctx.output_format }}\n \"#\n}\n", "test-files/functions/output/serialization-error.baml": "class DummyOutput {\n nonce string\n nonce2 string\n @@dynamic\n}\n\nfunction DummyOutputFunction(input: string) -> DummyOutput {\n client GPT35\n prompt #\"\n Say \"hello there\".\n \"#\n}", "test-files/functions/output/string-list.baml": "function FnOutputStringList(input: string) -> string[] {\n client GPT35\n prompt #\"\n Return a list of strings in json format like [\"string1\", \"string2\", \"string3\"].\n\n JSON:\n \"#\n}\n\ntest FnOutputStringList {\n functions [FnOutputStringList]\n args {\n input \"example input\"\n }\n}\n", "test-files/functions/output/type-aliases.baml": "type Primitive = int | string | bool | float\n\ntype List = string[]\n\ntype Graph = map<string, string[]>\n\ntype Combination = Primitive | List | Graph\n\nfunction PrimitiveAlias(p: Primitive) -> Primitive {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given value back: {{ p }}\n \"#\n}\n\nfunction MapAlias(m: Graph) -> Graph {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given Graph back:\n\n {{ m }}\n\n {{ ctx.output_format }}\n \"#\n}\n\nfunction NestedAlias(c: Combination) -> Combination {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given value back:\n\n {{ c }}\n\n {{ ctx.output_format }}\n \"#\n}\n", diff --git a/integ-tests/typescript/baml_client/sync_client.ts b/integ-tests/typescript/baml_client/sync_client.ts index dc46c958b..92f2ad742 100644 --- a/integ-tests/typescript/baml_client/sync_client.ts +++ b/integ-tests/typescript/baml_client/sync_client.ts @@ -17,7 +17,7 @@ $ pnpm add @boundaryml/baml // biome-ignore format: autogenerated code import { BamlRuntime, FunctionResult, BamlCtxManager, BamlSyncStream, Image, ClientRegistry, createBamlValidationError, BamlValidationError } from "@boundaryml/baml" import { Checked, Check } from "./types" -import {BigNumbers, BinaryNode, Blah, BlockConstraint, BlockConstraintForParam, BookOrder, ClassOptionalOutput, ClassOptionalOutput2, ClassWithImage, CompoundBigNumbers, ContactInfo, CustomTaskResult, DummyOutput, DynInputOutput, DynamicClassOne, DynamicClassTwo, DynamicOutput, Earthling, Education, Email, EmailAddress, Event, FakeImage, FlightConfirmation, FooAny, Forest, GroceryReceipt, InnerClass, InnerClass2, InputClass, InputClassNested, LinkedList, LiteralClassHello, LiteralClassOne, LiteralClassTwo, MalformedConstraints, MalformedConstraints2, Martian, NamedArgsSingleClass, Nested, Nested2, NestedBlockConstraint, NestedBlockConstraintForParam, Node, OptionalTest_Prop1, OptionalTest_ReturnType, OrderInfo, OriginalA, OriginalB, Person, PhoneNumber, Quantity, RaysData, ReceiptInfo, ReceiptItem, Recipe, Resume, Schema, SearchParams, SomeClassNestedDynamic, StringToClassEntry, TestClassAlias, TestClassNested, TestClassWithEnum, TestOutputClass, Tree, TwoStoriesOneTitle, UnionTest_ReturnType, WithReasoning, AliasedEnum, Category, Category2, Category3, Color, DataType, DynEnumOne, DynEnumTwo, EnumInClass, EnumOutput, Hobby, MapKey, NamedArgsSingleEnum, NamedArgsSingleEnumList, OptionalTest_CategoryType, OrderStatus, Tag, TestEnum} from "./types" +import {BigNumbers, BinaryNode, Blah, BlockConstraint, BlockConstraintForParam, BookOrder, ClassOptionalOutput, ClassOptionalOutput2, ClassWithImage, CompoundBigNumbers, ContactInfo, CustomTaskResult, DummyOutput, DynInputOutput, DynamicClassOne, DynamicClassTwo, DynamicOutput, Earthling, Education, Email, EmailAddress, Event, FakeImage, FlightConfirmation, FooAny, Forest, GroceryReceipt, InnerClass, InnerClass2, InputClass, InputClassNested, LinkedList, LinkedListAliasNode, LiteralClassHello, LiteralClassOne, LiteralClassTwo, MalformedConstraints, MalformedConstraints2, Martian, NamedArgsSingleClass, Nested, Nested2, NestedBlockConstraint, NestedBlockConstraintForParam, Node, OptionalTest_Prop1, OptionalTest_ReturnType, OrderInfo, OriginalA, OriginalB, Person, PhoneNumber, Quantity, RaysData, ReceiptInfo, ReceiptItem, Recipe, Resume, Schema, SearchParams, SomeClassNestedDynamic, StringToClassEntry, TestClassAlias, TestClassNested, TestClassWithEnum, TestOutputClass, Tree, TwoStoriesOneTitle, UnionTest_ReturnType, WithReasoning, AliasedEnum, Category, Category2, Category3, Color, DataType, DynEnumOne, DynEnumTwo, EnumInClass, EnumOutput, Hobby, MapKey, NamedArgsSingleEnum, NamedArgsSingleEnumList, OptionalTest_CategoryType, OrderStatus, Tag, TestEnum} from "./types" import TypeBuilder from "./type_builder" import { DO_NOT_USE_DIRECTLY_UNLESS_YOU_KNOW_WHAT_YOURE_DOING_CTX, DO_NOT_USE_DIRECTLY_UNLESS_YOU_KNOW_WHAT_YOURE_DOING_RUNTIME } from "./globals" @@ -68,6 +68,31 @@ export class BamlSyncClient { } } + AliasThatPointsToRecursiveType( + list: LinkedListAliasNode, + __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } + ): LinkedListAliasNode { + try { + const raw = this.runtime.callFunctionSync( + "AliasThatPointsToRecursiveType", + { + "list": list + }, + this.ctx_manager.cloneContext(), + __baml_options__?.tb?.__tb(), + __baml_options__?.clientRegistry, + ) + return raw.parsed() as LinkedListAliasNode + } catch (error: any) { + const bamlError = createBamlValidationError(error); + if (bamlError instanceof BamlValidationError) { + throw bamlError; + } else { + throw error; + } + } + } + AliasedInputClass( input: InputClass, __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } diff --git a/integ-tests/typescript/baml_client/type_builder.ts b/integ-tests/typescript/baml_client/type_builder.ts index db7a96d0a..e85c51804 100644 --- a/integ-tests/typescript/baml_client/type_builder.ts +++ b/integ-tests/typescript/baml_client/type_builder.ts @@ -50,7 +50,7 @@ export default class TypeBuilder { constructor() { this.tb = new _TypeBuilder({ classes: new Set([ - "BigNumbers","BinaryNode","Blah","BlockConstraint","BlockConstraintForParam","BookOrder","ClassOptionalOutput","ClassOptionalOutput2","ClassWithImage","CompoundBigNumbers","ContactInfo","CustomTaskResult","DummyOutput","DynInputOutput","DynamicClassOne","DynamicClassTwo","DynamicOutput","Earthling","Education","Email","EmailAddress","Event","FakeImage","FlightConfirmation","FooAny","Forest","GroceryReceipt","InnerClass","InnerClass2","InputClass","InputClassNested","LinkedList","LiteralClassHello","LiteralClassOne","LiteralClassTwo","MalformedConstraints","MalformedConstraints2","Martian","NamedArgsSingleClass","Nested","Nested2","NestedBlockConstraint","NestedBlockConstraintForParam","Node","OptionalTest_Prop1","OptionalTest_ReturnType","OrderInfo","OriginalA","OriginalB","Person","PhoneNumber","Quantity","RaysData","ReceiptInfo","ReceiptItem","Recipe","Resume","Schema","SearchParams","SomeClassNestedDynamic","StringToClassEntry","TestClassAlias","TestClassNested","TestClassWithEnum","TestOutputClass","Tree","TwoStoriesOneTitle","UnionTest_ReturnType","WithReasoning", + "BigNumbers","BinaryNode","Blah","BlockConstraint","BlockConstraintForParam","BookOrder","ClassOptionalOutput","ClassOptionalOutput2","ClassWithImage","CompoundBigNumbers","ContactInfo","CustomTaskResult","DummyOutput","DynInputOutput","DynamicClassOne","DynamicClassTwo","DynamicOutput","Earthling","Education","Email","EmailAddress","Event","FakeImage","FlightConfirmation","FooAny","Forest","GroceryReceipt","InnerClass","InnerClass2","InputClass","InputClassNested","LinkedList","LinkedListAliasNode","LiteralClassHello","LiteralClassOne","LiteralClassTwo","MalformedConstraints","MalformedConstraints2","Martian","NamedArgsSingleClass","Nested","Nested2","NestedBlockConstraint","NestedBlockConstraintForParam","Node","OptionalTest_Prop1","OptionalTest_ReturnType","OrderInfo","OriginalA","OriginalB","Person","PhoneNumber","Quantity","RaysData","ReceiptInfo","ReceiptItem","Recipe","Resume","Schema","SearchParams","SomeClassNestedDynamic","StringToClassEntry","TestClassAlias","TestClassNested","TestClassWithEnum","TestOutputClass","Tree","TwoStoriesOneTitle","UnionTest_ReturnType","WithReasoning", ]), enums: new Set([ "AliasedEnum","Category","Category2","Category3","Color","DataType","DynEnumOne","DynEnumTwo","EnumInClass","EnumOutput","Hobby","MapKey","NamedArgsSingleEnum","NamedArgsSingleEnumList","OptionalTest_CategoryType","OrderStatus","Tag","TestEnum", diff --git a/integ-tests/typescript/baml_client/types.ts b/integ-tests/typescript/baml_client/types.ts index 72928abe8..474d2dea4 100644 --- a/integ-tests/typescript/baml_client/types.ts +++ b/integ-tests/typescript/baml_client/types.ts @@ -364,6 +364,12 @@ export interface LinkedList { } +export interface LinkedListAliasNode { + value: number + next?: LinkedListAliasNode | null + +} + export interface LiteralClassHello { prop: "hello" diff --git a/integ-tests/typescript/test-report.html b/integ-tests/typescript/test-report.html index 8d84aa198..3ee4b7a30 100644 --- a/integ-tests/typescript/test-report.html +++ b/integ-tests/typescript/test-report.html @@ -1,920 +1,260 @@ -<html> - -<head> - <meta charset="utf-8" /> - <title>Test Report</title> - <style type="text/css"> - html, - body { - font-family: Arial, Helvetica, sans-serif; - font-size: 1rem; - margin: 0; - padding: 0; - color: #333; - } - - body { - padding: 2rem 1rem; - font-size: 0.85rem; - } - - .jesthtml-content { - margin: 0 auto; - max-width: 70rem; - } - - header { - display: flex; - align-items: center; - } - - #title { - margin: 0; - flex-grow: 1; - } - - #logo { - height: 4rem; - } - - #timestamp { - color: #777; - margin-top: 0.5rem; - } - - /** SUMMARY */ - #summary { - color: #333; - margin: 2rem 0; - display: flex; - font-family: monospace; - font-size: 1rem; - } - - #summary>div { - margin-right: 2rem; - background: #eee; - padding: 1rem; - min-width: 15rem; - } - - #summary>div:last-child { - margin-right: 0; - } - - @media only screen and (max-width: 720px) { - #summary { - flex-direction: column; - } - - #summary>div { - margin-right: 0; - margin-top: 2rem; - } - - #summary>div:first-child { - margin-top: 0; - } - } - - .summary-total { - font-weight: bold; - margin-bottom: 0.5rem; - } - - .summary-passed { - color: #4f8a10; - border-left: 0.4rem solid #4f8a10; - padding-left: 0.5rem; - } - - .summary-failed, - .summary-obsolete-snapshots { - color: #d8000c; - border-left: 0.4rem solid #d8000c; - padding-left: 0.5rem; - } - - .summary-pending { - color: #9f6000; - border-left: 0.4rem solid #9f6000; - padding-left: 0.5rem; - } - - .summary-empty { - color: #999; - border-left: 0.4rem solid #999; - } - - .test-result { - padding: 1rem; - margin-bottom: 0.25rem; - } - - .test-result:last-child { - border: 0; - } - - .test-result.passed { - background-color: #dff2bf; - color: #4f8a10; - } - - .test-result.failed { - background-color: #ffbaba; - color: #d8000c; - } - - .test-result.pending { - background-color: #ffdf61; - color: #9f6000; - } - - .test-info { - display: flex; - justify-content: space-between; - } - - .test-suitename { - width: 20%; - text-align: left; - font-weight: bold; - word-break: break-word; - } - - .test-title { - width: 40%; - text-align: left; - font-style: italic; - } - - .test-status { - width: 20%; - text-align: right; - } - - .test-duration { - width: 10%; - text-align: right; - font-size: 0.75rem; - } - - .failureMessages { - padding: 0 1rem; - margin-top: 1rem; - border-top: 1px dashed #d8000c; - } - - .failureMessages.suiteFailure { - border-top: none; - } - - .failureMsg { - white-space: pre-wrap; - white-space: -moz-pre-wrap; - white-space: -pre-wrap; - white-space: -o-pre-wrap; - word-wrap: break-word; - } - - .suite-container { - margin-bottom: 2rem; - } - - .suite-container>input[type="checkbox"] { - position: absolute; - left: -100vw; - } - - .suite-container label { - display: block; - } - - .suite-container .suite-tests { - overflow-y: hidden; - height: 0; - } - - .suite-container>input[type="checkbox"]:checked~.suite-tests { - height: auto; - overflow: visible; - } - - .suite-info { - padding: 1rem; - background-color: #eee; - color: #777; - display: flex; - align-items: center; - margin-bottom: 0.25rem; - } - - .suite-info:hover { - background-color: #ddd; - cursor: pointer; - } - - .suite-info .suite-path { - word-break: break-all; - flex-grow: 1; - font-family: monospace; - font-size: 1rem; - } - - .suite-info .suite-time { - margin-left: 0.5rem; - padding: 0.2rem 0.3rem; - font-size: 0.75rem; - } - - .suite-info .suite-time.warn { - background-color: #d8000c; - color: #fff; - } - - .suite-info:before { - content: "\2303"; - display: inline-block; - margin-right: 0.5rem; - transform: rotate(0deg); - } - - .suite-container>input[type="checkbox"]:checked~label .suite-info:before { - transform: rotate(180deg); - } - - /* CONSOLE LOGS */ - .suite-consolelog { - margin-bottom: 0.25rem; - padding: 1rem; - background-color: #efefef; - } - - .suite-consolelog-header { - font-weight: bold; - } - - .suite-consolelog-item { - padding: 0.5rem; - } - - .suite-consolelog-item pre { - margin: 0.5rem 0; - white-space: pre-wrap; - white-space: -moz-pre-wrap; - white-space: -pre-wrap; - white-space: -o-pre-wrap; - word-wrap: break-word; - } - - .suite-consolelog-item-origin { - color: #777; - font-weight: bold; - } - - .suite-consolelog-item-message { - color: #000; - font-size: 1rem; - padding: 0 0.5rem; - } - - /* OBSOLETE SNAPSHOTS */ - .suite-obsolete-snapshots { - margin-bottom: 0.25rem; - padding: 1rem; - background-color: #ffbaba; - color: #d8000c; - } - - .suite-obsolete-snapshots-header { - font-weight: bold; - } - - .suite-obsolete-snapshots-item { - padding: 0.5rem; - } - - .suite-obsolete-snapshots-item pre { - margin: 0.5rem 0; - white-space: pre-wrap; - white-space: -moz-pre-wrap; - white-space: -pre-wrap; - white-space: -o-pre-wrap; - word-wrap: break-word; - } - - .suite-obsolete-snapshots-item-message { - color: #000; - font-size: 1rem; - padding: 0 0.5rem; - } - </style> -</head> - -<body> - <div class="jesthtml-content"> - <header> - <h1 id="title">Test Report</h1> - </header> - <div id="metadata-container"> - <div id="timestamp">Started: 2024-11-30 17:41:18</div> - <div id="summary"> - <div id="suite-summary"> - <div class="summary-total">Suites (1)</div> - <div class="summary-passed summary-empty">0 passed</div> - <div class="summary-failed ">1 failed</div> - <div class="summary-pending summary-empty">0 pending</div> - </div> - <div id="test-summary"> - <div class="summary-total">Tests (70)</div> - <div class="summary-passed ">68 passed</div> - <div class="summary-failed ">2 failed</div> - <div class="summary-pending summary-empty">0 pending</div> - </div> - </div> - </div> - <div id="suite-1" class="suite-container"><input id="collapsible-0" type="checkbox" class="toggle" - checked="checked" /><label for="collapsible-0"> - <div class="suite-info"> - <div class="suite-path">/workspaces/baml/integ-tests/typescript/tests/integ-tests.test.ts</div> - <div class="suite-time warn">117.765s</div> - </div> - </label> - <div class="suite-tests"> - <div class="test-result passed"> - <div class="test-info"> - <div class="test-suitename">Integ tests > should work for all inputs</div> - <div class="test-title">single bool</div> - <div class="test-status">passed</div> - <div class="test-duration">1.768s</div> - </div> - </div> - <div class="test-result passed"> - <div class="test-info"> - <div class="test-suitename">Integ tests > should work for all inputs</div> - <div class="test-title">single string list</div> - <div class="test-status">passed</div> - <div class="test-duration">0.466s</div> - </div> - </div> - <div class="test-result passed"> - <div class="test-info"> - <div class="test-suitename">Integ tests > should work for all inputs</div> - <div class="test-title">return literal union</div> - <div class="test-status">passed</div> - <div class="test-duration">0.658s</div> - </div> - </div> - <div class="test-result passed"> - <div class="test-info"> - <div class="test-suitename">Integ tests > should work for all inputs</div> - <div class="test-title">single class</div> - <div class="test-status">passed</div> - <div class="test-duration">0.515s</div> - </div> - </div> - <div class="test-result passed"> - <div class="test-info"> - <div class="test-suitename">Integ tests > should work for all inputs</div> - <div class="test-title">multiple classes</div> - <div class="test-status">passed</div> - <div class="test-duration">0.513s</div> - </div> - </div> - <div class="test-result passed"> - <div class="test-info"> - <div class="test-suitename">Integ tests > should work for all inputs</div> - <div class="test-title">single enum list</div> - <div class="test-status">passed</div> - <div class="test-duration">0.496s</div> - </div> - </div> - <div class="test-result passed"> - <div class="test-info"> - <div class="test-suitename">Integ tests > should work for all inputs</div> - <div class="test-title">single float</div> - <div class="test-status">passed</div> - <div class="test-duration">0.725s</div> - </div> - </div> - <div class="test-result passed"> - <div class="test-info"> - <div class="test-suitename">Integ tests > should work for all inputs</div> - <div class="test-title">single int</div> - <div class="test-status">passed</div> - <div class="test-duration">0.716s</div> - </div> - </div> - <div class="test-result passed"> - <div class="test-info"> - <div class="test-suitename">Integ tests > should work for all inputs</div> - <div class="test-title">single literal int</div> - <div class="test-status">passed</div> - <div class="test-duration">0.512s</div> - </div> - </div> - <div class="test-result passed"> - <div class="test-info"> - <div class="test-suitename">Integ tests > should work for all inputs</div> - <div class="test-title">single literal bool</div> - <div class="test-status">passed</div> - <div class="test-duration">0.405s</div> - </div> - </div> - <div class="test-result passed"> - <div class="test-info"> - <div class="test-suitename">Integ tests > should work for all inputs</div> - <div class="test-title">single literal string</div> - <div class="test-status">passed</div> - <div class="test-duration">0.483s</div> - </div> - </div> - <div class="test-result passed"> - <div class="test-info"> - <div class="test-suitename">Integ tests > should work for all inputs</div> - <div class="test-title">single class with literal prop</div> - <div class="test-status">passed</div> - <div class="test-duration">1.135s</div> - </div> - </div> - <div class="test-result passed"> - <div class="test-info"> - <div class="test-suitename">Integ tests > should work for all inputs</div> - <div class="test-title">single class with literal union prop</div> - <div class="test-status">passed</div> - <div class="test-duration">0.967s</div> - </div> - </div> - <div class="test-result passed"> - <div class="test-info"> - <div class="test-suitename">Integ tests > should work for all inputs</div> - <div class="test-title">single optional string</div> - <div class="test-status">passed</div> - <div class="test-duration">0.532s</div> - </div> - </div> - <div class="test-result passed"> - <div class="test-info"> - <div class="test-suitename">Integ tests > should work for all inputs</div> - <div class="test-title">single map string to string</div> - <div class="test-status">passed</div> - <div class="test-duration">0.773s</div> - </div> - </div> - <div class="test-result passed"> - <div class="test-info"> - <div class="test-suitename">Integ tests > should work for all inputs</div> - <div class="test-title">single map string to class</div> - <div class="test-status">passed</div> - <div class="test-duration">0.78s</div> - </div> - </div> - <div class="test-result passed"> - <div class="test-info"> - <div class="test-suitename">Integ tests > should work for all inputs</div> - <div class="test-title">single map string to map</div> - <div class="test-status">passed</div> - <div class="test-duration">0.63s</div> - </div> - </div> - <div class="test-result passed"> - <div class="test-info"> - <div class="test-suitename">Integ tests > should work for all inputs</div> - <div class="test-title">enum key in map</div> - <div class="test-status">passed</div> - <div class="test-duration">0.758s</div> - </div> - </div> - <div class="test-result passed"> - <div class="test-info"> - <div class="test-suitename">Integ tests > should work for all inputs</div> - <div class="test-title">literal string union key in map</div> - <div class="test-status">passed</div> - <div class="test-duration">0.7s</div> - </div> - </div> - <div class="test-result passed"> - <div class="test-info"> - <div class="test-suitename">Integ tests > should work for all inputs</div> - <div class="test-title">single literal string key in map</div> - <div class="test-status">passed</div> - <div class="test-duration">0.915s</div> - </div> - </div> - <div class="test-result passed"> - <div class="test-info"> - <div class="test-suitename">Integ tests > should work for all inputs</div> - <div class="test-title">primitive union alias</div> - <div class="test-status">passed</div> - <div class="test-duration">0.621s</div> - </div> - </div> - <div class="test-result passed"> - <div class="test-info"> - <div class="test-suitename">Integ tests > should work for all inputs</div> - <div class="test-title">map alias</div> - <div class="test-status">passed</div> - <div class="test-duration">0.821s</div> - </div> - </div> - <div class="test-result passed"> - <div class="test-info"> - <div class="test-suitename">Integ tests > should work for all inputs</div> - <div class="test-title">alias union</div> - <div class="test-status">passed</div> - <div class="test-duration">1.688s</div> - </div> - </div> - <div class="test-result passed"> - <div class="test-info"> - <div class="test-suitename">Integ tests</div> - <div class="test-title">should work for all outputs</div> - <div class="test-status">passed</div> - <div class="test-duration">5.447s</div> - </div> - </div> - <div class="test-result passed"> - <div class="test-info"> - <div class="test-suitename">Integ tests</div> - <div class="test-title">works with retries1</div> - <div class="test-status">passed</div> - <div class="test-duration">1.967s</div> - </div> - </div> - <div class="test-result passed"> - <div class="test-info"> - <div class="test-suitename">Integ tests</div> - <div class="test-title">works with retries2</div> - <div class="test-status">passed</div> - <div class="test-duration">2.874s</div> - </div> - </div> - <div class="test-result passed"> - <div class="test-info"> - <div class="test-suitename">Integ tests</div> - <div class="test-title">works with fallbacks</div> - <div class="test-status">passed</div> - <div class="test-duration">2.354s</div> - </div> - </div> - <div class="test-result passed"> - <div class="test-info"> - <div class="test-suitename">Integ tests</div> - <div class="test-title">should work with image from url</div> - <div class="test-status">passed</div> - <div class="test-duration">1.435s</div> - </div> - </div> - <div class="test-result passed"> - <div class="test-info"> - <div class="test-suitename">Integ tests</div> - <div class="test-title">should work with image from base 64</div> - <div class="test-status">passed</div> - <div class="test-duration">1.742s</div> - </div> - </div> - <div class="test-result passed"> - <div class="test-info"> - <div class="test-suitename">Integ tests</div> - <div class="test-title">should work with audio base 64</div> - <div class="test-status">passed</div> - <div class="test-duration">1.839s</div> - </div> - </div> - <div class="test-result passed"> - <div class="test-info"> - <div class="test-suitename">Integ tests</div> - <div class="test-title">should work with audio from url</div> - <div class="test-status">passed</div> - <div class="test-duration">2.048s</div> - </div> - </div> - <div class="test-result passed"> - <div class="test-info"> - <div class="test-suitename">Integ tests</div> - <div class="test-title">should support streaming in OpenAI</div> - <div class="test-status">passed</div> - <div class="test-duration">2.222s</div> - </div> - </div> - <div class="test-result passed"> - <div class="test-info"> - <div class="test-suitename">Integ tests</div> - <div class="test-title">should support streaming in Gemini</div> - <div class="test-status">passed</div> - <div class="test-duration">9.431s</div> - </div> - </div> - <div class="test-result passed"> - <div class="test-info"> - <div class="test-suitename">Integ tests</div> - <div class="test-title">should support AWS</div> - <div class="test-status">passed</div> - <div class="test-duration">1.86s</div> - </div> - </div> - <div class="test-result passed"> - <div class="test-info"> - <div class="test-suitename">Integ tests</div> - <div class="test-title">should support streaming in AWS</div> - <div class="test-status">passed</div> - <div class="test-duration">1.821s</div> - </div> - </div> - <div class="test-result passed"> - <div class="test-info"> - <div class="test-suitename">Integ tests</div> - <div class="test-title">should allow overriding the region</div> - <div class="test-status">passed</div> - <div class="test-duration">0.104s</div> - </div> - </div> - <div class="test-result passed"> - <div class="test-info"> - <div class="test-suitename">Integ tests</div> - <div class="test-title">should support OpenAI shorthand</div> - <div class="test-status">passed</div> - <div class="test-duration">17.882s</div> - </div> - </div> - <div class="test-result passed"> - <div class="test-info"> - <div class="test-suitename">Integ tests</div> - <div class="test-title">should support OpenAI shorthand streaming</div> - <div class="test-status">passed</div> - <div class="test-duration">7.083s</div> - </div> - </div> - <div class="test-result passed"> - <div class="test-info"> - <div class="test-suitename">Integ tests</div> - <div class="test-title">should support anthropic shorthand</div> - <div class="test-status">passed</div> - <div class="test-duration">2.549s</div> - </div> - </div> - <div class="test-result passed"> - <div class="test-info"> - <div class="test-suitename">Integ tests</div> - <div class="test-title">should support anthropic shorthand streaming</div> - <div class="test-status">passed</div> - <div class="test-duration">2.43s</div> - </div> - </div> - <div class="test-result passed"> - <div class="test-info"> - <div class="test-suitename">Integ tests</div> - <div class="test-title">should support streaming without iterating</div> - <div class="test-status">passed</div> - <div class="test-duration">2.498s</div> - </div> - </div> - <div class="test-result passed"> - <div class="test-info"> - <div class="test-suitename">Integ tests</div> - <div class="test-title">should support streaming in Claude</div> - <div class="test-status">passed</div> - <div class="test-duration">1.023s</div> - </div> - </div> - <div class="test-result failed"> - <div class="test-info"> - <div class="test-suitename">Integ tests</div> - <div class="test-title">should support vertex</div> - <div class="test-status">failed</div> - <div class="test-duration">0.001s</div> - </div> - <div class="failureMessages"> - <pre class="failureMsg">Error: BamlError: Failed to read service account file: - -Caused by: - No such file or directory (os error 2)</pre> - </div> - </div> - <div class="test-result passed"> - <div class="test-info"> - <div class="test-suitename">Integ tests</div> - <div class="test-title">supports tracing sync</div> - <div class="test-status">passed</div> - <div class="test-duration">0.006s</div> - </div> - </div> - <div class="test-result passed"> - <div class="test-info"> - <div class="test-suitename">Integ tests</div> - <div class="test-title">supports tracing async</div> - <div class="test-status">passed</div> - <div class="test-duration">3.518s</div> - </div> - </div> - <div class="test-result passed"> - <div class="test-info"> - <div class="test-suitename">Integ tests</div> - <div class="test-title">should work with dynamic types single</div> - <div class="test-status">passed</div> - <div class="test-duration">1.019s</div> - </div> - </div> - <div class="test-result passed"> - <div class="test-info"> - <div class="test-suitename">Integ tests</div> - <div class="test-title">should work with dynamic types enum</div> - <div class="test-status">passed</div> - <div class="test-duration">1.078s</div> - </div> - </div> - <div class="test-result passed"> - <div class="test-info"> - <div class="test-suitename">Integ tests</div> - <div class="test-title">should work with dynamic literals</div> - <div class="test-status">passed</div> - <div class="test-duration">0.969s</div> - </div> - </div> - <div class="test-result passed"> - <div class="test-info"> - <div class="test-suitename">Integ tests</div> - <div class="test-title">should work with dynamic types class</div> - <div class="test-status">passed</div> - <div class="test-duration">1.229s</div> - </div> - </div> - <div class="test-result passed"> - <div class="test-info"> - <div class="test-suitename">Integ tests</div> - <div class="test-title">should work with dynamic inputs class</div> - <div class="test-status">passed</div> - <div class="test-duration">0.608s</div> - </div> - </div> - <div class="test-result passed"> - <div class="test-info"> - <div class="test-suitename">Integ tests</div> - <div class="test-title">should work with dynamic inputs list</div> - <div class="test-status">passed</div> - <div class="test-duration">0.574s</div> - </div> - </div> - <div class="test-result passed"> - <div class="test-info"> - <div class="test-suitename">Integ tests</div> - <div class="test-title">should work with dynamic output map</div> - <div class="test-status">passed</div> - <div class="test-duration">0.76s</div> - </div> - </div> - <div class="test-result passed"> - <div class="test-info"> - <div class="test-suitename">Integ tests</div> - <div class="test-title">should work with dynamic output union</div> - <div class="test-status">passed</div> - <div class="test-duration">1.787s</div> - </div> - </div> - <div class="test-result failed"> - <div class="test-info"> - <div class="test-suitename">Integ tests</div> - <div class="test-title">should work with nested classes</div> - <div class="test-status">failed</div> - <div class="test-duration">0.108s</div> - </div> - <div class="failureMessages"> - <pre class="failureMsg">Error: BamlError: BamlClientError: Something went wrong with the LLM client: reqwest::Error { kind: Request, url: Url { scheme: "http", cannot_be_a_base: false, username: "", password: None, host: Some(Domain("localhost")), port: Some(11434), path: "/v1/chat/completions", query: None, fragment: None }, source: hyper_util::client::legacy::Error(Connect, ConnectError("tcp connect error", Os { code: 111, kind: ConnectionRefused, message: "Connection refused" })) } - at BamlStream.parsed [as getFinalResponse] (/workspaces/baml/engine/language_client_typescript/stream.js:58:39) - at Object.<anonymous> (/workspaces/baml/integ-tests/typescript/tests/integ-tests.test.ts:620:19)</pre> - </div> - </div> - <div class="test-result passed"> - <div class="test-info"> - <div class="test-suitename">Integ tests</div> - <div class="test-title">should work with dynamic client</div> - <div class="test-status">passed</div> - <div class="test-duration">0.609s</div> - </div> - </div> - <div class="test-result passed"> - <div class="test-info"> - <div class="test-suitename">Integ tests</div> - <div class="test-title">should work with 'onLogEvent'</div> - <div class="test-status">passed</div> - <div class="test-duration">2.471s</div> - </div> - </div> - <div class="test-result passed"> - <div class="test-info"> - <div class="test-suitename">Integ tests</div> - <div class="test-title">should work with a sync client</div> - <div class="test-status">passed</div> - <div class="test-duration">0.551s</div> - </div> - </div> - <div class="test-result passed"> - <div class="test-info"> - <div class="test-suitename">Integ tests</div> - <div class="test-title">should raise an error when appropriate</div> - <div class="test-status">passed</div> - <div class="test-duration">1.458s</div> - </div> - </div> - <div class="test-result passed"> - <div class="test-info"> - <div class="test-suitename">Integ tests</div> - <div class="test-title">should raise a BAMLValidationError</div> - <div class="test-status">passed</div> - <div class="test-duration">0.509s</div> - </div> - </div> - <div class="test-result passed"> - <div class="test-info"> - <div class="test-suitename">Integ tests</div> - <div class="test-title">should reset environment variables correctly</div> - <div class="test-status">passed</div> - <div class="test-duration">2.135s</div> - </div> - </div> - <div class="test-result passed"> - <div class="test-info"> - <div class="test-suitename">Integ tests</div> - <div class="test-title">should use aliases when serializing input objects - classes</div> - <div class="test-status">passed</div> - <div class="test-duration">1.223s</div> - </div> - </div> - <div class="test-result passed"> - <div class="test-info"> - <div class="test-suitename">Integ tests</div> - <div class="test-title">should use aliases when serializing, but still have original keys in jinja</div> - <div class="test-status">passed</div> - <div class="test-duration">1.264s</div> - </div> - </div> - <div class="test-result passed"> - <div class="test-info"> - <div class="test-suitename">Integ tests</div> - <div class="test-title">should use aliases when serializing input objects - enums</div> - <div class="test-status">passed</div> - <div class="test-duration">0.853s</div> - </div> - </div> - <div class="test-result passed"> - <div class="test-info"> - <div class="test-suitename">Integ tests</div> - <div class="test-title">should use aliases when serializing input objects - lists</div> - <div class="test-status">passed</div> - <div class="test-duration">0.543s</div> - </div> - </div> - <div class="test-result passed"> - <div class="test-info"> - <div class="test-suitename">Integ tests</div> - <div class="test-title">constraints: should handle checks in return types</div> - <div class="test-status">passed</div> - <div class="test-duration">0.72s</div> - </div> - </div> - <div class="test-result passed"> - <div class="test-info"> - <div class="test-suitename">Integ tests</div> - <div class="test-title">constraints: should handle checks in returned unions</div> - <div class="test-status">passed</div> - <div class="test-duration">0.754s</div> - </div> - </div> - <div class="test-result passed"> - <div class="test-info"> - <div class="test-suitename">Integ tests</div> - <div class="test-title">constraints: should handle block-level checks</div> - <div class="test-status">passed</div> - <div class="test-duration">0.673s</div> - </div> - </div> - <div class="test-result passed"> - <div class="test-info"> - <div class="test-suitename">Integ tests</div> - <div class="test-title">constraints: should handle nested-block-level checks</div> - <div class="test-status">passed</div> - <div class="test-duration">0.623s</div> - </div> - </div> - <div class="test-result passed"> - <div class="test-info"> - <div class="test-suitename">Integ tests</div> - <div class="test-title">simple recursive type</div> - <div class="test-status">passed</div> - <div class="test-duration">2.759s</div> - </div> - </div> - <div class="test-result passed"> - <div class="test-info"> - <div class="test-suitename">Integ tests</div> - <div class="test-title">mutually recursive type</div> - <div class="test-status">passed</div> - <div class="test-duration">1.802s</div> - </div> - </div> - </div> - </div> - </div> -</body> - -</html> \ No newline at end of file +<html><head><meta charset="utf-8"/><title>Test Report</title><style type="text/css">html, +body { + font-family: Arial, Helvetica, sans-serif; + font-size: 1rem; + margin: 0; + padding: 0; + color: #333; +} +body { + padding: 2rem 1rem; + font-size: 0.85rem; +} +.jesthtml-content { + margin: 0 auto; + max-width: 70rem; +} +header { + display: flex; + align-items: center; +} +#title { + margin: 0; + flex-grow: 1; +} +#logo { + height: 4rem; +} +#timestamp { + color: #777; + margin-top: 0.5rem; +} + +/** SUMMARY */ +#summary { + color: #333; + margin: 2rem 0; + display: flex; + font-family: monospace; + font-size: 1rem; +} +#summary > div { + margin-right: 2rem; + background: #eee; + padding: 1rem; + min-width: 15rem; +} +#summary > div:last-child { + margin-right: 0; +} +@media only screen and (max-width: 720px) { + #summary { + flex-direction: column; + } + #summary > div { + margin-right: 0; + margin-top: 2rem; + } + #summary > div:first-child { + margin-top: 0; + } +} + +.summary-total { + font-weight: bold; + margin-bottom: 0.5rem; +} +.summary-passed { + color: #4f8a10; + border-left: 0.4rem solid #4f8a10; + padding-left: 0.5rem; +} +.summary-failed, +.summary-obsolete-snapshots { + color: #d8000c; + border-left: 0.4rem solid #d8000c; + padding-left: 0.5rem; +} +.summary-pending { + color: #9f6000; + border-left: 0.4rem solid #9f6000; + padding-left: 0.5rem; +} +.summary-empty { + color: #999; + border-left: 0.4rem solid #999; +} + +.test-result { + padding: 1rem; + margin-bottom: 0.25rem; +} +.test-result:last-child { + border: 0; +} +.test-result.passed { + background-color: #dff2bf; + color: #4f8a10; +} +.test-result.failed { + background-color: #ffbaba; + color: #d8000c; +} +.test-result.pending { + background-color: #ffdf61; + color: #9f6000; +} + +.test-info { + display: flex; + justify-content: space-between; +} +.test-suitename { + width: 20%; + text-align: left; + font-weight: bold; + word-break: break-word; +} +.test-title { + width: 40%; + text-align: left; + font-style: italic; +} +.test-status { + width: 20%; + text-align: right; +} +.test-duration { + width: 10%; + text-align: right; + font-size: 0.75rem; +} + +.failureMessages { + padding: 0 1rem; + margin-top: 1rem; + border-top: 1px dashed #d8000c; +} +.failureMessages.suiteFailure { + border-top: none; +} +.failureMsg { + white-space: pre-wrap; + white-space: -moz-pre-wrap; + white-space: -pre-wrap; + white-space: -o-pre-wrap; + word-wrap: break-word; +} + +.suite-container { + margin-bottom: 2rem; +} +.suite-container > input[type="checkbox"] { + position: absolute; + left: -100vw; +} +.suite-container label { + display: block; +} +.suite-container .suite-tests { + overflow-y: hidden; + height: 0; +} +.suite-container > input[type="checkbox"]:checked ~ .suite-tests { + height: auto; + overflow: visible; +} +.suite-info { + padding: 1rem; + background-color: #eee; + color: #777; + display: flex; + align-items: center; + margin-bottom: 0.25rem; +} +.suite-info:hover { + background-color: #ddd; + cursor: pointer; +} +.suite-info .suite-path { + word-break: break-all; + flex-grow: 1; + font-family: monospace; + font-size: 1rem; +} +.suite-info .suite-time { + margin-left: 0.5rem; + padding: 0.2rem 0.3rem; + font-size: 0.75rem; +} +.suite-info .suite-time.warn { + background-color: #d8000c; + color: #fff; +} +.suite-info:before { + content: "\2303"; + display: inline-block; + margin-right: 0.5rem; + transform: rotate(0deg); +} +.suite-container > input[type="checkbox"]:checked ~ label .suite-info:before { + transform: rotate(180deg); +} + +/* CONSOLE LOGS */ +.suite-consolelog { + margin-bottom: 0.25rem; + padding: 1rem; + background-color: #efefef; +} +.suite-consolelog-header { + font-weight: bold; +} +.suite-consolelog-item { + padding: 0.5rem; +} +.suite-consolelog-item pre { + margin: 0.5rem 0; + white-space: pre-wrap; + white-space: -moz-pre-wrap; + white-space: -pre-wrap; + white-space: -o-pre-wrap; + word-wrap: break-word; +} +.suite-consolelog-item-origin { + color: #777; + font-weight: bold; +} +.suite-consolelog-item-message { + color: #000; + font-size: 1rem; + padding: 0 0.5rem; +} + +/* OBSOLETE SNAPSHOTS */ +.suite-obsolete-snapshots { + margin-bottom: 0.25rem; + padding: 1rem; + background-color: #ffbaba; + color: #d8000c; +} +.suite-obsolete-snapshots-header { + font-weight: bold; +} +.suite-obsolete-snapshots-item { + padding: 0.5rem; +} +.suite-obsolete-snapshots-item pre { + margin: 0.5rem 0; + white-space: pre-wrap; + white-space: -moz-pre-wrap; + white-space: -pre-wrap; + white-space: -o-pre-wrap; + word-wrap: break-word; +} +.suite-obsolete-snapshots-item-message { + color: #000; + font-size: 1rem; + padding: 0 0.5rem; +} +</style></head><body><div class="jesthtml-content"><header><h1 id="title">Test Report</h1></header><div id="metadata-container"><div id="timestamp">Started: 2024-11-30 22:49:17</div><div id="summary"><div id="suite-summary"><div class="summary-total">Suites (1)</div><div class="summary-passed summary-empty">0 passed</div><div class="summary-failed summary-empty">0 failed</div><div class="summary-pending ">1 pending</div></div><div id="test-summary"><div class="summary-total">Tests (71)</div><div class="summary-passed summary-empty">0 passed</div><div class="summary-failed summary-empty">0 failed</div><div class="summary-pending ">71 pending</div></div></div></div><div id="suite-1" class="suite-container"><input id="collapsible-0" type="checkbox" class="toggle" checked="checked"/><label for="collapsible-0"><div class="suite-info"><div class="suite-path">/workspaces/baml/integ-tests/typescript/tests/integ-tests.test.ts</div><div class="suite-time">0.466s</div></div></label><div class="suite-tests"><div class="test-result pending"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single bool</div><div class="test-status">pending</div><div class="test-duration">0s</div></div></div><div class="test-result pending"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single string list</div><div class="test-status">pending</div><div class="test-duration">0s</div></div></div><div class="test-result pending"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">return literal union</div><div class="test-status">pending</div><div class="test-duration">0s</div></div></div><div class="test-result pending"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single class</div><div class="test-status">pending</div><div class="test-duration">0s</div></div></div><div class="test-result pending"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">multiple classes</div><div class="test-status">pending</div><div class="test-duration">0s</div></div></div><div class="test-result pending"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single enum list</div><div class="test-status">pending</div><div class="test-duration">0s</div></div></div><div class="test-result pending"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single float</div><div class="test-status">pending</div><div class="test-duration">0s</div></div></div><div class="test-result pending"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single int</div><div class="test-status">pending</div><div class="test-duration">0s</div></div></div><div class="test-result pending"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single literal int</div><div class="test-status">pending</div><div class="test-duration">0s</div></div></div><div class="test-result pending"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single literal bool</div><div class="test-status">pending</div><div class="test-duration">0s</div></div></div><div class="test-result pending"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single literal string</div><div class="test-status">pending</div><div class="test-duration">0s</div></div></div><div class="test-result pending"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single class with literal prop</div><div class="test-status">pending</div><div class="test-duration">0s</div></div></div><div class="test-result pending"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single class with literal union prop</div><div class="test-status">pending</div><div class="test-duration">0s</div></div></div><div class="test-result pending"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single optional string</div><div class="test-status">pending</div><div class="test-duration">0s</div></div></div><div class="test-result pending"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single map string to string</div><div class="test-status">pending</div><div class="test-duration">0s</div></div></div><div class="test-result pending"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single map string to class</div><div class="test-status">pending</div><div class="test-duration">0s</div></div></div><div class="test-result pending"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single map string to map</div><div class="test-status">pending</div><div class="test-duration">0s</div></div></div><div class="test-result pending"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">enum key in map</div><div class="test-status">pending</div><div class="test-duration">0s</div></div></div><div class="test-result pending"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">literal string union key in map</div><div class="test-status">pending</div><div class="test-duration">0s</div></div></div><div class="test-result pending"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single literal string key in map</div><div class="test-status">pending</div><div class="test-duration">0s</div></div></div><div class="test-result pending"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">primitive union alias</div><div class="test-status">pending</div><div class="test-duration">0s</div></div></div><div class="test-result pending"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">map alias</div><div class="test-status">pending</div><div class="test-duration">0s</div></div></div><div class="test-result pending"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">alias union</div><div class="test-status">pending</div><div class="test-duration">0s</div></div></div><div class="test-result pending"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">alias pointing to recursive class</div><div class="test-status">pending</div><div class="test-duration">0s</div></div></div><div class="test-result pending"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work for all outputs</div><div class="test-status">pending</div><div class="test-duration">0s</div></div></div><div class="test-result pending"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">works with retries1</div><div class="test-status">pending</div><div class="test-duration">0s</div></div></div><div class="test-result pending"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">works with retries2</div><div class="test-status">pending</div><div class="test-duration">0s</div></div></div><div class="test-result pending"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">works with fallbacks</div><div class="test-status">pending</div><div class="test-duration">0s</div></div></div><div class="test-result pending"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with image from url</div><div class="test-status">pending</div><div class="test-duration">0s</div></div></div><div class="test-result pending"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with image from base 64</div><div class="test-status">pending</div><div class="test-duration">0s</div></div></div><div class="test-result pending"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with audio base 64</div><div class="test-status">pending</div><div class="test-duration">0s</div></div></div><div class="test-result pending"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with audio from url</div><div class="test-status">pending</div><div class="test-duration">0s</div></div></div><div class="test-result pending"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support streaming in OpenAI</div><div class="test-status">pending</div><div class="test-duration">0s</div></div></div><div class="test-result pending"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support streaming in Gemini</div><div class="test-status">pending</div><div class="test-duration">0s</div></div></div><div class="test-result pending"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support AWS</div><div class="test-status">pending</div><div class="test-duration">0s</div></div></div><div class="test-result pending"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support streaming in AWS</div><div class="test-status">pending</div><div class="test-duration">0s</div></div></div><div class="test-result pending"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should allow overriding the region</div><div class="test-status">pending</div><div class="test-duration">0s</div></div></div><div class="test-result pending"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support OpenAI shorthand</div><div class="test-status">pending</div><div class="test-duration">0s</div></div></div><div class="test-result pending"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support OpenAI shorthand streaming</div><div class="test-status">pending</div><div class="test-duration">0s</div></div></div><div class="test-result pending"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support anthropic shorthand</div><div class="test-status">pending</div><div class="test-duration">0s</div></div></div><div class="test-result pending"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support anthropic shorthand streaming</div><div class="test-status">pending</div><div class="test-duration">0s</div></div></div><div class="test-result pending"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support streaming without iterating</div><div class="test-status">pending</div><div class="test-duration">0s</div></div></div><div class="test-result pending"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support streaming in Claude</div><div class="test-status">pending</div><div class="test-duration">0s</div></div></div><div class="test-result pending"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support vertex</div><div class="test-status">pending</div><div class="test-duration">0s</div></div></div><div class="test-result pending"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">supports tracing sync</div><div class="test-status">pending</div><div class="test-duration">0s</div></div></div><div class="test-result pending"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">supports tracing async</div><div class="test-status">pending</div><div class="test-duration">0s</div></div></div><div class="test-result pending"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic types single</div><div class="test-status">pending</div><div class="test-duration">0s</div></div></div><div class="test-result pending"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic types enum</div><div class="test-status">pending</div><div class="test-duration">0s</div></div></div><div class="test-result pending"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic literals</div><div class="test-status">pending</div><div class="test-duration">0s</div></div></div><div class="test-result pending"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic types class</div><div class="test-status">pending</div><div class="test-duration">0s</div></div></div><div class="test-result pending"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic inputs class</div><div class="test-status">pending</div><div class="test-duration">0s</div></div></div><div class="test-result pending"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic inputs list</div><div class="test-status">pending</div><div class="test-duration">0s</div></div></div><div class="test-result pending"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic output map</div><div class="test-status">pending</div><div class="test-duration">0s</div></div></div><div class="test-result pending"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic output union</div><div class="test-status">pending</div><div class="test-duration">0s</div></div></div><div class="test-result pending"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with nested classes</div><div class="test-status">pending</div><div class="test-duration">0s</div></div></div><div class="test-result pending"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic client</div><div class="test-status">pending</div><div class="test-duration">0s</div></div></div><div class="test-result pending"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with 'onLogEvent'</div><div class="test-status">pending</div><div class="test-duration">0s</div></div></div><div class="test-result pending"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with a sync client</div><div class="test-status">pending</div><div class="test-duration">0s</div></div></div><div class="test-result pending"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should raise an error when appropriate</div><div class="test-status">pending</div><div class="test-duration">0s</div></div></div><div class="test-result pending"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should raise a BAMLValidationError</div><div class="test-status">pending</div><div class="test-duration">0s</div></div></div><div class="test-result pending"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should reset environment variables correctly</div><div class="test-status">pending</div><div class="test-duration">0s</div></div></div><div class="test-result pending"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should use aliases when serializing input objects - classes</div><div class="test-status">pending</div><div class="test-duration">0s</div></div></div><div class="test-result pending"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should use aliases when serializing, but still have original keys in jinja</div><div class="test-status">pending</div><div class="test-duration">0s</div></div></div><div class="test-result pending"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should use aliases when serializing input objects - enums</div><div class="test-status">pending</div><div class="test-duration">0s</div></div></div><div class="test-result pending"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should use aliases when serializing input objects - lists</div><div class="test-status">pending</div><div class="test-duration">0s</div></div></div><div class="test-result pending"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">constraints: should handle checks in return types</div><div class="test-status">pending</div><div class="test-duration">0s</div></div></div><div class="test-result pending"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">constraints: should handle checks in returned unions</div><div class="test-status">pending</div><div class="test-duration">0s</div></div></div><div class="test-result pending"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">constraints: should handle block-level checks</div><div class="test-status">pending</div><div class="test-duration">0s</div></div></div><div class="test-result pending"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">constraints: should handle nested-block-level checks</div><div class="test-status">pending</div><div class="test-duration">0s</div></div></div><div class="test-result pending"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">simple recursive type</div><div class="test-status">pending</div><div class="test-duration">0s</div></div></div><div class="test-result pending"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">mutually recursive type</div><div class="test-status">pending</div><div class="test-duration">0s</div></div></div></div></div></div></body></html> \ No newline at end of file diff --git a/integ-tests/typescript/tests/integ-tests.test.ts b/integ-tests/typescript/tests/integ-tests.test.ts index 301edc4cb..7e7a10a72 100644 --- a/integ-tests/typescript/tests/integ-tests.test.ts +++ b/integ-tests/typescript/tests/integ-tests.test.ts @@ -166,6 +166,11 @@ describe('Integ tests', () => { res = await b.NestedAlias({ A: ['B', 'C'], B: [], C: [] }) expect(res).toEqual({ A: ['B', 'C'], B: [], C: [] }) }) + + it('alias pointing to recursive class', async () => { + const res = await b.AliasThatPointsToRecursiveType({ value: 1, next: null }) + expect(res).toEqual({ value: 1, next: null }) + }) }) it('should work for all outputs', async () => { From 5d071e79710cc75d69c945757480dad02213ca31 Mon Sep 17 00:00:00 2001 From: Antonio Sarosi <sarosiantonio@gmail.com> Date: Sat, 30 Nov 2024 23:57:54 +0100 Subject: [PATCH 31/51] Fix ruby test --- integ-tests/ruby/test_functions.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/integ-tests/ruby/test_functions.rb b/integ-tests/ruby/test_functions.rb index b28ef001e..9ddd9f236 100644 --- a/integ-tests/ruby/test_functions.rb +++ b/integ-tests/ruby/test_functions.rb @@ -91,8 +91,8 @@ res = b.NestedAlias(c: {"A" => ["B", "C"], "B" => [], "C" => []}) assert_equal res, {"A" => ["B", "C"], "B" => [], "C" => []} - res = b.AliasThatPointsToRecursiveType(list: { "value" => 1, "next" => null }) - assert_equal res, { "value" => 1, "next" => null } + res = b.AliasThatPointsToRecursiveType(list: { "value" => 1, "next" => nil }) + assert_equal res, { "value" => 1, "next" => nil } end it "accepts subclass of baml type" do From e508af1a284bc8726ba83e059b163ba023a8ef41 Mon Sep 17 00:00:00 2001 From: Antonio Sarosi <sarosiantonio@gmail.com> Date: Sun, 1 Dec 2024 00:10:23 +0100 Subject: [PATCH 32/51] Equality is not implemnted in Ruby for Baml Types --- integ-tests/ruby/test_functions.rb | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/integ-tests/ruby/test_functions.rb b/integ-tests/ruby/test_functions.rb index 9ddd9f236..654de2d6e 100644 --- a/integ-tests/ruby/test_functions.rb +++ b/integ-tests/ruby/test_functions.rb @@ -91,8 +91,15 @@ res = b.NestedAlias(c: {"A" => ["B", "C"], "B" => [], "C" => []}) assert_equal res, {"A" => ["B", "C"], "B" => [], "C" => []} - res = b.AliasThatPointsToRecursiveType(list: { "value" => 1, "next" => nil }) - assert_equal res, { "value" => 1, "next" => nil } + res = b.AliasThatPointsToRecursiveType(list: Baml::Types::LinkedListAliasNode.new( + value: 1, + next: nil, + )) + # TODO: Doesn't implement equality + # assert_equal res, Baml::Types::LinkedListAliasNode.new( + # value: 1, + # next: nil, + # ) end it "accepts subclass of baml type" do From 4bcc9423469dd75fefa2c5f16f4c3a4824df70c5 Mon Sep 17 00:00:00 2001 From: Antonio Sarosi <sarosiantonio@gmail.com> Date: Sun, 1 Dec 2024 00:34:43 +0100 Subject: [PATCH 33/51] Small refactor --- engine/baml-lib/baml-core/src/ir/repr.rs | 5 +++-- .../parser-database/src/walkers/alias.rs | 16 ---------------- 2 files changed, 3 insertions(+), 18 deletions(-) diff --git a/engine/baml-lib/baml-core/src/ir/repr.rs b/engine/baml-lib/baml-core/src/ir/repr.rs index 8b8971864..64431059c 100644 --- a/engine/baml-lib/baml-core/src/ir/repr.rs +++ b/engine/baml-lib/baml-core/src/ir/repr.rs @@ -27,13 +27,14 @@ use crate::Configuration; pub struct IntermediateRepr { enums: Vec<Node<Enum>>, classes: Vec<Node<Class>>, - /// Strongly connected components of the dependency graph (finite cycles). - finite_recursive_cycles: Vec<IndexSet<String>>, functions: Vec<Node<Function>>, clients: Vec<Node<Client>>, retry_policies: Vec<Node<RetryPolicy>>, template_strings: Vec<Node<TemplateString>>, + /// Strongly connected components of the dependency graph (finite cycles). + finite_recursive_cycles: Vec<IndexSet<String>>, + configuration: Configuration, } diff --git a/engine/baml-lib/parser-database/src/walkers/alias.rs b/engine/baml-lib/parser-database/src/walkers/alias.rs index cd0fbc469..8ba968dfc 100644 --- a/engine/baml-lib/parser-database/src/walkers/alias.rs +++ b/engine/baml-lib/parser-database/src/walkers/alias.rs @@ -23,20 +23,4 @@ impl<'db> TypeAliasWalker<'db> { pub fn resolved(&self) -> &'db FieldType { &self.db.types.resolved_type_aliases[&self.id] } - - /// Returns a [`TypeWalker`] over the resolved type if it's a symbol. - /// - /// # Panics - /// - /// Panics if the resolved type is a symbol but the symbol is not found. - pub fn resolved_as_walker(&self) -> Option<TypeWalker<'db>> { - match self.resolved() { - FieldType::Symbol(_, ident, _) => match self.db.find_type_by_str(ident.name()) { - Some(walker) => Some(walker), - _ => panic!("Unknown class or enum `{ident}`"), - }, - - _ => None, - } - } } From 34d163c111dc76952464d2742df07408adeedaac Mon Sep 17 00:00:00 2001 From: Antonio Sarosi <sarosiantonio@gmail.com> Date: Wed, 4 Dec 2024 20:43:51 +0100 Subject: [PATCH 34/51] Resolve type aliases in dependency graph --- engine/baml-lib/parser-database/src/lib.rs | 55 ++++++++++++++-------- 1 file changed, 35 insertions(+), 20 deletions(-) diff --git a/engine/baml-lib/parser-database/src/lib.rs b/engine/baml-lib/parser-database/src/lib.rs index 4666df2ca..4caa69ca8 100644 --- a/engine/baml-lib/parser-database/src/lib.rs +++ b/engine/baml-lib/parser-database/src/lib.rs @@ -159,27 +159,42 @@ impl ParserDatabase { // instead of strings (class names). That requires less conversions when // working with the graph. Once the work is done, IDs can be converted // to names where needed. - let finite_cycles = Tarjan::components(&HashMap::from_iter( - self.types.class_dependencies.iter().map(|(id, deps)| { - let deps = - HashSet::from_iter(deps.iter().filter_map( - |dep| match self.find_type_by_str(dep) { - Some(TypeWalker::Class(cls)) => Some(cls.id), - Some(TypeWalker::Enum(_)) => None, - Some(TypeWalker::TypeAlias(_)) => None, - None => panic!("Unknown class `{dep}`"), - }, - )); - (*id, deps) - }), - )); + let mut resolved_dependency_graph = HashMap::new(); + + for (id, deps) in self.types.class_dependencies.iter() { + let mut resolved_deps = HashSet::new(); + + for dep in deps { + match self.find_type_by_str(dep) { + Some(TypeWalker::Class(cls)) => { + resolved_deps.insert(cls.id); + } + Some(TypeWalker::Enum(_)) => {} + // Gotta resolve type aliases. + Some(TypeWalker::TypeAlias(alias)) => { + resolved_deps.extend(alias.resolved().flat_idns().iter().map(|ident| { + match self.find_type_by_str(ident.name()) { + Some(TypeWalker::Class(cls)) => cls.id, + Some(TypeWalker::Enum(_)) => { + panic!("Enums are not allowed in type aliases") + } + Some(TypeWalker::TypeAlias(alias)) => { + panic!("Alias should be resolved at this point") + } + None => panic!("Unknown class `{dep}`"), + } + })) + } + None => panic!("Unknown class `{dep}`"), + } + } + + resolved_dependency_graph.insert(*id, resolved_deps); + } - // Inject finite cycles into parser DB. This will then be passed into - // the IR and then into the Jinja output format. - self.types.finite_recursive_cycles = finite_cycles - .into_iter() - .map(|cycle| cycle.into_iter().collect()) - .collect(); + // Find the cycles and inject them into parser DB. This will then be + // passed into the IR and then into the Jinja output format. + self.types.finite_recursive_cycles = Tarjan::components(&resolved_dependency_graph); // Fully resolve function dependencies. let extends = self From c73fa4c3f9d22d1c185302f8dd3885825ea3f275 Mon Sep 17 00:00:00 2001 From: Antonio Sarosi <sarosiantonio@gmail.com> Date: Thu, 5 Dec 2024 17:10:27 +0100 Subject: [PATCH 35/51] Add more tests and fix bugs --- .../jinja-runtime/src/output_format/types.rs | 9 +- engine/baml-lib/parser-database/src/lib.rs | 3 + .../prompt_renderer/render_output_format.rs | 8 ++ .../output/recursive-type-aliases.baml | 38 ++++- .../python/baml_client/async_client.py | 106 ++++++++++++++ integ-tests/python/baml_client/inlinedbaml.py | 2 +- .../python/baml_client/partial_types.py | 7 + integ-tests/python/baml_client/sync_client.py | 106 ++++++++++++++ .../python/baml_client/type_builder.py | 2 +- integ-tests/python/baml_client/types.py | 7 + integ-tests/python/tests/test_functions.py | 20 +++ integ-tests/ruby/baml_client/client.rb | 134 ++++++++++++++++++ integ-tests/ruby/baml_client/inlined.rb | 2 +- integ-tests/ruby/baml_client/partial-types.rb | 28 ++++ integ-tests/ruby/baml_client/type-registry.rb | 2 +- integ-tests/ruby/baml_client/types.rb | 28 ++++ integ-tests/ruby/test_functions.rb | 19 +++ .../typescript/baml_client/async_client.ts | 118 ++++++++++++++- .../typescript/baml_client/inlinedbaml.ts | 2 +- .../typescript/baml_client/sync_client.ts | 52 ++++++- .../typescript/baml_client/type_builder.ts | 2 +- integ-tests/typescript/baml_client/types.ts | 11 ++ integ-tests/typescript/test-report.html | 8 +- .../typescript/tests/integ-tests.test.ts | 10 ++ 24 files changed, 709 insertions(+), 15 deletions(-) diff --git a/engine/baml-lib/jinja-runtime/src/output_format/types.rs b/engine/baml-lib/jinja-runtime/src/output_format/types.rs index 992f46c20..d08b929c5 100644 --- a/engine/baml-lib/jinja-runtime/src/output_format/types.rs +++ b/engine/baml-lib/jinja-runtime/src/output_format/types.rs @@ -484,9 +484,12 @@ impl OutputFormatContent { } .to_string() } - FieldType::Alias { resolution, .. } => { - self.inner_type_render(options, &resolution, render_state, group_hoisted_literals)? - } + FieldType::Alias { resolution, .. } => self.render_possibly_recursive_type( + options, + &resolution, + render_state, + group_hoisted_literals, + )?, FieldType::List(inner) => { let is_recursive = match inner.as_ref() { FieldType::Class(nested_class) => self.recursive_classes.contains(nested_class), diff --git a/engine/baml-lib/parser-database/src/lib.rs b/engine/baml-lib/parser-database/src/lib.rs index cd4f42a94..b830f3ec4 100644 --- a/engine/baml-lib/parser-database/src/lib.rs +++ b/engine/baml-lib/parser-database/src/lib.rs @@ -194,6 +194,9 @@ impl ParserDatabase { // Find the cycles and inject them into parser DB. This will then be // passed into the IR and then into the Jinja output format. + // + // TODO: Should we update `class_dependencies` to include resolved + // aliases or not? self.types.finite_recursive_cycles = Tarjan::components(&resolved_dependency_graph); // Fully resolve function dependencies. diff --git a/engine/baml-runtime/src/internal/prompt_renderer/render_output_format.rs b/engine/baml-runtime/src/internal/prompt_renderer/render_output_format.rs index eca39046a..1e8e8c57f 100644 --- a/engine/baml-runtime/src/internal/prompt_renderer/render_output_format.rs +++ b/engine/baml-runtime/src/internal/prompt_renderer/render_output_format.rs @@ -365,6 +365,14 @@ fn relevant_data_models<'a>( constraints, }); } else { + // TODO: @antonio This one was nasty! If aliases are not + // resolved in the `ir.finite_recursive_cycles()` function + // then an alias that points to a recursive class will get + // resolved below and then this code will run, introducing + // a recursive class in the relevant data models that does + // not exist in the IR although it should!. Now it's been + // fixed so this should be safe to remove, it wasn't even + // a bug it was "why is this working when IT SHOULD NOT". recursive_classes.insert(cls.to_owned()); } } diff --git a/integ-tests/baml_src/test-files/functions/output/recursive-type-aliases.baml b/integ-tests/baml_src/test-files/functions/output/recursive-type-aliases.baml index 3446f6423..a875f567f 100644 --- a/integ-tests/baml_src/test-files/functions/output/recursive-type-aliases.baml +++ b/integ-tests/baml_src/test-files/functions/output/recursive-type-aliases.baml @@ -1,9 +1,9 @@ -// Simple alias that points to recursive type. class LinkedListAliasNode { value int next LinkedListAliasNode? } +// Simple alias that points to recursive type. type LinkedListAlias = LinkedListAliasNode function AliasThatPointsToRecursiveType(list: LinkedListAlias) -> LinkedListAlias { @@ -16,3 +16,39 @@ function AliasThatPointsToRecursiveType(list: LinkedListAlias) -> LinkedListAlia {{ ctx.output_format }} "# } + +// Class that points to an alias that points to a recursive type. +class ClassToRecAlias { + list LinkedListAlias +} + +function ClassThatPointsToRecursiveClassThroughAlias(cls: ClassToRecAlias) -> ClassToRecAlias { + client "openai/gpt-4o" + prompt r#" + Return the given object back: + + {{ cls }} + + {{ ctx.output_format }} + "# +} + +// This is tricky cause this class should be hoisted, but classes and aliases +// are two different types in the AST. This test will make sure they can interop. +class NodeWithAliasIndirection { + value int + next NodeIndirection? +} + +type NodeIndirection = NodeWithAliasIndirection + +function RecursiveClassWithAliasIndirection(cls: NodeWithAliasIndirection) -> NodeWithAliasIndirection { + client "openai/gpt-4o" + prompt r#" + Return the given object back: + + {{ cls }} + + {{ ctx.output_format }} + "# +} \ No newline at end of file diff --git a/integ-tests/python/baml_client/async_client.py b/integ-tests/python/baml_client/async_client.py index 4f542e68e..8a11ddf4c 100644 --- a/integ-tests/python/baml_client/async_client.py +++ b/integ-tests/python/baml_client/async_client.py @@ -280,6 +280,29 @@ async def BuildTree( ) return cast(types.Tree, raw.cast_to(types, types)) + async def ClassThatPointsToRecursiveClassThroughAlias( + self, + cls: types.ClassToRecAlias, + baml_options: BamlCallOptions = {}, + ) -> types.ClassToRecAlias: + __tb__ = baml_options.get("tb", None) + if __tb__ is not None: + tb = __tb__._tb # type: ignore (we know how to use this private attribute) + else: + tb = None + __cr__ = baml_options.get("client_registry", None) + + raw = await self.__runtime.call_function( + "ClassThatPointsToRecursiveClassThroughAlias", + { + "cls": cls, + }, + self.__ctx_manager.get(), + tb, + __cr__, + ) + return cast(types.ClassToRecAlias, raw.cast_to(types, types)) + async def ClassifyDynEnumTwo( self, input: str, @@ -1798,6 +1821,29 @@ async def PromptTestStreaming( ) return cast(str, raw.cast_to(types, types)) + async def RecursiveClassWithAliasIndirection( + self, + cls: types.NodeWithAliasIndirection, + baml_options: BamlCallOptions = {}, + ) -> types.NodeWithAliasIndirection: + __tb__ = baml_options.get("tb", None) + if __tb__ is not None: + tb = __tb__._tb # type: ignore (we know how to use this private attribute) + else: + tb = None + __cr__ = baml_options.get("client_registry", None) + + raw = await self.__runtime.call_function( + "RecursiveClassWithAliasIndirection", + { + "cls": cls, + }, + self.__ctx_manager.get(), + tb, + __cr__, + ) + return cast(types.NodeWithAliasIndirection, raw.cast_to(types, types)) + async def ReturnFailingAssert( self, inp: int, @@ -3167,6 +3213,36 @@ def BuildTree( self.__ctx_manager.get(), ) + def ClassThatPointsToRecursiveClassThroughAlias( + self, + cls: types.ClassToRecAlias, + baml_options: BamlCallOptions = {}, + ) -> baml_py.BamlStream[partial_types.ClassToRecAlias, types.ClassToRecAlias]: + __tb__ = baml_options.get("tb", None) + if __tb__ is not None: + tb = __tb__._tb # type: ignore (we know how to use this private attribute) + else: + tb = None + __cr__ = baml_options.get("client_registry", None) + + raw = self.__runtime.stream_function( + "ClassThatPointsToRecursiveClassThroughAlias", + { + "cls": cls, + }, + None, + self.__ctx_manager.get(), + tb, + __cr__, + ) + + return baml_py.BamlStream[partial_types.ClassToRecAlias, types.ClassToRecAlias]( + raw, + lambda x: cast(partial_types.ClassToRecAlias, x.cast_to(types, partial_types)), + lambda x: cast(types.ClassToRecAlias, x.cast_to(types, types)), + self.__ctx_manager.get(), + ) + def ClassifyDynEnumTwo( self, input: str, @@ -5152,6 +5228,36 @@ def PromptTestStreaming( self.__ctx_manager.get(), ) + def RecursiveClassWithAliasIndirection( + self, + cls: types.NodeWithAliasIndirection, + baml_options: BamlCallOptions = {}, + ) -> baml_py.BamlStream[partial_types.NodeWithAliasIndirection, types.NodeWithAliasIndirection]: + __tb__ = baml_options.get("tb", None) + if __tb__ is not None: + tb = __tb__._tb # type: ignore (we know how to use this private attribute) + else: + tb = None + __cr__ = baml_options.get("client_registry", None) + + raw = self.__runtime.stream_function( + "RecursiveClassWithAliasIndirection", + { + "cls": cls, + }, + None, + self.__ctx_manager.get(), + tb, + __cr__, + ) + + return baml_py.BamlStream[partial_types.NodeWithAliasIndirection, types.NodeWithAliasIndirection]( + raw, + lambda x: cast(partial_types.NodeWithAliasIndirection, x.cast_to(types, partial_types)), + lambda x: cast(types.NodeWithAliasIndirection, x.cast_to(types, types)), + self.__ctx_manager.get(), + ) + def ReturnFailingAssert( self, inp: int, diff --git a/integ-tests/python/baml_client/inlinedbaml.py b/integ-tests/python/baml_client/inlinedbaml.py index 0725494b2..5323c1d02 100644 --- a/integ-tests/python/baml_client/inlinedbaml.py +++ b/integ-tests/python/baml_client/inlinedbaml.py @@ -80,7 +80,7 @@ "test-files/functions/output/optional-class.baml": "class ClassOptionalOutput {\n prop1 string\n prop2 string\n}\n\nfunction FnClassOptionalOutput(input: string) -> ClassOptionalOutput? {\n client GPT35\n prompt #\"\n Return a json blob for the following input:\n {{input}}\n\n {{ctx.output_format}}\n\n JSON:\n \"#\n}\n\n\nclass Blah {\n prop4 string?\n}\n\nclass ClassOptionalOutput2 {\n prop1 string?\n prop2 string?\n prop3 Blah?\n}\n\nfunction FnClassOptionalOutput2(input: string) -> ClassOptionalOutput2? {\n client GPT35\n prompt #\"\n Return a json blob for the following input:\n {{input}}\n\n {{ctx.output_format}}\n\n JSON:\n \"#\n}\n\ntest FnClassOptionalOutput2 {\n functions [FnClassOptionalOutput2, FnClassOptionalOutput]\n args {\n input \"example input\"\n }\n}\n", "test-files/functions/output/optional.baml": "class OptionalTest_Prop1 {\n omega_a string\n omega_b int\n}\n\nenum OptionalTest_CategoryType {\n Aleph\n Beta\n Gamma\n}\n \nclass OptionalTest_ReturnType {\n omega_1 OptionalTest_Prop1?\n omega_2 string?\n omega_3 (OptionalTest_CategoryType?)[]\n} \n \nfunction OptionalTest_Function(input: string) -> (OptionalTest_ReturnType?)[]\n{ \n client GPT35\n prompt #\"\n Return a JSON blob with this schema: \n {{ctx.output_format}}\n\n JSON:\n \"#\n}\n\ntest OptionalTest_Function {\n functions [OptionalTest_Function]\n args {\n input \"example input\"\n }\n}\n", "test-files/functions/output/recursive-class.baml": "class Node {\n data int\n next Node?\n}\n\nclass LinkedList {\n head Node?\n len int\n}\n\nclient<llm> O1 {\n provider \"openai\"\n options {\n model \"o1-mini\"\n default_role \"user\"\n }\n}\n\nfunction BuildLinkedList(input: int[]) -> LinkedList {\n client O1\n prompt #\"\n Build a linked list from the input array of integers.\n\n INPUT:\n {{ input }}\n\n {{ ctx.output_format }} \n \"#\n}\n\ntest TestLinkedList {\n functions [BuildLinkedList]\n args {\n input [1, 2, 3, 4, 5]\n }\n}\n", - "test-files/functions/output/recursive-type-aliases.baml": "// Simple alias that points to recursive type.\nclass LinkedListAliasNode {\n value int\n next LinkedListAliasNode?\n}\n\ntype LinkedListAlias = LinkedListAliasNode\n\nfunction AliasThatPointsToRecursiveType(list: LinkedListAlias) -> LinkedListAlias {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given linked list back:\n \n {{ list }}\n \n {{ ctx.output_format }}\n \"#\n}\n", + "test-files/functions/output/recursive-type-aliases.baml": "class LinkedListAliasNode {\n value int\n next LinkedListAliasNode?\n}\n\n// Simple alias that points to recursive type.\ntype LinkedListAlias = LinkedListAliasNode\n\nfunction AliasThatPointsToRecursiveType(list: LinkedListAlias) -> LinkedListAlias {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given linked list back:\n \n {{ list }}\n \n {{ ctx.output_format }}\n \"#\n}\n\n// Class that points to an alias that points to a recursive type.\nclass ClassToRecAlias {\n list LinkedListAlias\n}\n\nfunction ClassThatPointsToRecursiveClassThroughAlias(cls: ClassToRecAlias) -> ClassToRecAlias {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given object back:\n \n {{ cls }}\n \n {{ ctx.output_format }}\n \"#\n}\n\n// This is tricky cause this class should be hoisted, but classes and aliases\n// are two different types in the AST. This test will make sure they can interop.\nclass NodeWithAliasIndirection {\n value int\n next NodeIndirection?\n}\n\ntype NodeIndirection = NodeWithAliasIndirection\n\nfunction RecursiveClassWithAliasIndirection(cls: NodeWithAliasIndirection) -> NodeWithAliasIndirection {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given object back:\n \n {{ cls }}\n \n {{ ctx.output_format }}\n \"#\n}", "test-files/functions/output/serialization-error.baml": "class DummyOutput {\n nonce string\n nonce2 string\n @@dynamic\n}\n\nfunction DummyOutputFunction(input: string) -> DummyOutput {\n client GPT35\n prompt #\"\n Say \"hello there\".\n \"#\n}", "test-files/functions/output/string-list.baml": "function FnOutputStringList(input: string) -> string[] {\n client GPT35\n prompt #\"\n Return a list of strings in json format like [\"string1\", \"string2\", \"string3\"].\n\n JSON:\n \"#\n}\n\ntest FnOutputStringList {\n functions [FnOutputStringList]\n args {\n input \"example input\"\n }\n}\n", "test-files/functions/output/type-aliases.baml": "type Primitive = int | string | bool | float\n\ntype List = string[]\n\ntype Graph = map<string, string[]>\n\ntype Combination = Primitive | List | Graph\n\nfunction PrimitiveAlias(p: Primitive) -> Primitive {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given value back: {{ p }}\n \"#\n}\n\nfunction MapAlias(m: Graph) -> Graph {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given Graph back:\n\n {{ m }}\n\n {{ ctx.output_format }}\n \"#\n}\n\nfunction NestedAlias(c: Combination) -> Combination {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given value back:\n\n {{ c }}\n\n {{ ctx.output_format }}\n \"#\n}\n", diff --git a/integ-tests/python/baml_client/partial_types.py b/integ-tests/python/baml_client/partial_types.py index 67465e54d..6d5219e3e 100644 --- a/integ-tests/python/baml_client/partial_types.py +++ b/integ-tests/python/baml_client/partial_types.py @@ -64,6 +64,9 @@ class ClassOptionalOutput2(BaseModel): prop2: Optional[str] = None prop3: Optional["Blah"] = None +class ClassToRecAlias(BaseModel): + list: Optional["LinkedListAliasNode"] = None + class ClassWithImage(BaseModel): myImage: Optional[baml_py.Image] = None param2: Optional[str] = None @@ -223,6 +226,10 @@ class Node(BaseModel): data: Optional[int] = None next: Optional["Node"] = None +class NodeWithAliasIndirection(BaseModel): + value: Optional[int] = None + next: Optional["NodeWithAliasIndirection"] = None + class OptionalTest_Prop1(BaseModel): omega_a: Optional[str] = None omega_b: Optional[int] = None diff --git a/integ-tests/python/baml_client/sync_client.py b/integ-tests/python/baml_client/sync_client.py index 5313bae25..3c2197718 100644 --- a/integ-tests/python/baml_client/sync_client.py +++ b/integ-tests/python/baml_client/sync_client.py @@ -277,6 +277,29 @@ def BuildTree( ) return cast(types.Tree, raw.cast_to(types, types)) + def ClassThatPointsToRecursiveClassThroughAlias( + self, + cls: types.ClassToRecAlias, + baml_options: BamlCallOptions = {}, + ) -> types.ClassToRecAlias: + __tb__ = baml_options.get("tb", None) + if __tb__ is not None: + tb = __tb__._tb # type: ignore (we know how to use this private attribute) + else: + tb = None + __cr__ = baml_options.get("client_registry", None) + + raw = self.__runtime.call_function_sync( + "ClassThatPointsToRecursiveClassThroughAlias", + { + "cls": cls, + }, + self.__ctx_manager.get(), + tb, + __cr__, + ) + return cast(types.ClassToRecAlias, raw.cast_to(types, types)) + def ClassifyDynEnumTwo( self, input: str, @@ -1795,6 +1818,29 @@ def PromptTestStreaming( ) return cast(str, raw.cast_to(types, types)) + def RecursiveClassWithAliasIndirection( + self, + cls: types.NodeWithAliasIndirection, + baml_options: BamlCallOptions = {}, + ) -> types.NodeWithAliasIndirection: + __tb__ = baml_options.get("tb", None) + if __tb__ is not None: + tb = __tb__._tb # type: ignore (we know how to use this private attribute) + else: + tb = None + __cr__ = baml_options.get("client_registry", None) + + raw = self.__runtime.call_function_sync( + "RecursiveClassWithAliasIndirection", + { + "cls": cls, + }, + self.__ctx_manager.get(), + tb, + __cr__, + ) + return cast(types.NodeWithAliasIndirection, raw.cast_to(types, types)) + def ReturnFailingAssert( self, inp: int, @@ -3165,6 +3211,36 @@ def BuildTree( self.__ctx_manager.get(), ) + def ClassThatPointsToRecursiveClassThroughAlias( + self, + cls: types.ClassToRecAlias, + baml_options: BamlCallOptions = {}, + ) -> baml_py.BamlSyncStream[partial_types.ClassToRecAlias, types.ClassToRecAlias]: + __tb__ = baml_options.get("tb", None) + if __tb__ is not None: + tb = __tb__._tb # type: ignore (we know how to use this private attribute) + else: + tb = None + __cr__ = baml_options.get("client_registry", None) + + raw = self.__runtime.stream_function_sync( + "ClassThatPointsToRecursiveClassThroughAlias", + { + "cls": cls, + }, + None, + self.__ctx_manager.get(), + tb, + __cr__, + ) + + return baml_py.BamlSyncStream[partial_types.ClassToRecAlias, types.ClassToRecAlias]( + raw, + lambda x: cast(partial_types.ClassToRecAlias, x.cast_to(types, partial_types)), + lambda x: cast(types.ClassToRecAlias, x.cast_to(types, types)), + self.__ctx_manager.get(), + ) + def ClassifyDynEnumTwo( self, input: str, @@ -5150,6 +5226,36 @@ def PromptTestStreaming( self.__ctx_manager.get(), ) + def RecursiveClassWithAliasIndirection( + self, + cls: types.NodeWithAliasIndirection, + baml_options: BamlCallOptions = {}, + ) -> baml_py.BamlSyncStream[partial_types.NodeWithAliasIndirection, types.NodeWithAliasIndirection]: + __tb__ = baml_options.get("tb", None) + if __tb__ is not None: + tb = __tb__._tb # type: ignore (we know how to use this private attribute) + else: + tb = None + __cr__ = baml_options.get("client_registry", None) + + raw = self.__runtime.stream_function_sync( + "RecursiveClassWithAliasIndirection", + { + "cls": cls, + }, + None, + self.__ctx_manager.get(), + tb, + __cr__, + ) + + return baml_py.BamlSyncStream[partial_types.NodeWithAliasIndirection, types.NodeWithAliasIndirection]( + raw, + lambda x: cast(partial_types.NodeWithAliasIndirection, x.cast_to(types, partial_types)), + lambda x: cast(types.NodeWithAliasIndirection, x.cast_to(types, types)), + self.__ctx_manager.get(), + ) + def ReturnFailingAssert( self, inp: int, diff --git a/integ-tests/python/baml_client/type_builder.py b/integ-tests/python/baml_client/type_builder.py index 913d8e74d..03de2a5e9 100644 --- a/integ-tests/python/baml_client/type_builder.py +++ b/integ-tests/python/baml_client/type_builder.py @@ -20,7 +20,7 @@ class TypeBuilder(_TypeBuilder): def __init__(self): super().__init__(classes=set( - ["BigNumbers","BinaryNode","Blah","BlockConstraint","BlockConstraintForParam","BookOrder","ClassOptionalOutput","ClassOptionalOutput2","ClassWithImage","CompoundBigNumbers","ContactInfo","CustomTaskResult","DummyOutput","DynInputOutput","DynamicClassOne","DynamicClassTwo","DynamicOutput","Earthling","Education","Email","EmailAddress","Event","FakeImage","FlightConfirmation","FooAny","Forest","GroceryReceipt","InnerClass","InnerClass2","InputClass","InputClassNested","LinkedList","LinkedListAliasNode","LiteralClassHello","LiteralClassOne","LiteralClassTwo","MalformedConstraints","MalformedConstraints2","Martian","NamedArgsSingleClass","Nested","Nested2","NestedBlockConstraint","NestedBlockConstraintForParam","Node","OptionalTest_Prop1","OptionalTest_ReturnType","OrderInfo","OriginalA","OriginalB","Person","PhoneNumber","Quantity","RaysData","ReceiptInfo","ReceiptItem","Recipe","Resume","Schema","SearchParams","SomeClassNestedDynamic","StringToClassEntry","TestClassAlias","TestClassNested","TestClassWithEnum","TestOutputClass","Tree","TwoStoriesOneTitle","UnionTest_ReturnType","WithReasoning",] + ["BigNumbers","BinaryNode","Blah","BlockConstraint","BlockConstraintForParam","BookOrder","ClassOptionalOutput","ClassOptionalOutput2","ClassToRecAlias","ClassWithImage","CompoundBigNumbers","ContactInfo","CustomTaskResult","DummyOutput","DynInputOutput","DynamicClassOne","DynamicClassTwo","DynamicOutput","Earthling","Education","Email","EmailAddress","Event","FakeImage","FlightConfirmation","FooAny","Forest","GroceryReceipt","InnerClass","InnerClass2","InputClass","InputClassNested","LinkedList","LinkedListAliasNode","LiteralClassHello","LiteralClassOne","LiteralClassTwo","MalformedConstraints","MalformedConstraints2","Martian","NamedArgsSingleClass","Nested","Nested2","NestedBlockConstraint","NestedBlockConstraintForParam","Node","NodeWithAliasIndirection","OptionalTest_Prop1","OptionalTest_ReturnType","OrderInfo","OriginalA","OriginalB","Person","PhoneNumber","Quantity","RaysData","ReceiptInfo","ReceiptItem","Recipe","Resume","Schema","SearchParams","SomeClassNestedDynamic","StringToClassEntry","TestClassAlias","TestClassNested","TestClassWithEnum","TestOutputClass","Tree","TwoStoriesOneTitle","UnionTest_ReturnType","WithReasoning",] ), enums=set( ["AliasedEnum","Category","Category2","Category3","Color","DataType","DynEnumOne","DynEnumTwo","EnumInClass","EnumOutput","Hobby","MapKey","NamedArgsSingleEnum","NamedArgsSingleEnumList","OptionalTest_CategoryType","OrderStatus","Tag","TestEnum",] )) diff --git a/integ-tests/python/baml_client/types.py b/integ-tests/python/baml_client/types.py index 1b65032e9..21545e11f 100644 --- a/integ-tests/python/baml_client/types.py +++ b/integ-tests/python/baml_client/types.py @@ -189,6 +189,9 @@ class ClassOptionalOutput2(BaseModel): prop2: Optional[str] = None prop3: Optional["Blah"] = None +class ClassToRecAlias(BaseModel): + list: "LinkedListAliasNode" + class ClassWithImage(BaseModel): myImage: baml_py.Image param2: str @@ -348,6 +351,10 @@ class Node(BaseModel): data: int next: Optional["Node"] = None +class NodeWithAliasIndirection(BaseModel): + value: int + next: Optional["NodeWithAliasIndirection"] = None + class OptionalTest_Prop1(BaseModel): omega_a: str omega_b: int diff --git a/integ-tests/python/tests/test_functions.py b/integ-tests/python/tests/test_functions.py index 46c67ae3b..2838e8331 100644 --- a/integ-tests/python/tests/test_functions.py +++ b/integ-tests/python/tests/test_functions.py @@ -41,6 +41,8 @@ NestedBlockConstraintForParam, MapKey, LinkedListAliasNode, + ClassToRecAlias, + NodeWithAliasIndirection, ) import baml_client.types as types from ..baml_client.tracing import trace, set_tags, flush, on_log_event @@ -275,6 +277,24 @@ async def test_alias_pointing_to_recursive_class(self): ) assert res == LinkedListAliasNode(value=1, next=None) + @pytest.mark.asyncio + async def test_class_pointing_to_alias_that_points_to_recursive_class(self): + res = await b.ClassThatPointsToRecursiveClassThroughAlias( + ClassToRecAlias(list=LinkedListAliasNode(value=1, next=None)) + ) + assert res == ClassToRecAlias(list=LinkedListAliasNode(value=1, next=None)) + + @pytest.mark.asyncio + async def test_recursive_class_with_alias_indirection(self): + res = await b.RecursiveClassWithAliasIndirection( + NodeWithAliasIndirection( + value=1, next=NodeWithAliasIndirection(value=2, next=None) + ) + ) + assert res == NodeWithAliasIndirection( + value=1, next=NodeWithAliasIndirection(value=2, next=None) + ) + class MyCustomClass(NamedArgsSingleClass): date: datetime.datetime diff --git a/integ-tests/ruby/baml_client/client.rb b/integ-tests/ruby/baml_client/client.rb index b56a6fce2..b2fb18bad 100644 --- a/integ-tests/ruby/baml_client/client.rb +++ b/integ-tests/ruby/baml_client/client.rb @@ -370,6 +370,38 @@ def BuildTree( (raw.parsed_using_types(Baml::Types)) end + sig { + params( + varargs: T.untyped, + cls: Baml::Types::ClassToRecAlias, + baml_options: T::Hash[Symbol, T.any(Baml::TypeBuilder, Baml::ClientRegistry)] + ).returns(Baml::Types::ClassToRecAlias) + } + def ClassThatPointsToRecursiveClassThroughAlias( + *varargs, + cls:, + baml_options: {} + ) + if varargs.any? + + raise ArgumentError.new("ClassThatPointsToRecursiveClassThroughAlias may only be called with keyword arguments") + end + if (baml_options.keys - [:client_registry, :tb]).any? + raise ArgumentError.new("Received unknown keys in baml_options (valid keys: :client_registry, :tb): #{baml_options.keys - [:client_registry, :tb]}") + end + + raw = @runtime.call_function( + "ClassThatPointsToRecursiveClassThroughAlias", + { + cls: cls, + }, + @ctx_manager, + baml_options[:tb]&.instance_variable_get(:@registry), + baml_options[:client_registry], + ) + (raw.parsed_using_types(Baml::Types)) + end + sig { params( varargs: T.untyped, @@ -2482,6 +2514,38 @@ def PromptTestStreaming( (raw.parsed_using_types(Baml::Types)) end + sig { + params( + varargs: T.untyped, + cls: Baml::Types::NodeWithAliasIndirection, + baml_options: T::Hash[Symbol, T.any(Baml::TypeBuilder, Baml::ClientRegistry)] + ).returns(Baml::Types::NodeWithAliasIndirection) + } + def RecursiveClassWithAliasIndirection( + *varargs, + cls:, + baml_options: {} + ) + if varargs.any? + + raise ArgumentError.new("RecursiveClassWithAliasIndirection may only be called with keyword arguments") + end + if (baml_options.keys - [:client_registry, :tb]).any? + raise ArgumentError.new("Received unknown keys in baml_options (valid keys: :client_registry, :tb): #{baml_options.keys - [:client_registry, :tb]}") + end + + raw = @runtime.call_function( + "RecursiveClassWithAliasIndirection", + { + cls: cls, + }, + @ctx_manager, + baml_options[:tb]&.instance_variable_get(:@registry), + baml_options[:client_registry], + ) + (raw.parsed_using_types(Baml::Types)) + end + sig { params( varargs: T.untyped, @@ -4317,6 +4381,41 @@ def BuildTree( ) end + sig { + params( + varargs: T.untyped, + cls: Baml::Types::ClassToRecAlias, + baml_options: T::Hash[Symbol, T.any(Baml::TypeBuilder, Baml::ClientRegistry)] + ).returns(Baml::BamlStream[Baml::Types::ClassToRecAlias]) + } + def ClassThatPointsToRecursiveClassThroughAlias( + *varargs, + cls:, + baml_options: {} + ) + if varargs.any? + + raise ArgumentError.new("ClassThatPointsToRecursiveClassThroughAlias may only be called with keyword arguments") + end + if (baml_options.keys - [:client_registry, :tb]).any? + raise ArgumentError.new("Received unknown keys in baml_options (valid keys: :client_registry, :tb): #{baml_options.keys - [:client_registry, :tb]}") + end + + raw = @runtime.stream_function( + "ClassThatPointsToRecursiveClassThroughAlias", + { + cls: cls, + }, + @ctx_manager, + baml_options[:tb]&.instance_variable_get(:@registry), + baml_options[:client_registry], + ) + Baml::BamlStream[Baml::PartialTypes::ClassToRecAlias, Baml::Types::ClassToRecAlias].new( + ffi_stream: raw, + ctx_manager: @ctx_manager + ) + end + sig { params( varargs: T.untyped, @@ -6627,6 +6726,41 @@ def PromptTestStreaming( ) end + sig { + params( + varargs: T.untyped, + cls: Baml::Types::NodeWithAliasIndirection, + baml_options: T::Hash[Symbol, T.any(Baml::TypeBuilder, Baml::ClientRegistry)] + ).returns(Baml::BamlStream[Baml::Types::NodeWithAliasIndirection]) + } + def RecursiveClassWithAliasIndirection( + *varargs, + cls:, + baml_options: {} + ) + if varargs.any? + + raise ArgumentError.new("RecursiveClassWithAliasIndirection may only be called with keyword arguments") + end + if (baml_options.keys - [:client_registry, :tb]).any? + raise ArgumentError.new("Received unknown keys in baml_options (valid keys: :client_registry, :tb): #{baml_options.keys - [:client_registry, :tb]}") + end + + raw = @runtime.stream_function( + "RecursiveClassWithAliasIndirection", + { + cls: cls, + }, + @ctx_manager, + baml_options[:tb]&.instance_variable_get(:@registry), + baml_options[:client_registry], + ) + Baml::BamlStream[Baml::PartialTypes::NodeWithAliasIndirection, Baml::Types::NodeWithAliasIndirection].new( + ffi_stream: raw, + ctx_manager: @ctx_manager + ) + end + sig { params( varargs: T.untyped, diff --git a/integ-tests/ruby/baml_client/inlined.rb b/integ-tests/ruby/baml_client/inlined.rb index 50eade883..9be182f56 100644 --- a/integ-tests/ruby/baml_client/inlined.rb +++ b/integ-tests/ruby/baml_client/inlined.rb @@ -80,7 +80,7 @@ module Inlined "test-files/functions/output/optional-class.baml" => "class ClassOptionalOutput {\n prop1 string\n prop2 string\n}\n\nfunction FnClassOptionalOutput(input: string) -> ClassOptionalOutput? {\n client GPT35\n prompt #\"\n Return a json blob for the following input:\n {{input}}\n\n {{ctx.output_format}}\n\n JSON:\n \"#\n}\n\n\nclass Blah {\n prop4 string?\n}\n\nclass ClassOptionalOutput2 {\n prop1 string?\n prop2 string?\n prop3 Blah?\n}\n\nfunction FnClassOptionalOutput2(input: string) -> ClassOptionalOutput2? {\n client GPT35\n prompt #\"\n Return a json blob for the following input:\n {{input}}\n\n {{ctx.output_format}}\n\n JSON:\n \"#\n}\n\ntest FnClassOptionalOutput2 {\n functions [FnClassOptionalOutput2, FnClassOptionalOutput]\n args {\n input \"example input\"\n }\n}\n", "test-files/functions/output/optional.baml" => "class OptionalTest_Prop1 {\n omega_a string\n omega_b int\n}\n\nenum OptionalTest_CategoryType {\n Aleph\n Beta\n Gamma\n}\n \nclass OptionalTest_ReturnType {\n omega_1 OptionalTest_Prop1?\n omega_2 string?\n omega_3 (OptionalTest_CategoryType?)[]\n} \n \nfunction OptionalTest_Function(input: string) -> (OptionalTest_ReturnType?)[]\n{ \n client GPT35\n prompt #\"\n Return a JSON blob with this schema: \n {{ctx.output_format}}\n\n JSON:\n \"#\n}\n\ntest OptionalTest_Function {\n functions [OptionalTest_Function]\n args {\n input \"example input\"\n }\n}\n", "test-files/functions/output/recursive-class.baml" => "class Node {\n data int\n next Node?\n}\n\nclass LinkedList {\n head Node?\n len int\n}\n\nclient<llm> O1 {\n provider \"openai\"\n options {\n model \"o1-mini\"\n default_role \"user\"\n }\n}\n\nfunction BuildLinkedList(input: int[]) -> LinkedList {\n client O1\n prompt #\"\n Build a linked list from the input array of integers.\n\n INPUT:\n {{ input }}\n\n {{ ctx.output_format }} \n \"#\n}\n\ntest TestLinkedList {\n functions [BuildLinkedList]\n args {\n input [1, 2, 3, 4, 5]\n }\n}\n", - "test-files/functions/output/recursive-type-aliases.baml" => "// Simple alias that points to recursive type.\nclass LinkedListAliasNode {\n value int\n next LinkedListAliasNode?\n}\n\ntype LinkedListAlias = LinkedListAliasNode\n\nfunction AliasThatPointsToRecursiveType(list: LinkedListAlias) -> LinkedListAlias {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given linked list back:\n \n {{ list }}\n \n {{ ctx.output_format }}\n \"#\n}\n", + "test-files/functions/output/recursive-type-aliases.baml" => "class LinkedListAliasNode {\n value int\n next LinkedListAliasNode?\n}\n\n// Simple alias that points to recursive type.\ntype LinkedListAlias = LinkedListAliasNode\n\nfunction AliasThatPointsToRecursiveType(list: LinkedListAlias) -> LinkedListAlias {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given linked list back:\n \n {{ list }}\n \n {{ ctx.output_format }}\n \"#\n}\n\n// Class that points to an alias that points to a recursive type.\nclass ClassToRecAlias {\n list LinkedListAlias\n}\n\nfunction ClassThatPointsToRecursiveClassThroughAlias(cls: ClassToRecAlias) -> ClassToRecAlias {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given object back:\n \n {{ cls }}\n \n {{ ctx.output_format }}\n \"#\n}\n\n// This is tricky cause this class should be hoisted, but classes and aliases\n// are two different types in the AST. This test will make sure they can interop.\nclass NodeWithAliasIndirection {\n value int\n next NodeIndirection?\n}\n\ntype NodeIndirection = NodeWithAliasIndirection\n\nfunction RecursiveClassWithAliasIndirection(cls: NodeWithAliasIndirection) -> NodeWithAliasIndirection {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given object back:\n \n {{ cls }}\n \n {{ ctx.output_format }}\n \"#\n}", "test-files/functions/output/serialization-error.baml" => "class DummyOutput {\n nonce string\n nonce2 string\n @@dynamic\n}\n\nfunction DummyOutputFunction(input: string) -> DummyOutput {\n client GPT35\n prompt #\"\n Say \"hello there\".\n \"#\n}", "test-files/functions/output/string-list.baml" => "function FnOutputStringList(input: string) -> string[] {\n client GPT35\n prompt #\"\n Return a list of strings in json format like [\"string1\", \"string2\", \"string3\"].\n\n JSON:\n \"#\n}\n\ntest FnOutputStringList {\n functions [FnOutputStringList]\n args {\n input \"example input\"\n }\n}\n", "test-files/functions/output/type-aliases.baml" => "type Primitive = int | string | bool | float\n\ntype List = string[]\n\ntype Graph = map<string, string[]>\n\ntype Combination = Primitive | List | Graph\n\nfunction PrimitiveAlias(p: Primitive) -> Primitive {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given value back: {{ p }}\n \"#\n}\n\nfunction MapAlias(m: Graph) -> Graph {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given Graph back:\n\n {{ m }}\n\n {{ ctx.output_format }}\n \"#\n}\n\nfunction NestedAlias(c: Combination) -> Combination {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given value back:\n\n {{ c }}\n\n {{ ctx.output_format }}\n \"#\n}\n", diff --git a/integ-tests/ruby/baml_client/partial-types.rb b/integ-tests/ruby/baml_client/partial-types.rb index 7a37aaa67..7dfdb7448 100644 --- a/integ-tests/ruby/baml_client/partial-types.rb +++ b/integ-tests/ruby/baml_client/partial-types.rb @@ -28,6 +28,7 @@ class BlockConstraintForParam < T::Struct; end class BookOrder < T::Struct; end class ClassOptionalOutput < T::Struct; end class ClassOptionalOutput2 < T::Struct; end + class ClassToRecAlias < T::Struct; end class ClassWithImage < T::Struct; end class CompoundBigNumbers < T::Struct; end class ContactInfo < T::Struct; end @@ -65,6 +66,7 @@ class Nested2 < T::Struct; end class NestedBlockConstraint < T::Struct; end class NestedBlockConstraintForParam < T::Struct; end class Node < T::Struct; end + class NodeWithAliasIndirection < T::Struct; end class OptionalTest_Prop1 < T::Struct; end class OptionalTest_ReturnType < T::Struct; end class OrderInfo < T::Struct; end @@ -208,6 +210,18 @@ def initialize(props) @props = props end end + class ClassToRecAlias < T::Struct + include Baml::Sorbet::Struct + const :list, Baml::PartialTypes::LinkedListAliasNode + + def initialize(props) + super( + list: props[:list], + ) + + @props = props + end + end class ClassWithImage < T::Struct include Baml::Sorbet::Struct const :myImage, T.nilable(Baml::Image) @@ -734,6 +748,20 @@ def initialize(props) @props = props end end + class NodeWithAliasIndirection < T::Struct + include Baml::Sorbet::Struct + const :value, T.nilable(Integer) + const :next, Baml::PartialTypes::NodeWithAliasIndirection + + def initialize(props) + super( + value: props[:value], + next: props[:next], + ) + + @props = props + end + end class OptionalTest_Prop1 < T::Struct include Baml::Sorbet::Struct const :omega_a, T.nilable(String) diff --git a/integ-tests/ruby/baml_client/type-registry.rb b/integ-tests/ruby/baml_client/type-registry.rb index b9b66634a..42fec89ee 100644 --- a/integ-tests/ruby/baml_client/type-registry.rb +++ b/integ-tests/ruby/baml_client/type-registry.rb @@ -18,7 +18,7 @@ module Baml class TypeBuilder def initialize @registry = Baml::Ffi::TypeBuilder.new - @classes = Set[ "BigNumbers", "BinaryNode", "Blah", "BlockConstraint", "BlockConstraintForParam", "BookOrder", "ClassOptionalOutput", "ClassOptionalOutput2", "ClassWithImage", "CompoundBigNumbers", "ContactInfo", "CustomTaskResult", "DummyOutput", "DynInputOutput", "DynamicClassOne", "DynamicClassTwo", "DynamicOutput", "Earthling", "Education", "Email", "EmailAddress", "Event", "FakeImage", "FlightConfirmation", "FooAny", "Forest", "GroceryReceipt", "InnerClass", "InnerClass2", "InputClass", "InputClassNested", "LinkedList", "LinkedListAliasNode", "LiteralClassHello", "LiteralClassOne", "LiteralClassTwo", "MalformedConstraints", "MalformedConstraints2", "Martian", "NamedArgsSingleClass", "Nested", "Nested2", "NestedBlockConstraint", "NestedBlockConstraintForParam", "Node", "OptionalTest_Prop1", "OptionalTest_ReturnType", "OrderInfo", "OriginalA", "OriginalB", "Person", "PhoneNumber", "Quantity", "RaysData", "ReceiptInfo", "ReceiptItem", "Recipe", "Resume", "Schema", "SearchParams", "SomeClassNestedDynamic", "StringToClassEntry", "TestClassAlias", "TestClassNested", "TestClassWithEnum", "TestOutputClass", "Tree", "TwoStoriesOneTitle", "UnionTest_ReturnType", "WithReasoning", ] + @classes = Set[ "BigNumbers", "BinaryNode", "Blah", "BlockConstraint", "BlockConstraintForParam", "BookOrder", "ClassOptionalOutput", "ClassOptionalOutput2", "ClassToRecAlias", "ClassWithImage", "CompoundBigNumbers", "ContactInfo", "CustomTaskResult", "DummyOutput", "DynInputOutput", "DynamicClassOne", "DynamicClassTwo", "DynamicOutput", "Earthling", "Education", "Email", "EmailAddress", "Event", "FakeImage", "FlightConfirmation", "FooAny", "Forest", "GroceryReceipt", "InnerClass", "InnerClass2", "InputClass", "InputClassNested", "LinkedList", "LinkedListAliasNode", "LiteralClassHello", "LiteralClassOne", "LiteralClassTwo", "MalformedConstraints", "MalformedConstraints2", "Martian", "NamedArgsSingleClass", "Nested", "Nested2", "NestedBlockConstraint", "NestedBlockConstraintForParam", "Node", "NodeWithAliasIndirection", "OptionalTest_Prop1", "OptionalTest_ReturnType", "OrderInfo", "OriginalA", "OriginalB", "Person", "PhoneNumber", "Quantity", "RaysData", "ReceiptInfo", "ReceiptItem", "Recipe", "Resume", "Schema", "SearchParams", "SomeClassNestedDynamic", "StringToClassEntry", "TestClassAlias", "TestClassNested", "TestClassWithEnum", "TestOutputClass", "Tree", "TwoStoriesOneTitle", "UnionTest_ReturnType", "WithReasoning", ] @enums = Set[ "AliasedEnum", "Category", "Category2", "Category3", "Color", "DataType", "DynEnumOne", "DynEnumTwo", "EnumInClass", "EnumOutput", "Hobby", "MapKey", "NamedArgsSingleEnum", "NamedArgsSingleEnumList", "OptionalTest_CategoryType", "OrderStatus", "Tag", "TestEnum", ] end diff --git a/integ-tests/ruby/baml_client/types.rb b/integ-tests/ruby/baml_client/types.rb index 0b9eddbdd..f864f2247 100644 --- a/integ-tests/ruby/baml_client/types.rb +++ b/integ-tests/ruby/baml_client/types.rb @@ -153,6 +153,7 @@ class BlockConstraintForParam < T::Struct; end class BookOrder < T::Struct; end class ClassOptionalOutput < T::Struct; end class ClassOptionalOutput2 < T::Struct; end + class ClassToRecAlias < T::Struct; end class ClassWithImage < T::Struct; end class CompoundBigNumbers < T::Struct; end class ContactInfo < T::Struct; end @@ -190,6 +191,7 @@ class Nested2 < T::Struct; end class NestedBlockConstraint < T::Struct; end class NestedBlockConstraintForParam < T::Struct; end class Node < T::Struct; end + class NodeWithAliasIndirection < T::Struct; end class OptionalTest_Prop1 < T::Struct; end class OptionalTest_ReturnType < T::Struct; end class OrderInfo < T::Struct; end @@ -333,6 +335,18 @@ def initialize(props) @props = props end end + class ClassToRecAlias < T::Struct + include Baml::Sorbet::Struct + const :list, Baml::Types::LinkedListAliasNode + + def initialize(props) + super( + list: props[:list], + ) + + @props = props + end + end class ClassWithImage < T::Struct include Baml::Sorbet::Struct const :myImage, Baml::Image @@ -859,6 +873,20 @@ def initialize(props) @props = props end end + class NodeWithAliasIndirection < T::Struct + include Baml::Sorbet::Struct + const :value, Integer + const :next, T.nilable(Baml::Types::NodeWithAliasIndirection) + + def initialize(props) + super( + value: props[:value], + next: props[:next], + ) + + @props = props + end + end class OptionalTest_Prop1 < T::Struct include Baml::Sorbet::Struct const :omega_a, String diff --git a/integ-tests/ruby/test_functions.rb b/integ-tests/ruby/test_functions.rb index 654de2d6e..392cfcd19 100644 --- a/integ-tests/ruby/test_functions.rb +++ b/integ-tests/ruby/test_functions.rb @@ -100,6 +100,25 @@ # value: 1, # next: nil, # ) + + res = b.ClassThatPointsToRecursiveClassThroughAlias( + cls: Baml::Types::ClassToRecAlias.new( + list: Baml::Types::LinkedListAliasNode.new( + value: 1, + next: nil, + ) + ) + ) + + res = b.RecursiveClassWithAliasIndirection.new( + cls: Baml::Types::NodeWithAliasIndirection.new( + value: 1, + next: Baml::Types::NodeWithAliasIndirection.new( + value: 2, + next: nil, + ) + ) + ) end it "accepts subclass of baml type" do diff --git a/integ-tests/typescript/baml_client/async_client.ts b/integ-tests/typescript/baml_client/async_client.ts index 0e37e3503..8c55efc0e 100644 --- a/integ-tests/typescript/baml_client/async_client.ts +++ b/integ-tests/typescript/baml_client/async_client.ts @@ -17,7 +17,7 @@ $ pnpm add @boundaryml/baml // biome-ignore format: autogenerated code import { BamlRuntime, FunctionResult, BamlCtxManager, BamlStream, Image, ClientRegistry, BamlValidationError, createBamlValidationError } from "@boundaryml/baml" import { Checked, Check } from "./types" -import {BigNumbers, BinaryNode, Blah, BlockConstraint, BlockConstraintForParam, BookOrder, ClassOptionalOutput, ClassOptionalOutput2, ClassWithImage, CompoundBigNumbers, ContactInfo, CustomTaskResult, DummyOutput, DynInputOutput, DynamicClassOne, DynamicClassTwo, DynamicOutput, Earthling, Education, Email, EmailAddress, Event, FakeImage, FlightConfirmation, FooAny, Forest, GroceryReceipt, InnerClass, InnerClass2, InputClass, InputClassNested, LinkedList, LinkedListAliasNode, LiteralClassHello, LiteralClassOne, LiteralClassTwo, MalformedConstraints, MalformedConstraints2, Martian, NamedArgsSingleClass, Nested, Nested2, NestedBlockConstraint, NestedBlockConstraintForParam, Node, OptionalTest_Prop1, OptionalTest_ReturnType, OrderInfo, OriginalA, OriginalB, Person, PhoneNumber, Quantity, RaysData, ReceiptInfo, ReceiptItem, Recipe, Resume, Schema, SearchParams, SomeClassNestedDynamic, StringToClassEntry, TestClassAlias, TestClassNested, TestClassWithEnum, TestOutputClass, Tree, TwoStoriesOneTitle, UnionTest_ReturnType, WithReasoning, AliasedEnum, Category, Category2, Category3, Color, DataType, DynEnumOne, DynEnumTwo, EnumInClass, EnumOutput, Hobby, MapKey, NamedArgsSingleEnum, NamedArgsSingleEnumList, OptionalTest_CategoryType, OrderStatus, Tag, TestEnum} from "./types" +import {BigNumbers, BinaryNode, Blah, BlockConstraint, BlockConstraintForParam, BookOrder, ClassOptionalOutput, ClassOptionalOutput2, ClassToRecAlias, ClassWithImage, CompoundBigNumbers, ContactInfo, CustomTaskResult, DummyOutput, DynInputOutput, DynamicClassOne, DynamicClassTwo, DynamicOutput, Earthling, Education, Email, EmailAddress, Event, FakeImage, FlightConfirmation, FooAny, Forest, GroceryReceipt, InnerClass, InnerClass2, InputClass, InputClassNested, LinkedList, LinkedListAliasNode, LiteralClassHello, LiteralClassOne, LiteralClassTwo, MalformedConstraints, MalformedConstraints2, Martian, NamedArgsSingleClass, Nested, Nested2, NestedBlockConstraint, NestedBlockConstraintForParam, Node, NodeWithAliasIndirection, OptionalTest_Prop1, OptionalTest_ReturnType, OrderInfo, OriginalA, OriginalB, Person, PhoneNumber, Quantity, RaysData, ReceiptInfo, ReceiptItem, Recipe, Resume, Schema, SearchParams, SomeClassNestedDynamic, StringToClassEntry, TestClassAlias, TestClassNested, TestClassWithEnum, TestOutputClass, Tree, TwoStoriesOneTitle, UnionTest_ReturnType, WithReasoning, AliasedEnum, Category, Category2, Category3, Color, DataType, DynEnumOne, DynEnumTwo, EnumInClass, EnumOutput, Hobby, MapKey, NamedArgsSingleEnum, NamedArgsSingleEnumList, OptionalTest_CategoryType, OrderStatus, Tag, TestEnum} from "./types" import TypeBuilder from "./type_builder" import { DO_NOT_USE_DIRECTLY_UNLESS_YOU_KNOW_WHAT_YOURE_DOING_CTX, DO_NOT_USE_DIRECTLY_UNLESS_YOU_KNOW_WHAT_YOURE_DOING_RUNTIME } from "./globals" @@ -293,6 +293,31 @@ export class BamlAsyncClient { } } + async ClassThatPointsToRecursiveClassThroughAlias( + cls: ClassToRecAlias, + __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } + ): Promise<ClassToRecAlias> { + try { + const raw = await this.runtime.callFunction( + "ClassThatPointsToRecursiveClassThroughAlias", + { + "cls": cls + }, + this.ctx_manager.cloneContext(), + __baml_options__?.tb?.__tb(), + __baml_options__?.clientRegistry, + ) + return raw.parsed() as ClassToRecAlias + } catch (error: any) { + const bamlError = createBamlValidationError(error); + if (bamlError instanceof BamlValidationError) { + throw bamlError; + } else { + throw error; + } + } + } + async ClassifyDynEnumTwo( input: string, __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } @@ -1943,6 +1968,31 @@ export class BamlAsyncClient { } } + async RecursiveClassWithAliasIndirection( + cls: NodeWithAliasIndirection, + __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } + ): Promise<NodeWithAliasIndirection> { + try { + const raw = await this.runtime.callFunction( + "RecursiveClassWithAliasIndirection", + { + "cls": cls + }, + this.ctx_manager.cloneContext(), + __baml_options__?.tb?.__tb(), + __baml_options__?.clientRegistry, + ) + return raw.parsed() as NodeWithAliasIndirection + } catch (error: any) { + const bamlError = createBamlValidationError(error); + if (bamlError instanceof BamlValidationError) { + throw bamlError; + } else { + throw error; + } + } + } + async ReturnFailingAssert( inp: number, __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } @@ -3429,6 +3479,39 @@ class BamlStreamClient { } } + ClassThatPointsToRecursiveClassThroughAlias( + cls: ClassToRecAlias, + __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } + ): BamlStream<RecursivePartialNull<ClassToRecAlias>, ClassToRecAlias> { + try { + const raw = this.runtime.streamFunction( + "ClassThatPointsToRecursiveClassThroughAlias", + { + "cls": cls + }, + undefined, + this.ctx_manager.cloneContext(), + __baml_options__?.tb?.__tb(), + __baml_options__?.clientRegistry, + ) + return new BamlStream<RecursivePartialNull<ClassToRecAlias>, ClassToRecAlias>( + raw, + (a): a is RecursivePartialNull<ClassToRecAlias> => a, + (a): a is ClassToRecAlias => a, + this.ctx_manager.cloneContext(), + __baml_options__?.tb?.__tb(), + ) + } catch (error) { + if (error instanceof Error) { + const bamlError = createBamlValidationError(error); + if (bamlError instanceof BamlValidationError) { + throw bamlError; + } + } + throw error; + } + } + ClassifyDynEnumTwo( input: string, __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } @@ -5607,6 +5690,39 @@ class BamlStreamClient { } } + RecursiveClassWithAliasIndirection( + cls: NodeWithAliasIndirection, + __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } + ): BamlStream<RecursivePartialNull<NodeWithAliasIndirection>, NodeWithAliasIndirection> { + try { + const raw = this.runtime.streamFunction( + "RecursiveClassWithAliasIndirection", + { + "cls": cls + }, + undefined, + this.ctx_manager.cloneContext(), + __baml_options__?.tb?.__tb(), + __baml_options__?.clientRegistry, + ) + return new BamlStream<RecursivePartialNull<NodeWithAliasIndirection>, NodeWithAliasIndirection>( + raw, + (a): a is RecursivePartialNull<NodeWithAliasIndirection> => a, + (a): a is NodeWithAliasIndirection => a, + this.ctx_manager.cloneContext(), + __baml_options__?.tb?.__tb(), + ) + } catch (error) { + if (error instanceof Error) { + const bamlError = createBamlValidationError(error); + if (bamlError instanceof BamlValidationError) { + throw bamlError; + } + } + throw error; + } + } + ReturnFailingAssert( inp: number, __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } diff --git a/integ-tests/typescript/baml_client/inlinedbaml.ts b/integ-tests/typescript/baml_client/inlinedbaml.ts index eb682ddb1..aa2054805 100644 --- a/integ-tests/typescript/baml_client/inlinedbaml.ts +++ b/integ-tests/typescript/baml_client/inlinedbaml.ts @@ -81,7 +81,7 @@ const fileMap = { "test-files/functions/output/optional-class.baml": "class ClassOptionalOutput {\n prop1 string\n prop2 string\n}\n\nfunction FnClassOptionalOutput(input: string) -> ClassOptionalOutput? {\n client GPT35\n prompt #\"\n Return a json blob for the following input:\n {{input}}\n\n {{ctx.output_format}}\n\n JSON:\n \"#\n}\n\n\nclass Blah {\n prop4 string?\n}\n\nclass ClassOptionalOutput2 {\n prop1 string?\n prop2 string?\n prop3 Blah?\n}\n\nfunction FnClassOptionalOutput2(input: string) -> ClassOptionalOutput2? {\n client GPT35\n prompt #\"\n Return a json blob for the following input:\n {{input}}\n\n {{ctx.output_format}}\n\n JSON:\n \"#\n}\n\ntest FnClassOptionalOutput2 {\n functions [FnClassOptionalOutput2, FnClassOptionalOutput]\n args {\n input \"example input\"\n }\n}\n", "test-files/functions/output/optional.baml": "class OptionalTest_Prop1 {\n omega_a string\n omega_b int\n}\n\nenum OptionalTest_CategoryType {\n Aleph\n Beta\n Gamma\n}\n \nclass OptionalTest_ReturnType {\n omega_1 OptionalTest_Prop1?\n omega_2 string?\n omega_3 (OptionalTest_CategoryType?)[]\n} \n \nfunction OptionalTest_Function(input: string) -> (OptionalTest_ReturnType?)[]\n{ \n client GPT35\n prompt #\"\n Return a JSON blob with this schema: \n {{ctx.output_format}}\n\n JSON:\n \"#\n}\n\ntest OptionalTest_Function {\n functions [OptionalTest_Function]\n args {\n input \"example input\"\n }\n}\n", "test-files/functions/output/recursive-class.baml": "class Node {\n data int\n next Node?\n}\n\nclass LinkedList {\n head Node?\n len int\n}\n\nclient<llm> O1 {\n provider \"openai\"\n options {\n model \"o1-mini\"\n default_role \"user\"\n }\n}\n\nfunction BuildLinkedList(input: int[]) -> LinkedList {\n client O1\n prompt #\"\n Build a linked list from the input array of integers.\n\n INPUT:\n {{ input }}\n\n {{ ctx.output_format }} \n \"#\n}\n\ntest TestLinkedList {\n functions [BuildLinkedList]\n args {\n input [1, 2, 3, 4, 5]\n }\n}\n", - "test-files/functions/output/recursive-type-aliases.baml": "// Simple alias that points to recursive type.\nclass LinkedListAliasNode {\n value int\n next LinkedListAliasNode?\n}\n\ntype LinkedListAlias = LinkedListAliasNode\n\nfunction AliasThatPointsToRecursiveType(list: LinkedListAlias) -> LinkedListAlias {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given linked list back:\n \n {{ list }}\n \n {{ ctx.output_format }}\n \"#\n}\n", + "test-files/functions/output/recursive-type-aliases.baml": "class LinkedListAliasNode {\n value int\n next LinkedListAliasNode?\n}\n\n// Simple alias that points to recursive type.\ntype LinkedListAlias = LinkedListAliasNode\n\nfunction AliasThatPointsToRecursiveType(list: LinkedListAlias) -> LinkedListAlias {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given linked list back:\n \n {{ list }}\n \n {{ ctx.output_format }}\n \"#\n}\n\n// Class that points to an alias that points to a recursive type.\nclass ClassToRecAlias {\n list LinkedListAlias\n}\n\nfunction ClassThatPointsToRecursiveClassThroughAlias(cls: ClassToRecAlias) -> ClassToRecAlias {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given object back:\n \n {{ cls }}\n \n {{ ctx.output_format }}\n \"#\n}\n\n// This is tricky cause this class should be hoisted, but classes and aliases\n// are two different types in the AST. This test will make sure they can interop.\nclass NodeWithAliasIndirection {\n value int\n next NodeIndirection?\n}\n\ntype NodeIndirection = NodeWithAliasIndirection\n\nfunction RecursiveClassWithAliasIndirection(cls: NodeWithAliasIndirection) -> NodeWithAliasIndirection {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given object back:\n \n {{ cls }}\n \n {{ ctx.output_format }}\n \"#\n}", "test-files/functions/output/serialization-error.baml": "class DummyOutput {\n nonce string\n nonce2 string\n @@dynamic\n}\n\nfunction DummyOutputFunction(input: string) -> DummyOutput {\n client GPT35\n prompt #\"\n Say \"hello there\".\n \"#\n}", "test-files/functions/output/string-list.baml": "function FnOutputStringList(input: string) -> string[] {\n client GPT35\n prompt #\"\n Return a list of strings in json format like [\"string1\", \"string2\", \"string3\"].\n\n JSON:\n \"#\n}\n\ntest FnOutputStringList {\n functions [FnOutputStringList]\n args {\n input \"example input\"\n }\n}\n", "test-files/functions/output/type-aliases.baml": "type Primitive = int | string | bool | float\n\ntype List = string[]\n\ntype Graph = map<string, string[]>\n\ntype Combination = Primitive | List | Graph\n\nfunction PrimitiveAlias(p: Primitive) -> Primitive {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given value back: {{ p }}\n \"#\n}\n\nfunction MapAlias(m: Graph) -> Graph {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given Graph back:\n\n {{ m }}\n\n {{ ctx.output_format }}\n \"#\n}\n\nfunction NestedAlias(c: Combination) -> Combination {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given value back:\n\n {{ c }}\n\n {{ ctx.output_format }}\n \"#\n}\n", diff --git a/integ-tests/typescript/baml_client/sync_client.ts b/integ-tests/typescript/baml_client/sync_client.ts index 92f2ad742..7f5077d4e 100644 --- a/integ-tests/typescript/baml_client/sync_client.ts +++ b/integ-tests/typescript/baml_client/sync_client.ts @@ -17,7 +17,7 @@ $ pnpm add @boundaryml/baml // biome-ignore format: autogenerated code import { BamlRuntime, FunctionResult, BamlCtxManager, BamlSyncStream, Image, ClientRegistry, createBamlValidationError, BamlValidationError } from "@boundaryml/baml" import { Checked, Check } from "./types" -import {BigNumbers, BinaryNode, Blah, BlockConstraint, BlockConstraintForParam, BookOrder, ClassOptionalOutput, ClassOptionalOutput2, ClassWithImage, CompoundBigNumbers, ContactInfo, CustomTaskResult, DummyOutput, DynInputOutput, DynamicClassOne, DynamicClassTwo, DynamicOutput, Earthling, Education, Email, EmailAddress, Event, FakeImage, FlightConfirmation, FooAny, Forest, GroceryReceipt, InnerClass, InnerClass2, InputClass, InputClassNested, LinkedList, LinkedListAliasNode, LiteralClassHello, LiteralClassOne, LiteralClassTwo, MalformedConstraints, MalformedConstraints2, Martian, NamedArgsSingleClass, Nested, Nested2, NestedBlockConstraint, NestedBlockConstraintForParam, Node, OptionalTest_Prop1, OptionalTest_ReturnType, OrderInfo, OriginalA, OriginalB, Person, PhoneNumber, Quantity, RaysData, ReceiptInfo, ReceiptItem, Recipe, Resume, Schema, SearchParams, SomeClassNestedDynamic, StringToClassEntry, TestClassAlias, TestClassNested, TestClassWithEnum, TestOutputClass, Tree, TwoStoriesOneTitle, UnionTest_ReturnType, WithReasoning, AliasedEnum, Category, Category2, Category3, Color, DataType, DynEnumOne, DynEnumTwo, EnumInClass, EnumOutput, Hobby, MapKey, NamedArgsSingleEnum, NamedArgsSingleEnumList, OptionalTest_CategoryType, OrderStatus, Tag, TestEnum} from "./types" +import {BigNumbers, BinaryNode, Blah, BlockConstraint, BlockConstraintForParam, BookOrder, ClassOptionalOutput, ClassOptionalOutput2, ClassToRecAlias, ClassWithImage, CompoundBigNumbers, ContactInfo, CustomTaskResult, DummyOutput, DynInputOutput, DynamicClassOne, DynamicClassTwo, DynamicOutput, Earthling, Education, Email, EmailAddress, Event, FakeImage, FlightConfirmation, FooAny, Forest, GroceryReceipt, InnerClass, InnerClass2, InputClass, InputClassNested, LinkedList, LinkedListAliasNode, LiteralClassHello, LiteralClassOne, LiteralClassTwo, MalformedConstraints, MalformedConstraints2, Martian, NamedArgsSingleClass, Nested, Nested2, NestedBlockConstraint, NestedBlockConstraintForParam, Node, NodeWithAliasIndirection, OptionalTest_Prop1, OptionalTest_ReturnType, OrderInfo, OriginalA, OriginalB, Person, PhoneNumber, Quantity, RaysData, ReceiptInfo, ReceiptItem, Recipe, Resume, Schema, SearchParams, SomeClassNestedDynamic, StringToClassEntry, TestClassAlias, TestClassNested, TestClassWithEnum, TestOutputClass, Tree, TwoStoriesOneTitle, UnionTest_ReturnType, WithReasoning, AliasedEnum, Category, Category2, Category3, Color, DataType, DynEnumOne, DynEnumTwo, EnumInClass, EnumOutput, Hobby, MapKey, NamedArgsSingleEnum, NamedArgsSingleEnumList, OptionalTest_CategoryType, OrderStatus, Tag, TestEnum} from "./types" import TypeBuilder from "./type_builder" import { DO_NOT_USE_DIRECTLY_UNLESS_YOU_KNOW_WHAT_YOURE_DOING_CTX, DO_NOT_USE_DIRECTLY_UNLESS_YOU_KNOW_WHAT_YOURE_DOING_RUNTIME } from "./globals" @@ -293,6 +293,31 @@ export class BamlSyncClient { } } + ClassThatPointsToRecursiveClassThroughAlias( + cls: ClassToRecAlias, + __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } + ): ClassToRecAlias { + try { + const raw = this.runtime.callFunctionSync( + "ClassThatPointsToRecursiveClassThroughAlias", + { + "cls": cls + }, + this.ctx_manager.cloneContext(), + __baml_options__?.tb?.__tb(), + __baml_options__?.clientRegistry, + ) + return raw.parsed() as ClassToRecAlias + } catch (error: any) { + const bamlError = createBamlValidationError(error); + if (bamlError instanceof BamlValidationError) { + throw bamlError; + } else { + throw error; + } + } + } + ClassifyDynEnumTwo( input: string, __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } @@ -1943,6 +1968,31 @@ export class BamlSyncClient { } } + RecursiveClassWithAliasIndirection( + cls: NodeWithAliasIndirection, + __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } + ): NodeWithAliasIndirection { + try { + const raw = this.runtime.callFunctionSync( + "RecursiveClassWithAliasIndirection", + { + "cls": cls + }, + this.ctx_manager.cloneContext(), + __baml_options__?.tb?.__tb(), + __baml_options__?.clientRegistry, + ) + return raw.parsed() as NodeWithAliasIndirection + } catch (error: any) { + const bamlError = createBamlValidationError(error); + if (bamlError instanceof BamlValidationError) { + throw bamlError; + } else { + throw error; + } + } + } + ReturnFailingAssert( inp: number, __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } diff --git a/integ-tests/typescript/baml_client/type_builder.ts b/integ-tests/typescript/baml_client/type_builder.ts index e85c51804..d1d4b0d7d 100644 --- a/integ-tests/typescript/baml_client/type_builder.ts +++ b/integ-tests/typescript/baml_client/type_builder.ts @@ -50,7 +50,7 @@ export default class TypeBuilder { constructor() { this.tb = new _TypeBuilder({ classes: new Set([ - "BigNumbers","BinaryNode","Blah","BlockConstraint","BlockConstraintForParam","BookOrder","ClassOptionalOutput","ClassOptionalOutput2","ClassWithImage","CompoundBigNumbers","ContactInfo","CustomTaskResult","DummyOutput","DynInputOutput","DynamicClassOne","DynamicClassTwo","DynamicOutput","Earthling","Education","Email","EmailAddress","Event","FakeImage","FlightConfirmation","FooAny","Forest","GroceryReceipt","InnerClass","InnerClass2","InputClass","InputClassNested","LinkedList","LinkedListAliasNode","LiteralClassHello","LiteralClassOne","LiteralClassTwo","MalformedConstraints","MalformedConstraints2","Martian","NamedArgsSingleClass","Nested","Nested2","NestedBlockConstraint","NestedBlockConstraintForParam","Node","OptionalTest_Prop1","OptionalTest_ReturnType","OrderInfo","OriginalA","OriginalB","Person","PhoneNumber","Quantity","RaysData","ReceiptInfo","ReceiptItem","Recipe","Resume","Schema","SearchParams","SomeClassNestedDynamic","StringToClassEntry","TestClassAlias","TestClassNested","TestClassWithEnum","TestOutputClass","Tree","TwoStoriesOneTitle","UnionTest_ReturnType","WithReasoning", + "BigNumbers","BinaryNode","Blah","BlockConstraint","BlockConstraintForParam","BookOrder","ClassOptionalOutput","ClassOptionalOutput2","ClassToRecAlias","ClassWithImage","CompoundBigNumbers","ContactInfo","CustomTaskResult","DummyOutput","DynInputOutput","DynamicClassOne","DynamicClassTwo","DynamicOutput","Earthling","Education","Email","EmailAddress","Event","FakeImage","FlightConfirmation","FooAny","Forest","GroceryReceipt","InnerClass","InnerClass2","InputClass","InputClassNested","LinkedList","LinkedListAliasNode","LiteralClassHello","LiteralClassOne","LiteralClassTwo","MalformedConstraints","MalformedConstraints2","Martian","NamedArgsSingleClass","Nested","Nested2","NestedBlockConstraint","NestedBlockConstraintForParam","Node","NodeWithAliasIndirection","OptionalTest_Prop1","OptionalTest_ReturnType","OrderInfo","OriginalA","OriginalB","Person","PhoneNumber","Quantity","RaysData","ReceiptInfo","ReceiptItem","Recipe","Resume","Schema","SearchParams","SomeClassNestedDynamic","StringToClassEntry","TestClassAlias","TestClassNested","TestClassWithEnum","TestOutputClass","Tree","TwoStoriesOneTitle","UnionTest_ReturnType","WithReasoning", ]), enums: new Set([ "AliasedEnum","Category","Category2","Category3","Color","DataType","DynEnumOne","DynEnumTwo","EnumInClass","EnumOutput","Hobby","MapKey","NamedArgsSingleEnum","NamedArgsSingleEnumList","OptionalTest_CategoryType","OrderStatus","Tag","TestEnum", diff --git a/integ-tests/typescript/baml_client/types.ts b/integ-tests/typescript/baml_client/types.ts index 474d2dea4..fe047dd09 100644 --- a/integ-tests/typescript/baml_client/types.ts +++ b/integ-tests/typescript/baml_client/types.ts @@ -207,6 +207,11 @@ export interface ClassOptionalOutput2 { } +export interface ClassToRecAlias { + list: LinkedListAliasNode + +} + export interface ClassWithImage { myImage: Image param2: string @@ -444,6 +449,12 @@ export interface Node { } +export interface NodeWithAliasIndirection { + value: number + next?: NodeWithAliasIndirection | null + +} + export interface OptionalTest_Prop1 { omega_a: string omega_b: number diff --git a/integ-tests/typescript/test-report.html b/integ-tests/typescript/test-report.html index 8e764242a..3a449b8f5 100644 --- a/integ-tests/typescript/test-report.html +++ b/integ-tests/typescript/test-report.html @@ -257,9 +257,11 @@ font-size: 1rem; padding: 0 0.5rem; } -</style></head><body><div class="jesthtml-content"><header><h1 id="title">Test Report</h1></header><div id="metadata-container"><div id="timestamp">Started: 2024-12-04 23:14:14</div><div id="summary"><div id="suite-summary"><div class="summary-total">Suites (1)</div><div class="summary-passed summary-empty">0 passed</div><div class="summary-failed ">1 failed</div><div class="summary-pending summary-empty">0 pending</div></div><div id="test-summary"><div class="summary-total">Tests (71)</div><div class="summary-passed ">69 passed</div><div class="summary-failed ">2 failed</div><div class="summary-pending summary-empty">0 pending</div></div></div></div><div id="suite-1" class="suite-container"><input id="collapsible-0" type="checkbox" class="toggle" checked="checked"/><label for="collapsible-0"><div class="suite-info"><div class="suite-path">/workspaces/baml/integ-tests/typescript/tests/integ-tests.test.ts</div><div class="suite-time warn">127.513s</div></div></label><div class="suite-tests"><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single bool</div><div class="test-status">passed</div><div class="test-duration">1.172s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single string list</div><div class="test-status">passed</div><div class="test-duration">0.558s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">return literal union</div><div class="test-status">passed</div><div class="test-duration">0.512s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single class</div><div class="test-status">passed</div><div class="test-duration">0.512s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">multiple classes</div><div class="test-status">passed</div><div class="test-duration">0.614s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single enum list</div><div class="test-status">passed</div><div class="test-duration">0.613s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single float</div><div class="test-status">passed</div><div class="test-duration">0.511s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single int</div><div class="test-status">passed</div><div class="test-duration">0.509s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single literal int</div><div class="test-status">passed</div><div class="test-duration">0.712s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single literal bool</div><div class="test-status">passed</div><div class="test-duration">0.722s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single literal string</div><div class="test-status">passed</div><div class="test-duration">0.612s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single class with literal prop</div><div class="test-status">passed</div><div class="test-duration">0.503s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single class with literal union prop</div><div class="test-status">passed</div><div class="test-duration">0.726s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single optional string</div><div class="test-status">passed</div><div class="test-duration">0.511s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single map string to string</div><div class="test-status">passed</div><div class="test-duration">0.789s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single map string to class</div><div class="test-status">passed</div><div class="test-duration">0.941s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single map string to map</div><div class="test-status">passed</div><div class="test-duration">0.932s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">enum key in map</div><div class="test-status">passed</div><div class="test-duration">0.793s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">literal string union key in map</div><div class="test-status">passed</div><div class="test-duration">0.74s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single literal string key in map</div><div class="test-status">passed</div><div class="test-duration">0.803s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">primitive union alias</div><div class="test-status">passed</div><div class="test-duration">0.541s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">map alias</div><div class="test-status">passed</div><div class="test-duration">1.011s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">alias union</div><div class="test-status">passed</div><div class="test-duration">1.485s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">alias pointing to recursive class</div><div class="test-status">passed</div><div class="test-duration">0.765s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work for all outputs</div><div class="test-status">passed</div><div class="test-duration">6.418s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">works with retries1</div><div class="test-status">passed</div><div class="test-duration">1.594s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">works with retries2</div><div class="test-status">passed</div><div class="test-duration">2.795s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">works with fallbacks</div><div class="test-status">passed</div><div class="test-duration">2.708s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with image from url</div><div class="test-status">passed</div><div class="test-duration">1.434s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with image from base 64</div><div class="test-status">passed</div><div class="test-duration">1.538s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with audio base 64</div><div class="test-status">passed</div><div class="test-duration">1.944s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with audio from url</div><div class="test-status">passed</div><div class="test-duration">1.575s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support streaming in OpenAI</div><div class="test-status">passed</div><div class="test-duration">2.538s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support streaming in Gemini</div><div class="test-status">passed</div><div class="test-duration">7.126s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support AWS</div><div class="test-status">passed</div><div class="test-duration">3.092s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support streaming in AWS</div><div class="test-status">passed</div><div class="test-duration">3.221s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should allow overriding the region</div><div class="test-status">passed</div><div class="test-duration">0.134s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support OpenAI shorthand</div><div class="test-status">passed</div><div class="test-duration">11.393s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support OpenAI shorthand streaming</div><div class="test-status">passed</div><div class="test-duration">12.27s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support anthropic shorthand</div><div class="test-status">passed</div><div class="test-duration">2.676s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support anthropic shorthand streaming</div><div class="test-status">passed</div><div class="test-duration">7.282s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support streaming without iterating</div><div class="test-status">passed</div><div class="test-duration">2.251s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support streaming in Claude</div><div class="test-status">passed</div><div class="test-duration">1.127s</div></div></div><div class="test-result failed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support vertex</div><div class="test-status">failed</div><div class="test-duration">0.003s</div></div><div class="failureMessages"> <pre class="failureMsg">Error: BamlError: Failed to read service account file: +</style></head><body><div class="jesthtml-content"><header><h1 id="title">Test Report</h1></header><div id="metadata-container"><div id="timestamp">Started: 2024-12-05 15:48:41</div><div id="summary"><div id="suite-summary"><div class="summary-total">Suites (1)</div><div class="summary-passed summary-empty">0 passed</div><div class="summary-failed ">1 failed</div><div class="summary-pending summary-empty">0 pending</div></div><div id="test-summary"><div class="summary-total">Tests (73)</div><div class="summary-passed ">70 passed</div><div class="summary-failed ">3 failed</div><div class="summary-pending summary-empty">0 pending</div></div></div></div><div id="suite-1" class="suite-container"><input id="collapsible-0" type="checkbox" class="toggle" checked="checked"/><label for="collapsible-0"><div class="suite-info"><div class="suite-path">/workspaces/baml/integ-tests/typescript/tests/integ-tests.test.ts</div><div class="suite-time warn">147.246s</div></div></label><div class="suite-tests"><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single bool</div><div class="test-status">passed</div><div class="test-duration">0.523s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single string list</div><div class="test-status">passed</div><div class="test-duration">0.575s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">return literal union</div><div class="test-status">passed</div><div class="test-duration">0.463s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single class</div><div class="test-status">passed</div><div class="test-duration">0.597s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">multiple classes</div><div class="test-status">passed</div><div class="test-duration">0.669s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single enum list</div><div class="test-status">passed</div><div class="test-duration">1.016s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single float</div><div class="test-status">passed</div><div class="test-duration">0.53s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single int</div><div class="test-status">passed</div><div class="test-duration">0.532s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single literal int</div><div class="test-status">passed</div><div class="test-duration">0.525s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single literal bool</div><div class="test-status">passed</div><div class="test-duration">0.505s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single literal string</div><div class="test-status">passed</div><div class="test-duration">0.514s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single class with literal prop</div><div class="test-status">passed</div><div class="test-duration">0.824s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single class with literal union prop</div><div class="test-status">passed</div><div class="test-duration">1.126s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single optional string</div><div class="test-status">passed</div><div class="test-duration">0.715s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single map string to string</div><div class="test-status">passed</div><div class="test-duration">0.714s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single map string to class</div><div class="test-status">passed</div><div class="test-duration">0.724s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single map string to map</div><div class="test-status">passed</div><div class="test-duration">0.709s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">enum key in map</div><div class="test-status">passed</div><div class="test-duration">0.919s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">literal string union key in map</div><div class="test-status">passed</div><div class="test-duration">1.276s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single literal string key in map</div><div class="test-status">passed</div><div class="test-duration">0.557s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">primitive union alias</div><div class="test-status">passed</div><div class="test-duration">0.482s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">map alias</div><div class="test-status">passed</div><div class="test-duration">0.85s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">alias union</div><div class="test-status">passed</div><div class="test-duration">1.669s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">alias pointing to recursive class</div><div class="test-status">passed</div><div class="test-duration">0.934s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">class pointing to alias that points to recursive class</div><div class="test-status">passed</div><div class="test-duration">1.196s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">recursive class with alias indirection</div><div class="test-status">passed</div><div class="test-duration">1.125s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work for all outputs</div><div class="test-status">passed</div><div class="test-duration">6.595s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">works with retries1</div><div class="test-status">passed</div><div class="test-duration">1.723s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">works with retries2</div><div class="test-status">passed</div><div class="test-duration">2.843s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">works with fallbacks</div><div class="test-status">passed</div><div class="test-duration">2.659s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with image from url</div><div class="test-status">passed</div><div class="test-duration">1.74s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with image from base 64</div><div class="test-status">passed</div><div class="test-duration">4.205s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with audio base 64</div><div class="test-status">passed</div><div class="test-duration">1.84s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with audio from url</div><div class="test-status">passed</div><div class="test-duration">2.061s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support streaming in OpenAI</div><div class="test-status">passed</div><div class="test-duration">2.629s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support streaming in Gemini</div><div class="test-status">passed</div><div class="test-duration">8.533s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support AWS</div><div class="test-status">passed</div><div class="test-duration">1.914s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support streaming in AWS</div><div class="test-status">passed</div><div class="test-duration">1.819s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should allow overriding the region</div><div class="test-status">passed</div><div class="test-duration">0.106s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support OpenAI shorthand</div><div class="test-status">passed</div><div class="test-duration">17.749s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support OpenAI shorthand streaming</div><div class="test-status">passed</div><div class="test-duration">27.291s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support anthropic shorthand</div><div class="test-status">passed</div><div class="test-duration">4.146s</div></div></div><div class="test-result failed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support anthropic shorthand streaming</div><div class="test-status">failed</div><div class="test-duration">0.407s</div></div><div class="failureMessages"> <pre class="failureMsg">Error: BamlError: BamlClientError: BamlClientHttpError: LLM call failed: LLMErrorResponse { client: "anthropic/claude-3-haiku-20240307", model: None, prompt: Chat([RenderedChatMessage { role: "user", allow_duplicate_role: false, parts: [Text("Write a nice short story about Dr. Pepper")] }]), request_options: {"model": String("claude-3-haiku-20240307"), "max_tokens": Number(4096)}, start_time: SystemTime { tv_sec: 1733413829, tv_nsec: 718207209 }, latency: 389.065836ms, message: "Failed to parse event: Error(\"missing field `type`\", line: 0, column: 0)", code: UnsupportedResponse(2) } + at BamlStream.parsed [as getFinalResponse] (/workspaces/baml/engine/language_client_typescript/stream.js:58:39) + at Object.<anonymous> (/workspaces/baml/integ-tests/typescript/tests/integ-tests.test.ts:357:17)</pre></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support streaming without iterating</div><div class="test-status">passed</div><div class="test-duration">2.252s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support streaming in Claude</div><div class="test-status">passed</div><div class="test-duration">2.021s</div></div></div><div class="test-result failed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support vertex</div><div class="test-status">failed</div><div class="test-duration">0.002s</div></div><div class="failureMessages"> <pre class="failureMsg">Error: BamlError: Failed to read service account file: Caused by: - No such file or directory (os error 2)</pre></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">supports tracing sync</div><div class="test-status">passed</div><div class="test-duration">0.022s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">supports tracing async</div><div class="test-status">passed</div><div class="test-duration">4.02s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic types single</div><div class="test-status">passed</div><div class="test-duration">1.168s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic types enum</div><div class="test-status">passed</div><div class="test-duration">1.483s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic literals</div><div class="test-status">passed</div><div class="test-duration">1.014s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic types class</div><div class="test-status">passed</div><div class="test-duration">2.621s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic inputs class</div><div class="test-status">passed</div><div class="test-duration">0.769s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic inputs list</div><div class="test-status">passed</div><div class="test-duration">0.633s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic output map</div><div class="test-status">passed</div><div class="test-duration">0.886s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic output union</div><div class="test-status">passed</div><div class="test-duration">2.32s</div></div></div><div class="test-result failed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with nested classes</div><div class="test-status">failed</div><div class="test-duration">0.108s</div></div><div class="failureMessages"> <pre class="failureMsg">Error: BamlError: BamlClientError: Something went wrong with the LLM client: reqwest::Error { kind: Request, url: Url { scheme: "http", cannot_be_a_base: false, username: "", password: None, host: Some(Domain("localhost")), port: Some(11434), path: "/v1/chat/completions", query: None, fragment: None }, source: hyper_util::client::legacy::Error(Connect, ConnectError("tcp connect error", Os { code: 111, kind: ConnectionRefused, message: "Connection refused" })) } + No such file or directory (os error 2)</pre></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">supports tracing sync</div><div class="test-status">passed</div><div class="test-duration">0.005s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">supports tracing async</div><div class="test-status">passed</div><div class="test-duration">3.826s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic types single</div><div class="test-status">passed</div><div class="test-duration">1.417s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic types enum</div><div class="test-status">passed</div><div class="test-duration">1.172s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic literals</div><div class="test-status">passed</div><div class="test-duration">1.19s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic types class</div><div class="test-status">passed</div><div class="test-duration">1.4s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic inputs class</div><div class="test-status">passed</div><div class="test-duration">0.637s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic inputs list</div><div class="test-status">passed</div><div class="test-duration">0.703s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic output map</div><div class="test-status">passed</div><div class="test-duration">1.142s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic output union</div><div class="test-status">passed</div><div class="test-duration">2.458s</div></div></div><div class="test-result failed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with nested classes</div><div class="test-status">failed</div><div class="test-duration">0.107s</div></div><div class="failureMessages"> <pre class="failureMsg">Error: BamlError: BamlClientError: Something went wrong with the LLM client: reqwest::Error { kind: Request, url: Url { scheme: "http", cannot_be_a_base: false, username: "", password: None, host: Some(Domain("localhost")), port: Some(11434), path: "/v1/chat/completions", query: None, fragment: None }, source: hyper_util::client::legacy::Error(Connect, ConnectError("tcp connect error", Os { code: 111, kind: ConnectionRefused, message: "Connection refused" })) } at BamlStream.parsed [as getFinalResponse] (/workspaces/baml/engine/language_client_typescript/stream.js:58:39) - at Object.<anonymous> (/workspaces/baml/integ-tests/typescript/tests/integ-tests.test.ts:625:19)</pre></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic client</div><div class="test-status">passed</div><div class="test-duration">0.598s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with 'onLogEvent'</div><div class="test-status">passed</div><div class="test-duration">2.318s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with a sync client</div><div class="test-status">passed</div><div class="test-duration">0.646s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should raise an error when appropriate</div><div class="test-status">passed</div><div class="test-duration">1.387s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should raise a BAMLValidationError</div><div class="test-status">passed</div><div class="test-duration">1.996s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should reset environment variables correctly</div><div class="test-status">passed</div><div class="test-duration">1.942s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should use aliases when serializing input objects - classes</div><div class="test-status">passed</div><div class="test-duration">1.125s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should use aliases when serializing, but still have original keys in jinja</div><div class="test-status">passed</div><div class="test-duration">1.052s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should use aliases when serializing input objects - enums</div><div class="test-status">passed</div><div class="test-duration">0.845s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should use aliases when serializing input objects - lists</div><div class="test-status">passed</div><div class="test-duration">0.571s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">constraints: should handle checks in return types</div><div class="test-status">passed</div><div class="test-duration">0.911s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">constraints: should handle checks in returned unions</div><div class="test-status">passed</div><div class="test-duration">0.885s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">constraints: should handle block-level checks</div><div class="test-status">passed</div><div class="test-duration">0.646s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">constraints: should handle nested-block-level checks</div><div class="test-status">passed</div><div class="test-duration">0.718s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">simple recursive type</div><div class="test-status">passed</div><div class="test-duration">3.277s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">mutually recursive type</div><div class="test-status">passed</div><div class="test-duration">2.053s</div></div></div></div></div></div></body></html> \ No newline at end of file + at Object.<anonymous> (/workspaces/baml/integ-tests/typescript/tests/integ-tests.test.ts:635:19)</pre></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic client</div><div class="test-status">passed</div><div class="test-duration">0.503s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with 'onLogEvent'</div><div class="test-status">passed</div><div class="test-duration">2.814s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with a sync client</div><div class="test-status">passed</div><div class="test-duration">0.667s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should raise an error when appropriate</div><div class="test-status">passed</div><div class="test-duration">1.374s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should raise a BAMLValidationError</div><div class="test-status">passed</div><div class="test-duration">0.522s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should reset environment variables correctly</div><div class="test-status">passed</div><div class="test-duration">1.892s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should use aliases when serializing input objects - classes</div><div class="test-status">passed</div><div class="test-duration">1.13s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should use aliases when serializing, but still have original keys in jinja</div><div class="test-status">passed</div><div class="test-duration">1.018s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should use aliases when serializing input objects - enums</div><div class="test-status">passed</div><div class="test-duration">0.527s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should use aliases when serializing input objects - lists</div><div class="test-status">passed</div><div class="test-duration">0.598s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">constraints: should handle checks in return types</div><div class="test-status">passed</div><div class="test-duration">0.81s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">constraints: should handle checks in returned unions</div><div class="test-status">passed</div><div class="test-duration">0.938s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">constraints: should handle block-level checks</div><div class="test-status">passed</div><div class="test-duration">0.615s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">constraints: should handle nested-block-level checks</div><div class="test-status">passed</div><div class="test-duration">0.713s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">simple recursive type</div><div class="test-status">passed</div><div class="test-duration">3.172s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">mutually recursive type</div><div class="test-status">passed</div><div class="test-duration">2.255s</div></div></div></div></div></div></body></html> \ No newline at end of file diff --git a/integ-tests/typescript/tests/integ-tests.test.ts b/integ-tests/typescript/tests/integ-tests.test.ts index 7e7a10a72..8eaeb690b 100644 --- a/integ-tests/typescript/tests/integ-tests.test.ts +++ b/integ-tests/typescript/tests/integ-tests.test.ts @@ -171,6 +171,16 @@ describe('Integ tests', () => { const res = await b.AliasThatPointsToRecursiveType({ value: 1, next: null }) expect(res).toEqual({ value: 1, next: null }) }) + + it('class pointing to alias that points to recursive class', async () => { + const res = await b.ClassThatPointsToRecursiveClassThroughAlias({ list: { value: 1, next: null } }) + expect(res).toEqual({ list: { value: 1, next: null } }) + }) + + it('recursive class with alias indirection', async () => { + const res = await b.RecursiveClassWithAliasIndirection({ value: 1, next: { value: 2, next: null } }) + expect(res).toEqual({ value: 1, next: { value: 2, next: null } }) + }) }) it('should work for all outputs', async () => { From f143beb2a1158df153a5158e32a00e8d2cdae8af Mon Sep 17 00:00:00 2001 From: Antonio Sarosi <sarosiantonio@gmail.com> Date: Thu, 5 Dec 2024 17:27:25 +0100 Subject: [PATCH 36/51] Compute alias cycles only once and pass ref --- .../validation_pipeline/validations/cycle.rs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/engine/baml-lib/baml-core/src/validate/validation_pipeline/validations/cycle.rs b/engine/baml-lib/baml-core/src/validate/validation_pipeline/validations/cycle.rs index 89e0abb40..3cc22d6a3 100644 --- a/engine/baml-lib/baml-core/src/validate/validation_pipeline/validations/cycle.rs +++ b/engine/baml-lib/baml-core/src/validate/validation_pipeline/validations/cycle.rs @@ -16,12 +16,15 @@ use crate::validate::validation_pipeline::context::Context; pub(super) fn validate(ctx: &mut Context<'_>) { // Solve cycles first. We need that information in case a class points to // an unresolveble type alias. - let alias_cycles = report_infinite_cycles( + let type_aliases_components = report_infinite_cycles( &ctx.db.type_alias_dependencies(), ctx, "These aliases form a dependency cycle", ); + // Store this locally to pass refs to the insert_required_deps function. + let alias_cycles = type_aliases_components.iter().flatten().copied().collect(); + // First, build a graph of all the "required" dependencies represented as an // adjacency list. We're only going to consider type dependencies that can // actually cause infinite recursion. Unions and optionals can stop the @@ -44,13 +47,7 @@ pub(super) fn validate(ctx: &mut Context<'_>) { for field in &expr_block.fields { if let Some(field_type) = &field.expr { - insert_required_deps( - class.id, - field_type, - ctx, - &mut dependencies, - &alias_cycles.iter().flatten().copied().collect(), - ); + insert_required_deps(class.id, field_type, ctx, &mut dependencies, &alias_cycles); } } From 18c1bde1b11323b4771298997f9e25995313e696 Mon Sep 17 00:00:00 2001 From: Antonio Sarosi <sarosiantonio@gmail.com> Date: Thu, 5 Dec 2024 17:32:20 +0100 Subject: [PATCH 37/51] Remove mut warning --- engine/baml-lib/parser-database/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/baml-lib/parser-database/src/lib.rs b/engine/baml-lib/parser-database/src/lib.rs index b830f3ec4..19dfdd845 100644 --- a/engine/baml-lib/parser-database/src/lib.rs +++ b/engine/baml-lib/parser-database/src/lib.rs @@ -326,7 +326,7 @@ mod test { } fn assert_finite_cycles(baml: &'static str, expected: &[&[&str]]) -> Result<(), Diagnostics> { - let mut db = parse(baml)?; + let db = parse(baml)?; assert_eq!( db.finite_recursive_cycles() From 2b8cce62d1cddc41accddb34b70be21b63bcf443 Mon Sep 17 00:00:00 2001 From: Antonio Sarosi <sarosiantonio@gmail.com> Date: Thu, 5 Dec 2024 17:38:07 +0100 Subject: [PATCH 38/51] Add line break --- .../test-files/functions/output/recursive-type-aliases.baml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integ-tests/baml_src/test-files/functions/output/recursive-type-aliases.baml b/integ-tests/baml_src/test-files/functions/output/recursive-type-aliases.baml index a875f567f..d308bd449 100644 --- a/integ-tests/baml_src/test-files/functions/output/recursive-type-aliases.baml +++ b/integ-tests/baml_src/test-files/functions/output/recursive-type-aliases.baml @@ -51,4 +51,4 @@ function RecursiveClassWithAliasIndirection(cls: NodeWithAliasIndirection) -> No {{ ctx.output_format }} "# -} \ No newline at end of file +} From bad63bf6736631651985aebce3c1632b2e2b0a17 Mon Sep 17 00:00:00 2001 From: Antonio Sarosi <sarosiantonio@gmail.com> Date: Mon, 9 Dec 2024 17:25:46 +0100 Subject: [PATCH 39/51] Merge attrs and allow only checks and asserts --- engine/baml-lib/baml-core/src/ir/repr.rs | 65 ++++++++- .../class/invalid_attrs_on_type_alias.baml | 24 ++++ .../validation_files/class/type_aliases.baml | 19 +++ .../parser-database/src/attributes/mod.rs | 46 +++++- engine/baml-lib/parser-database/src/lib.rs | 15 ++ .../baml-lib/parser-database/src/types/mod.rs | 21 ++- .../baml-lib/schema-ast/src/ast/attribute.rs | 8 ++ .../schema-ast/src/parser/datamodel.pest | 2 +- .../schema-ast/src/parser/parse_assignment.rs | 12 +- .../functions/output/type-aliases.baml | 30 ++++ .../python/baml_client/async_client.py | 106 ++++++++++++++ integ-tests/python/baml_client/inlinedbaml.py | 4 +- .../python/baml_client/partial_types.py | 3 + integ-tests/python/baml_client/sync_client.py | 106 ++++++++++++++ .../python/baml_client/type_builder.py | 2 +- integ-tests/python/baml_client/types.py | 3 + integ-tests/python/tests/test_functions.py | 13 ++ integ-tests/ruby/baml_client/client.rb | 134 ++++++++++++++++++ integ-tests/ruby/baml_client/inlined.rb | 4 +- integ-tests/ruby/baml_client/partial-types.rb | 13 ++ integ-tests/ruby/baml_client/type-registry.rb | 2 +- integ-tests/ruby/baml_client/types.rb | 13 ++ .../typescript/baml_client/async_client.ts | 118 ++++++++++++++- .../typescript/baml_client/inlinedbaml.ts | 4 +- .../typescript/baml_client/sync_client.ts | 52 ++++++- .../typescript/baml_client/type_builder.ts | 2 +- integ-tests/typescript/baml_client/types.ts | 5 + 27 files changed, 806 insertions(+), 20 deletions(-) create mode 100644 engine/baml-lib/baml/tests/validation_files/class/invalid_attrs_on_type_alias.baml diff --git a/engine/baml-lib/baml-core/src/ir/repr.rs b/engine/baml-lib/baml-core/src/ir/repr.rs index d942e989f..571b8e4b0 100644 --- a/engine/baml-lib/baml-core/src/ir/repr.rs +++ b/engine/baml-lib/baml-core/src/ir/repr.rs @@ -1112,7 +1112,7 @@ pub fn make_test_ir(source_code: &str) -> anyhow::Result<IntermediateRepr> { #[cfg(test)] mod tests { use super::*; - use crate::ir::ir_helpers::IRHelper; + use crate::ir::{ir_helpers::IRHelper, TypeValue}; #[test] fn test_docstrings() { @@ -1205,4 +1205,67 @@ mod tests { let walker = ir.find_test(&function, "Foo").unwrap(); assert_eq!(walker.item.1.elem.constraints.len(), 1); } + + #[test] + fn test_resolve_type_alias() { + let ir = make_test_ir( + r##" + type One = int + type Two = One + type Three = Two + + class Test { + field Three + } + "##, + ) + .unwrap(); + + let class = ir.find_class("Test").unwrap(); + let alias = class.find_field("field").unwrap(); + + let FieldType::Alias { resolution, .. } = alias.r#type() else { + panic!("expected alias type, found {:?}", alias.r#type()); + }; + + assert_eq!(**resolution, FieldType::Primitive(TypeValue::Int)); + } + + #[test] + fn test_merge_type_alias_attributes() { + let ir = make_test_ir( + r##" + type One = int @check(gt_ten, {{ this > 10 }}) + type Two = One @check(lt_twenty, {{ this < 20 }}) + type Three = Two @assert({{ this != 15 }}) + + class Test { + field Three + } + "##, + ) + .unwrap(); + + let class = ir.find_class("Test").unwrap(); + let alias = class.find_field("field").unwrap(); + + let FieldType::Alias { resolution, .. } = alias.r#type() else { + panic!("expected alias type, found {:?}", alias.r#type()); + }; + + let FieldType::Constrained { base, constraints } = &**resolution else { + panic!("expected resolved constrained type, found {:?}", resolution); + }; + + assert_eq!(constraints.len(), 3); + + assert_eq!(constraints[0].level, ConstraintLevel::Assert); + assert_eq!(constraints[0].label, None); + + assert_eq!(constraints[1].level, ConstraintLevel::Check); + assert_eq!(constraints[1].label, Some("lt_twenty".to_string())); + + assert_eq!(constraints[2].level, ConstraintLevel::Check); + assert_eq!(constraints[2].label, Some("gt_ten".to_string())); + } } diff --git a/engine/baml-lib/baml/tests/validation_files/class/invalid_attrs_on_type_alias.baml b/engine/baml-lib/baml/tests/validation_files/class/invalid_attrs_on_type_alias.baml new file mode 100644 index 000000000..51c63cdba --- /dev/null +++ b/engine/baml-lib/baml/tests/validation_files/class/invalid_attrs_on_type_alias.baml @@ -0,0 +1,24 @@ +type DescNotAllowed = string @description("This is not allowed") + +type AliasNotAllowed = float @alias("Alias not allowed") + +type SkipNotAllowed = float @skip + +// error: Error validating: type aliases may only have check and assert attributes +// --> class/invalid_attrs_on_type_alias.baml:1 +// | +// | +// 1 | type DescNotAllowed = string @description("This is not allowed") +// | +// error: Error validating: type aliases may only have check and assert attributes +// --> class/invalid_attrs_on_type_alias.baml:3 +// | +// 2 | +// 3 | type AliasNotAllowed = float @alias("Alias not allowed") +// | +// error: Error validating: type aliases may only have check and assert attributes +// --> class/invalid_attrs_on_type_alias.baml:5 +// | +// 4 | +// 5 | type SkipNotAllowed = float @skip +// | diff --git a/engine/baml-lib/baml/tests/validation_files/class/type_aliases.baml b/engine/baml-lib/baml/tests/validation_files/class/type_aliases.baml index 94d2cb6ac..42b0f781a 100644 --- a/engine/baml-lib/baml/tests/validation_files/class/type_aliases.baml +++ b/engine/baml-lib/baml/tests/validation_files/class/type_aliases.baml @@ -6,6 +6,14 @@ type Graph = map<string, string[]> type Combination = Primitive | List | Graph +// Alias with attrs. +type Currency = int @check(gt_ten, {{ this > 10 }}) +type Amount = Currency @assert ({{ this > 0 }}) + +class MergeAttrs { + amount Amount @description("In USD") +} + function PrimitiveAlias(p: Primitive) -> Primitive { client "openai/gpt-4o" prompt r#" @@ -34,3 +42,14 @@ function NestedAlias(c: Combination) -> Combination { {{ ctx.output_format }} "# } + +function MergeAliasAttributes(money: int) -> MergeAttrs { + client "openai/gpt-4o" + prompt r#" + Return the given integer in the specified format: + + {{ money }} + + {{ ctx.output_format }} + "# +} \ No newline at end of file diff --git a/engine/baml-lib/parser-database/src/attributes/mod.rs b/engine/baml-lib/parser-database/src/attributes/mod.rs index ec7dcdd2b..aae4b7b0b 100644 --- a/engine/baml-lib/parser-database/src/attributes/mod.rs +++ b/engine/baml-lib/parser-database/src/attributes/mod.rs @@ -1,5 +1,7 @@ -use internal_baml_diagnostics::Span; -use internal_baml_schema_ast::ast::{Top, TopId, TypeExpId, TypeExpressionBlock}; +use internal_baml_diagnostics::{DatamodelError, Span}; +use internal_baml_schema_ast::ast::{ + Assignment, Top, TopId, TypeAliasId, TypeExpId, TypeExpressionBlock, +}; mod alias; pub mod constraint; @@ -79,6 +81,9 @@ pub(super) fn resolve_attributes(ctx: &mut Context<'_>) { (TopId::Enum(enum_id), Top::Enum(ast_enum)) => { resolve_type_exp_block_attributes(enum_id, ast_enum, ctx, SubType::Enum) } + (TopId::TypeAlias(alias_id), Top::TypeAlias(assignment)) => { + resolve_type_alias_attributes(alias_id, assignment, ctx) + } _ => (), } } @@ -132,3 +137,40 @@ fn resolve_type_exp_block_attributes<'db>( _ => (), } } + +/// Quick hack to validate type alias attributes. +/// +/// Unlike classes and enums, type aliases only support checks and asserts. +/// Everything else is reported as an error. On top of that, checks and asserts +/// must be merged when aliases point to other aliases. We do this recursively +/// when resolving the type alias to its final "virtual" type at +/// [`crate::types::resolve_type_alias`]. +/// +/// Then checks and asserts are collected from the virtual type and stored in +/// the IR at `engine/baml-lib/baml-core/src/ir/repr.rs`, so there's no need to +/// store them in separate classes like [`ClassAttributes`] or similar, at least +/// for now. +fn resolve_type_alias_attributes<'db>( + alias_id: TypeAliasId, + assignment: &'db Assignment, + ctx: &mut Context<'db>, +) { + ctx.assert_all_attributes_processed(alias_id.into()); + let type_alias_attributes = to_string_attribute::visit(ctx, assignment.value.span(), false); + ctx.validate_visited_attributes(); + + // Some additional specific validation for type alias attributes. + if let Some(attrs) = &type_alias_attributes { + if attrs.dynamic_type().is_some() + || attrs.alias().is_some() + || attrs.skip().is_some() + || attrs.description().is_some() + { + ctx.diagnostics + .push_error(DatamodelError::new_validation_error( + "type aliases may only have check and assert attributes", + assignment.span.clone(), + )); + } + } +} diff --git a/engine/baml-lib/parser-database/src/lib.rs b/engine/baml-lib/parser-database/src/lib.rs index 19dfdd845..2f54efd1e 100644 --- a/engine/baml-lib/parser-database/src/lib.rs +++ b/engine/baml-lib/parser-database/src/lib.rs @@ -600,4 +600,19 @@ mod test { Ok(()) } + + #[test] + fn merged_alias_attrs() -> Result<(), Diagnostics> { + #[rustfmt::skip] + let db = parse(r#" + type One = int @assert({{ this < 5 }}) + type Two = One @assert({{ this > 0 }}) + "#)?; + + let resolved = db.resolved_type_alias_by_name("Two").unwrap(); + + assert_eq!(resolved.attributes().len(), 2); + + Ok(()) + } } diff --git a/engine/baml-lib/parser-database/src/types/mod.rs b/engine/baml-lib/parser-database/src/types/mod.rs index ceff32439..966757b3a 100644 --- a/engine/baml-lib/parser-database/src/types/mod.rs +++ b/engine/baml-lib/parser-database/src/types/mod.rs @@ -393,7 +393,7 @@ fn visit_class<'db>( pub fn resolve_type_alias(field_type: &FieldType, db: &ParserDatabase) -> FieldType { match field_type { // For symbols we need to check if we're dealing with aliases. - FieldType::Symbol(arity, ident, span) => { + FieldType::Symbol(arity, ident, attrs) => { let Some(string_id) = db.interner.lookup(ident.name()) else { unreachable!( "Attempting to resolve alias `{ident}` that does not exist in the interner" @@ -406,7 +406,7 @@ pub fn resolve_type_alias(field_type: &FieldType, db: &ParserDatabase) -> FieldT match top_id { ast::TopId::TypeAlias(alias_id) => { - let resolved = match db.types.resolved_type_aliases.get(alias_id) { + let mut resolved = match db.types.resolved_type_aliases.get(alias_id) { // Check if we can avoid deeper recursion. Some(already_resolved) => already_resolved.to_owned(), // No luck, recurse. @@ -419,11 +419,24 @@ pub fn resolve_type_alias(field_type: &FieldType, db: &ParserDatabase) -> FieldT // type AliasTwo = AliasOne // // AliasTwo resolves to an "optional" type. - if resolved.is_optional() || arity.is_optional() { + // + // TODO: Add a `set_arity` function or something and avoid + // this clone. + resolved = if resolved.is_optional() || arity.is_optional() { resolved.to_nullable() } else { resolved - } + }; + + // Merge attributes. + resolved.set_attributes({ + let mut merged_attrs = Vec::from(field_type.attributes()); + merged_attrs.extend(resolved.attributes().to_owned()); + + merged_attrs + }); + + resolved } // Class or enum. Already "resolved", pop off the stack. diff --git a/engine/baml-lib/schema-ast/src/ast/attribute.rs b/engine/baml-lib/schema-ast/src/ast/attribute.rs index 18c05be40..544b07656 100644 --- a/engine/baml-lib/schema-ast/src/ast/attribute.rs +++ b/engine/baml-lib/schema-ast/src/ast/attribute.rs @@ -65,6 +65,7 @@ pub enum AttributeContainer { ClassField(super::TypeExpId, super::FieldId), Enum(super::TypeExpId), EnumValue(super::TypeExpId, super::FieldId), + TypeAlias(super::TypeAliasId), } impl From<super::TypeExpId> for AttributeContainer { @@ -79,6 +80,12 @@ impl From<(super::TypeExpId, super::FieldId)> for AttributeContainer { } } +impl From<super::TypeAliasId> for AttributeContainer { + fn from(v: super::TypeAliasId) -> Self { + Self::TypeAlias(v) + } +} + /// An attribute (@ or @@) node in the AST. #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] pub struct AttributeId(AttributeContainer, u32); @@ -102,6 +109,7 @@ impl Index<AttributeContainer> for super::SchemaAst { AttributeContainer::EnumValue(enum_id, value_idx) => { &self[enum_id][value_idx].attributes } + AttributeContainer::TypeAlias(alias_id) => &self[alias_id].value.attributes(), } } } diff --git a/engine/baml-lib/schema-ast/src/parser/datamodel.pest b/engine/baml-lib/schema-ast/src/parser/datamodel.pest index 83e4f49df..f914a460c 100644 --- a/engine/baml-lib/schema-ast/src/parser/datamodel.pest +++ b/engine/baml-lib/schema-ast/src/parser/datamodel.pest @@ -78,7 +78,7 @@ single_word = @{ ASCII_ALPHA ~ (ASCII_ALPHANUMERIC | "_" | "-")* } // ###################################### // Type Alias // ###################################### -type_alias = { identifier ~ identifier ~ assignment ~ field_type } +type_alias = { identifier ~ identifier ~ assignment ~ field_type_with_attr } // ###################################### // Arguments diff --git a/engine/baml-lib/schema-ast/src/parser/parse_assignment.rs b/engine/baml-lib/schema-ast/src/parser/parse_assignment.rs index 35298d315..a66c64264 100644 --- a/engine/baml-lib/schema-ast/src/parser/parse_assignment.rs +++ b/engine/baml-lib/schema-ast/src/parser/parse_assignment.rs @@ -5,7 +5,11 @@ use super::{ Rule, }; -use crate::{assert_correct_parser, ast::*, parser::parse_types::parse_field_type}; +use crate::{ + assert_correct_parser, + ast::*, + parser::{parse_field::parse_field_type_with_attr, parse_types::parse_field_type}, +}; use internal_baml_diagnostics::{DatamodelError, Diagnostics}; @@ -45,8 +49,14 @@ pub(crate) fn parse_assignment(pair: Pair<'_>, diagnostics: &mut Diagnostics) -> Rule::assignment => {} // Ok, equal sign. + // TODO: We probably only need field_type_with_attr since that's how + // the PEST syntax is defined. Rule::field_type => field_type = parse_field_type(current, diagnostics), + Rule::field_type_with_attr => { + field_type = parse_field_type_with_attr(current, false, diagnostics) + } + _ => parsing_catch_all(current, "type_alias"), } } diff --git a/integ-tests/baml_src/test-files/functions/output/type-aliases.baml b/integ-tests/baml_src/test-files/functions/output/type-aliases.baml index 94d2cb6ac..69005e6ce 100644 --- a/integ-tests/baml_src/test-files/functions/output/type-aliases.baml +++ b/integ-tests/baml_src/test-files/functions/output/type-aliases.baml @@ -34,3 +34,33 @@ function NestedAlias(c: Combination) -> Combination { {{ ctx.output_format }} "# } + +// Test attribute merging. +type Currency = int @check(gt_ten, {{ this > 10 }}) +type Amount = Currency @assert ({{ this > 0 }}) + +class MergeAttrs { + amount Amount @description("In USD") +} + +function MergeAliasAttributes(money: int) -> MergeAttrs { + client "openai/gpt-4o" + prompt r#" + Return the given integer in the specified format: + + {{ money }} + + {{ ctx.output_format }} + "# +} + +function ReturnAliasWithMergedAttributes(money: Amount) -> Amount { + client "openai/gpt-4o" + prompt r#" + Return the given integer without additional context: + + {{ money }} + + {{ ctx.output_format }} + "# +} \ No newline at end of file diff --git a/integ-tests/python/baml_client/async_client.py b/integ-tests/python/baml_client/async_client.py index 8a11ddf4c..03d1283e9 100644 --- a/integ-tests/python/baml_client/async_client.py +++ b/integ-tests/python/baml_client/async_client.py @@ -1522,6 +1522,29 @@ async def MapAlias( ) return cast(Dict[str, List[str]], raw.cast_to(types, types)) + async def MergeAliasAttributes( + self, + money: int, + baml_options: BamlCallOptions = {}, + ) -> types.MergeAttrs: + __tb__ = baml_options.get("tb", None) + if __tb__ is not None: + tb = __tb__._tb # type: ignore (we know how to use this private attribute) + else: + tb = None + __cr__ = baml_options.get("client_registry", None) + + raw = await self.__runtime.call_function( + "MergeAliasAttributes", + { + "money": money, + }, + self.__ctx_manager.get(), + tb, + __cr__, + ) + return cast(types.MergeAttrs, raw.cast_to(types, types)) + async def MyFunc( self, input: str, @@ -1844,6 +1867,29 @@ async def RecursiveClassWithAliasIndirection( ) return cast(types.NodeWithAliasIndirection, raw.cast_to(types, types)) + async def ReturnAliasWithMergedAttributes( + self, + money: Checked[int,types.Literal["gt_ten"]], + baml_options: BamlCallOptions = {}, + ) -> Checked[int,types.Literal["gt_ten"]]: + __tb__ = baml_options.get("tb", None) + if __tb__ is not None: + tb = __tb__._tb # type: ignore (we know how to use this private attribute) + else: + tb = None + __cr__ = baml_options.get("client_registry", None) + + raw = await self.__runtime.call_function( + "ReturnAliasWithMergedAttributes", + { + "money": money, + }, + self.__ctx_manager.get(), + tb, + __cr__, + ) + return cast(Checked[int,types.Literal["gt_ten"]], raw.cast_to(types, types)) + async def ReturnFailingAssert( self, inp: int, @@ -4838,6 +4884,36 @@ def MapAlias( self.__ctx_manager.get(), ) + def MergeAliasAttributes( + self, + money: int, + baml_options: BamlCallOptions = {}, + ) -> baml_py.BamlStream[partial_types.MergeAttrs, types.MergeAttrs]: + __tb__ = baml_options.get("tb", None) + if __tb__ is not None: + tb = __tb__._tb # type: ignore (we know how to use this private attribute) + else: + tb = None + __cr__ = baml_options.get("client_registry", None) + + raw = self.__runtime.stream_function( + "MergeAliasAttributes", + { + "money": money, + }, + None, + self.__ctx_manager.get(), + tb, + __cr__, + ) + + return baml_py.BamlStream[partial_types.MergeAttrs, types.MergeAttrs]( + raw, + lambda x: cast(partial_types.MergeAttrs, x.cast_to(types, partial_types)), + lambda x: cast(types.MergeAttrs, x.cast_to(types, types)), + self.__ctx_manager.get(), + ) + def MyFunc( self, input: str, @@ -5258,6 +5334,36 @@ def RecursiveClassWithAliasIndirection( self.__ctx_manager.get(), ) + def ReturnAliasWithMergedAttributes( + self, + money: Checked[int,types.Literal["gt_ten"]], + baml_options: BamlCallOptions = {}, + ) -> baml_py.BamlStream[Checked[Optional[int],types.Literal["gt_ten"]], Checked[int,types.Literal["gt_ten"]]]: + __tb__ = baml_options.get("tb", None) + if __tb__ is not None: + tb = __tb__._tb # type: ignore (we know how to use this private attribute) + else: + tb = None + __cr__ = baml_options.get("client_registry", None) + + raw = self.__runtime.stream_function( + "ReturnAliasWithMergedAttributes", + { + "money": money, + }, + None, + self.__ctx_manager.get(), + tb, + __cr__, + ) + + return baml_py.BamlStream[Checked[Optional[int],types.Literal["gt_ten"]], Checked[int,types.Literal["gt_ten"]]]( + raw, + lambda x: cast(Checked[Optional[int],types.Literal["gt_ten"]], x.cast_to(types, partial_types)), + lambda x: cast(Checked[int,types.Literal["gt_ten"]], x.cast_to(types, types)), + self.__ctx_manager.get(), + ) + def ReturnFailingAssert( self, inp: int, diff --git a/integ-tests/python/baml_client/inlinedbaml.py b/integ-tests/python/baml_client/inlinedbaml.py index 5323c1d02..cbcab08d6 100644 --- a/integ-tests/python/baml_client/inlinedbaml.py +++ b/integ-tests/python/baml_client/inlinedbaml.py @@ -80,10 +80,10 @@ "test-files/functions/output/optional-class.baml": "class ClassOptionalOutput {\n prop1 string\n prop2 string\n}\n\nfunction FnClassOptionalOutput(input: string) -> ClassOptionalOutput? {\n client GPT35\n prompt #\"\n Return a json blob for the following input:\n {{input}}\n\n {{ctx.output_format}}\n\n JSON:\n \"#\n}\n\n\nclass Blah {\n prop4 string?\n}\n\nclass ClassOptionalOutput2 {\n prop1 string?\n prop2 string?\n prop3 Blah?\n}\n\nfunction FnClassOptionalOutput2(input: string) -> ClassOptionalOutput2? {\n client GPT35\n prompt #\"\n Return a json blob for the following input:\n {{input}}\n\n {{ctx.output_format}}\n\n JSON:\n \"#\n}\n\ntest FnClassOptionalOutput2 {\n functions [FnClassOptionalOutput2, FnClassOptionalOutput]\n args {\n input \"example input\"\n }\n}\n", "test-files/functions/output/optional.baml": "class OptionalTest_Prop1 {\n omega_a string\n omega_b int\n}\n\nenum OptionalTest_CategoryType {\n Aleph\n Beta\n Gamma\n}\n \nclass OptionalTest_ReturnType {\n omega_1 OptionalTest_Prop1?\n omega_2 string?\n omega_3 (OptionalTest_CategoryType?)[]\n} \n \nfunction OptionalTest_Function(input: string) -> (OptionalTest_ReturnType?)[]\n{ \n client GPT35\n prompt #\"\n Return a JSON blob with this schema: \n {{ctx.output_format}}\n\n JSON:\n \"#\n}\n\ntest OptionalTest_Function {\n functions [OptionalTest_Function]\n args {\n input \"example input\"\n }\n}\n", "test-files/functions/output/recursive-class.baml": "class Node {\n data int\n next Node?\n}\n\nclass LinkedList {\n head Node?\n len int\n}\n\nclient<llm> O1 {\n provider \"openai\"\n options {\n model \"o1-mini\"\n default_role \"user\"\n }\n}\n\nfunction BuildLinkedList(input: int[]) -> LinkedList {\n client O1\n prompt #\"\n Build a linked list from the input array of integers.\n\n INPUT:\n {{ input }}\n\n {{ ctx.output_format }} \n \"#\n}\n\ntest TestLinkedList {\n functions [BuildLinkedList]\n args {\n input [1, 2, 3, 4, 5]\n }\n}\n", - "test-files/functions/output/recursive-type-aliases.baml": "class LinkedListAliasNode {\n value int\n next LinkedListAliasNode?\n}\n\n// Simple alias that points to recursive type.\ntype LinkedListAlias = LinkedListAliasNode\n\nfunction AliasThatPointsToRecursiveType(list: LinkedListAlias) -> LinkedListAlias {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given linked list back:\n \n {{ list }}\n \n {{ ctx.output_format }}\n \"#\n}\n\n// Class that points to an alias that points to a recursive type.\nclass ClassToRecAlias {\n list LinkedListAlias\n}\n\nfunction ClassThatPointsToRecursiveClassThroughAlias(cls: ClassToRecAlias) -> ClassToRecAlias {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given object back:\n \n {{ cls }}\n \n {{ ctx.output_format }}\n \"#\n}\n\n// This is tricky cause this class should be hoisted, but classes and aliases\n// are two different types in the AST. This test will make sure they can interop.\nclass NodeWithAliasIndirection {\n value int\n next NodeIndirection?\n}\n\ntype NodeIndirection = NodeWithAliasIndirection\n\nfunction RecursiveClassWithAliasIndirection(cls: NodeWithAliasIndirection) -> NodeWithAliasIndirection {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given object back:\n \n {{ cls }}\n \n {{ ctx.output_format }}\n \"#\n}", + "test-files/functions/output/recursive-type-aliases.baml": "class LinkedListAliasNode {\n value int\n next LinkedListAliasNode?\n}\n\n// Simple alias that points to recursive type.\ntype LinkedListAlias = LinkedListAliasNode\n\nfunction AliasThatPointsToRecursiveType(list: LinkedListAlias) -> LinkedListAlias {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given linked list back:\n \n {{ list }}\n \n {{ ctx.output_format }}\n \"#\n}\n\n// Class that points to an alias that points to a recursive type.\nclass ClassToRecAlias {\n list LinkedListAlias\n}\n\nfunction ClassThatPointsToRecursiveClassThroughAlias(cls: ClassToRecAlias) -> ClassToRecAlias {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given object back:\n \n {{ cls }}\n \n {{ ctx.output_format }}\n \"#\n}\n\n// This is tricky cause this class should be hoisted, but classes and aliases\n// are two different types in the AST. This test will make sure they can interop.\nclass NodeWithAliasIndirection {\n value int\n next NodeIndirection?\n}\n\ntype NodeIndirection = NodeWithAliasIndirection\n\nfunction RecursiveClassWithAliasIndirection(cls: NodeWithAliasIndirection) -> NodeWithAliasIndirection {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given object back:\n \n {{ cls }}\n \n {{ ctx.output_format }}\n \"#\n}\n", "test-files/functions/output/serialization-error.baml": "class DummyOutput {\n nonce string\n nonce2 string\n @@dynamic\n}\n\nfunction DummyOutputFunction(input: string) -> DummyOutput {\n client GPT35\n prompt #\"\n Say \"hello there\".\n \"#\n}", "test-files/functions/output/string-list.baml": "function FnOutputStringList(input: string) -> string[] {\n client GPT35\n prompt #\"\n Return a list of strings in json format like [\"string1\", \"string2\", \"string3\"].\n\n JSON:\n \"#\n}\n\ntest FnOutputStringList {\n functions [FnOutputStringList]\n args {\n input \"example input\"\n }\n}\n", - "test-files/functions/output/type-aliases.baml": "type Primitive = int | string | bool | float\n\ntype List = string[]\n\ntype Graph = map<string, string[]>\n\ntype Combination = Primitive | List | Graph\n\nfunction PrimitiveAlias(p: Primitive) -> Primitive {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given value back: {{ p }}\n \"#\n}\n\nfunction MapAlias(m: Graph) -> Graph {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given Graph back:\n\n {{ m }}\n\n {{ ctx.output_format }}\n \"#\n}\n\nfunction NestedAlias(c: Combination) -> Combination {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given value back:\n\n {{ c }}\n\n {{ ctx.output_format }}\n \"#\n}\n", + "test-files/functions/output/type-aliases.baml": "type Primitive = int | string | bool | float\n\ntype List = string[]\n\ntype Graph = map<string, string[]>\n\ntype Combination = Primitive | List | Graph\n\nfunction PrimitiveAlias(p: Primitive) -> Primitive {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given value back: {{ p }}\n \"#\n}\n\nfunction MapAlias(m: Graph) -> Graph {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given Graph back:\n\n {{ m }}\n\n {{ ctx.output_format }}\n \"#\n}\n\nfunction NestedAlias(c: Combination) -> Combination {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given value back:\n\n {{ c }}\n\n {{ ctx.output_format }}\n \"#\n}\n\n// Test attribute merging.\ntype Currency = int @check(gt_ten, {{ this > 10 }})\ntype Amount = Currency @assert ({{ this > 0 }})\n\nclass MergeAttrs {\n amount Amount @description(\"In USD\")\n}\n\nfunction MergeAliasAttributes(money: int) -> MergeAttrs {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given integer in the specified format:\n\n {{ money }}\n\n {{ ctx.output_format }}\n \"#\n}\n\nfunction ReturnAliasWithMergedAttributes(money: Amount) -> Amount {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given integer without additional context:\n\n {{ money }}\n\n {{ ctx.output_format }}\n \"#\n}", "test-files/functions/output/unions.baml": "class UnionTest_ReturnType {\n prop1 string | bool\n prop2 (float | bool)[]\n prop3 (bool[] | int[])\n}\n\nfunction UnionTest_Function(input: string | bool) -> UnionTest_ReturnType {\n client GPT35\n prompt #\"\n Return a JSON blob with this schema: \n {{ctx.output_format}}\n\n JSON:\n \"#\n}\n\ntest UnionTest_Function {\n functions [UnionTest_Function]\n args {\n input \"example input\"\n }\n}\n", "test-files/functions/prompts/no-chat-messages.baml": "\n\nfunction PromptTestClaude(input: string) -> string {\n client Sonnet\n prompt #\"\n Tell me a haiku about {{ input }}\n \"#\n}\n\n\nfunction PromptTestStreaming(input: string) -> string {\n client GPT35\n prompt #\"\n Tell me a short story about {{ input }}\n \"#\n}\n\ntest TestName {\n functions [PromptTestStreaming]\n args {\n input #\"\n hello world\n \"#\n }\n}\n", "test-files/functions/prompts/with-chat-messages.baml": "\nfunction PromptTestOpenAIChat(input: string) -> string {\n client GPT35\n prompt #\"\n {{ _.role(\"system\") }}\n You are an assistant that always responds in a very excited way with emojis and also outputs this word 4 times after giving a response: {{ input }}\n \n {{ _.role(\"user\") }}\n Tell me a haiku about {{ input }}\n \"#\n}\n\nfunction PromptTestOpenAIChatNoSystem(input: string) -> string {\n client GPT35\n prompt #\"\n You are an assistant that always responds in a very excited way with emojis and also outputs this word 4 times after giving a response: {{ input }}\n \n {{ _.role(\"user\") }}\n Tell me a haiku about {{ input }}\n \"#\n}\n\nfunction PromptTestClaudeChat(input: string) -> string {\n client Claude\n prompt #\"\n {{ _.role(\"system\") }}\n You are an assistant that always responds in a very excited way with emojis and also outputs this word 4 times after giving a response: {{ input }}\n \n {{ _.role(\"user\") }}\n Tell me a haiku about {{ input }}\n \"#\n}\n\nfunction PromptTestClaudeChatNoSystem(input: string) -> string {\n client Claude\n prompt #\"\n You are an assistant that always responds in a very excited way with emojis and also outputs this word 4 times after giving a response: {{ input }}\n \n {{ _.role(\"user\") }}\n Tell me a haiku about {{ input }}\n \"#\n}\n\ntest TestSystemAndNonSystemChat1 {\n functions [PromptTestClaude, PromptTestOpenAI, PromptTestOpenAIChat, PromptTestOpenAIChatNoSystem, PromptTestClaudeChat, PromptTestClaudeChatNoSystem]\n args {\n input \"cats\"\n }\n}\n\ntest TestSystemAndNonSystemChat2 {\n functions [PromptTestClaude, PromptTestOpenAI, PromptTestOpenAIChat, PromptTestOpenAIChatNoSystem, PromptTestClaudeChat, PromptTestClaudeChatNoSystem]\n args {\n input \"lion\"\n }\n}", diff --git a/integ-tests/python/baml_client/partial_types.py b/integ-tests/python/baml_client/partial_types.py index 6d5219e3e..c2109ecaa 100644 --- a/integ-tests/python/baml_client/partial_types.py +++ b/integ-tests/python/baml_client/partial_types.py @@ -202,6 +202,9 @@ class Martian(BaseModel): """The age of the Martian in Mars years. So many Mars years.""" +class MergeAttrs(BaseModel): + amount: Checked[Optional[int],Literal["gt_ten"]] + class NamedArgsSingleClass(BaseModel): key: Optional[str] = None key_two: Optional[bool] = None diff --git a/integ-tests/python/baml_client/sync_client.py b/integ-tests/python/baml_client/sync_client.py index 3c2197718..ba24760c7 100644 --- a/integ-tests/python/baml_client/sync_client.py +++ b/integ-tests/python/baml_client/sync_client.py @@ -1519,6 +1519,29 @@ def MapAlias( ) return cast(Dict[str, List[str]], raw.cast_to(types, types)) + def MergeAliasAttributes( + self, + money: int, + baml_options: BamlCallOptions = {}, + ) -> types.MergeAttrs: + __tb__ = baml_options.get("tb", None) + if __tb__ is not None: + tb = __tb__._tb # type: ignore (we know how to use this private attribute) + else: + tb = None + __cr__ = baml_options.get("client_registry", None) + + raw = self.__runtime.call_function_sync( + "MergeAliasAttributes", + { + "money": money, + }, + self.__ctx_manager.get(), + tb, + __cr__, + ) + return cast(types.MergeAttrs, raw.cast_to(types, types)) + def MyFunc( self, input: str, @@ -1841,6 +1864,29 @@ def RecursiveClassWithAliasIndirection( ) return cast(types.NodeWithAliasIndirection, raw.cast_to(types, types)) + def ReturnAliasWithMergedAttributes( + self, + money: Checked[int,types.Literal["gt_ten"]], + baml_options: BamlCallOptions = {}, + ) -> Checked[int,types.Literal["gt_ten"]]: + __tb__ = baml_options.get("tb", None) + if __tb__ is not None: + tb = __tb__._tb # type: ignore (we know how to use this private attribute) + else: + tb = None + __cr__ = baml_options.get("client_registry", None) + + raw = self.__runtime.call_function_sync( + "ReturnAliasWithMergedAttributes", + { + "money": money, + }, + self.__ctx_manager.get(), + tb, + __cr__, + ) + return cast(Checked[int,types.Literal["gt_ten"]], raw.cast_to(types, types)) + def ReturnFailingAssert( self, inp: int, @@ -4836,6 +4882,36 @@ def MapAlias( self.__ctx_manager.get(), ) + def MergeAliasAttributes( + self, + money: int, + baml_options: BamlCallOptions = {}, + ) -> baml_py.BamlSyncStream[partial_types.MergeAttrs, types.MergeAttrs]: + __tb__ = baml_options.get("tb", None) + if __tb__ is not None: + tb = __tb__._tb # type: ignore (we know how to use this private attribute) + else: + tb = None + __cr__ = baml_options.get("client_registry", None) + + raw = self.__runtime.stream_function_sync( + "MergeAliasAttributes", + { + "money": money, + }, + None, + self.__ctx_manager.get(), + tb, + __cr__, + ) + + return baml_py.BamlSyncStream[partial_types.MergeAttrs, types.MergeAttrs]( + raw, + lambda x: cast(partial_types.MergeAttrs, x.cast_to(types, partial_types)), + lambda x: cast(types.MergeAttrs, x.cast_to(types, types)), + self.__ctx_manager.get(), + ) + def MyFunc( self, input: str, @@ -5256,6 +5332,36 @@ def RecursiveClassWithAliasIndirection( self.__ctx_manager.get(), ) + def ReturnAliasWithMergedAttributes( + self, + money: Checked[int,types.Literal["gt_ten"]], + baml_options: BamlCallOptions = {}, + ) -> baml_py.BamlSyncStream[Checked[Optional[int],types.Literal["gt_ten"]], Checked[int,types.Literal["gt_ten"]]]: + __tb__ = baml_options.get("tb", None) + if __tb__ is not None: + tb = __tb__._tb # type: ignore (we know how to use this private attribute) + else: + tb = None + __cr__ = baml_options.get("client_registry", None) + + raw = self.__runtime.stream_function_sync( + "ReturnAliasWithMergedAttributes", + { + "money": money, + }, + None, + self.__ctx_manager.get(), + tb, + __cr__, + ) + + return baml_py.BamlSyncStream[Checked[Optional[int],types.Literal["gt_ten"]], Checked[int,types.Literal["gt_ten"]]]( + raw, + lambda x: cast(Checked[Optional[int],types.Literal["gt_ten"]], x.cast_to(types, partial_types)), + lambda x: cast(Checked[int,types.Literal["gt_ten"]], x.cast_to(types, types)), + self.__ctx_manager.get(), + ) + def ReturnFailingAssert( self, inp: int, diff --git a/integ-tests/python/baml_client/type_builder.py b/integ-tests/python/baml_client/type_builder.py index 03de2a5e9..805e525b4 100644 --- a/integ-tests/python/baml_client/type_builder.py +++ b/integ-tests/python/baml_client/type_builder.py @@ -20,7 +20,7 @@ class TypeBuilder(_TypeBuilder): def __init__(self): super().__init__(classes=set( - ["BigNumbers","BinaryNode","Blah","BlockConstraint","BlockConstraintForParam","BookOrder","ClassOptionalOutput","ClassOptionalOutput2","ClassToRecAlias","ClassWithImage","CompoundBigNumbers","ContactInfo","CustomTaskResult","DummyOutput","DynInputOutput","DynamicClassOne","DynamicClassTwo","DynamicOutput","Earthling","Education","Email","EmailAddress","Event","FakeImage","FlightConfirmation","FooAny","Forest","GroceryReceipt","InnerClass","InnerClass2","InputClass","InputClassNested","LinkedList","LinkedListAliasNode","LiteralClassHello","LiteralClassOne","LiteralClassTwo","MalformedConstraints","MalformedConstraints2","Martian","NamedArgsSingleClass","Nested","Nested2","NestedBlockConstraint","NestedBlockConstraintForParam","Node","NodeWithAliasIndirection","OptionalTest_Prop1","OptionalTest_ReturnType","OrderInfo","OriginalA","OriginalB","Person","PhoneNumber","Quantity","RaysData","ReceiptInfo","ReceiptItem","Recipe","Resume","Schema","SearchParams","SomeClassNestedDynamic","StringToClassEntry","TestClassAlias","TestClassNested","TestClassWithEnum","TestOutputClass","Tree","TwoStoriesOneTitle","UnionTest_ReturnType","WithReasoning",] + ["BigNumbers","BinaryNode","Blah","BlockConstraint","BlockConstraintForParam","BookOrder","ClassOptionalOutput","ClassOptionalOutput2","ClassToRecAlias","ClassWithImage","CompoundBigNumbers","ContactInfo","CustomTaskResult","DummyOutput","DynInputOutput","DynamicClassOne","DynamicClassTwo","DynamicOutput","Earthling","Education","Email","EmailAddress","Event","FakeImage","FlightConfirmation","FooAny","Forest","GroceryReceipt","InnerClass","InnerClass2","InputClass","InputClassNested","LinkedList","LinkedListAliasNode","LiteralClassHello","LiteralClassOne","LiteralClassTwo","MalformedConstraints","MalformedConstraints2","Martian","MergeAttrs","NamedArgsSingleClass","Nested","Nested2","NestedBlockConstraint","NestedBlockConstraintForParam","Node","NodeWithAliasIndirection","OptionalTest_Prop1","OptionalTest_ReturnType","OrderInfo","OriginalA","OriginalB","Person","PhoneNumber","Quantity","RaysData","ReceiptInfo","ReceiptItem","Recipe","Resume","Schema","SearchParams","SomeClassNestedDynamic","StringToClassEntry","TestClassAlias","TestClassNested","TestClassWithEnum","TestOutputClass","Tree","TwoStoriesOneTitle","UnionTest_ReturnType","WithReasoning",] ), enums=set( ["AliasedEnum","Category","Category2","Category3","Color","DataType","DynEnumOne","DynEnumTwo","EnumInClass","EnumOutput","Hobby","MapKey","NamedArgsSingleEnum","NamedArgsSingleEnumList","OptionalTest_CategoryType","OrderStatus","Tag","TestEnum",] )) diff --git a/integ-tests/python/baml_client/types.py b/integ-tests/python/baml_client/types.py index 21545e11f..1b64063b6 100644 --- a/integ-tests/python/baml_client/types.py +++ b/integ-tests/python/baml_client/types.py @@ -327,6 +327,9 @@ class Martian(BaseModel): """The age of the Martian in Mars years. So many Mars years.""" +class MergeAttrs(BaseModel): + amount: Checked[int,Literal["gt_ten"]] + class NamedArgsSingleClass(BaseModel): key: str key_two: bool diff --git a/integ-tests/python/tests/test_functions.py b/integ-tests/python/tests/test_functions.py index 2838e8331..c6cb84656 100644 --- a/integ-tests/python/tests/test_functions.py +++ b/integ-tests/python/tests/test_functions.py @@ -43,6 +43,7 @@ LinkedListAliasNode, ClassToRecAlias, NodeWithAliasIndirection, + MergeAttrs, ) import baml_client.types as types from ..baml_client.tracing import trace, set_tags, flush, on_log_event @@ -295,6 +296,18 @@ async def test_recursive_class_with_alias_indirection(self): value=1, next=NodeWithAliasIndirection(value=2, next=None) ) + @pytest.mark.asyncio + async def test_merge_alias_attributes(self): + res = await b.MergeAliasAttributes(123) + assert res.amount.value == 123 + assert res.amount.checks["gt_ten"].status == "succeeded" + + @pytest.mark.asyncio + async def test_return_alias_with_merged_attrs(self): + res = await b.ReturnAliasWithMergedAttributes(123) + assert res.value == 123 + assert res.checks["gt_ten"].status == "succeeded" + class MyCustomClass(NamedArgsSingleClass): date: datetime.datetime diff --git a/integ-tests/ruby/baml_client/client.rb b/integ-tests/ruby/baml_client/client.rb index b2fb18bad..4fbefbdbf 100644 --- a/integ-tests/ruby/baml_client/client.rb +++ b/integ-tests/ruby/baml_client/client.rb @@ -2098,6 +2098,38 @@ def MapAlias( (raw.parsed_using_types(Baml::Types)) end + sig { + params( + varargs: T.untyped, + money: Integer, + baml_options: T::Hash[Symbol, T.any(Baml::TypeBuilder, Baml::ClientRegistry)] + ).returns(Baml::Types::MergeAttrs) + } + def MergeAliasAttributes( + *varargs, + money:, + baml_options: {} + ) + if varargs.any? + + raise ArgumentError.new("MergeAliasAttributes may only be called with keyword arguments") + end + if (baml_options.keys - [:client_registry, :tb]).any? + raise ArgumentError.new("Received unknown keys in baml_options (valid keys: :client_registry, :tb): #{baml_options.keys - [:client_registry, :tb]}") + end + + raw = @runtime.call_function( + "MergeAliasAttributes", + { + money: money, + }, + @ctx_manager, + baml_options[:tb]&.instance_variable_get(:@registry), + baml_options[:client_registry], + ) + (raw.parsed_using_types(Baml::Types)) + end + sig { params( varargs: T.untyped, @@ -2546,6 +2578,38 @@ def RecursiveClassWithAliasIndirection( (raw.parsed_using_types(Baml::Types)) end + sig { + params( + varargs: T.untyped, + money: Baml::Checked[Integer], + baml_options: T::Hash[Symbol, T.any(Baml::TypeBuilder, Baml::ClientRegistry)] + ).returns(Baml::Checked[Integer]) + } + def ReturnAliasWithMergedAttributes( + *varargs, + money:, + baml_options: {} + ) + if varargs.any? + + raise ArgumentError.new("ReturnAliasWithMergedAttributes may only be called with keyword arguments") + end + if (baml_options.keys - [:client_registry, :tb]).any? + raise ArgumentError.new("Received unknown keys in baml_options (valid keys: :client_registry, :tb): #{baml_options.keys - [:client_registry, :tb]}") + end + + raw = @runtime.call_function( + "ReturnAliasWithMergedAttributes", + { + money: money, + }, + @ctx_manager, + baml_options[:tb]&.instance_variable_get(:@registry), + baml_options[:client_registry], + ) + (raw.parsed_using_types(Baml::Types)) + end + sig { params( varargs: T.untyped, @@ -6271,6 +6335,41 @@ def MapAlias( ) end + sig { + params( + varargs: T.untyped, + money: Integer, + baml_options: T::Hash[Symbol, T.any(Baml::TypeBuilder, Baml::ClientRegistry)] + ).returns(Baml::BamlStream[Baml::Types::MergeAttrs]) + } + def MergeAliasAttributes( + *varargs, + money:, + baml_options: {} + ) + if varargs.any? + + raise ArgumentError.new("MergeAliasAttributes may only be called with keyword arguments") + end + if (baml_options.keys - [:client_registry, :tb]).any? + raise ArgumentError.new("Received unknown keys in baml_options (valid keys: :client_registry, :tb): #{baml_options.keys - [:client_registry, :tb]}") + end + + raw = @runtime.stream_function( + "MergeAliasAttributes", + { + money: money, + }, + @ctx_manager, + baml_options[:tb]&.instance_variable_get(:@registry), + baml_options[:client_registry], + ) + Baml::BamlStream[Baml::PartialTypes::MergeAttrs, Baml::Types::MergeAttrs].new( + ffi_stream: raw, + ctx_manager: @ctx_manager + ) + end + sig { params( varargs: T.untyped, @@ -6761,6 +6860,41 @@ def RecursiveClassWithAliasIndirection( ) end + sig { + params( + varargs: T.untyped, + money: Baml::Checked[Integer], + baml_options: T::Hash[Symbol, T.any(Baml::TypeBuilder, Baml::ClientRegistry)] + ).returns(Baml::BamlStream[Baml::Checked[Integer]]) + } + def ReturnAliasWithMergedAttributes( + *varargs, + money:, + baml_options: {} + ) + if varargs.any? + + raise ArgumentError.new("ReturnAliasWithMergedAttributes may only be called with keyword arguments") + end + if (baml_options.keys - [:client_registry, :tb]).any? + raise ArgumentError.new("Received unknown keys in baml_options (valid keys: :client_registry, :tb): #{baml_options.keys - [:client_registry, :tb]}") + end + + raw = @runtime.stream_function( + "ReturnAliasWithMergedAttributes", + { + money: money, + }, + @ctx_manager, + baml_options[:tb]&.instance_variable_get(:@registry), + baml_options[:client_registry], + ) + Baml::BamlStream[Baml::Checked[T.nilable(Integer)], Baml::Checked[Integer]].new( + ffi_stream: raw, + ctx_manager: @ctx_manager + ) + end + sig { params( varargs: T.untyped, diff --git a/integ-tests/ruby/baml_client/inlined.rb b/integ-tests/ruby/baml_client/inlined.rb index 9be182f56..43b3a083c 100644 --- a/integ-tests/ruby/baml_client/inlined.rb +++ b/integ-tests/ruby/baml_client/inlined.rb @@ -80,10 +80,10 @@ module Inlined "test-files/functions/output/optional-class.baml" => "class ClassOptionalOutput {\n prop1 string\n prop2 string\n}\n\nfunction FnClassOptionalOutput(input: string) -> ClassOptionalOutput? {\n client GPT35\n prompt #\"\n Return a json blob for the following input:\n {{input}}\n\n {{ctx.output_format}}\n\n JSON:\n \"#\n}\n\n\nclass Blah {\n prop4 string?\n}\n\nclass ClassOptionalOutput2 {\n prop1 string?\n prop2 string?\n prop3 Blah?\n}\n\nfunction FnClassOptionalOutput2(input: string) -> ClassOptionalOutput2? {\n client GPT35\n prompt #\"\n Return a json blob for the following input:\n {{input}}\n\n {{ctx.output_format}}\n\n JSON:\n \"#\n}\n\ntest FnClassOptionalOutput2 {\n functions [FnClassOptionalOutput2, FnClassOptionalOutput]\n args {\n input \"example input\"\n }\n}\n", "test-files/functions/output/optional.baml" => "class OptionalTest_Prop1 {\n omega_a string\n omega_b int\n}\n\nenum OptionalTest_CategoryType {\n Aleph\n Beta\n Gamma\n}\n \nclass OptionalTest_ReturnType {\n omega_1 OptionalTest_Prop1?\n omega_2 string?\n omega_3 (OptionalTest_CategoryType?)[]\n} \n \nfunction OptionalTest_Function(input: string) -> (OptionalTest_ReturnType?)[]\n{ \n client GPT35\n prompt #\"\n Return a JSON blob with this schema: \n {{ctx.output_format}}\n\n JSON:\n \"#\n}\n\ntest OptionalTest_Function {\n functions [OptionalTest_Function]\n args {\n input \"example input\"\n }\n}\n", "test-files/functions/output/recursive-class.baml" => "class Node {\n data int\n next Node?\n}\n\nclass LinkedList {\n head Node?\n len int\n}\n\nclient<llm> O1 {\n provider \"openai\"\n options {\n model \"o1-mini\"\n default_role \"user\"\n }\n}\n\nfunction BuildLinkedList(input: int[]) -> LinkedList {\n client O1\n prompt #\"\n Build a linked list from the input array of integers.\n\n INPUT:\n {{ input }}\n\n {{ ctx.output_format }} \n \"#\n}\n\ntest TestLinkedList {\n functions [BuildLinkedList]\n args {\n input [1, 2, 3, 4, 5]\n }\n}\n", - "test-files/functions/output/recursive-type-aliases.baml" => "class LinkedListAliasNode {\n value int\n next LinkedListAliasNode?\n}\n\n// Simple alias that points to recursive type.\ntype LinkedListAlias = LinkedListAliasNode\n\nfunction AliasThatPointsToRecursiveType(list: LinkedListAlias) -> LinkedListAlias {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given linked list back:\n \n {{ list }}\n \n {{ ctx.output_format }}\n \"#\n}\n\n// Class that points to an alias that points to a recursive type.\nclass ClassToRecAlias {\n list LinkedListAlias\n}\n\nfunction ClassThatPointsToRecursiveClassThroughAlias(cls: ClassToRecAlias) -> ClassToRecAlias {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given object back:\n \n {{ cls }}\n \n {{ ctx.output_format }}\n \"#\n}\n\n// This is tricky cause this class should be hoisted, but classes and aliases\n// are two different types in the AST. This test will make sure they can interop.\nclass NodeWithAliasIndirection {\n value int\n next NodeIndirection?\n}\n\ntype NodeIndirection = NodeWithAliasIndirection\n\nfunction RecursiveClassWithAliasIndirection(cls: NodeWithAliasIndirection) -> NodeWithAliasIndirection {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given object back:\n \n {{ cls }}\n \n {{ ctx.output_format }}\n \"#\n}", + "test-files/functions/output/recursive-type-aliases.baml" => "class LinkedListAliasNode {\n value int\n next LinkedListAliasNode?\n}\n\n// Simple alias that points to recursive type.\ntype LinkedListAlias = LinkedListAliasNode\n\nfunction AliasThatPointsToRecursiveType(list: LinkedListAlias) -> LinkedListAlias {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given linked list back:\n \n {{ list }}\n \n {{ ctx.output_format }}\n \"#\n}\n\n// Class that points to an alias that points to a recursive type.\nclass ClassToRecAlias {\n list LinkedListAlias\n}\n\nfunction ClassThatPointsToRecursiveClassThroughAlias(cls: ClassToRecAlias) -> ClassToRecAlias {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given object back:\n \n {{ cls }}\n \n {{ ctx.output_format }}\n \"#\n}\n\n// This is tricky cause this class should be hoisted, but classes and aliases\n// are two different types in the AST. This test will make sure they can interop.\nclass NodeWithAliasIndirection {\n value int\n next NodeIndirection?\n}\n\ntype NodeIndirection = NodeWithAliasIndirection\n\nfunction RecursiveClassWithAliasIndirection(cls: NodeWithAliasIndirection) -> NodeWithAliasIndirection {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given object back:\n \n {{ cls }}\n \n {{ ctx.output_format }}\n \"#\n}\n", "test-files/functions/output/serialization-error.baml" => "class DummyOutput {\n nonce string\n nonce2 string\n @@dynamic\n}\n\nfunction DummyOutputFunction(input: string) -> DummyOutput {\n client GPT35\n prompt #\"\n Say \"hello there\".\n \"#\n}", "test-files/functions/output/string-list.baml" => "function FnOutputStringList(input: string) -> string[] {\n client GPT35\n prompt #\"\n Return a list of strings in json format like [\"string1\", \"string2\", \"string3\"].\n\n JSON:\n \"#\n}\n\ntest FnOutputStringList {\n functions [FnOutputStringList]\n args {\n input \"example input\"\n }\n}\n", - "test-files/functions/output/type-aliases.baml" => "type Primitive = int | string | bool | float\n\ntype List = string[]\n\ntype Graph = map<string, string[]>\n\ntype Combination = Primitive | List | Graph\n\nfunction PrimitiveAlias(p: Primitive) -> Primitive {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given value back: {{ p }}\n \"#\n}\n\nfunction MapAlias(m: Graph) -> Graph {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given Graph back:\n\n {{ m }}\n\n {{ ctx.output_format }}\n \"#\n}\n\nfunction NestedAlias(c: Combination) -> Combination {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given value back:\n\n {{ c }}\n\n {{ ctx.output_format }}\n \"#\n}\n", + "test-files/functions/output/type-aliases.baml" => "type Primitive = int | string | bool | float\n\ntype List = string[]\n\ntype Graph = map<string, string[]>\n\ntype Combination = Primitive | List | Graph\n\nfunction PrimitiveAlias(p: Primitive) -> Primitive {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given value back: {{ p }}\n \"#\n}\n\nfunction MapAlias(m: Graph) -> Graph {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given Graph back:\n\n {{ m }}\n\n {{ ctx.output_format }}\n \"#\n}\n\nfunction NestedAlias(c: Combination) -> Combination {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given value back:\n\n {{ c }}\n\n {{ ctx.output_format }}\n \"#\n}\n\n// Test attribute merging.\ntype Currency = int @check(gt_ten, {{ this > 10 }})\ntype Amount = Currency @assert ({{ this > 0 }})\n\nclass MergeAttrs {\n amount Amount @description(\"In USD\")\n}\n\nfunction MergeAliasAttributes(money: int) -> MergeAttrs {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given integer in the specified format:\n\n {{ money }}\n\n {{ ctx.output_format }}\n \"#\n}\n\nfunction ReturnAliasWithMergedAttributes(money: Amount) -> Amount {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given integer without additional context:\n\n {{ money }}\n\n {{ ctx.output_format }}\n \"#\n}", "test-files/functions/output/unions.baml" => "class UnionTest_ReturnType {\n prop1 string | bool\n prop2 (float | bool)[]\n prop3 (bool[] | int[])\n}\n\nfunction UnionTest_Function(input: string | bool) -> UnionTest_ReturnType {\n client GPT35\n prompt #\"\n Return a JSON blob with this schema: \n {{ctx.output_format}}\n\n JSON:\n \"#\n}\n\ntest UnionTest_Function {\n functions [UnionTest_Function]\n args {\n input \"example input\"\n }\n}\n", "test-files/functions/prompts/no-chat-messages.baml" => "\n\nfunction PromptTestClaude(input: string) -> string {\n client Sonnet\n prompt #\"\n Tell me a haiku about {{ input }}\n \"#\n}\n\n\nfunction PromptTestStreaming(input: string) -> string {\n client GPT35\n prompt #\"\n Tell me a short story about {{ input }}\n \"#\n}\n\ntest TestName {\n functions [PromptTestStreaming]\n args {\n input #\"\n hello world\n \"#\n }\n}\n", "test-files/functions/prompts/with-chat-messages.baml" => "\nfunction PromptTestOpenAIChat(input: string) -> string {\n client GPT35\n prompt #\"\n {{ _.role(\"system\") }}\n You are an assistant that always responds in a very excited way with emojis and also outputs this word 4 times after giving a response: {{ input }}\n \n {{ _.role(\"user\") }}\n Tell me a haiku about {{ input }}\n \"#\n}\n\nfunction PromptTestOpenAIChatNoSystem(input: string) -> string {\n client GPT35\n prompt #\"\n You are an assistant that always responds in a very excited way with emojis and also outputs this word 4 times after giving a response: {{ input }}\n \n {{ _.role(\"user\") }}\n Tell me a haiku about {{ input }}\n \"#\n}\n\nfunction PromptTestClaudeChat(input: string) -> string {\n client Claude\n prompt #\"\n {{ _.role(\"system\") }}\n You are an assistant that always responds in a very excited way with emojis and also outputs this word 4 times after giving a response: {{ input }}\n \n {{ _.role(\"user\") }}\n Tell me a haiku about {{ input }}\n \"#\n}\n\nfunction PromptTestClaudeChatNoSystem(input: string) -> string {\n client Claude\n prompt #\"\n You are an assistant that always responds in a very excited way with emojis and also outputs this word 4 times after giving a response: {{ input }}\n \n {{ _.role(\"user\") }}\n Tell me a haiku about {{ input }}\n \"#\n}\n\ntest TestSystemAndNonSystemChat1 {\n functions [PromptTestClaude, PromptTestOpenAI, PromptTestOpenAIChat, PromptTestOpenAIChatNoSystem, PromptTestClaudeChat, PromptTestClaudeChatNoSystem]\n args {\n input \"cats\"\n }\n}\n\ntest TestSystemAndNonSystemChat2 {\n functions [PromptTestClaude, PromptTestOpenAI, PromptTestOpenAIChat, PromptTestOpenAIChatNoSystem, PromptTestClaudeChat, PromptTestClaudeChatNoSystem]\n args {\n input \"lion\"\n }\n}", diff --git a/integ-tests/ruby/baml_client/partial-types.rb b/integ-tests/ruby/baml_client/partial-types.rb index 7dfdb7448..ba3256e8d 100644 --- a/integ-tests/ruby/baml_client/partial-types.rb +++ b/integ-tests/ruby/baml_client/partial-types.rb @@ -60,6 +60,7 @@ class LiteralClassTwo < T::Struct; end class MalformedConstraints < T::Struct; end class MalformedConstraints2 < T::Struct; end class Martian < T::Struct; end + class MergeAttrs < T::Struct; end class NamedArgsSingleClass < T::Struct; end class Nested < T::Struct; end class Nested2 < T::Struct; end @@ -664,6 +665,18 @@ def initialize(props) @props = props end end + class MergeAttrs < T::Struct + include Baml::Sorbet::Struct + const :amount, Baml::Checked[T.nilable(Integer)] + + def initialize(props) + super( + amount: props[:amount], + ) + + @props = props + end + end class NamedArgsSingleClass < T::Struct include Baml::Sorbet::Struct const :key, T.nilable(String) diff --git a/integ-tests/ruby/baml_client/type-registry.rb b/integ-tests/ruby/baml_client/type-registry.rb index 42fec89ee..ef0466d26 100644 --- a/integ-tests/ruby/baml_client/type-registry.rb +++ b/integ-tests/ruby/baml_client/type-registry.rb @@ -18,7 +18,7 @@ module Baml class TypeBuilder def initialize @registry = Baml::Ffi::TypeBuilder.new - @classes = Set[ "BigNumbers", "BinaryNode", "Blah", "BlockConstraint", "BlockConstraintForParam", "BookOrder", "ClassOptionalOutput", "ClassOptionalOutput2", "ClassToRecAlias", "ClassWithImage", "CompoundBigNumbers", "ContactInfo", "CustomTaskResult", "DummyOutput", "DynInputOutput", "DynamicClassOne", "DynamicClassTwo", "DynamicOutput", "Earthling", "Education", "Email", "EmailAddress", "Event", "FakeImage", "FlightConfirmation", "FooAny", "Forest", "GroceryReceipt", "InnerClass", "InnerClass2", "InputClass", "InputClassNested", "LinkedList", "LinkedListAliasNode", "LiteralClassHello", "LiteralClassOne", "LiteralClassTwo", "MalformedConstraints", "MalformedConstraints2", "Martian", "NamedArgsSingleClass", "Nested", "Nested2", "NestedBlockConstraint", "NestedBlockConstraintForParam", "Node", "NodeWithAliasIndirection", "OptionalTest_Prop1", "OptionalTest_ReturnType", "OrderInfo", "OriginalA", "OriginalB", "Person", "PhoneNumber", "Quantity", "RaysData", "ReceiptInfo", "ReceiptItem", "Recipe", "Resume", "Schema", "SearchParams", "SomeClassNestedDynamic", "StringToClassEntry", "TestClassAlias", "TestClassNested", "TestClassWithEnum", "TestOutputClass", "Tree", "TwoStoriesOneTitle", "UnionTest_ReturnType", "WithReasoning", ] + @classes = Set[ "BigNumbers", "BinaryNode", "Blah", "BlockConstraint", "BlockConstraintForParam", "BookOrder", "ClassOptionalOutput", "ClassOptionalOutput2", "ClassToRecAlias", "ClassWithImage", "CompoundBigNumbers", "ContactInfo", "CustomTaskResult", "DummyOutput", "DynInputOutput", "DynamicClassOne", "DynamicClassTwo", "DynamicOutput", "Earthling", "Education", "Email", "EmailAddress", "Event", "FakeImage", "FlightConfirmation", "FooAny", "Forest", "GroceryReceipt", "InnerClass", "InnerClass2", "InputClass", "InputClassNested", "LinkedList", "LinkedListAliasNode", "LiteralClassHello", "LiteralClassOne", "LiteralClassTwo", "MalformedConstraints", "MalformedConstraints2", "Martian", "MergeAttrs", "NamedArgsSingleClass", "Nested", "Nested2", "NestedBlockConstraint", "NestedBlockConstraintForParam", "Node", "NodeWithAliasIndirection", "OptionalTest_Prop1", "OptionalTest_ReturnType", "OrderInfo", "OriginalA", "OriginalB", "Person", "PhoneNumber", "Quantity", "RaysData", "ReceiptInfo", "ReceiptItem", "Recipe", "Resume", "Schema", "SearchParams", "SomeClassNestedDynamic", "StringToClassEntry", "TestClassAlias", "TestClassNested", "TestClassWithEnum", "TestOutputClass", "Tree", "TwoStoriesOneTitle", "UnionTest_ReturnType", "WithReasoning", ] @enums = Set[ "AliasedEnum", "Category", "Category2", "Category3", "Color", "DataType", "DynEnumOne", "DynEnumTwo", "EnumInClass", "EnumOutput", "Hobby", "MapKey", "NamedArgsSingleEnum", "NamedArgsSingleEnumList", "OptionalTest_CategoryType", "OrderStatus", "Tag", "TestEnum", ] end diff --git a/integ-tests/ruby/baml_client/types.rb b/integ-tests/ruby/baml_client/types.rb index f864f2247..32cb4a17f 100644 --- a/integ-tests/ruby/baml_client/types.rb +++ b/integ-tests/ruby/baml_client/types.rb @@ -185,6 +185,7 @@ class LiteralClassTwo < T::Struct; end class MalformedConstraints < T::Struct; end class MalformedConstraints2 < T::Struct; end class Martian < T::Struct; end + class MergeAttrs < T::Struct; end class NamedArgsSingleClass < T::Struct; end class Nested < T::Struct; end class Nested2 < T::Struct; end @@ -789,6 +790,18 @@ def initialize(props) @props = props end end + class MergeAttrs < T::Struct + include Baml::Sorbet::Struct + const :amount, Baml::Checked[Integer] + + def initialize(props) + super( + amount: props[:amount], + ) + + @props = props + end + end class NamedArgsSingleClass < T::Struct include Baml::Sorbet::Struct const :key, String diff --git a/integ-tests/typescript/baml_client/async_client.ts b/integ-tests/typescript/baml_client/async_client.ts index 8c55efc0e..d4c9bd2dc 100644 --- a/integ-tests/typescript/baml_client/async_client.ts +++ b/integ-tests/typescript/baml_client/async_client.ts @@ -17,7 +17,7 @@ $ pnpm add @boundaryml/baml // biome-ignore format: autogenerated code import { BamlRuntime, FunctionResult, BamlCtxManager, BamlStream, Image, ClientRegistry, BamlValidationError, createBamlValidationError } from "@boundaryml/baml" import { Checked, Check } from "./types" -import {BigNumbers, BinaryNode, Blah, BlockConstraint, BlockConstraintForParam, BookOrder, ClassOptionalOutput, ClassOptionalOutput2, ClassToRecAlias, ClassWithImage, CompoundBigNumbers, ContactInfo, CustomTaskResult, DummyOutput, DynInputOutput, DynamicClassOne, DynamicClassTwo, DynamicOutput, Earthling, Education, Email, EmailAddress, Event, FakeImage, FlightConfirmation, FooAny, Forest, GroceryReceipt, InnerClass, InnerClass2, InputClass, InputClassNested, LinkedList, LinkedListAliasNode, LiteralClassHello, LiteralClassOne, LiteralClassTwo, MalformedConstraints, MalformedConstraints2, Martian, NamedArgsSingleClass, Nested, Nested2, NestedBlockConstraint, NestedBlockConstraintForParam, Node, NodeWithAliasIndirection, OptionalTest_Prop1, OptionalTest_ReturnType, OrderInfo, OriginalA, OriginalB, Person, PhoneNumber, Quantity, RaysData, ReceiptInfo, ReceiptItem, Recipe, Resume, Schema, SearchParams, SomeClassNestedDynamic, StringToClassEntry, TestClassAlias, TestClassNested, TestClassWithEnum, TestOutputClass, Tree, TwoStoriesOneTitle, UnionTest_ReturnType, WithReasoning, AliasedEnum, Category, Category2, Category3, Color, DataType, DynEnumOne, DynEnumTwo, EnumInClass, EnumOutput, Hobby, MapKey, NamedArgsSingleEnum, NamedArgsSingleEnumList, OptionalTest_CategoryType, OrderStatus, Tag, TestEnum} from "./types" +import {BigNumbers, BinaryNode, Blah, BlockConstraint, BlockConstraintForParam, BookOrder, ClassOptionalOutput, ClassOptionalOutput2, ClassToRecAlias, ClassWithImage, CompoundBigNumbers, ContactInfo, CustomTaskResult, DummyOutput, DynInputOutput, DynamicClassOne, DynamicClassTwo, DynamicOutput, Earthling, Education, Email, EmailAddress, Event, FakeImage, FlightConfirmation, FooAny, Forest, GroceryReceipt, InnerClass, InnerClass2, InputClass, InputClassNested, LinkedList, LinkedListAliasNode, LiteralClassHello, LiteralClassOne, LiteralClassTwo, MalformedConstraints, MalformedConstraints2, Martian, MergeAttrs, NamedArgsSingleClass, Nested, Nested2, NestedBlockConstraint, NestedBlockConstraintForParam, Node, NodeWithAliasIndirection, OptionalTest_Prop1, OptionalTest_ReturnType, OrderInfo, OriginalA, OriginalB, Person, PhoneNumber, Quantity, RaysData, ReceiptInfo, ReceiptItem, Recipe, Resume, Schema, SearchParams, SomeClassNestedDynamic, StringToClassEntry, TestClassAlias, TestClassNested, TestClassWithEnum, TestOutputClass, Tree, TwoStoriesOneTitle, UnionTest_ReturnType, WithReasoning, AliasedEnum, Category, Category2, Category3, Color, DataType, DynEnumOne, DynEnumTwo, EnumInClass, EnumOutput, Hobby, MapKey, NamedArgsSingleEnum, NamedArgsSingleEnumList, OptionalTest_CategoryType, OrderStatus, Tag, TestEnum} from "./types" import TypeBuilder from "./type_builder" import { DO_NOT_USE_DIRECTLY_UNLESS_YOU_KNOW_WHAT_YOURE_DOING_CTX, DO_NOT_USE_DIRECTLY_UNLESS_YOU_KNOW_WHAT_YOURE_DOING_RUNTIME } from "./globals" @@ -1643,6 +1643,31 @@ export class BamlAsyncClient { } } + async MergeAliasAttributes( + money: number, + __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } + ): Promise<MergeAttrs> { + try { + const raw = await this.runtime.callFunction( + "MergeAliasAttributes", + { + "money": money + }, + this.ctx_manager.cloneContext(), + __baml_options__?.tb?.__tb(), + __baml_options__?.clientRegistry, + ) + return raw.parsed() as MergeAttrs + } catch (error: any) { + const bamlError = createBamlValidationError(error); + if (bamlError instanceof BamlValidationError) { + throw bamlError; + } else { + throw error; + } + } + } + async MyFunc( input: string, __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } @@ -1993,6 +2018,31 @@ export class BamlAsyncClient { } } + async ReturnAliasWithMergedAttributes( + money: Checked<number,"gt_ten">, + __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } + ): Promise<Checked<number,"gt_ten">> { + try { + const raw = await this.runtime.callFunction( + "ReturnAliasWithMergedAttributes", + { + "money": money + }, + this.ctx_manager.cloneContext(), + __baml_options__?.tb?.__tb(), + __baml_options__?.clientRegistry, + ) + return raw.parsed() as Checked<number,"gt_ten"> + } catch (error: any) { + const bamlError = createBamlValidationError(error); + if (bamlError instanceof BamlValidationError) { + throw bamlError; + } else { + throw error; + } + } + } + async ReturnFailingAssert( inp: number, __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } @@ -5261,6 +5311,39 @@ class BamlStreamClient { } } + MergeAliasAttributes( + money: number, + __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } + ): BamlStream<RecursivePartialNull<MergeAttrs>, MergeAttrs> { + try { + const raw = this.runtime.streamFunction( + "MergeAliasAttributes", + { + "money": money + }, + undefined, + this.ctx_manager.cloneContext(), + __baml_options__?.tb?.__tb(), + __baml_options__?.clientRegistry, + ) + return new BamlStream<RecursivePartialNull<MergeAttrs>, MergeAttrs>( + raw, + (a): a is RecursivePartialNull<MergeAttrs> => a, + (a): a is MergeAttrs => a, + this.ctx_manager.cloneContext(), + __baml_options__?.tb?.__tb(), + ) + } catch (error) { + if (error instanceof Error) { + const bamlError = createBamlValidationError(error); + if (bamlError instanceof BamlValidationError) { + throw bamlError; + } + } + throw error; + } + } + MyFunc( input: string, __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } @@ -5723,6 +5806,39 @@ class BamlStreamClient { } } + ReturnAliasWithMergedAttributes( + money: Checked<number,"gt_ten">, + __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } + ): BamlStream<RecursivePartialNull<Checked<number,"gt_ten">>, Checked<number,"gt_ten">> { + try { + const raw = this.runtime.streamFunction( + "ReturnAliasWithMergedAttributes", + { + "money": money + }, + undefined, + this.ctx_manager.cloneContext(), + __baml_options__?.tb?.__tb(), + __baml_options__?.clientRegistry, + ) + return new BamlStream<RecursivePartialNull<Checked<number,"gt_ten">>, Checked<number,"gt_ten">>( + raw, + (a): a is RecursivePartialNull<Checked<number,"gt_ten">> => a, + (a): a is Checked<number,"gt_ten"> => a, + this.ctx_manager.cloneContext(), + __baml_options__?.tb?.__tb(), + ) + } catch (error) { + if (error instanceof Error) { + const bamlError = createBamlValidationError(error); + if (bamlError instanceof BamlValidationError) { + throw bamlError; + } + } + throw error; + } + } + ReturnFailingAssert( inp: number, __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } diff --git a/integ-tests/typescript/baml_client/inlinedbaml.ts b/integ-tests/typescript/baml_client/inlinedbaml.ts index aa2054805..78786fd8e 100644 --- a/integ-tests/typescript/baml_client/inlinedbaml.ts +++ b/integ-tests/typescript/baml_client/inlinedbaml.ts @@ -81,10 +81,10 @@ const fileMap = { "test-files/functions/output/optional-class.baml": "class ClassOptionalOutput {\n prop1 string\n prop2 string\n}\n\nfunction FnClassOptionalOutput(input: string) -> ClassOptionalOutput? {\n client GPT35\n prompt #\"\n Return a json blob for the following input:\n {{input}}\n\n {{ctx.output_format}}\n\n JSON:\n \"#\n}\n\n\nclass Blah {\n prop4 string?\n}\n\nclass ClassOptionalOutput2 {\n prop1 string?\n prop2 string?\n prop3 Blah?\n}\n\nfunction FnClassOptionalOutput2(input: string) -> ClassOptionalOutput2? {\n client GPT35\n prompt #\"\n Return a json blob for the following input:\n {{input}}\n\n {{ctx.output_format}}\n\n JSON:\n \"#\n}\n\ntest FnClassOptionalOutput2 {\n functions [FnClassOptionalOutput2, FnClassOptionalOutput]\n args {\n input \"example input\"\n }\n}\n", "test-files/functions/output/optional.baml": "class OptionalTest_Prop1 {\n omega_a string\n omega_b int\n}\n\nenum OptionalTest_CategoryType {\n Aleph\n Beta\n Gamma\n}\n \nclass OptionalTest_ReturnType {\n omega_1 OptionalTest_Prop1?\n omega_2 string?\n omega_3 (OptionalTest_CategoryType?)[]\n} \n \nfunction OptionalTest_Function(input: string) -> (OptionalTest_ReturnType?)[]\n{ \n client GPT35\n prompt #\"\n Return a JSON blob with this schema: \n {{ctx.output_format}}\n\n JSON:\n \"#\n}\n\ntest OptionalTest_Function {\n functions [OptionalTest_Function]\n args {\n input \"example input\"\n }\n}\n", "test-files/functions/output/recursive-class.baml": "class Node {\n data int\n next Node?\n}\n\nclass LinkedList {\n head Node?\n len int\n}\n\nclient<llm> O1 {\n provider \"openai\"\n options {\n model \"o1-mini\"\n default_role \"user\"\n }\n}\n\nfunction BuildLinkedList(input: int[]) -> LinkedList {\n client O1\n prompt #\"\n Build a linked list from the input array of integers.\n\n INPUT:\n {{ input }}\n\n {{ ctx.output_format }} \n \"#\n}\n\ntest TestLinkedList {\n functions [BuildLinkedList]\n args {\n input [1, 2, 3, 4, 5]\n }\n}\n", - "test-files/functions/output/recursive-type-aliases.baml": "class LinkedListAliasNode {\n value int\n next LinkedListAliasNode?\n}\n\n// Simple alias that points to recursive type.\ntype LinkedListAlias = LinkedListAliasNode\n\nfunction AliasThatPointsToRecursiveType(list: LinkedListAlias) -> LinkedListAlias {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given linked list back:\n \n {{ list }}\n \n {{ ctx.output_format }}\n \"#\n}\n\n// Class that points to an alias that points to a recursive type.\nclass ClassToRecAlias {\n list LinkedListAlias\n}\n\nfunction ClassThatPointsToRecursiveClassThroughAlias(cls: ClassToRecAlias) -> ClassToRecAlias {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given object back:\n \n {{ cls }}\n \n {{ ctx.output_format }}\n \"#\n}\n\n// This is tricky cause this class should be hoisted, but classes and aliases\n// are two different types in the AST. This test will make sure they can interop.\nclass NodeWithAliasIndirection {\n value int\n next NodeIndirection?\n}\n\ntype NodeIndirection = NodeWithAliasIndirection\n\nfunction RecursiveClassWithAliasIndirection(cls: NodeWithAliasIndirection) -> NodeWithAliasIndirection {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given object back:\n \n {{ cls }}\n \n {{ ctx.output_format }}\n \"#\n}", + "test-files/functions/output/recursive-type-aliases.baml": "class LinkedListAliasNode {\n value int\n next LinkedListAliasNode?\n}\n\n// Simple alias that points to recursive type.\ntype LinkedListAlias = LinkedListAliasNode\n\nfunction AliasThatPointsToRecursiveType(list: LinkedListAlias) -> LinkedListAlias {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given linked list back:\n \n {{ list }}\n \n {{ ctx.output_format }}\n \"#\n}\n\n// Class that points to an alias that points to a recursive type.\nclass ClassToRecAlias {\n list LinkedListAlias\n}\n\nfunction ClassThatPointsToRecursiveClassThroughAlias(cls: ClassToRecAlias) -> ClassToRecAlias {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given object back:\n \n {{ cls }}\n \n {{ ctx.output_format }}\n \"#\n}\n\n// This is tricky cause this class should be hoisted, but classes and aliases\n// are two different types in the AST. This test will make sure they can interop.\nclass NodeWithAliasIndirection {\n value int\n next NodeIndirection?\n}\n\ntype NodeIndirection = NodeWithAliasIndirection\n\nfunction RecursiveClassWithAliasIndirection(cls: NodeWithAliasIndirection) -> NodeWithAliasIndirection {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given object back:\n \n {{ cls }}\n \n {{ ctx.output_format }}\n \"#\n}\n", "test-files/functions/output/serialization-error.baml": "class DummyOutput {\n nonce string\n nonce2 string\n @@dynamic\n}\n\nfunction DummyOutputFunction(input: string) -> DummyOutput {\n client GPT35\n prompt #\"\n Say \"hello there\".\n \"#\n}", "test-files/functions/output/string-list.baml": "function FnOutputStringList(input: string) -> string[] {\n client GPT35\n prompt #\"\n Return a list of strings in json format like [\"string1\", \"string2\", \"string3\"].\n\n JSON:\n \"#\n}\n\ntest FnOutputStringList {\n functions [FnOutputStringList]\n args {\n input \"example input\"\n }\n}\n", - "test-files/functions/output/type-aliases.baml": "type Primitive = int | string | bool | float\n\ntype List = string[]\n\ntype Graph = map<string, string[]>\n\ntype Combination = Primitive | List | Graph\n\nfunction PrimitiveAlias(p: Primitive) -> Primitive {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given value back: {{ p }}\n \"#\n}\n\nfunction MapAlias(m: Graph) -> Graph {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given Graph back:\n\n {{ m }}\n\n {{ ctx.output_format }}\n \"#\n}\n\nfunction NestedAlias(c: Combination) -> Combination {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given value back:\n\n {{ c }}\n\n {{ ctx.output_format }}\n \"#\n}\n", + "test-files/functions/output/type-aliases.baml": "type Primitive = int | string | bool | float\n\ntype List = string[]\n\ntype Graph = map<string, string[]>\n\ntype Combination = Primitive | List | Graph\n\nfunction PrimitiveAlias(p: Primitive) -> Primitive {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given value back: {{ p }}\n \"#\n}\n\nfunction MapAlias(m: Graph) -> Graph {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given Graph back:\n\n {{ m }}\n\n {{ ctx.output_format }}\n \"#\n}\n\nfunction NestedAlias(c: Combination) -> Combination {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given value back:\n\n {{ c }}\n\n {{ ctx.output_format }}\n \"#\n}\n\n// Test attribute merging.\ntype Currency = int @check(gt_ten, {{ this > 10 }})\ntype Amount = Currency @assert ({{ this > 0 }})\n\nclass MergeAttrs {\n amount Amount @description(\"In USD\")\n}\n\nfunction MergeAliasAttributes(money: int) -> MergeAttrs {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given integer in the specified format:\n\n {{ money }}\n\n {{ ctx.output_format }}\n \"#\n}\n\nfunction ReturnAliasWithMergedAttributes(money: Amount) -> Amount {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given integer without additional context:\n\n {{ money }}\n\n {{ ctx.output_format }}\n \"#\n}", "test-files/functions/output/unions.baml": "class UnionTest_ReturnType {\n prop1 string | bool\n prop2 (float | bool)[]\n prop3 (bool[] | int[])\n}\n\nfunction UnionTest_Function(input: string | bool) -> UnionTest_ReturnType {\n client GPT35\n prompt #\"\n Return a JSON blob with this schema: \n {{ctx.output_format}}\n\n JSON:\n \"#\n}\n\ntest UnionTest_Function {\n functions [UnionTest_Function]\n args {\n input \"example input\"\n }\n}\n", "test-files/functions/prompts/no-chat-messages.baml": "\n\nfunction PromptTestClaude(input: string) -> string {\n client Sonnet\n prompt #\"\n Tell me a haiku about {{ input }}\n \"#\n}\n\n\nfunction PromptTestStreaming(input: string) -> string {\n client GPT35\n prompt #\"\n Tell me a short story about {{ input }}\n \"#\n}\n\ntest TestName {\n functions [PromptTestStreaming]\n args {\n input #\"\n hello world\n \"#\n }\n}\n", "test-files/functions/prompts/with-chat-messages.baml": "\nfunction PromptTestOpenAIChat(input: string) -> string {\n client GPT35\n prompt #\"\n {{ _.role(\"system\") }}\n You are an assistant that always responds in a very excited way with emojis and also outputs this word 4 times after giving a response: {{ input }}\n \n {{ _.role(\"user\") }}\n Tell me a haiku about {{ input }}\n \"#\n}\n\nfunction PromptTestOpenAIChatNoSystem(input: string) -> string {\n client GPT35\n prompt #\"\n You are an assistant that always responds in a very excited way with emojis and also outputs this word 4 times after giving a response: {{ input }}\n \n {{ _.role(\"user\") }}\n Tell me a haiku about {{ input }}\n \"#\n}\n\nfunction PromptTestClaudeChat(input: string) -> string {\n client Claude\n prompt #\"\n {{ _.role(\"system\") }}\n You are an assistant that always responds in a very excited way with emojis and also outputs this word 4 times after giving a response: {{ input }}\n \n {{ _.role(\"user\") }}\n Tell me a haiku about {{ input }}\n \"#\n}\n\nfunction PromptTestClaudeChatNoSystem(input: string) -> string {\n client Claude\n prompt #\"\n You are an assistant that always responds in a very excited way with emojis and also outputs this word 4 times after giving a response: {{ input }}\n \n {{ _.role(\"user\") }}\n Tell me a haiku about {{ input }}\n \"#\n}\n\ntest TestSystemAndNonSystemChat1 {\n functions [PromptTestClaude, PromptTestOpenAI, PromptTestOpenAIChat, PromptTestOpenAIChatNoSystem, PromptTestClaudeChat, PromptTestClaudeChatNoSystem]\n args {\n input \"cats\"\n }\n}\n\ntest TestSystemAndNonSystemChat2 {\n functions [PromptTestClaude, PromptTestOpenAI, PromptTestOpenAIChat, PromptTestOpenAIChatNoSystem, PromptTestClaudeChat, PromptTestClaudeChatNoSystem]\n args {\n input \"lion\"\n }\n}", diff --git a/integ-tests/typescript/baml_client/sync_client.ts b/integ-tests/typescript/baml_client/sync_client.ts index 7f5077d4e..6f84a7191 100644 --- a/integ-tests/typescript/baml_client/sync_client.ts +++ b/integ-tests/typescript/baml_client/sync_client.ts @@ -17,7 +17,7 @@ $ pnpm add @boundaryml/baml // biome-ignore format: autogenerated code import { BamlRuntime, FunctionResult, BamlCtxManager, BamlSyncStream, Image, ClientRegistry, createBamlValidationError, BamlValidationError } from "@boundaryml/baml" import { Checked, Check } from "./types" -import {BigNumbers, BinaryNode, Blah, BlockConstraint, BlockConstraintForParam, BookOrder, ClassOptionalOutput, ClassOptionalOutput2, ClassToRecAlias, ClassWithImage, CompoundBigNumbers, ContactInfo, CustomTaskResult, DummyOutput, DynInputOutput, DynamicClassOne, DynamicClassTwo, DynamicOutput, Earthling, Education, Email, EmailAddress, Event, FakeImage, FlightConfirmation, FooAny, Forest, GroceryReceipt, InnerClass, InnerClass2, InputClass, InputClassNested, LinkedList, LinkedListAliasNode, LiteralClassHello, LiteralClassOne, LiteralClassTwo, MalformedConstraints, MalformedConstraints2, Martian, NamedArgsSingleClass, Nested, Nested2, NestedBlockConstraint, NestedBlockConstraintForParam, Node, NodeWithAliasIndirection, OptionalTest_Prop1, OptionalTest_ReturnType, OrderInfo, OriginalA, OriginalB, Person, PhoneNumber, Quantity, RaysData, ReceiptInfo, ReceiptItem, Recipe, Resume, Schema, SearchParams, SomeClassNestedDynamic, StringToClassEntry, TestClassAlias, TestClassNested, TestClassWithEnum, TestOutputClass, Tree, TwoStoriesOneTitle, UnionTest_ReturnType, WithReasoning, AliasedEnum, Category, Category2, Category3, Color, DataType, DynEnumOne, DynEnumTwo, EnumInClass, EnumOutput, Hobby, MapKey, NamedArgsSingleEnum, NamedArgsSingleEnumList, OptionalTest_CategoryType, OrderStatus, Tag, TestEnum} from "./types" +import {BigNumbers, BinaryNode, Blah, BlockConstraint, BlockConstraintForParam, BookOrder, ClassOptionalOutput, ClassOptionalOutput2, ClassToRecAlias, ClassWithImage, CompoundBigNumbers, ContactInfo, CustomTaskResult, DummyOutput, DynInputOutput, DynamicClassOne, DynamicClassTwo, DynamicOutput, Earthling, Education, Email, EmailAddress, Event, FakeImage, FlightConfirmation, FooAny, Forest, GroceryReceipt, InnerClass, InnerClass2, InputClass, InputClassNested, LinkedList, LinkedListAliasNode, LiteralClassHello, LiteralClassOne, LiteralClassTwo, MalformedConstraints, MalformedConstraints2, Martian, MergeAttrs, NamedArgsSingleClass, Nested, Nested2, NestedBlockConstraint, NestedBlockConstraintForParam, Node, NodeWithAliasIndirection, OptionalTest_Prop1, OptionalTest_ReturnType, OrderInfo, OriginalA, OriginalB, Person, PhoneNumber, Quantity, RaysData, ReceiptInfo, ReceiptItem, Recipe, Resume, Schema, SearchParams, SomeClassNestedDynamic, StringToClassEntry, TestClassAlias, TestClassNested, TestClassWithEnum, TestOutputClass, Tree, TwoStoriesOneTitle, UnionTest_ReturnType, WithReasoning, AliasedEnum, Category, Category2, Category3, Color, DataType, DynEnumOne, DynEnumTwo, EnumInClass, EnumOutput, Hobby, MapKey, NamedArgsSingleEnum, NamedArgsSingleEnumList, OptionalTest_CategoryType, OrderStatus, Tag, TestEnum} from "./types" import TypeBuilder from "./type_builder" import { DO_NOT_USE_DIRECTLY_UNLESS_YOU_KNOW_WHAT_YOURE_DOING_CTX, DO_NOT_USE_DIRECTLY_UNLESS_YOU_KNOW_WHAT_YOURE_DOING_RUNTIME } from "./globals" @@ -1643,6 +1643,31 @@ export class BamlSyncClient { } } + MergeAliasAttributes( + money: number, + __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } + ): MergeAttrs { + try { + const raw = this.runtime.callFunctionSync( + "MergeAliasAttributes", + { + "money": money + }, + this.ctx_manager.cloneContext(), + __baml_options__?.tb?.__tb(), + __baml_options__?.clientRegistry, + ) + return raw.parsed() as MergeAttrs + } catch (error: any) { + const bamlError = createBamlValidationError(error); + if (bamlError instanceof BamlValidationError) { + throw bamlError; + } else { + throw error; + } + } + } + MyFunc( input: string, __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } @@ -1993,6 +2018,31 @@ export class BamlSyncClient { } } + ReturnAliasWithMergedAttributes( + money: Checked<number,"gt_ten">, + __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } + ): Checked<number,"gt_ten"> { + try { + const raw = this.runtime.callFunctionSync( + "ReturnAliasWithMergedAttributes", + { + "money": money + }, + this.ctx_manager.cloneContext(), + __baml_options__?.tb?.__tb(), + __baml_options__?.clientRegistry, + ) + return raw.parsed() as Checked<number,"gt_ten"> + } catch (error: any) { + const bamlError = createBamlValidationError(error); + if (bamlError instanceof BamlValidationError) { + throw bamlError; + } else { + throw error; + } + } + } + ReturnFailingAssert( inp: number, __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } diff --git a/integ-tests/typescript/baml_client/type_builder.ts b/integ-tests/typescript/baml_client/type_builder.ts index d1d4b0d7d..9b163cab4 100644 --- a/integ-tests/typescript/baml_client/type_builder.ts +++ b/integ-tests/typescript/baml_client/type_builder.ts @@ -50,7 +50,7 @@ export default class TypeBuilder { constructor() { this.tb = new _TypeBuilder({ classes: new Set([ - "BigNumbers","BinaryNode","Blah","BlockConstraint","BlockConstraintForParam","BookOrder","ClassOptionalOutput","ClassOptionalOutput2","ClassToRecAlias","ClassWithImage","CompoundBigNumbers","ContactInfo","CustomTaskResult","DummyOutput","DynInputOutput","DynamicClassOne","DynamicClassTwo","DynamicOutput","Earthling","Education","Email","EmailAddress","Event","FakeImage","FlightConfirmation","FooAny","Forest","GroceryReceipt","InnerClass","InnerClass2","InputClass","InputClassNested","LinkedList","LinkedListAliasNode","LiteralClassHello","LiteralClassOne","LiteralClassTwo","MalformedConstraints","MalformedConstraints2","Martian","NamedArgsSingleClass","Nested","Nested2","NestedBlockConstraint","NestedBlockConstraintForParam","Node","NodeWithAliasIndirection","OptionalTest_Prop1","OptionalTest_ReturnType","OrderInfo","OriginalA","OriginalB","Person","PhoneNumber","Quantity","RaysData","ReceiptInfo","ReceiptItem","Recipe","Resume","Schema","SearchParams","SomeClassNestedDynamic","StringToClassEntry","TestClassAlias","TestClassNested","TestClassWithEnum","TestOutputClass","Tree","TwoStoriesOneTitle","UnionTest_ReturnType","WithReasoning", + "BigNumbers","BinaryNode","Blah","BlockConstraint","BlockConstraintForParam","BookOrder","ClassOptionalOutput","ClassOptionalOutput2","ClassToRecAlias","ClassWithImage","CompoundBigNumbers","ContactInfo","CustomTaskResult","DummyOutput","DynInputOutput","DynamicClassOne","DynamicClassTwo","DynamicOutput","Earthling","Education","Email","EmailAddress","Event","FakeImage","FlightConfirmation","FooAny","Forest","GroceryReceipt","InnerClass","InnerClass2","InputClass","InputClassNested","LinkedList","LinkedListAliasNode","LiteralClassHello","LiteralClassOne","LiteralClassTwo","MalformedConstraints","MalformedConstraints2","Martian","MergeAttrs","NamedArgsSingleClass","Nested","Nested2","NestedBlockConstraint","NestedBlockConstraintForParam","Node","NodeWithAliasIndirection","OptionalTest_Prop1","OptionalTest_ReturnType","OrderInfo","OriginalA","OriginalB","Person","PhoneNumber","Quantity","RaysData","ReceiptInfo","ReceiptItem","Recipe","Resume","Schema","SearchParams","SomeClassNestedDynamic","StringToClassEntry","TestClassAlias","TestClassNested","TestClassWithEnum","TestOutputClass","Tree","TwoStoriesOneTitle","UnionTest_ReturnType","WithReasoning", ]), enums: new Set([ "AliasedEnum","Category","Category2","Category3","Color","DataType","DynEnumOne","DynEnumTwo","EnumInClass","EnumOutput","Hobby","MapKey","NamedArgsSingleEnum","NamedArgsSingleEnumList","OptionalTest_CategoryType","OrderStatus","Tag","TestEnum", diff --git a/integ-tests/typescript/baml_client/types.ts b/integ-tests/typescript/baml_client/types.ts index fe047dd09..17a6fb1d9 100644 --- a/integ-tests/typescript/baml_client/types.ts +++ b/integ-tests/typescript/baml_client/types.ts @@ -413,6 +413,11 @@ export interface Martian { } +export interface MergeAttrs { + amount: Checked<number,"gt_ten"> + +} + export interface NamedArgsSingleClass { key: string key_two: boolean From 6bd91c507b6a4a55ca3e45d88a23656c1af677dd Mon Sep 17 00:00:00 2001 From: Antonio Sarosi <sarosiantonio@gmail.com> Date: Mon, 9 Dec 2024 17:28:41 +0100 Subject: [PATCH 40/51] Add line break for `type_aliases.baml` test --- .../baml/tests/validation_files/class/type_aliases.baml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/baml-lib/baml/tests/validation_files/class/type_aliases.baml b/engine/baml-lib/baml/tests/validation_files/class/type_aliases.baml index 42b0f781a..a3753f6b5 100644 --- a/engine/baml-lib/baml/tests/validation_files/class/type_aliases.baml +++ b/engine/baml-lib/baml/tests/validation_files/class/type_aliases.baml @@ -52,4 +52,4 @@ function MergeAliasAttributes(money: int) -> MergeAttrs { {{ ctx.output_format }} "# -} \ No newline at end of file +} From 6e64ed043b4c9666b75bd2555b42968d1c2badd3 Mon Sep 17 00:00:00 2001 From: Antonio Sarosi <sarosiantonio@gmail.com> Date: Mon, 9 Dec 2024 18:01:41 +0100 Subject: [PATCH 41/51] Fix parser test --- .../schema-ast/src/parser/parse_assignment.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/engine/baml-lib/schema-ast/src/parser/parse_assignment.rs b/engine/baml-lib/schema-ast/src/parser/parse_assignment.rs index a66c64264..a0b52efc6 100644 --- a/engine/baml-lib/schema-ast/src/parser/parse_assignment.rs +++ b/engine/baml-lib/schema-ast/src/parser/parse_assignment.rs @@ -109,12 +109,14 @@ mod tests { identifier(0, 4, [single_word(0, 4)]), identifier(5, 9, [single_word(5, 9)]), assignment(10, 11), - field_type(12, 15, [ - non_union(12, 15, [ - identifier(12, 15, [single_word(12, 15)]) - ]) + field_type_with_attr(12, 15, [ + field_type(12, 15, [ + non_union(12, 15, [ + identifier(12, 15, [single_word(12, 15)]) + ]), + ]), ]), - ]) + ]), ] } From 35cbcc2cc274f30af7321eafc40446685bfe4ac4 Mon Sep 17 00:00:00 2001 From: Antonio Sarosi <sarosiantonio@gmail.com> Date: Mon, 9 Dec 2024 18:29:13 +0100 Subject: [PATCH 42/51] Fix vscode syntax --- .../baml_src/test-files/functions/output/type-aliases.baml | 2 +- typescript/vscode-ext/packages/syntaxes/baml.tmLanguage.json | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/integ-tests/baml_src/test-files/functions/output/type-aliases.baml b/integ-tests/baml_src/test-files/functions/output/type-aliases.baml index 69005e6ce..7219b1915 100644 --- a/integ-tests/baml_src/test-files/functions/output/type-aliases.baml +++ b/integ-tests/baml_src/test-files/functions/output/type-aliases.baml @@ -37,7 +37,7 @@ function NestedAlias(c: Combination) -> Combination { // Test attribute merging. type Currency = int @check(gt_ten, {{ this > 10 }}) -type Amount = Currency @assert ({{ this > 0 }}) +type Amount = Currency @assert({{ this > 0 }}) class MergeAttrs { amount Amount @description("In USD") diff --git a/typescript/vscode-ext/packages/syntaxes/baml.tmLanguage.json b/typescript/vscode-ext/packages/syntaxes/baml.tmLanguage.json index 66b8fd9ff..5034ded68 100644 --- a/typescript/vscode-ext/packages/syntaxes/baml.tmLanguage.json +++ b/typescript/vscode-ext/packages/syntaxes/baml.tmLanguage.json @@ -731,9 +731,10 @@ { "include": "#comment" }, { "begin": "(?<=\\=)\\s*", - "end": "(?=$|\\n)", + "end": "(?=//|$|\\n)", "patterns": [ - { "include": "#type_definition" } + { "include": "#type_definition" }, + { "include": "#block_attribute" } ] } ] From 1a33f277121b624f2474482edb5bed586d2cbe27 Mon Sep 17 00:00:00 2001 From: Antonio Sarosi <sarosiantonio@gmail.com> Date: Tue, 10 Dec 2024 02:38:38 +0100 Subject: [PATCH 43/51] Allow multiple checks and asserts --- .../class/invalid_attrs_on_type_alias.baml | 14 +++- .../validation_files/class/type_aliases.baml | 5 +- .../parser-database/src/attributes/mod.rs | 41 ++++++++---- .../parser-database/src/context/mod.rs | 50 +++++--------- .../functions/output/type-aliases.baml | 14 ++++ .../python/baml_client/async_client.py | 53 +++++++++++++++ integ-tests/python/baml_client/inlinedbaml.py | 2 +- integ-tests/python/baml_client/sync_client.py | 53 +++++++++++++++ integ-tests/python/tests/test_functions.py | 6 ++ integ-tests/ruby/baml_client/client.rb | 67 +++++++++++++++++++ integ-tests/ruby/baml_client/inlined.rb | 2 +- .../typescript/baml_client/async_client.ts | 58 ++++++++++++++++ .../typescript/baml_client/inlinedbaml.ts | 2 +- .../typescript/baml_client/sync_client.ts | 25 +++++++ 14 files changed, 337 insertions(+), 55 deletions(-) diff --git a/engine/baml-lib/baml/tests/validation_files/class/invalid_attrs_on_type_alias.baml b/engine/baml-lib/baml/tests/validation_files/class/invalid_attrs_on_type_alias.baml index 51c63cdba..6cec9d633 100644 --- a/engine/baml-lib/baml/tests/validation_files/class/invalid_attrs_on_type_alias.baml +++ b/engine/baml-lib/baml/tests/validation_files/class/invalid_attrs_on_type_alias.baml @@ -4,21 +4,29 @@ type AliasNotAllowed = float @alias("Alias not allowed") type SkipNotAllowed = float @skip -// error: Error validating: type aliases may only have check and assert attributes +type AttrNotFound = int @assert({{ this > 0 }}) @unknown + +// error: Error validating: type aliases may only have @check and @assert attributes // --> class/invalid_attrs_on_type_alias.baml:1 // | // | // 1 | type DescNotAllowed = string @description("This is not allowed") // | -// error: Error validating: type aliases may only have check and assert attributes +// error: Error validating: type aliases may only have @check and @assert attributes // --> class/invalid_attrs_on_type_alias.baml:3 // | // 2 | // 3 | type AliasNotAllowed = float @alias("Alias not allowed") // | -// error: Error validating: type aliases may only have check and assert attributes +// error: Error validating: type aliases may only have @check and @assert attributes // --> class/invalid_attrs_on_type_alias.baml:5 // | // 4 | // 5 | type SkipNotAllowed = float @skip // | +// error: Attribute not known: "@unknown". +// --> class/invalid_attrs_on_type_alias.baml:7 +// | +// 6 | +// 7 | type AttrNotFound = int @assert({{ this > 0 }}) @unknown +// | diff --git a/engine/baml-lib/baml/tests/validation_files/class/type_aliases.baml b/engine/baml-lib/baml/tests/validation_files/class/type_aliases.baml index a3753f6b5..27963ba76 100644 --- a/engine/baml-lib/baml/tests/validation_files/class/type_aliases.baml +++ b/engine/baml-lib/baml/tests/validation_files/class/type_aliases.baml @@ -8,7 +8,10 @@ type Combination = Primitive | List | Graph // Alias with attrs. type Currency = int @check(gt_ten, {{ this > 10 }}) -type Amount = Currency @assert ({{ this > 0 }}) +type Amount = Currency @assert({{ this > 0 }}) + +// Should be allowed. +type MultipleAttrs = int @assert({{ this > 0 }}) @check(gt_ten, {{ this > 10 }}) class MergeAttrs { amount Amount @description("In USD") diff --git a/engine/baml-lib/parser-database/src/attributes/mod.rs b/engine/baml-lib/parser-database/src/attributes/mod.rs index aae4b7b0b..92e8c58bf 100644 --- a/engine/baml-lib/parser-database/src/attributes/mod.rs +++ b/engine/baml-lib/parser-database/src/attributes/mod.rs @@ -156,21 +156,34 @@ fn resolve_type_alias_attributes<'db>( ctx: &mut Context<'db>, ) { ctx.assert_all_attributes_processed(alias_id.into()); - let type_alias_attributes = to_string_attribute::visit(ctx, assignment.value.span(), false); - ctx.validate_visited_attributes(); - // Some additional specific validation for type alias attributes. - if let Some(attrs) = &type_alias_attributes { - if attrs.dynamic_type().is_some() - || attrs.alias().is_some() - || attrs.skip().is_some() - || attrs.description().is_some() - { - ctx.diagnostics - .push_error(DatamodelError::new_validation_error( - "type aliases may only have check and assert attributes", - assignment.span.clone(), - )); + for _ in 0..assignment.value.attributes().len() { + // TODO: How does this thing work exactly, the code in the functions + // above for visiting class fields suggests that this returns "all" the + // attributes that it finds but it does not return repeated checks and + // asserts, they are left in the state machine and the Context panics. + // So we're gonna run this in a for loop so that the visit function + // calls visit_repeated_attr_from_names enough times to consume all the + // checks and asserts. + let type_alias_attributes = to_string_attribute::visit(ctx, assignment.value.span(), false); + + // Some additional specific validation for type alias attributes. + if let Some(attrs) = &type_alias_attributes { + if attrs.dynamic_type().is_some() + || attrs.alias().is_some() + || attrs.skip().is_some() + || attrs.description().is_some() + { + ctx.diagnostics + .push_error(DatamodelError::new_validation_error( + "type aliases may only have @check and @assert attributes", + assignment.span.clone(), + )); + } } } + + // Now this should be safe to call and it should not panic because there are + // checks and asserts left. + ctx.validate_visited_attributes(); } diff --git a/engine/baml-lib/parser-database/src/context/mod.rs b/engine/baml-lib/parser-database/src/context/mod.rs index b9a46fb0a..6a417c15b 100644 --- a/engine/baml-lib/parser-database/src/context/mod.rs +++ b/engine/baml-lib/parser-database/src/context/mod.rs @@ -16,12 +16,13 @@ mod attributes; /// /// ## Attribute Validation /// -/// The Context also acts as a state machine for attribute validation. The goal is to avoid manual -/// work validating things that are valid for every attribute set, and every argument set inside an -/// attribute: multiple unnamed arguments are not valid, attributes we do not use in parser-database -/// are not valid, multiple arguments with the same name are not valid, etc. +/// The Context also acts as a state machine for attribute validation. The goal +/// is to avoid manual work validating things that are valid for every attribute +/// set, and every argument set inside an attribute: multiple unnamed arguments +/// are not valid, attributes we do not use in parser-database are not valid, +/// multiple arguments with the same name are not valid, etc. /// -/// See `visit_attributes()`. +/// See [`Self::assert_all_attributes_processed`]. pub(crate) struct Context<'db> { pub(crate) ast: &'db ast::SchemaAst, pub(crate) interner: &'db mut StringInterner, @@ -75,14 +76,17 @@ impl<'db> Context<'db> { self.diagnostics.push_warning(warning) } - /// All attribute validation should go through `visit_attributes()`. It lets - /// us enforce some rules, for example that certain attributes should not be - /// repeated, and make sure that _all_ attributes are visited during the - /// validation process, emitting unknown attribute errors when it is not - /// the case. + /// Attribute processing entry point. /// - /// - When you are done validating an attribute, you must call `discard_arguments()` or - /// `validate_visited_arguments()`. Otherwise, Context will helpfully panic. + /// All attribute validation should go through + /// [`Self::assert_all_attributes_processed`]. It lets us enforce some + /// rules, for example that certain attributes should not be repeated, and + /// make sure that _all_ attributes are visited during the validation + /// process, emitting unknown attribute errors when it is not the case. + /// + /// - When you are done validating an attribute, you must call + /// [`Self::discard_arguments()`] or [`Self::validate_visited_arguments()`]. + /// Otherwise, [`Context`] will helpfully panic. pub(super) fn assert_all_attributes_processed( &mut self, ast_attributes: ast::AttributeContainer, @@ -98,28 +102,6 @@ impl<'db> Context<'db> { self.attributes.set_attributes(ast_attributes, self.ast); } - /// Extract an attribute that can occur zero or more times. Example: @@index on models. - /// - /// Returns `true` as long as a next attribute is found. - pub(crate) fn _visit_repeated_attr(&mut self, name: &'static str) -> bool { - let mut has_valid_attribute = false; - - while !has_valid_attribute { - let first_attr = iter_attributes(self.attributes.attributes.as_ref(), self.ast) - .filter(|(_, attr)| attr.name.name() == name) - .find(|(attr_id, _)| self.attributes.unused_attributes.contains(attr_id)); - let (attr_id, attr) = if let Some(first_attr) = first_attr { - first_attr - } else { - break; - }; - self.attributes.unused_attributes.remove(&attr_id); - has_valid_attribute = self.set_attribute(attr_id, attr); - } - - has_valid_attribute - } - /// Extract an attribute that can occur zero or more times. Example: @assert on types. /// Argument is a list of names that are all valid for this attribute. /// diff --git a/integ-tests/baml_src/test-files/functions/output/type-aliases.baml b/integ-tests/baml_src/test-files/functions/output/type-aliases.baml index 7219b1915..5da6d06ef 100644 --- a/integ-tests/baml_src/test-files/functions/output/type-aliases.baml +++ b/integ-tests/baml_src/test-files/functions/output/type-aliases.baml @@ -43,6 +43,9 @@ class MergeAttrs { amount Amount @description("In USD") } +// This should be allowed. +type MultipleAttrs = int @assert({{ this > 0 }}) @check(gt_ten, {{ this > 10 }}) + function MergeAliasAttributes(money: int) -> MergeAttrs { client "openai/gpt-4o" prompt r#" @@ -63,4 +66,15 @@ function ReturnAliasWithMergedAttributes(money: Amount) -> Amount { {{ ctx.output_format }} "# +} + +function AliasWithMultipleAttrs(money: MultipleAttrs) -> MultipleAttrs { + client "openai/gpt-4o" + prompt r#" + Return the given integer without additional context: + + {{ money }} + + {{ ctx.output_format }} + "# } \ No newline at end of file diff --git a/integ-tests/python/baml_client/async_client.py b/integ-tests/python/baml_client/async_client.py index 03d1283e9..1bb0c3836 100644 --- a/integ-tests/python/baml_client/async_client.py +++ b/integ-tests/python/baml_client/async_client.py @@ -96,6 +96,29 @@ async def AliasThatPointsToRecursiveType( ) return cast(types.LinkedListAliasNode, raw.cast_to(types, types)) + async def AliasWithMultipleAttrs( + self, + money: Checked[int,types.Literal["gt_ten"]], + baml_options: BamlCallOptions = {}, + ) -> Checked[int,types.Literal["gt_ten"]]: + __tb__ = baml_options.get("tb", None) + if __tb__ is not None: + tb = __tb__._tb # type: ignore (we know how to use this private attribute) + else: + tb = None + __cr__ = baml_options.get("client_registry", None) + + raw = await self.__runtime.call_function( + "AliasWithMultipleAttrs", + { + "money": money, + }, + self.__ctx_manager.get(), + tb, + __cr__, + ) + return cast(Checked[int,types.Literal["gt_ten"]], raw.cast_to(types, types)) + async def AliasedInputClass( self, input: types.InputClass, @@ -3019,6 +3042,36 @@ def AliasThatPointsToRecursiveType( self.__ctx_manager.get(), ) + def AliasWithMultipleAttrs( + self, + money: Checked[int,types.Literal["gt_ten"]], + baml_options: BamlCallOptions = {}, + ) -> baml_py.BamlStream[Checked[Optional[int],types.Literal["gt_ten"]], Checked[int,types.Literal["gt_ten"]]]: + __tb__ = baml_options.get("tb", None) + if __tb__ is not None: + tb = __tb__._tb # type: ignore (we know how to use this private attribute) + else: + tb = None + __cr__ = baml_options.get("client_registry", None) + + raw = self.__runtime.stream_function( + "AliasWithMultipleAttrs", + { + "money": money, + }, + None, + self.__ctx_manager.get(), + tb, + __cr__, + ) + + return baml_py.BamlStream[Checked[Optional[int],types.Literal["gt_ten"]], Checked[int,types.Literal["gt_ten"]]]( + raw, + lambda x: cast(Checked[Optional[int],types.Literal["gt_ten"]], x.cast_to(types, partial_types)), + lambda x: cast(Checked[int,types.Literal["gt_ten"]], x.cast_to(types, types)), + self.__ctx_manager.get(), + ) + def AliasedInputClass( self, input: types.InputClass, diff --git a/integ-tests/python/baml_client/inlinedbaml.py b/integ-tests/python/baml_client/inlinedbaml.py index 2aed92bf5..43fe97e37 100644 --- a/integ-tests/python/baml_client/inlinedbaml.py +++ b/integ-tests/python/baml_client/inlinedbaml.py @@ -83,7 +83,7 @@ "test-files/functions/output/recursive-type-aliases.baml": "class LinkedListAliasNode {\n value int\n next LinkedListAliasNode?\n}\n\n// Simple alias that points to recursive type.\ntype LinkedListAlias = LinkedListAliasNode\n\nfunction AliasThatPointsToRecursiveType(list: LinkedListAlias) -> LinkedListAlias {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given linked list back:\n \n {{ list }}\n \n {{ ctx.output_format }}\n \"#\n}\n\n// Class that points to an alias that points to a recursive type.\nclass ClassToRecAlias {\n list LinkedListAlias\n}\n\nfunction ClassThatPointsToRecursiveClassThroughAlias(cls: ClassToRecAlias) -> ClassToRecAlias {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given object back:\n \n {{ cls }}\n \n {{ ctx.output_format }}\n \"#\n}\n\n// This is tricky cause this class should be hoisted, but classes and aliases\n// are two different types in the AST. This test will make sure they can interop.\nclass NodeWithAliasIndirection {\n value int\n next NodeIndirection?\n}\n\ntype NodeIndirection = NodeWithAliasIndirection\n\nfunction RecursiveClassWithAliasIndirection(cls: NodeWithAliasIndirection) -> NodeWithAliasIndirection {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given object back:\n \n {{ cls }}\n \n {{ ctx.output_format }}\n \"#\n}\n", "test-files/functions/output/serialization-error.baml": "class DummyOutput {\n nonce string\n nonce2 string\n @@dynamic\n}\n\nfunction DummyOutputFunction(input: string) -> DummyOutput {\n client GPT35\n prompt #\"\n Say \"hello there\".\n \"#\n}", "test-files/functions/output/string-list.baml": "function FnOutputStringList(input: string) -> string[] {\n client GPT35\n prompt #\"\n Return a list of strings in json format like [\"string1\", \"string2\", \"string3\"].\n\n JSON:\n \"#\n}\n\ntest FnOutputStringList {\n functions [FnOutputStringList]\n args {\n input \"example input\"\n }\n}\n", - "test-files/functions/output/type-aliases.baml": "type Primitive = int | string | bool | float\n\ntype List = string[]\n\ntype Graph = map<string, string[]>\n\ntype Combination = Primitive | List | Graph\n\nfunction PrimitiveAlias(p: Primitive) -> Primitive {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given value back: {{ p }}\n \"#\n}\n\nfunction MapAlias(m: Graph) -> Graph {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given Graph back:\n\n {{ m }}\n\n {{ ctx.output_format }}\n \"#\n}\n\nfunction NestedAlias(c: Combination) -> Combination {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given value back:\n\n {{ c }}\n\n {{ ctx.output_format }}\n \"#\n}\n\n// Test attribute merging.\ntype Currency = int @check(gt_ten, {{ this > 10 }})\ntype Amount = Currency @assert ({{ this > 0 }})\n\nclass MergeAttrs {\n amount Amount @description(\"In USD\")\n}\n\nfunction MergeAliasAttributes(money: int) -> MergeAttrs {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given integer in the specified format:\n\n {{ money }}\n\n {{ ctx.output_format }}\n \"#\n}\n\nfunction ReturnAliasWithMergedAttributes(money: Amount) -> Amount {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given integer without additional context:\n\n {{ money }}\n\n {{ ctx.output_format }}\n \"#\n}", + "test-files/functions/output/type-aliases.baml": "type Primitive = int | string | bool | float\n\ntype List = string[]\n\ntype Graph = map<string, string[]>\n\ntype Combination = Primitive | List | Graph\n\nfunction PrimitiveAlias(p: Primitive) -> Primitive {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given value back: {{ p }}\n \"#\n}\n\nfunction MapAlias(m: Graph) -> Graph {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given Graph back:\n\n {{ m }}\n\n {{ ctx.output_format }}\n \"#\n}\n\nfunction NestedAlias(c: Combination) -> Combination {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given value back:\n\n {{ c }}\n\n {{ ctx.output_format }}\n \"#\n}\n\n// Test attribute merging.\ntype Currency = int @check(gt_ten, {{ this > 10 }})\ntype Amount = Currency @assert({{ this > 0 }})\n\nclass MergeAttrs {\n amount Amount @description(\"In USD\")\n}\n\n// This should be allowed.\ntype MultipleAttrs = int @assert({{ this > 0 }}) @check(gt_ten, {{ this > 10 }})\n\nfunction MergeAliasAttributes(money: int) -> MergeAttrs {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given integer in the specified format:\n\n {{ money }}\n\n {{ ctx.output_format }}\n \"#\n}\n\nfunction ReturnAliasWithMergedAttributes(money: Amount) -> Amount {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given integer without additional context:\n\n {{ money }}\n\n {{ ctx.output_format }}\n \"#\n}\n\nfunction AliasWithMultipleAttrs(money: MultipleAttrs) -> MultipleAttrs {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given integer without additional context:\n\n {{ money }}\n\n {{ ctx.output_format }}\n \"#\n}", "test-files/functions/output/unions.baml": "class UnionTest_ReturnType {\n prop1 string | bool\n prop2 (float | bool)[]\n prop3 (bool[] | int[])\n}\n\nfunction UnionTest_Function(input: string | bool) -> UnionTest_ReturnType {\n client GPT35\n prompt #\"\n Return a JSON blob with this schema: \n {{ctx.output_format}}\n\n JSON:\n \"#\n}\n\ntest UnionTest_Function {\n functions [UnionTest_Function]\n args {\n input \"example input\"\n }\n}\n", "test-files/functions/prompts/no-chat-messages.baml": "\n\nfunction PromptTestClaude(input: string) -> string {\n client Sonnet\n prompt #\"\n Tell me a haiku about {{ input }}\n \"#\n}\n\n\nfunction PromptTestStreaming(input: string) -> string {\n client GPT35\n prompt #\"\n Tell me a short story about {{ input }}\n \"#\n}\n\ntest TestName {\n functions [PromptTestStreaming]\n args {\n input #\"\n hello world\n \"#\n }\n}\n", "test-files/functions/prompts/with-chat-messages.baml": "\nfunction PromptTestOpenAIChat(input: string) -> string {\n client GPT35\n prompt #\"\n {{ _.role(\"system\") }}\n You are an assistant that always responds in a very excited way with emojis and also outputs this word 4 times after giving a response: {{ input }}\n \n {{ _.role(\"user\") }}\n Tell me a haiku about {{ input }}\n \"#\n}\n\nfunction PromptTestOpenAIChatNoSystem(input: string) -> string {\n client GPT35\n prompt #\"\n You are an assistant that always responds in a very excited way with emojis and also outputs this word 4 times after giving a response: {{ input }}\n \n {{ _.role(\"user\") }}\n Tell me a haiku about {{ input }}\n \"#\n}\n\nfunction PromptTestClaudeChat(input: string) -> string {\n client Claude\n prompt #\"\n {{ _.role(\"system\") }}\n You are an assistant that always responds in a very excited way with emojis and also outputs this word 4 times after giving a response: {{ input }}\n \n {{ _.role(\"user\") }}\n Tell me a haiku about {{ input }}\n \"#\n}\n\nfunction PromptTestClaudeChatNoSystem(input: string) -> string {\n client Claude\n prompt #\"\n You are an assistant that always responds in a very excited way with emojis and also outputs this word 4 times after giving a response: {{ input }}\n \n {{ _.role(\"user\") }}\n Tell me a haiku about {{ input }}\n \"#\n}\n\ntest TestSystemAndNonSystemChat1 {\n functions [PromptTestClaude, PromptTestOpenAI, PromptTestOpenAIChat, PromptTestOpenAIChatNoSystem, PromptTestClaudeChat, PromptTestClaudeChatNoSystem]\n args {\n input \"cats\"\n }\n}\n\ntest TestSystemAndNonSystemChat2 {\n functions [PromptTestClaude, PromptTestOpenAI, PromptTestOpenAIChat, PromptTestOpenAIChatNoSystem, PromptTestClaudeChat, PromptTestClaudeChatNoSystem]\n args {\n input \"lion\"\n }\n}", diff --git a/integ-tests/python/baml_client/sync_client.py b/integ-tests/python/baml_client/sync_client.py index ba24760c7..1202e8360 100644 --- a/integ-tests/python/baml_client/sync_client.py +++ b/integ-tests/python/baml_client/sync_client.py @@ -93,6 +93,29 @@ def AliasThatPointsToRecursiveType( ) return cast(types.LinkedListAliasNode, raw.cast_to(types, types)) + def AliasWithMultipleAttrs( + self, + money: Checked[int,types.Literal["gt_ten"]], + baml_options: BamlCallOptions = {}, + ) -> Checked[int,types.Literal["gt_ten"]]: + __tb__ = baml_options.get("tb", None) + if __tb__ is not None: + tb = __tb__._tb # type: ignore (we know how to use this private attribute) + else: + tb = None + __cr__ = baml_options.get("client_registry", None) + + raw = self.__runtime.call_function_sync( + "AliasWithMultipleAttrs", + { + "money": money, + }, + self.__ctx_manager.get(), + tb, + __cr__, + ) + return cast(Checked[int,types.Literal["gt_ten"]], raw.cast_to(types, types)) + def AliasedInputClass( self, input: types.InputClass, @@ -3017,6 +3040,36 @@ def AliasThatPointsToRecursiveType( self.__ctx_manager.get(), ) + def AliasWithMultipleAttrs( + self, + money: Checked[int,types.Literal["gt_ten"]], + baml_options: BamlCallOptions = {}, + ) -> baml_py.BamlSyncStream[Checked[Optional[int],types.Literal["gt_ten"]], Checked[int,types.Literal["gt_ten"]]]: + __tb__ = baml_options.get("tb", None) + if __tb__ is not None: + tb = __tb__._tb # type: ignore (we know how to use this private attribute) + else: + tb = None + __cr__ = baml_options.get("client_registry", None) + + raw = self.__runtime.stream_function_sync( + "AliasWithMultipleAttrs", + { + "money": money, + }, + None, + self.__ctx_manager.get(), + tb, + __cr__, + ) + + return baml_py.BamlSyncStream[Checked[Optional[int],types.Literal["gt_ten"]], Checked[int,types.Literal["gt_ten"]]]( + raw, + lambda x: cast(Checked[Optional[int],types.Literal["gt_ten"]], x.cast_to(types, partial_types)), + lambda x: cast(Checked[int,types.Literal["gt_ten"]], x.cast_to(types, types)), + self.__ctx_manager.get(), + ) + def AliasedInputClass( self, input: types.InputClass, diff --git a/integ-tests/python/tests/test_functions.py b/integ-tests/python/tests/test_functions.py index c6cb84656..11d92c800 100644 --- a/integ-tests/python/tests/test_functions.py +++ b/integ-tests/python/tests/test_functions.py @@ -308,6 +308,12 @@ async def test_return_alias_with_merged_attrs(self): assert res.value == 123 assert res.checks["gt_ten"].status == "succeeded" + @pytest.mark.asyncio + async def test_alias_with_multiple_attrs(self): + res = await b.AliasWithMultipleAttrs(123) + assert res.value == 123 + assert res.checks["gt_ten"].status == "succeeded" + class MyCustomClass(NamedArgsSingleClass): date: datetime.datetime diff --git a/integ-tests/ruby/baml_client/client.rb b/integ-tests/ruby/baml_client/client.rb index 4fbefbdbf..e8cc900c6 100644 --- a/integ-tests/ruby/baml_client/client.rb +++ b/integ-tests/ruby/baml_client/client.rb @@ -114,6 +114,38 @@ def AliasThatPointsToRecursiveType( (raw.parsed_using_types(Baml::Types)) end + sig { + params( + varargs: T.untyped, + money: Baml::Checked[Integer], + baml_options: T::Hash[Symbol, T.any(Baml::TypeBuilder, Baml::ClientRegistry)] + ).returns(Baml::Checked[Integer]) + } + def AliasWithMultipleAttrs( + *varargs, + money:, + baml_options: {} + ) + if varargs.any? + + raise ArgumentError.new("AliasWithMultipleAttrs may only be called with keyword arguments") + end + if (baml_options.keys - [:client_registry, :tb]).any? + raise ArgumentError.new("Received unknown keys in baml_options (valid keys: :client_registry, :tb): #{baml_options.keys - [:client_registry, :tb]}") + end + + raw = @runtime.call_function( + "AliasWithMultipleAttrs", + { + money: money, + }, + @ctx_manager, + baml_options[:tb]&.instance_variable_get(:@registry), + baml_options[:client_registry], + ) + (raw.parsed_using_types(Baml::Types)) + end + sig { params( varargs: T.untyped, @@ -4165,6 +4197,41 @@ def AliasThatPointsToRecursiveType( ) end + sig { + params( + varargs: T.untyped, + money: Baml::Checked[Integer], + baml_options: T::Hash[Symbol, T.any(Baml::TypeBuilder, Baml::ClientRegistry)] + ).returns(Baml::BamlStream[Baml::Checked[Integer]]) + } + def AliasWithMultipleAttrs( + *varargs, + money:, + baml_options: {} + ) + if varargs.any? + + raise ArgumentError.new("AliasWithMultipleAttrs may only be called with keyword arguments") + end + if (baml_options.keys - [:client_registry, :tb]).any? + raise ArgumentError.new("Received unknown keys in baml_options (valid keys: :client_registry, :tb): #{baml_options.keys - [:client_registry, :tb]}") + end + + raw = @runtime.stream_function( + "AliasWithMultipleAttrs", + { + money: money, + }, + @ctx_manager, + baml_options[:tb]&.instance_variable_get(:@registry), + baml_options[:client_registry], + ) + Baml::BamlStream[Baml::Checked[T.nilable(Integer)], Baml::Checked[Integer]].new( + ffi_stream: raw, + ctx_manager: @ctx_manager + ) + end + sig { params( varargs: T.untyped, diff --git a/integ-tests/ruby/baml_client/inlined.rb b/integ-tests/ruby/baml_client/inlined.rb index 33522277a..0751e9f54 100644 --- a/integ-tests/ruby/baml_client/inlined.rb +++ b/integ-tests/ruby/baml_client/inlined.rb @@ -83,7 +83,7 @@ module Inlined "test-files/functions/output/recursive-type-aliases.baml" => "class LinkedListAliasNode {\n value int\n next LinkedListAliasNode?\n}\n\n// Simple alias that points to recursive type.\ntype LinkedListAlias = LinkedListAliasNode\n\nfunction AliasThatPointsToRecursiveType(list: LinkedListAlias) -> LinkedListAlias {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given linked list back:\n \n {{ list }}\n \n {{ ctx.output_format }}\n \"#\n}\n\n// Class that points to an alias that points to a recursive type.\nclass ClassToRecAlias {\n list LinkedListAlias\n}\n\nfunction ClassThatPointsToRecursiveClassThroughAlias(cls: ClassToRecAlias) -> ClassToRecAlias {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given object back:\n \n {{ cls }}\n \n {{ ctx.output_format }}\n \"#\n}\n\n// This is tricky cause this class should be hoisted, but classes and aliases\n// are two different types in the AST. This test will make sure they can interop.\nclass NodeWithAliasIndirection {\n value int\n next NodeIndirection?\n}\n\ntype NodeIndirection = NodeWithAliasIndirection\n\nfunction RecursiveClassWithAliasIndirection(cls: NodeWithAliasIndirection) -> NodeWithAliasIndirection {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given object back:\n \n {{ cls }}\n \n {{ ctx.output_format }}\n \"#\n}\n", "test-files/functions/output/serialization-error.baml" => "class DummyOutput {\n nonce string\n nonce2 string\n @@dynamic\n}\n\nfunction DummyOutputFunction(input: string) -> DummyOutput {\n client GPT35\n prompt #\"\n Say \"hello there\".\n \"#\n}", "test-files/functions/output/string-list.baml" => "function FnOutputStringList(input: string) -> string[] {\n client GPT35\n prompt #\"\n Return a list of strings in json format like [\"string1\", \"string2\", \"string3\"].\n\n JSON:\n \"#\n}\n\ntest FnOutputStringList {\n functions [FnOutputStringList]\n args {\n input \"example input\"\n }\n}\n", - "test-files/functions/output/type-aliases.baml" => "type Primitive = int | string | bool | float\n\ntype List = string[]\n\ntype Graph = map<string, string[]>\n\ntype Combination = Primitive | List | Graph\n\nfunction PrimitiveAlias(p: Primitive) -> Primitive {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given value back: {{ p }}\n \"#\n}\n\nfunction MapAlias(m: Graph) -> Graph {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given Graph back:\n\n {{ m }}\n\n {{ ctx.output_format }}\n \"#\n}\n\nfunction NestedAlias(c: Combination) -> Combination {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given value back:\n\n {{ c }}\n\n {{ ctx.output_format }}\n \"#\n}\n\n// Test attribute merging.\ntype Currency = int @check(gt_ten, {{ this > 10 }})\ntype Amount = Currency @assert ({{ this > 0 }})\n\nclass MergeAttrs {\n amount Amount @description(\"In USD\")\n}\n\nfunction MergeAliasAttributes(money: int) -> MergeAttrs {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given integer in the specified format:\n\n {{ money }}\n\n {{ ctx.output_format }}\n \"#\n}\n\nfunction ReturnAliasWithMergedAttributes(money: Amount) -> Amount {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given integer without additional context:\n\n {{ money }}\n\n {{ ctx.output_format }}\n \"#\n}", + "test-files/functions/output/type-aliases.baml" => "type Primitive = int | string | bool | float\n\ntype List = string[]\n\ntype Graph = map<string, string[]>\n\ntype Combination = Primitive | List | Graph\n\nfunction PrimitiveAlias(p: Primitive) -> Primitive {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given value back: {{ p }}\n \"#\n}\n\nfunction MapAlias(m: Graph) -> Graph {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given Graph back:\n\n {{ m }}\n\n {{ ctx.output_format }}\n \"#\n}\n\nfunction NestedAlias(c: Combination) -> Combination {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given value back:\n\n {{ c }}\n\n {{ ctx.output_format }}\n \"#\n}\n\n// Test attribute merging.\ntype Currency = int @check(gt_ten, {{ this > 10 }})\ntype Amount = Currency @assert({{ this > 0 }})\n\nclass MergeAttrs {\n amount Amount @description(\"In USD\")\n}\n\n// This should be allowed.\ntype MultipleAttrs = int @assert({{ this > 0 }}) @check(gt_ten, {{ this > 10 }})\n\nfunction MergeAliasAttributes(money: int) -> MergeAttrs {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given integer in the specified format:\n\n {{ money }}\n\n {{ ctx.output_format }}\n \"#\n}\n\nfunction ReturnAliasWithMergedAttributes(money: Amount) -> Amount {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given integer without additional context:\n\n {{ money }}\n\n {{ ctx.output_format }}\n \"#\n}\n\nfunction AliasWithMultipleAttrs(money: MultipleAttrs) -> MultipleAttrs {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given integer without additional context:\n\n {{ money }}\n\n {{ ctx.output_format }}\n \"#\n}", "test-files/functions/output/unions.baml" => "class UnionTest_ReturnType {\n prop1 string | bool\n prop2 (float | bool)[]\n prop3 (bool[] | int[])\n}\n\nfunction UnionTest_Function(input: string | bool) -> UnionTest_ReturnType {\n client GPT35\n prompt #\"\n Return a JSON blob with this schema: \n {{ctx.output_format}}\n\n JSON:\n \"#\n}\n\ntest UnionTest_Function {\n functions [UnionTest_Function]\n args {\n input \"example input\"\n }\n}\n", "test-files/functions/prompts/no-chat-messages.baml" => "\n\nfunction PromptTestClaude(input: string) -> string {\n client Sonnet\n prompt #\"\n Tell me a haiku about {{ input }}\n \"#\n}\n\n\nfunction PromptTestStreaming(input: string) -> string {\n client GPT35\n prompt #\"\n Tell me a short story about {{ input }}\n \"#\n}\n\ntest TestName {\n functions [PromptTestStreaming]\n args {\n input #\"\n hello world\n \"#\n }\n}\n", "test-files/functions/prompts/with-chat-messages.baml" => "\nfunction PromptTestOpenAIChat(input: string) -> string {\n client GPT35\n prompt #\"\n {{ _.role(\"system\") }}\n You are an assistant that always responds in a very excited way with emojis and also outputs this word 4 times after giving a response: {{ input }}\n \n {{ _.role(\"user\") }}\n Tell me a haiku about {{ input }}\n \"#\n}\n\nfunction PromptTestOpenAIChatNoSystem(input: string) -> string {\n client GPT35\n prompt #\"\n You are an assistant that always responds in a very excited way with emojis and also outputs this word 4 times after giving a response: {{ input }}\n \n {{ _.role(\"user\") }}\n Tell me a haiku about {{ input }}\n \"#\n}\n\nfunction PromptTestClaudeChat(input: string) -> string {\n client Claude\n prompt #\"\n {{ _.role(\"system\") }}\n You are an assistant that always responds in a very excited way with emojis and also outputs this word 4 times after giving a response: {{ input }}\n \n {{ _.role(\"user\") }}\n Tell me a haiku about {{ input }}\n \"#\n}\n\nfunction PromptTestClaudeChatNoSystem(input: string) -> string {\n client Claude\n prompt #\"\n You are an assistant that always responds in a very excited way with emojis and also outputs this word 4 times after giving a response: {{ input }}\n \n {{ _.role(\"user\") }}\n Tell me a haiku about {{ input }}\n \"#\n}\n\ntest TestSystemAndNonSystemChat1 {\n functions [PromptTestClaude, PromptTestOpenAI, PromptTestOpenAIChat, PromptTestOpenAIChatNoSystem, PromptTestClaudeChat, PromptTestClaudeChatNoSystem]\n args {\n input \"cats\"\n }\n}\n\ntest TestSystemAndNonSystemChat2 {\n functions [PromptTestClaude, PromptTestOpenAI, PromptTestOpenAIChat, PromptTestOpenAIChatNoSystem, PromptTestClaudeChat, PromptTestClaudeChatNoSystem]\n args {\n input \"lion\"\n }\n}", diff --git a/integ-tests/typescript/baml_client/async_client.ts b/integ-tests/typescript/baml_client/async_client.ts index d4c9bd2dc..bd88336c3 100644 --- a/integ-tests/typescript/baml_client/async_client.ts +++ b/integ-tests/typescript/baml_client/async_client.ts @@ -93,6 +93,31 @@ export class BamlAsyncClient { } } + async AliasWithMultipleAttrs( + money: Checked<number,"gt_ten">, + __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } + ): Promise<Checked<number,"gt_ten">> { + try { + const raw = await this.runtime.callFunction( + "AliasWithMultipleAttrs", + { + "money": money + }, + this.ctx_manager.cloneContext(), + __baml_options__?.tb?.__tb(), + __baml_options__?.clientRegistry, + ) + return raw.parsed() as Checked<number,"gt_ten"> + } catch (error: any) { + const bamlError = createBamlValidationError(error); + if (bamlError instanceof BamlValidationError) { + throw bamlError; + } else { + throw error; + } + } + } + async AliasedInputClass( input: InputClass, __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } @@ -3265,6 +3290,39 @@ class BamlStreamClient { } } + AliasWithMultipleAttrs( + money: Checked<number,"gt_ten">, + __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } + ): BamlStream<RecursivePartialNull<Checked<number,"gt_ten">>, Checked<number,"gt_ten">> { + try { + const raw = this.runtime.streamFunction( + "AliasWithMultipleAttrs", + { + "money": money + }, + undefined, + this.ctx_manager.cloneContext(), + __baml_options__?.tb?.__tb(), + __baml_options__?.clientRegistry, + ) + return new BamlStream<RecursivePartialNull<Checked<number,"gt_ten">>, Checked<number,"gt_ten">>( + raw, + (a): a is RecursivePartialNull<Checked<number,"gt_ten">> => a, + (a): a is Checked<number,"gt_ten"> => a, + this.ctx_manager.cloneContext(), + __baml_options__?.tb?.__tb(), + ) + } catch (error) { + if (error instanceof Error) { + const bamlError = createBamlValidationError(error); + if (bamlError instanceof BamlValidationError) { + throw bamlError; + } + } + throw error; + } + } + AliasedInputClass( input: InputClass, __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } diff --git a/integ-tests/typescript/baml_client/inlinedbaml.ts b/integ-tests/typescript/baml_client/inlinedbaml.ts index bed32e639..f03520004 100644 --- a/integ-tests/typescript/baml_client/inlinedbaml.ts +++ b/integ-tests/typescript/baml_client/inlinedbaml.ts @@ -84,7 +84,7 @@ const fileMap = { "test-files/functions/output/recursive-type-aliases.baml": "class LinkedListAliasNode {\n value int\n next LinkedListAliasNode?\n}\n\n// Simple alias that points to recursive type.\ntype LinkedListAlias = LinkedListAliasNode\n\nfunction AliasThatPointsToRecursiveType(list: LinkedListAlias) -> LinkedListAlias {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given linked list back:\n \n {{ list }}\n \n {{ ctx.output_format }}\n \"#\n}\n\n// Class that points to an alias that points to a recursive type.\nclass ClassToRecAlias {\n list LinkedListAlias\n}\n\nfunction ClassThatPointsToRecursiveClassThroughAlias(cls: ClassToRecAlias) -> ClassToRecAlias {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given object back:\n \n {{ cls }}\n \n {{ ctx.output_format }}\n \"#\n}\n\n// This is tricky cause this class should be hoisted, but classes and aliases\n// are two different types in the AST. This test will make sure they can interop.\nclass NodeWithAliasIndirection {\n value int\n next NodeIndirection?\n}\n\ntype NodeIndirection = NodeWithAliasIndirection\n\nfunction RecursiveClassWithAliasIndirection(cls: NodeWithAliasIndirection) -> NodeWithAliasIndirection {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given object back:\n \n {{ cls }}\n \n {{ ctx.output_format }}\n \"#\n}\n", "test-files/functions/output/serialization-error.baml": "class DummyOutput {\n nonce string\n nonce2 string\n @@dynamic\n}\n\nfunction DummyOutputFunction(input: string) -> DummyOutput {\n client GPT35\n prompt #\"\n Say \"hello there\".\n \"#\n}", "test-files/functions/output/string-list.baml": "function FnOutputStringList(input: string) -> string[] {\n client GPT35\n prompt #\"\n Return a list of strings in json format like [\"string1\", \"string2\", \"string3\"].\n\n JSON:\n \"#\n}\n\ntest FnOutputStringList {\n functions [FnOutputStringList]\n args {\n input \"example input\"\n }\n}\n", - "test-files/functions/output/type-aliases.baml": "type Primitive = int | string | bool | float\n\ntype List = string[]\n\ntype Graph = map<string, string[]>\n\ntype Combination = Primitive | List | Graph\n\nfunction PrimitiveAlias(p: Primitive) -> Primitive {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given value back: {{ p }}\n \"#\n}\n\nfunction MapAlias(m: Graph) -> Graph {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given Graph back:\n\n {{ m }}\n\n {{ ctx.output_format }}\n \"#\n}\n\nfunction NestedAlias(c: Combination) -> Combination {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given value back:\n\n {{ c }}\n\n {{ ctx.output_format }}\n \"#\n}\n\n// Test attribute merging.\ntype Currency = int @check(gt_ten, {{ this > 10 }})\ntype Amount = Currency @assert ({{ this > 0 }})\n\nclass MergeAttrs {\n amount Amount @description(\"In USD\")\n}\n\nfunction MergeAliasAttributes(money: int) -> MergeAttrs {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given integer in the specified format:\n\n {{ money }}\n\n {{ ctx.output_format }}\n \"#\n}\n\nfunction ReturnAliasWithMergedAttributes(money: Amount) -> Amount {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given integer without additional context:\n\n {{ money }}\n\n {{ ctx.output_format }}\n \"#\n}", + "test-files/functions/output/type-aliases.baml": "type Primitive = int | string | bool | float\n\ntype List = string[]\n\ntype Graph = map<string, string[]>\n\ntype Combination = Primitive | List | Graph\n\nfunction PrimitiveAlias(p: Primitive) -> Primitive {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given value back: {{ p }}\n \"#\n}\n\nfunction MapAlias(m: Graph) -> Graph {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given Graph back:\n\n {{ m }}\n\n {{ ctx.output_format }}\n \"#\n}\n\nfunction NestedAlias(c: Combination) -> Combination {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given value back:\n\n {{ c }}\n\n {{ ctx.output_format }}\n \"#\n}\n\n// Test attribute merging.\ntype Currency = int @check(gt_ten, {{ this > 10 }})\ntype Amount = Currency @assert({{ this > 0 }})\n\nclass MergeAttrs {\n amount Amount @description(\"In USD\")\n}\n\n// This should be allowed.\ntype MultipleAttrs = int @assert({{ this > 0 }}) @check(gt_ten, {{ this > 10 }})\n\nfunction MergeAliasAttributes(money: int) -> MergeAttrs {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given integer in the specified format:\n\n {{ money }}\n\n {{ ctx.output_format }}\n \"#\n}\n\nfunction ReturnAliasWithMergedAttributes(money: Amount) -> Amount {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given integer without additional context:\n\n {{ money }}\n\n {{ ctx.output_format }}\n \"#\n}\n\nfunction AliasWithMultipleAttrs(money: MultipleAttrs) -> MultipleAttrs {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given integer without additional context:\n\n {{ money }}\n\n {{ ctx.output_format }}\n \"#\n}", "test-files/functions/output/unions.baml": "class UnionTest_ReturnType {\n prop1 string | bool\n prop2 (float | bool)[]\n prop3 (bool[] | int[])\n}\n\nfunction UnionTest_Function(input: string | bool) -> UnionTest_ReturnType {\n client GPT35\n prompt #\"\n Return a JSON blob with this schema: \n {{ctx.output_format}}\n\n JSON:\n \"#\n}\n\ntest UnionTest_Function {\n functions [UnionTest_Function]\n args {\n input \"example input\"\n }\n}\n", "test-files/functions/prompts/no-chat-messages.baml": "\n\nfunction PromptTestClaude(input: string) -> string {\n client Sonnet\n prompt #\"\n Tell me a haiku about {{ input }}\n \"#\n}\n\n\nfunction PromptTestStreaming(input: string) -> string {\n client GPT35\n prompt #\"\n Tell me a short story about {{ input }}\n \"#\n}\n\ntest TestName {\n functions [PromptTestStreaming]\n args {\n input #\"\n hello world\n \"#\n }\n}\n", "test-files/functions/prompts/with-chat-messages.baml": "\nfunction PromptTestOpenAIChat(input: string) -> string {\n client GPT35\n prompt #\"\n {{ _.role(\"system\") }}\n You are an assistant that always responds in a very excited way with emojis and also outputs this word 4 times after giving a response: {{ input }}\n \n {{ _.role(\"user\") }}\n Tell me a haiku about {{ input }}\n \"#\n}\n\nfunction PromptTestOpenAIChatNoSystem(input: string) -> string {\n client GPT35\n prompt #\"\n You are an assistant that always responds in a very excited way with emojis and also outputs this word 4 times after giving a response: {{ input }}\n \n {{ _.role(\"user\") }}\n Tell me a haiku about {{ input }}\n \"#\n}\n\nfunction PromptTestClaudeChat(input: string) -> string {\n client Claude\n prompt #\"\n {{ _.role(\"system\") }}\n You are an assistant that always responds in a very excited way with emojis and also outputs this word 4 times after giving a response: {{ input }}\n \n {{ _.role(\"user\") }}\n Tell me a haiku about {{ input }}\n \"#\n}\n\nfunction PromptTestClaudeChatNoSystem(input: string) -> string {\n client Claude\n prompt #\"\n You are an assistant that always responds in a very excited way with emojis and also outputs this word 4 times after giving a response: {{ input }}\n \n {{ _.role(\"user\") }}\n Tell me a haiku about {{ input }}\n \"#\n}\n\ntest TestSystemAndNonSystemChat1 {\n functions [PromptTestClaude, PromptTestOpenAI, PromptTestOpenAIChat, PromptTestOpenAIChatNoSystem, PromptTestClaudeChat, PromptTestClaudeChatNoSystem]\n args {\n input \"cats\"\n }\n}\n\ntest TestSystemAndNonSystemChat2 {\n functions [PromptTestClaude, PromptTestOpenAI, PromptTestOpenAIChat, PromptTestOpenAIChatNoSystem, PromptTestClaudeChat, PromptTestClaudeChatNoSystem]\n args {\n input \"lion\"\n }\n}", diff --git a/integ-tests/typescript/baml_client/sync_client.ts b/integ-tests/typescript/baml_client/sync_client.ts index 6f84a7191..35306dac1 100644 --- a/integ-tests/typescript/baml_client/sync_client.ts +++ b/integ-tests/typescript/baml_client/sync_client.ts @@ -93,6 +93,31 @@ export class BamlSyncClient { } } + AliasWithMultipleAttrs( + money: Checked<number,"gt_ten">, + __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } + ): Checked<number,"gt_ten"> { + try { + const raw = this.runtime.callFunctionSync( + "AliasWithMultipleAttrs", + { + "money": money + }, + this.ctx_manager.cloneContext(), + __baml_options__?.tb?.__tb(), + __baml_options__?.clientRegistry, + ) + return raw.parsed() as Checked<number,"gt_ten"> + } catch (error: any) { + const bamlError = createBamlValidationError(error); + if (bamlError instanceof BamlValidationError) { + throw bamlError; + } else { + throw error; + } + } + } + AliasedInputClass( input: InputClass, __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } From a912fc910d04fb01ab719fb41102a3b1acf57ccc Mon Sep 17 00:00:00 2001 From: Antonio Sarosi <sarosiantonio@gmail.com> Date: Wed, 11 Dec 2024 19:47:47 +0100 Subject: [PATCH 44/51] Fix reachable unreachable... XD --- .../class/invalid_type_aliases.baml | 9 +++++ .../baml-lib/parser-database/src/types/mod.rs | 6 +++- .../tests/test_file_manager.rs | 35 +++++++++++++++++++ 3 files changed, 49 insertions(+), 1 deletion(-) diff --git a/engine/baml-lib/baml/tests/validation_files/class/invalid_type_aliases.baml b/engine/baml-lib/baml/tests/validation_files/class/invalid_type_aliases.baml index a8e4a7c3c..0a13664ba 100644 --- a/engine/baml-lib/baml/tests/validation_files/class/invalid_type_aliases.baml +++ b/engine/baml-lib/baml/tests/validation_files/class/invalid_type_aliases.baml @@ -8,6 +8,9 @@ type One = int // Unexpected keyword. typpe Two = float +// Unknown identifier. +type Three = i + // error: Error validating: Unexpected keyword used in assignment: typpe // --> class/invalid_type_aliases.baml:9 // | @@ -20,3 +23,9 @@ typpe Two = float // 5 | // Already existing name. // 6 | type One = int // | +// error: Error validating: Type alias points to unknown identifier `i` +// --> class/invalid_type_aliases.baml:12 +// | +// 11 | // Unknown identifier. +// 12 | type Three = i +// | diff --git a/engine/baml-lib/parser-database/src/types/mod.rs b/engine/baml-lib/parser-database/src/types/mod.rs index 966757b3a..3fe3f46b8 100644 --- a/engine/baml-lib/parser-database/src/types/mod.rs +++ b/engine/baml-lib/parser-database/src/types/mod.rs @@ -489,7 +489,11 @@ fn visit_type_alias<'db>( match item { FieldType::Symbol(_, ident, _) => { let Some(string_id) = ctx.interner.lookup(ident.name()) else { - unreachable!("Visiting alias `{ident}` that does not exist in the interner"); + ctx.push_error(DatamodelError::new_validation_error( + &format!("Type alias points to unknown identifier `{ident}`"), + item.span().clone(), + )); + return; }; let Some(top_id) = ctx.names.tops.get(&string_id) else { diff --git a/engine/baml-schema-wasm/tests/test_file_manager.rs b/engine/baml-schema-wasm/tests/test_file_manager.rs index 8de0e0743..ff0f8b1db 100644 --- a/engine/baml-schema-wasm/tests/test_file_manager.rs +++ b/engine/baml-schema-wasm/tests/test_file_manager.rs @@ -229,4 +229,39 @@ test Two { assert!(diagnostics.errors().is_empty()); } + + #[wasm_bindgen_test] + fn test_type_alias() { + wasm_logger::init(wasm_logger::Config::new(log::Level::Info)); + let sample_baml_content = r##" + type Foo = i + "##; + let mut files = HashMap::new(); + files.insert("error.baml".to_string(), sample_baml_content.to_string()); + let files_js = to_value(&files).unwrap(); + let project = WasmProject::new("baml_src", files_js) + .map_err(JsValue::from) + .unwrap(); + + let env_vars = [("OPENAI_API_KEY", "12345")] + .iter() + .cloned() + .collect::<HashMap<_, _>>(); + let env_vars_js = to_value(&env_vars).unwrap(); + + let Err(js_error) = project.runtime(env_vars_js) else { + panic!("Expected error, got Ok"); + }; + + assert!(js_error.is_object()); + + assert_eq!( + js_error, + serde_wasm_bindgen::to_value::<HashMap<String, Vec<String>>>(&HashMap::from_iter([( + "all_files".to_string(), + vec!["error.baml".to_string()] + )])) + .unwrap() + ); + } } From 3d95e20d0f5faeb629f69f276486cb2b4777ab51 Mon Sep 17 00:00:00 2001 From: Antonio Sarosi <sarosiantonio@gmail.com> Date: Thu, 12 Dec 2024 21:13:51 +0100 Subject: [PATCH 45/51] Fix `FieldType::Constrained` prompt rendering with aliases <!-- ELLIPSIS_HIDDEN --> > [!IMPORTANT] > Adds `RunFoo2` function across clients, new types, refines recursive type handling, and updates unreachable code with context. > > - **New Functionality**: > - Adds `RunFoo2` function to Python (`async_client.py`, `sync_client.py`), Ruby (`client.rb`), and TypeScript (`async_client.ts`, `sync_client.ts`) clients. > - Introduces `Foo2` and `Foo3` types in `types.py`, `types.rb`, and `types.ts`. > - **Code Improvements**: > - Replaces `unreachable!()` with `unreachable!("context")` in `expr.rs`, `coerce_array.rs`, `coerce_optional.rs`, and `coerce_union.rs`. > - Refactors recursive type handling in `types.rs`. > - **Testing**: > - Adds `test_constrained_type_alias` in `test_runtime.rs`. > - Adds `test_type_alias_with_assert` in `test_file_manager.rs`. > - Adds `test_alias_bug` in `test_functions.py`. > - **Miscellaneous**: > - Adds `antonio.baml` test file for integration tests. > > <sup>This description was created by </sup>[<img alt="Ellipsis" src="https://img.shields.io/badge/Ellipsis-blue?color=175173">](https://www.ellipsis.dev?ref=BoundaryML%2Fbaml&utm_source=github&utm_medium=referral)<sup> for 10f49990a4e4bd3840e0b8a78a5776f799c6c9a9. It will automatically update as commits are pushed.</sup> <!-- ELLIPSIS_HIDDEN --> --------- Co-authored-by: Greg Hale <imalsogreg@gmail.com> --- .../jinja-runtime/src/output_format/types.rs | 20 ++++--- .../baml-lib/jinja/src/evaluate_type/expr.rs | 2 +- .../src/deserializer/coercer/coerce_array.rs | 2 +- .../deserializer/coercer/coerce_optional.rs | 2 +- .../src/deserializer/coercer/coerce_union.rs | 2 +- engine/baml-runtime/tests/test_runtime.rs | 54 +++++++++++++++++++ .../tests/test_file_manager.rs | 17 +++--- 7 files changed, 81 insertions(+), 18 deletions(-) diff --git a/engine/baml-lib/jinja-runtime/src/output_format/types.rs b/engine/baml-lib/jinja-runtime/src/output_format/types.rs index d08b929c5..f3e357ba2 100644 --- a/engine/baml-lib/jinja-runtime/src/output_format/types.rs +++ b/engine/baml-lib/jinja-runtime/src/output_format/types.rs @@ -426,9 +426,12 @@ impl OutputFormatContent { } }, FieldType::Literal(v) => v.to_string(), - FieldType::Constrained { base, .. } => { - self.inner_type_render(options, base, render_state, group_hoisted_literals)? - } + FieldType::Constrained { base, .. } => self.render_possibly_recursive_type( + options, + base, + render_state, + group_hoisted_literals, + )?, FieldType::Enum(e) => { let Some(enm) = self.enums.get(e) else { return Err(minijinja::Error::new( @@ -536,9 +539,14 @@ impl OutputFormatContent { } FieldType::Map(key_type, value_type) => MapRender { style: &options.map_style, - // TODO: Key can't be recursive because we only support strings - // as keys. Change this if needed in the future. - key_type: self.inner_type_render(options, key_type, render_state, false)?, + // NOTE: Key can't be recursive because we only support strings + // as keys. + key_type: self.render_possibly_recursive_type( + options, + key_type, + render_state, + false, + )?, value_type: self.render_possibly_recursive_type( options, value_type, diff --git a/engine/baml-lib/jinja/src/evaluate_type/expr.rs b/engine/baml-lib/jinja/src/evaluate_type/expr.rs index 4172ad03b..ed295d716 100644 --- a/engine/baml-lib/jinja/src/evaluate_type/expr.rs +++ b/engine/baml-lib/jinja/src/evaluate_type/expr.rs @@ -406,7 +406,7 @@ fn infer_const_type(v: &minijinja::value::Value) -> Type { acc.push(x); Some(Type::Union(acc)) } else { - unreachable!() + unreachable!("minijinja") } } Some(acc) => { diff --git a/engine/baml-lib/jsonish/src/deserializer/coercer/coerce_array.rs b/engine/baml-lib/jsonish/src/deserializer/coercer/coerce_array.rs index d22496147..2fcb32b21 100644 --- a/engine/baml-lib/jsonish/src/deserializer/coercer/coerce_array.rs +++ b/engine/baml-lib/jsonish/src/deserializer/coercer/coerce_array.rs @@ -24,7 +24,7 @@ pub(super) fn coerce_array( let inner = match list_target { FieldType::List(inner) => inner, - _ => unreachable!(), + _ => unreachable!("coerce_array"), }; let mut items = vec![]; diff --git a/engine/baml-lib/jsonish/src/deserializer/coercer/coerce_optional.rs b/engine/baml-lib/jsonish/src/deserializer/coercer/coerce_optional.rs index 76778913b..4a1e16faa 100644 --- a/engine/baml-lib/jsonish/src/deserializer/coercer/coerce_optional.rs +++ b/engine/baml-lib/jsonish/src/deserializer/coercer/coerce_optional.rs @@ -23,7 +23,7 @@ pub(super) fn coerce_optional( let inner = match optional_target { FieldType::Optional(inner) => inner, - _ => unreachable!(), + _ => unreachable!("coerce_optional"), }; let mut flags = DeserializerConditions::new(); diff --git a/engine/baml-lib/jsonish/src/deserializer/coercer/coerce_union.rs b/engine/baml-lib/jsonish/src/deserializer/coercer/coerce_union.rs index 27cf3acb4..8a2312a2a 100644 --- a/engine/baml-lib/jsonish/src/deserializer/coercer/coerce_union.rs +++ b/engine/baml-lib/jsonish/src/deserializer/coercer/coerce_union.rs @@ -20,7 +20,7 @@ pub(super) fn coerce_union( let options = match union_target { FieldType::Union(options) => options, - _ => unreachable!(), + _ => unreachable!("coerce_union"), }; let parsed = options diff --git a/engine/baml-runtime/tests/test_runtime.rs b/engine/baml-runtime/tests/test_runtime.rs index e10a957a4..79efe7151 100644 --- a/engine/baml-runtime/tests/test_runtime.rs +++ b/engine/baml-runtime/tests/test_runtime.rs @@ -498,4 +498,58 @@ test TestTree { Ok(()) } + + #[test] + fn test_constrained_type_alias() -> anyhow::Result<()> { + let runtime = make_test_runtime( + r##" +class Foo2 { + bar int + baz string + sub Subthing @assert( {{ this.bar == 10}} ) | null +} + +class Foo3 { + bar int + baz string + sub Foo3 | null +} + +type Subthing = Foo2 @assert( {{ this.bar == 10 }}) + +function RunFoo2(input: Foo3) -> Foo2 { + client "openai/gpt-4o" + prompt #"Generate a Foo2 wrapping 30. Use {{ input }}. + {{ ctx.output_format }} + "# +} + +test RunFoo2Test { + functions [RunFoo2] + args { + input { + bar 30 + baz "hello" + sub null + } + } +} + "##, + )?; + + let ctx = runtime + .create_ctx_manager(BamlValue::String("test".to_string()), None) + .create_ctx_with_default(); + + let function_name = "RunFoo2"; + let test_name = "RunFoo2Test"; + let params = runtime.get_test_params(function_name, test_name, &ctx, true)?; + let render_prompt_future = + runtime + .internal() + .render_prompt(function_name, &ctx, ¶ms, None); + let (prompt, scope, _) = runtime.async_runtime.block_on(render_prompt_future)?; + + Ok(()) + } } diff --git a/engine/baml-schema-wasm/tests/test_file_manager.rs b/engine/baml-schema-wasm/tests/test_file_manager.rs index ff0f8b1db..bdcb841c2 100644 --- a/engine/baml-schema-wasm/tests/test_file_manager.rs +++ b/engine/baml-schema-wasm/tests/test_file_manager.rs @@ -255,13 +255,14 @@ test Two { assert!(js_error.is_object()); - assert_eq!( - js_error, - serde_wasm_bindgen::to_value::<HashMap<String, Vec<String>>>(&HashMap::from_iter([( - "all_files".to_string(), - vec!["error.baml".to_string()] - )])) - .unwrap() - ); + // TODO: Don't know how to build Object + // assert_eq!( + // js_error, + // serde_wasm_bindgen::to_value::<HashMap<String, Vec<String>>>(&HashMap::from_iter([( + // "all_files".to_string(), + // vec!["error.baml".to_string()] + // )])) + // .unwrap() + // ); } } From 4eb543a71a295f8cdf8541156ffebc1545e6e29f Mon Sep 17 00:00:00 2001 From: Antonio Sarosi <sarosiantonio@gmail.com> Date: Wed, 18 Dec 2024 20:23:15 +0100 Subject: [PATCH 46/51] Allow structural recursion in type aliases (#1207) Allow structural recursion (like TS): ```baml type RecursiveMap = map<string, RecursiveMap> type A = A[] ``` Follow up on #1163 <!-- ELLIPSIS_HIDDEN --> ---- > [!IMPORTANT] > Enable structural recursion in type aliases by updating cycle validation logic, adding tests, and supporting recursive type aliases in Python, Ruby, and TypeScript clients. > > - **Behavior**: > - Allow structural recursion for type alias cycles involving maps and lists in `cycle.rs`. > - Update `validate()` in `cycle.rs` to handle structural recursion and report cycles. > - Modify `ParserDatabase::finalize_dependencies()` in `lib.rs` to resolve type aliases and handle cycles. > - **Functions**: > - Add `insert_required_alias_deps()` in `cycle.rs` to handle alias dependencies. > - Rename `insert_required_deps()` to `insert_required_class_deps()` in `cycle.rs`. > - **Models**: > - Add `structural_recursive_alias_cycles` to `Types` in `mod.rs`. > - **Tests**: > - Update `recursive_type_aliases.baml` to reflect new cycle validation behavior. > - Add tests in `lib.rs` to verify structural recursion handling. > - **Language Support**: > - Update Python, Ruby, and TypeScript clients to handle recursive type aliases. > - **Misc**: > - Add `is_subtype()` to `IRHelper` in `mod.rs` to support subtyping checks. > - Add `coerce_alias()` in `coerce_alias.rs` to handle alias coercion. > - Add integration tests for recursive alias cycles in `integ-tests/typescript/tests/integ-tests.test.ts`. > > <sup>This description was created by </sup>[<img alt="Ellipsis" src="https://img.shields.io/badge/Ellipsis-blue?color=175173">](https://www.ellipsis.dev?ref=BoundaryML%2Fbaml&utm_source=github&utm_medium=referral)<sup> for fc25050f0383d00374dda6ff2192ea2a831c45c4. It will automatically update as commits are pushed.</sup> <!-- ELLIPSIS_HIDDEN --> --- .../baml-core/src/ir/ir_helpers/mod.rs | 274 ++++++++++++++++-- .../src/ir/ir_helpers/to_baml_arg.rs | 51 +++- .../baml-lib/baml-core/src/ir/json_schema.rs | 4 +- engine/baml-lib/baml-core/src/ir/repr.rs | 62 +++- .../validation_pipeline/validations/cycle.rs | 71 ++++- .../baml-lib/baml-types/src/field_type/mod.rs | 196 +------------ .../class/recursive_type_aliases.baml | 6 - .../jinja-runtime/src/output_format/types.rs | 153 +++++++++- .../src/deserializer/coercer/array_helper.rs | 31 +- .../src/deserializer/coercer/field_type.rs | 33 ++- .../coercer/ir_ref/coerce_alias.rs | 44 +++ .../src/deserializer/coercer/ir_ref/mod.rs | 8 + .../jsonish/src/deserializer/types.rs | 16 + engine/baml-lib/jsonish/src/tests/mod.rs | 33 ++- .../jsonish/src/tests/test_aliases.rs | 221 ++++++++++++++ .../jsonish/src/tests/test_constraints.rs | 14 +- .../baml-lib/jsonish/src/tests/test_maps.rs | 1 + engine/baml-lib/parser-database/src/lib.rs | 108 ++++++- engine/baml-lib/parser-database/src/tarjan.rs | 10 +- .../baml-lib/parser-database/src/types/mod.rs | 25 +- .../parser-database/src/walkers/mod.rs | 23 +- engine/baml-lib/schema-ast/src/ast.rs | 8 + .../prompt_renderer/render_output_format.rs | 32 +- engine/baml-runtime/tests/test_runtime.rs | 48 +++ .../baml-schema-wasm/src/runtime_wasm/mod.rs | 4 +- engine/language_client_codegen/src/openapi.rs | 24 +- .../src/python/generate_types.rs | 37 ++- .../language_client_codegen/src/python/mod.rs | 4 +- .../src/python/templates/types.py.j2 | 7 +- .../src/ruby/field_type.rs | 4 +- .../src/ruby/generate_types.rs | 3 +- .../src/typescript/generate_types.rs | 29 +- .../src/typescript/mod.rs | 2 +- .../src/typescript/templates/types.ts.j2 | 5 + .../functions/output/type-aliases.baml | 58 +++- .../python/baml_client/async_client.py | 212 ++++++++++++++ integ-tests/python/baml_client/inlinedbaml.py | 2 +- integ-tests/python/baml_client/sync_client.py | 212 ++++++++++++++ integ-tests/python/baml_client/types.py | 18 +- integ-tests/python/tests/test_functions.py | 41 +++ integ-tests/ruby/baml_client/client.rb | 268 +++++++++++++++++ integ-tests/ruby/baml_client/inlined.rb | 2 +- .../typescript/baml_client/async_client.ts | 232 +++++++++++++++ .../typescript/baml_client/inlinedbaml.ts | 2 +- .../typescript/baml_client/sync_client.ts | 100 +++++++ integ-tests/typescript/baml_client/types.ts | 16 + integ-tests/typescript/test-report.html | 6 +- .../typescript/tests/integ-tests.test.ts | 53 ++++ 48 files changed, 2484 insertions(+), 329 deletions(-) create mode 100644 engine/baml-lib/jsonish/src/deserializer/coercer/ir_ref/coerce_alias.rs create mode 100644 engine/baml-lib/jsonish/src/tests/test_aliases.rs diff --git a/engine/baml-lib/baml-core/src/ir/ir_helpers/mod.rs b/engine/baml-lib/baml-core/src/ir/ir_helpers/mod.rs index 40d932dce..6c5e71a9d 100644 --- a/engine/baml-lib/baml-core/src/ir/ir_helpers/mod.rs +++ b/engine/baml-lib/baml-core/src/ir/ir_helpers/mod.rs @@ -57,6 +57,7 @@ pub trait IRHelper { value: BamlValue, field_type: FieldType, ) -> Result<BamlValueWithMeta<FieldType>>; + fn is_subtype(&self, base: &FieldType, other: &FieldType) -> bool; fn distribute_constraints<'a>( &'a self, field_type: &'a FieldType, @@ -203,6 +204,124 @@ impl IRHelper for IntermediateRepr { } } + /// BAML does not support class-based subtyping. Nonetheless some builtin + /// BAML types are subtypes of others, and we need to be able to test this + /// when checking the types of values. + /// + /// For examples of pairs of types and their subtyping relationship, see + /// this module's test suite. + /// + /// Consider renaming this to `is_assignable`. + fn is_subtype(&self, base: &FieldType, other: &FieldType) -> bool { + if base == other { + return true; + } + + if let FieldType::Union(items) = other { + if items.iter().any(|item| self.is_subtype(base, item)) { + return true; + } + } + + match (base, other) { + // TODO: O(n) + (FieldType::RecursiveTypeAlias(name), _) => self + .structural_recursive_alias_cycles() + .iter() + .any(|cycle| match cycle.get(name) { + Some(target) => self.is_subtype(target, other), + None => false, + }), + (_, FieldType::RecursiveTypeAlias(name)) => self + .structural_recursive_alias_cycles() + .iter() + .any(|cycle| match cycle.get(name) { + Some(target) => self.is_subtype(base, target), + None => false, + }), + + (FieldType::Primitive(TypeValue::Null), FieldType::Optional(_)) => true, + (FieldType::Optional(base_item), FieldType::Optional(other_item)) => { + self.is_subtype(base_item, other_item) + } + (_, FieldType::Optional(t)) => self.is_subtype(base, t), + (FieldType::Optional(_), _) => false, + + // Handle types that nest other types. + (FieldType::List(base_item), FieldType::List(other_item)) => { + self.is_subtype(&base_item, other_item) + } + (FieldType::List(_), _) => false, + + (FieldType::Map(base_k, base_v), FieldType::Map(other_k, other_v)) => { + self.is_subtype(other_k, base_k) && self.is_subtype(&**base_v, other_v) + } + (FieldType::Map(_, _), _) => false, + + ( + FieldType::Constrained { + base: constrained_base, + constraints: base_constraints, + }, + FieldType::Constrained { + base: other_base, + constraints: other_constraints, + }, + ) => { + self.is_subtype(constrained_base, other_base) + && base_constraints == other_constraints + } + ( + FieldType::Constrained { + base: contrained_base, + .. + }, + _, + ) => self.is_subtype(contrained_base, other), + ( + _, + FieldType::Constrained { + base: constrained_base, + .. + }, + ) => self.is_subtype(base, constrained_base), + + (FieldType::Literal(LiteralValue::Bool(_)), FieldType::Primitive(TypeValue::Bool)) => { + true + } + (FieldType::Literal(LiteralValue::Bool(_)), _) => { + self.is_subtype(base, &FieldType::Primitive(TypeValue::Bool)) + } + (FieldType::Literal(LiteralValue::Int(_)), FieldType::Primitive(TypeValue::Int)) => { + true + } + (FieldType::Literal(LiteralValue::Int(_)), _) => { + self.is_subtype(base, &FieldType::Primitive(TypeValue::Int)) + } + ( + FieldType::Literal(LiteralValue::String(_)), + FieldType::Primitive(TypeValue::String), + ) => true, + (FieldType::Literal(LiteralValue::String(_)), _) => { + self.is_subtype(base, &FieldType::Primitive(TypeValue::String)) + } + + (FieldType::Union(items), _) => items.iter().all(|item| self.is_subtype(item, other)), + + (FieldType::Tuple(base_items), FieldType::Tuple(other_items)) => { + base_items.len() == other_items.len() + && base_items + .iter() + .zip(other_items) + .all(|(base_item, other_item)| self.is_subtype(base_item, other_item)) + } + (FieldType::Tuple(_), _) => false, + (FieldType::Primitive(_), _) => false, + (FieldType::Enum(_), _) => false, + (FieldType::Class(_), _) => false, + } + } + /// For some `BamlValue` with type `FieldType`, walk the structure of both the value /// and the type simultaneously, associating each node in the `BamlValue` with its /// `FieldType`. @@ -216,40 +335,38 @@ impl IRHelper for IntermediateRepr { let literal_type = FieldType::Literal(LiteralValue::String(s.clone())); let primitive_type = FieldType::Primitive(TypeValue::String); - if literal_type.is_subtype_of(&field_type) - || primitive_type.is_subtype_of(&field_type) + if self.is_subtype(&literal_type, &field_type) + || self.is_subtype(&primitive_type, &field_type) { return Ok(BamlValueWithMeta::String(s, field_type)); } anyhow::bail!("Could not unify String with {:?}", field_type) } - BamlValue::Int(i) - if FieldType::Literal(LiteralValue::Int(i)).is_subtype_of(&field_type) => - { - Ok(BamlValueWithMeta::Int(i, field_type)) - } - BamlValue::Int(i) - if FieldType::Primitive(TypeValue::Int).is_subtype_of(&field_type) => - { - Ok(BamlValueWithMeta::Int(i, field_type)) - } BamlValue::Int(i) => { + let literal_type = FieldType::Literal(LiteralValue::Int(i)); + let primitive_type = FieldType::Primitive(TypeValue::Int); + + if self.is_subtype(&literal_type, &field_type) + || self.is_subtype(&primitive_type, &field_type) + { + return Ok(BamlValueWithMeta::Int(i, field_type)); + } anyhow::bail!("Could not unify Int with {:?}", field_type) } - BamlValue::Float(f) - if FieldType::Primitive(TypeValue::Float).is_subtype_of(&field_type) => - { - Ok(BamlValueWithMeta::Float(f, field_type)) + BamlValue::Float(f) => { + if self.is_subtype(&FieldType::Primitive(TypeValue::Float), &field_type) { + return Ok(BamlValueWithMeta::Float(f, field_type)); + } + anyhow::bail!("Could not unify Float with {:?}", field_type) } - BamlValue::Float(_) => anyhow::bail!("Could not unify Float with {:?}", field_type), BamlValue::Bool(b) => { let literal_type = FieldType::Literal(LiteralValue::Bool(b)); let primitive_type = FieldType::Primitive(TypeValue::Bool); - if literal_type.is_subtype_of(&field_type) - || primitive_type.is_subtype_of(&field_type) + if self.is_subtype(&literal_type, &field_type) + || self.is_subtype(&primitive_type, &field_type) { Ok(BamlValueWithMeta::Bool(b, field_type)) } else { @@ -257,7 +374,9 @@ impl IRHelper for IntermediateRepr { } } - BamlValue::Null if FieldType::Primitive(TypeValue::Null).is_subtype_of(&field_type) => { + BamlValue::Null + if self.is_subtype(&FieldType::Primitive(TypeValue::Null), &field_type) => + { Ok(BamlValueWithMeta::Null(field_type)) } BamlValue::Null => anyhow::bail!("Could not unify Null with {:?}", field_type), @@ -287,7 +406,7 @@ impl IRHelper for IntermediateRepr { Box::new(item_type.clone()), ); - if !map_type.is_subtype_of(&field_type) { + if !self.is_subtype(&map_type, &field_type) { anyhow::bail!("Could not unify {:?} with {:?}", map_type, field_type); } @@ -321,7 +440,7 @@ impl IRHelper for IntermediateRepr { Some(item_type) => { let list_type = FieldType::List(Box::new(item_type.clone())); - if !list_type.is_subtype_of(&field_type) { + if !self.is_subtype(&list_type, &field_type) { anyhow::bail!("Could not unify {:?} with {:?}", list_type, field_type); } else { let mapped_items: Vec<BamlValueWithMeta<FieldType>> = items @@ -335,15 +454,17 @@ impl IRHelper for IntermediateRepr { } BamlValue::Media(m) - if FieldType::Primitive(TypeValue::Media(m.media_type)) - .is_subtype_of(&field_type) => + if self.is_subtype( + &FieldType::Primitive(TypeValue::Media(m.media_type)), + &field_type, + ) => { Ok(BamlValueWithMeta::Media(m, field_type)) } BamlValue::Media(_) => anyhow::bail!("Could not unify Media with {:?}", field_type), BamlValue::Enum(name, val) => { - if FieldType::Enum(name.clone()).is_subtype_of(&field_type) { + if self.is_subtype(&FieldType::Enum(name.clone()), &field_type) { Ok(BamlValueWithMeta::Enum(name, val, field_type)) } else { anyhow::bail!("Could not unify Enum {} with {:?}", name, field_type) @@ -351,7 +472,7 @@ impl IRHelper for IntermediateRepr { } BamlValue::Class(name, fields) => { - if !FieldType::Class(name.clone()).is_subtype_of(&field_type) { + if !self.is_subtype(&FieldType::Class(name.clone()), &field_type) { anyhow::bail!("Could not unify Class {} with {:?}", name, field_type); } else { let class_type = &self.find_class(&name)?.item.elem; @@ -794,3 +915,104 @@ mod tests { assert_eq!(constraints, expected_constraints); } } + +// TODO: Copy pasted from baml-lib/baml-types/src/field_type/mod.rs and poorly +// refactored to match the `is_subtype` changes. Do something with this. +#[cfg(test)] +mod subtype_tests { + use baml_types::BamlMediaType; + use repr::make_test_ir; + + use super::*; + + fn mk_int() -> FieldType { + FieldType::Primitive(TypeValue::Int) + } + fn mk_bool() -> FieldType { + FieldType::Primitive(TypeValue::Bool) + } + fn mk_str() -> FieldType { + FieldType::Primitive(TypeValue::String) + } + + fn mk_optional(ft: FieldType) -> FieldType { + FieldType::Optional(Box::new(ft)) + } + + fn mk_list(ft: FieldType) -> FieldType { + FieldType::List(Box::new(ft)) + } + + fn mk_tuple(ft: Vec<FieldType>) -> FieldType { + FieldType::Tuple(ft) + } + fn mk_union(ft: Vec<FieldType>) -> FieldType { + FieldType::Union(ft) + } + fn mk_str_map(ft: FieldType) -> FieldType { + FieldType::Map(Box::new(mk_str()), Box::new(ft)) + } + + fn ir() -> IntermediateRepr { + make_test_ir("").unwrap() + } + + #[test] + fn subtype_trivial() { + assert!(ir().is_subtype(&mk_int(), &mk_int())) + } + + #[test] + fn subtype_union() { + let i = mk_int(); + let u = mk_union(vec![mk_int(), mk_str()]); + assert!(ir().is_subtype(&i, &u)); + assert!(!ir().is_subtype(&u, &i)); + + let u3 = mk_union(vec![mk_int(), mk_bool(), mk_str()]); + assert!(ir().is_subtype(&i, &u3)); + assert!(ir().is_subtype(&u, &u3)); + assert!(!ir().is_subtype(&u3, &u)); + } + + #[test] + fn subtype_optional() { + let i = mk_int(); + let o = mk_optional(mk_int()); + assert!(ir().is_subtype(&i, &o)); + assert!(!ir().is_subtype(&o, &i)); + } + + #[test] + fn subtype_list() { + let l_i = mk_list(mk_int()); + let l_o = mk_list(mk_optional(mk_int())); + assert!(ir().is_subtype(&l_i, &l_o)); + assert!(!ir().is_subtype(&l_o, &l_i)); + } + + #[test] + fn subtype_tuple() { + let x = mk_tuple(vec![mk_int(), mk_optional(mk_int())]); + let y = mk_tuple(vec![mk_int(), mk_int()]); + assert!(ir().is_subtype(&y, &x)); + assert!(!ir().is_subtype(&x, &y)); + } + + #[test] + fn subtype_map_of_list_of_unions() { + let x = mk_str_map(mk_list(FieldType::Class("Foo".to_string()))); + let y = mk_str_map(mk_list(mk_union(vec![ + mk_str(), + mk_int(), + FieldType::Class("Foo".to_string()), + ]))); + assert!(ir().is_subtype(&x, &y)); + } + + #[test] + fn subtype_media() { + let x = FieldType::Primitive(TypeValue::Media(BamlMediaType::Audio)); + assert!(ir().is_subtype(&x, &x)); + } +} diff --git a/engine/baml-lib/baml-core/src/ir/ir_helpers/to_baml_arg.rs b/engine/baml-lib/baml-core/src/ir/ir_helpers/to_baml_arg.rs index e2ae93083..a13c5cc49 100644 --- a/engine/baml-lib/baml-core/src/ir/ir_helpers/to_baml_arg.rs +++ b/engine/baml-lib/baml-core/src/ir/ir_helpers/to_baml_arg.rs @@ -263,8 +263,23 @@ impl ArgCoercer { Err(()) } }, - (FieldType::Alias { resolution, .. }, _) => { - self.coerce_arg(ir, &resolution, value, scope) + (FieldType::RecursiveTypeAlias(name), _) => { + let mut maybe_coerced = None; + // TODO: Fix this O(n) + for cycle in ir.structural_recursive_alias_cycles() { + if let Some(target) = cycle.get(name) { + maybe_coerced = Some(self.coerce_arg(ir, target, value, scope)?); + break; + } + } + + match maybe_coerced { + Some(coerced) => Ok(coerced), + None => { + scope.push_error(format!("Recursive type alias {} not found", name)); + Err(()) + } + } } (FieldType::List(item), _) => match value { BamlValue::List(arr) => { @@ -442,4 +457,36 @@ mod tests { let res = arg_coercer.coerce_arg(&ir, &type_, &value, &mut ScopeStack::new()); assert!(res.is_err()); } + + #[test] + fn test_mutually_recursive_aliases() { + let ir = make_test_ir( + r##" +type JsonValue = int | bool | float | string | JsonArray | JsonObject +type JsonObject = map<string, JsonValue> +type JsonArray = JsonValue[] + "##, + ) + .unwrap(); + + let arg_coercer = ArgCoercer { + span_path: None, + allow_implicit_cast_to_string: true, + }; + + let json = BamlValue::Map(BamlMap::from([ + ("number".to_string(), BamlValue::Int(1)), + ("string".to_string(), BamlValue::String("test".to_string())), + ("bool".to_string(), BamlValue::Bool(true)), + ])); + + let res = arg_coercer.coerce_arg( + &ir, + &FieldType::RecursiveTypeAlias("JsonValue".to_string()), + &json, + &mut ScopeStack::new(), + ); + + assert_eq!(res, Ok(json)); + } } diff --git a/engine/baml-lib/baml-core/src/ir/json_schema.rs b/engine/baml-lib/baml-core/src/ir/json_schema.rs index e0c7fa886..bccf278c6 100644 --- a/engine/baml-lib/baml-core/src/ir/json_schema.rs +++ b/engine/baml-lib/baml-core/src/ir/json_schema.rs @@ -156,10 +156,12 @@ impl WithJsonSchema for FieldType { FieldType::Class(name) | FieldType::Enum(name) => json!({ "$ref": format!("#/definitions/{}", name), }), - FieldType::Alias { resolution, .. } => resolution.json_schema(), FieldType::Literal(v) => json!({ "const": v.to_string(), }), + FieldType::RecursiveTypeAlias(_) => json!({ + "type": ["number", "string", "boolean", "object", "array", "null"] + }), FieldType::Primitive(t) => match t { TypeValue::String => json!({ "type": "string", diff --git a/engine/baml-lib/baml-core/src/ir/repr.rs b/engine/baml-lib/baml-core/src/ir/repr.rs index 571b8e4b0..292e30922 100644 --- a/engine/baml-lib/baml-core/src/ir/repr.rs +++ b/engine/baml-lib/baml-core/src/ir/repr.rs @@ -35,6 +35,9 @@ pub struct IntermediateRepr { /// Strongly connected components of the dependency graph (finite cycles). finite_recursive_cycles: Vec<IndexSet<String>>, + /// Type alias cycles introduced by lists and maps. + structural_recursive_alias_cycles: Vec<IndexMap<String, FieldType>>, + configuration: Configuration, } @@ -53,6 +56,7 @@ impl IntermediateRepr { enums: vec![], classes: vec![], finite_recursive_cycles: vec![], + structural_recursive_alias_cycles: vec![], functions: vec![], clients: vec![], retry_policies: vec![], @@ -98,6 +102,10 @@ impl IntermediateRepr { &self.finite_recursive_cycles } + pub fn structural_recursive_alias_cycles(&self) -> &[IndexMap<String, FieldType>] { + &self.structural_recursive_alias_cycles + } + pub fn walk_enums(&self) -> impl ExactSizeIterator<Item = Walker<'_, &Node<Enum>>> { self.enums.iter().map(|e| Walker { db: self, item: e }) } @@ -106,6 +114,14 @@ impl IntermediateRepr { self.classes.iter().map(|e| Walker { db: self, item: e }) } + // TODO: Exact size Iterator + Node<>? + pub fn walk_alias_cycles(&self) -> impl Iterator<Item = Walker<'_, (&String, &FieldType)>> { + self.structural_recursive_alias_cycles + .iter() + .flatten() + .map(|e| Walker { db: self, item: e }) + } + pub fn function_names(&self) -> impl ExactSizeIterator<Item = &str> { self.functions.iter().map(|f| f.elem.name()) } @@ -168,6 +184,18 @@ impl IntermediateRepr { .collect() }) .collect(), + structural_recursive_alias_cycles: { + let mut recursive_aliases = vec![]; + for cycle in db.structural_recursive_alias_cycles() { + let mut component = IndexMap::new(); + for id in cycle { + let alias = &db.ast()[*id]; + component.insert(alias.name().to_string(), alias.value.repr(db)?); + } + recursive_aliases.push(component); + } + recursive_aliases + }, functions: db .walk_functions() .map(|e| e.node(db)) @@ -419,11 +447,18 @@ impl WithRepr<FieldType> for ast::FieldType { _ => base_type, } } - Some(TypeWalker::TypeAlias(alias_walker)) => FieldType::Alias { - name: alias_walker.name().to_owned(), - target: Box::new(alias_walker.target().repr(db)?), - resolution: Box::new(alias_walker.resolved().repr(db)?), - }, + Some(TypeWalker::TypeAlias(alias_walker)) => { + if db + .structural_recursive_alias_cycles() + .iter() + .any(|cycle| cycle.contains(&alias_walker.id)) + { + FieldType::RecursiveTypeAlias(alias_walker.name().to_string()) + } else { + alias_walker.resolved().to_owned().repr(db)? + } + } + None => return Err(anyhow!("Field type uses unresolvable local identifier")), }, arity, @@ -1224,11 +1259,7 @@ mod tests { let class = ir.find_class("Test").unwrap(); let alias = class.find_field("field").unwrap(); - let FieldType::Alias { resolution, .. } = alias.r#type() else { - panic!("expected alias type, found {:?}", alias.r#type()); - }; - - assert_eq!(**resolution, FieldType::Primitive(TypeValue::Int)); + assert_eq!(*alias.r#type(), FieldType::Primitive(TypeValue::Int)); } #[test] @@ -1249,12 +1280,11 @@ mod tests { let class = ir.find_class("Test").unwrap(); let alias = class.find_field("field").unwrap(); - let FieldType::Alias { resolution, .. } = alias.r#type() else { - panic!("expected alias type, found {:?}", alias.r#type()); - }; - - let FieldType::Constrained { base, constraints } = &**resolution else { - panic!("expected resolved constrained type, found {:?}", resolution); + let FieldType::Constrained { base, constraints } = alias.r#type() else { + panic!( + "expected resolved constrained type, found {:?}", + alias.r#type() + ); }; assert_eq!(constraints.len(), 3); diff --git a/engine/baml-lib/baml-core/src/validate/validation_pipeline/validations/cycle.rs b/engine/baml-lib/baml-core/src/validate/validation_pipeline/validations/cycle.rs index 3cc22d6a3..f7cf76ebb 100644 --- a/engine/baml-lib/baml-core/src/validate/validation_pipeline/validations/cycle.rs +++ b/engine/baml-lib/baml-core/src/validate/validation_pipeline/validations/cycle.rs @@ -14,24 +14,40 @@ use crate::validate::validation_pipeline::context::Context; /// Validates if the dependency graph contains one or more infinite cycles. pub(super) fn validate(ctx: &mut Context<'_>) { - // Solve cycles first. We need that information in case a class points to - // an unresolveble type alias. - let type_aliases_components = report_infinite_cycles( - &ctx.db.type_alias_dependencies(), + // We'll check type alias cycles first. Just like Typescript, cycles are + // allowed only for maps and lists. We'll call such cycles "structural + // recursion". Anything else like nulls or unions won't terminate a cycle. + let structural_type_aliases = HashMap::from_iter(ctx.db.walk_type_aliases().map(|alias| { + let mut dependencies = HashSet::new(); + insert_required_alias_deps(alias.target(), ctx, &mut dependencies); + + (alias.id, dependencies) + })); + + // Based on the graph we've built with does not include the edges created + // by maps and lists, check the cycles and report them. + report_infinite_cycles( + &structural_type_aliases, ctx, "These aliases form a dependency cycle", ); - // Store this locally to pass refs to the insert_required_deps function. - let alias_cycles = type_aliases_components.iter().flatten().copied().collect(); + // In order to avoid infinite recursion when resolving types for class + // dependencies below, we'll compute the cycles of aliases including maps + // and lists so that the recursion can be stopped before entering a cycle. + let complete_alias_cycles = Tarjan::components(ctx.db.type_alias_dependencies()) + .iter() + .flatten() + .copied() + .collect(); - // First, build a graph of all the "required" dependencies represented as an + // Now build a graph of all the "required" dependencies represented as an // adjacency list. We're only going to consider type dependencies that can // actually cause infinite recursion. Unions and optionals can stop the // recursion at any point, so they don't have to be part of the "dependency" // graph because technically an optional field doesn't "depend" on anything, // it can just be null. - let dependency_graph = HashMap::from_iter(ctx.db.walk_classes().map(|class| { + let class_dependency_graph = HashMap::from_iter(ctx.db.walk_classes().map(|class| { let expr_block = &ctx.db.ast()[class.id]; // TODO: There's already a hash set that returns "dependencies" in @@ -47,7 +63,13 @@ pub(super) fn validate(ctx: &mut Context<'_>) { for field in &expr_block.fields { if let Some(field_type) = &field.expr { - insert_required_deps(class.id, field_type, ctx, &mut dependencies, &alias_cycles); + insert_required_class_deps( + class.id, + field_type, + ctx, + &mut dependencies, + &complete_alias_cycles, + ); } } @@ -55,7 +77,7 @@ pub(super) fn validate(ctx: &mut Context<'_>) { })); report_infinite_cycles( - &dependency_graph, + &class_dependency_graph, ctx, "These classes form a dependency cycle", ); @@ -103,7 +125,7 @@ where /// it reaches stack overflows with large inputs. /// /// TODO: Use a struct to keep all this state. Too many parameters already. -fn insert_required_deps( +fn insert_required_class_deps( id: TypeExpId, field: &FieldType, ctx: &Context<'_>, @@ -139,7 +161,7 @@ fn insert_required_deps( // We also have to stop recursion if we know the alias is // part of a cycle. if !alias_cycles.contains(&alias.id) { - insert_required_deps(id, alias.target(), ctx, deps, alias_cycles) + insert_required_class_deps(id, alias.target(), ctx, deps, alias_cycles) } } _ => {} @@ -156,7 +178,7 @@ fn insert_required_deps( let mut nested_deps = HashSet::new(); for f in field_types { - insert_required_deps(id, f, ctx, &mut nested_deps, alias_cycles); + insert_required_class_deps(id, f, ctx, &mut nested_deps, alias_cycles); // No nested deps found on this component, this makes the // union finite, so no need to go deeper. @@ -186,3 +208,26 @@ fn insert_required_deps( _ => {} } } + +/// Implemented a la TS, maps and lists are not included as edges. +fn insert_required_alias_deps( + field_type: &FieldType, + ctx: &Context<'_>, + required: &mut HashSet<TypeAliasId>, +) { + match field_type { + FieldType::Symbol(_, ident, _) => { + if let Some(TypeWalker::TypeAlias(alias)) = ctx.db.find_type_by_str(ident.name()) { + required.insert(alias.id); + } + } + + FieldType::Union(_, field_types, ..) | FieldType::Tuple(_, field_types, ..) => { + for f in field_types { + insert_required_alias_deps(f, ctx, required); + } + } + + _ => {} + } +} diff --git a/engine/baml-lib/baml-types/src/field_type/mod.rs b/engine/baml-lib/baml-types/src/field_type/mod.rs index 909f20426..52f59fae0 100644 --- a/engine/baml-lib/baml-types/src/field_type/mod.rs +++ b/engine/baml-lib/baml-types/src/field_type/mod.rs @@ -85,14 +85,7 @@ pub enum FieldType { Union(Vec<FieldType>), Tuple(Vec<FieldType>), Optional(Box<FieldType>), - Alias { - /// Name of the alias. - name: String, - /// Type that the alias points to. - target: Box<FieldType>, - /// Final resolved type (an alias can point to other aliases). - resolution: Box<FieldType>, - }, + RecursiveTypeAlias(String), Constrained { base: Box<FieldType>, constraints: Vec<Constraint>, @@ -103,8 +96,9 @@ pub enum FieldType { impl std::fmt::Display for FieldType { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - FieldType::Enum(name) | FieldType::Class(name) => write!(f, "{name}"), - FieldType::Alias { name, .. } => write!(f, "{name}"), + FieldType::Enum(name) + | FieldType::Class(name) + | FieldType::RecursiveTypeAlias(name) => write!(f, "{name}"), FieldType::Primitive(t) => write!(f, "{t}"), FieldType::Literal(v) => write!(f, "{v}"), FieldType::Union(choices) => { @@ -166,186 +160,4 @@ impl FieldType { _ => false, } } - - /// BAML does not support class-based subtyping. Nonetheless some builtin - /// BAML types are subtypes of others, and we need to be able to test this - /// when checking the types of values. - /// - /// For examples of pairs of types and their subtyping relationship, see - /// this module's test suite. - /// - /// Consider renaming this to `is_assignable_to`. - pub fn is_subtype_of(&self, other: &FieldType) -> bool { - if self == other { - return true; - } - - if let FieldType::Union(items) = other { - if items.iter().any(|item| self.is_subtype_of(item)) { - return true; - } - } - - match (self, other) { - (FieldType::Alias { resolution, .. }, _) => resolution.is_subtype_of(other), - (_, FieldType::Alias { resolution, .. }) => self.is_subtype_of(resolution), - (FieldType::Primitive(TypeValue::Null), FieldType::Optional(_)) => true, - (FieldType::Optional(self_item), FieldType::Optional(other_item)) => { - self_item.is_subtype_of(other_item) - } - (_, FieldType::Optional(t)) => self.is_subtype_of(t), - (FieldType::Optional(_), _) => false, - - // Handle types that nest other types. - (FieldType::List(self_item), FieldType::List(other_item)) => { - self_item.is_subtype_of(other_item) - } - (FieldType::List(_), _) => false, - - (FieldType::Map(self_k, self_v), FieldType::Map(other_k, other_v)) => { - other_k.is_subtype_of(self_k) && (**self_v).is_subtype_of(other_v) - } - (FieldType::Map(_, _), _) => false, - - ( - FieldType::Constrained { - base: self_base, - constraints: self_cs, - }, - FieldType::Constrained { - base: other_base, - constraints: other_cs, - }, - ) => self_base.is_subtype_of(other_base) && self_cs == other_cs, - (FieldType::Constrained { base, .. }, _) => base.is_subtype_of(other), - (_, FieldType::Constrained { base, .. }) => self.is_subtype_of(base), - (FieldType::Literal(LiteralValue::Bool(_)), FieldType::Primitive(TypeValue::Bool)) => { - true - } - (FieldType::Literal(LiteralValue::Bool(_)), _) => { - self.is_subtype_of(&FieldType::Primitive(TypeValue::Bool)) - } - (FieldType::Literal(LiteralValue::Int(_)), FieldType::Primitive(TypeValue::Int)) => { - true - } - (FieldType::Literal(LiteralValue::Int(_)), _) => { - self.is_subtype_of(&FieldType::Primitive(TypeValue::Int)) - } - ( - FieldType::Literal(LiteralValue::String(_)), - FieldType::Primitive(TypeValue::String), - ) => true, - (FieldType::Literal(LiteralValue::String(_)), _) => { - self.is_subtype_of(&FieldType::Primitive(TypeValue::String)) - } - - (FieldType::Union(self_items), _) => self_items - .iter() - .all(|self_item| self_item.is_subtype_of(other)), - - (FieldType::Tuple(self_items), FieldType::Tuple(other_items)) => { - self_items.len() == other_items.len() - && self_items - .iter() - .zip(other_items) - .all(|(self_item, other_item)| self_item.is_subtype_of(other_item)) - } - (FieldType::Tuple(_), _) => false, - (FieldType::Primitive(_), _) => false, - (FieldType::Enum(_), _) => false, - (FieldType::Class(_), _) => false, - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - fn mk_int() -> FieldType { - FieldType::Primitive(TypeValue::Int) - } - fn mk_bool() -> FieldType { - FieldType::Primitive(TypeValue::Bool) - } - fn mk_str() -> FieldType { - FieldType::Primitive(TypeValue::String) - } - - fn mk_optional(ft: FieldType) -> FieldType { - FieldType::Optional(Box::new(ft)) - } - - fn mk_list(ft: FieldType) -> FieldType { - FieldType::List(Box::new(ft)) - } - - fn mk_tuple(ft: Vec<FieldType>) -> FieldType { - FieldType::Tuple(ft) - } - fn mk_union(ft: Vec<FieldType>) -> FieldType { - FieldType::Union(ft) - } - fn mk_str_map(ft: FieldType) -> FieldType { - FieldType::Map(Box::new(mk_str()), Box::new(ft)) - } - - #[test] - fn subtype_trivial() { - assert!(mk_int().is_subtype_of(&mk_int())) - } - - #[test] - fn subtype_union() { - let i = mk_int(); - let u = mk_union(vec![mk_int(), mk_str()]); - assert!(i.is_subtype_of(&u)); - assert!(!u.is_subtype_of(&i)); - - let u3 = mk_union(vec![mk_int(), mk_bool(), mk_str()]); - assert!(i.is_subtype_of(&u3)); - assert!(u.is_subtype_of(&u3)); - assert!(!u3.is_subtype_of(&u)); - } - - #[test] - fn subtype_optional() { - let i = mk_int(); - let o = mk_optional(mk_int()); - assert!(i.is_subtype_of(&o)); - assert!(!o.is_subtype_of(&i)); - } - - #[test] - fn subtype_list() { - let l_i = mk_list(mk_int()); - let l_o = mk_list(mk_optional(mk_int())); - assert!(l_i.is_subtype_of(&l_o)); - assert!(!l_o.is_subtype_of(&l_i)); - } - - #[test] - fn subtype_tuple() { - let x = mk_tuple(vec![mk_int(), mk_optional(mk_int())]); - let y = mk_tuple(vec![mk_int(), mk_int()]); - assert!(y.is_subtype_of(&x)); - assert!(!x.is_subtype_of(&y)); - } - - #[test] - fn subtype_map_of_list_of_unions() { - let x = mk_str_map(mk_list(FieldType::Class("Foo".to_string()))); - let y = mk_str_map(mk_list(mk_union(vec![ - mk_str(), - mk_int(), - FieldType::Class("Foo".to_string()), - ]))); - assert!(x.is_subtype_of(&y)); - } - - #[test] - fn subtype_media() { - let x = FieldType::Primitive(TypeValue::Media(BamlMediaType::Audio)); - assert!(x.is_subtype_of(&x)); - } } diff --git a/engine/baml-lib/baml/tests/validation_files/class/recursive_type_aliases.baml b/engine/baml-lib/baml/tests/validation_files/class/recursive_type_aliases.baml index a200c3293..f7ff88ef0 100644 --- a/engine/baml-lib/baml/tests/validation_files/class/recursive_type_aliases.baml +++ b/engine/baml-lib/baml/tests/validation_files/class/recursive_type_aliases.baml @@ -73,12 +73,6 @@ type Map = map<string, Map> // 50 | // 51 | type EnterCycle = NoStop // | -// error: Error validating: These aliases form a dependency cycle: Map -// --> class/recursive_type_aliases.baml:56 -// | -// 55 | // RecursiveMap -// 56 | type Map = map<string, Map> -// | // error: Error validating: These classes form a dependency cycle: Recursive // --> class/recursive_type_aliases.baml:22 // | diff --git a/engine/baml-lib/jinja-runtime/src/output_format/types.rs b/engine/baml-lib/jinja-runtime/src/output_format/types.rs index f3e357ba2..188f37035 100644 --- a/engine/baml-lib/jinja-runtime/src/output_format/types.rs +++ b/engine/baml-lib/jinja-runtime/src/output_format/types.rs @@ -58,6 +58,7 @@ pub struct OutputFormatContent { pub enums: Arc<IndexMap<String, Enum>>, pub classes: Arc<IndexMap<String, Class>>, recursive_classes: Arc<IndexSet<String>>, + structural_recursive_aliases: Arc<IndexMap<String, FieldType>>, pub target: FieldType, } @@ -67,6 +68,8 @@ pub struct Builder { classes: Vec<Class>, /// Order matters for this one. recursive_classes: IndexSet<String>, + /// Recursive aliases introduced maps and lists. + structural_recursive_aliases: IndexMap<String, FieldType>, target: FieldType, } @@ -76,6 +79,7 @@ impl Builder { enums: vec![], classes: vec![], recursive_classes: IndexSet::new(), + structural_recursive_aliases: IndexMap::new(), target, } } @@ -95,6 +99,14 @@ impl Builder { self } + pub fn structural_recursive_aliases( + mut self, + structural_recursive_aliases: IndexMap<String, FieldType>, + ) -> Self { + self.structural_recursive_aliases = structural_recursive_aliases; + self + } + pub fn target(mut self, target: FieldType) -> Self { self.target = target; self @@ -115,6 +127,9 @@ impl Builder { .collect(), ), recursive_classes: Arc::new(self.recursive_classes.into_iter().collect()), + structural_recursive_aliases: Arc::new( + self.structural_recursive_aliases.into_iter().collect(), + ), target: self.target, } } @@ -333,8 +348,13 @@ impl OutputFormatContent { Some(format!("Answer in JSON using this {type_prefix}:{end}")) } - FieldType::Alias { resolution, .. } => { - auto_prefix(&resolution, options, output_format_content) + FieldType::RecursiveTypeAlias(_) => { + let type_prefix = match &options.hoisted_class_prefix { + RenderSetting::Always(prefix) if !prefix.is_empty() => prefix, + _ => RenderOptions::DEFAULT_TYPE_PREFIX_IN_RENDER_MESSAGE, + }; + + Some(format!("Answer in JSON using this {type_prefix}: ")) } FieldType::List(_) => Some(String::from( "Answer with a JSON Array using this schema:\n", @@ -487,15 +507,13 @@ impl OutputFormatContent { } .to_string() } - FieldType::Alias { resolution, .. } => self.render_possibly_recursive_type( - options, - &resolution, - render_state, - group_hoisted_literals, - )?, + FieldType::RecursiveTypeAlias(name) => name.to_owned(), FieldType::List(inner) => { let is_recursive = match inner.as_ref() { FieldType::Class(nested_class) => self.recursive_classes.contains(nested_class), + FieldType::RecursiveTypeAlias(name) => { + self.structural_recursive_aliases.contains_key(name) + } _ => false, }; @@ -597,6 +615,7 @@ impl OutputFormatContent { })); let mut class_definitions = Vec::new(); + let mut type_alias_definitions = Vec::new(); // Hoist recursive classes. The render_state struct doesn't need to // contain these classes because we already know that we're gonna hoist @@ -618,6 +637,18 @@ impl OutputFormatContent { }); } + for (alias, target) in self.structural_recursive_aliases.iter() { + let recursive_pointer = + self.inner_type_render(&options, target, &mut render_state, false)?; + + type_alias_definitions.push(match &options.hoisted_class_prefix { + RenderSetting::Always(prefix) if !prefix.is_empty() => { + format!("{prefix} {alias} = {recursive_pointer}") + } + _ => format!("{alias} = {recursive_pointer}"), + }); + } + let mut output = String::new(); if !enum_definitions.is_empty() { @@ -630,6 +661,11 @@ impl OutputFormatContent { output.push_str("\n\n"); } + if !type_alias_definitions.is_empty() { + output.push_str(&type_alias_definitions.join("\n")); + output.push_str("\n\n"); + } + if let Some(p) = prefix { output.push_str(&p); } @@ -666,13 +702,19 @@ impl OutputFormatContent { pub fn find_enum(&self, name: &str) -> Result<&Enum> { self.enums .get(name) - .ok_or_else(|| anyhow::anyhow!("Enum {} not found", name)) + .ok_or_else(|| anyhow::anyhow!("Enum {name} not found")) } pub fn find_class(&self, name: &str) -> Result<&Class> { self.classes .get(name) - .ok_or_else(|| anyhow::anyhow!("Class {} not found", name)) + .ok_or_else(|| anyhow::anyhow!("Class {name} not found")) + } + + pub fn find_recursive_alias_target(&self, name: &str) -> Result<&FieldType> { + self.structural_recursive_aliases + .get(name) + .ok_or_else(|| anyhow::anyhow!("Recursive alias {name} not found")) } } @@ -2231,4 +2273,95 @@ Answer in JSON using this schema: )) ); } + + #[test] + fn render_simple_recursive_aliases() { + let content = OutputFormatContent::target(FieldType::RecursiveTypeAlias( + "RecursiveMapAlias".to_string(), + )) + .structural_recursive_aliases(IndexMap::from([( + "RecursiveMapAlias".to_string(), + FieldType::map( + FieldType::string(), + FieldType::RecursiveTypeAlias("RecursiveMapAlias".to_string()), + ), + )])) + .build(); + let rendered = content.render(RenderOptions::default()).unwrap(); + #[rustfmt::skip] + assert_eq!( + rendered, + Some(String::from( +r#"RecursiveMapAlias = map<string, RecursiveMapAlias> + +Answer in JSON using this schema: RecursiveMapAlias"# + )) + ); + } + + #[test] + fn render_recursive_alias_cycle() { + let content = OutputFormatContent::target(FieldType::RecursiveTypeAlias("A".to_string())) + .structural_recursive_aliases(IndexMap::from([ + ( + "A".to_string(), + FieldType::RecursiveTypeAlias("B".to_string()), + ), + ( + "B".to_string(), + FieldType::RecursiveTypeAlias("C".to_string()), + ), + ( + "C".to_string(), + FieldType::list(FieldType::RecursiveTypeAlias("A".to_string())), + ), + ])) + .build(); + let rendered = content.render(RenderOptions::default()).unwrap(); + #[rustfmt::skip] + assert_eq!( + rendered, + Some(String::from( +r#"A = B +B = C +C = A[] + +Answer in JSON using this schema: A"# + )) + ); + } + + #[test] + fn render_recursive_alias_cycle_with_hoist_prefix() { + let content = OutputFormatContent::target(FieldType::RecursiveTypeAlias("A".to_string())) + .structural_recursive_aliases(IndexMap::from([ + ( + "A".to_string(), + FieldType::RecursiveTypeAlias("B".to_string()), + ), + ( + "B".to_string(), + FieldType::RecursiveTypeAlias("C".to_string()), + ), + ( + "C".to_string(), + FieldType::list(FieldType::RecursiveTypeAlias("A".to_string())), + ), + ])) + .build(); + let rendered = content + .render(RenderOptions::with_hoisted_class_prefix("type")) + .unwrap(); + #[rustfmt::skip] + assert_eq!( + rendered, + Some(String::from( +r#"type A = B +type B = C +type C = A[] + +Answer in JSON using this type: A"# + )) + ); + } } diff --git a/engine/baml-lib/jsonish/src/deserializer/coercer/array_helper.rs b/engine/baml-lib/jsonish/src/deserializer/coercer/array_helper.rs index 0f22e5bbf..5c1deb6a9 100644 --- a/engine/baml-lib/jsonish/src/deserializer/coercer/array_helper.rs +++ b/engine/baml-lib/jsonish/src/deserializer/coercer/array_helper.rs @@ -14,7 +14,13 @@ pub fn coerce_array_to_singular( ) -> Result<BamlValueWithFlags, ParsingError> { let parsed = items.iter().map(|item| coercion(item)).collect::<Vec<_>>(); - pick_best(ctx, target, &parsed) + let mut best = pick_best(ctx, target, &parsed); + + if let Ok(ref mut f) = best { + f.add_flag(Flag::FirstMatch(0, parsed.to_vec())) + } + + best } pub(super) fn pick_best( @@ -180,6 +186,29 @@ pub(super) fn pick_best( } } + // Devalue strings that were cast from objects. + if !a_val.is_composite() && b_val.is_composite() { + if a_val + .conditions() + .flags() + .iter() + .any(|f| matches!(f, Flag::JsonToString(..) | Flag::FirstMatch(_, _))) + { + return std::cmp::Ordering::Greater; + } + } + + if a_val.is_composite() && !b_val.is_composite() { + if b_val + .conditions() + .flags() + .iter() + .any(|f| matches!(f, Flag::JsonToString(..) | Flag::FirstMatch(_, _))) + { + return std::cmp::Ordering::Less; + } + } + match a_default.cmp(&b_default) { std::cmp::Ordering::Equal => match a_score.cmp(&b_score) { std::cmp::Ordering::Equal => a.cmp(&b), diff --git a/engine/baml-lib/jsonish/src/deserializer/coercer/field_type.rs b/engine/baml-lib/jsonish/src/deserializer/coercer/field_type.rs index d984d00a2..df7cf8048 100644 --- a/engine/baml-lib/jsonish/src/deserializer/coercer/field_type.rs +++ b/engine/baml-lib/jsonish/src/deserializer/coercer/field_type.rs @@ -9,11 +9,17 @@ use crate::deserializer::{ }; use super::{ - array_helper, coerce_array::coerce_array, coerce_map::coerce_map, - coerce_optional::coerce_optional, coerce_union::coerce_union, ir_ref::IrRef, ParsingContext, - ParsingError, + array_helper, + coerce_array::coerce_array, + coerce_map::coerce_map, + coerce_optional::coerce_optional, + coerce_union::coerce_union, + ir_ref::{coerce_alias::coerce_alias, IrRef}, + ParsingContext, ParsingError, }; +static mut LIMIT: usize = 0; + impl TypeCoercer for FieldType { fn coerce( &self, @@ -21,7 +27,12 @@ impl TypeCoercer for FieldType { target: &FieldType, value: Option<&crate::jsonish::Value>, ) -> Result<BamlValueWithFlags, ParsingError> { - match value { + unsafe { + LIMIT += 1; + eprintln!("LIMIT: {}", LIMIT); + } + + let ret_v = match value { Some(crate::jsonish::Value::AnyOf(candidates, primitive)) => { log::debug!( "scope: {scope} :: coercing to: {name} (current: {current})", @@ -79,7 +90,7 @@ impl TypeCoercer for FieldType { FieldType::Enum(e) => IrRef::Enum(e).coerce(ctx, target, value), FieldType::Literal(l) => l.coerce(ctx, target, value), FieldType::Class(c) => IrRef::Class(c).coerce(ctx, target, value), - FieldType::Alias { resolution, .. } => resolution.coerce(ctx, target, value), + FieldType::RecursiveTypeAlias(name) => coerce_alias(ctx, self, value), FieldType::List(_) => coerce_array(ctx, self, value), FieldType::Union(_) => coerce_union(ctx, self, value), FieldType::Optional(_) => coerce_optional(ctx, self, value), @@ -89,7 +100,7 @@ impl TypeCoercer for FieldType { let mut coerced_value = base.coerce(ctx, base, value)?; let constraint_results = run_user_checks(&coerced_value.clone().into(), self) .map_err(|e| ParsingError { - reason: format!("Failed to evaluate constraints: {:?}", e), + reason: format!("Failed to evaluate constraints: {e:?}"), scope: ctx.scope.clone(), causes: Vec::new(), })?; @@ -106,7 +117,15 @@ impl TypeCoercer for FieldType { Ok(coerced_value) } }, + }; + + unsafe { + LIMIT -= 1; } + + eprintln!("ret_v: {:?}", ret_v); + + ret_v } } @@ -165,7 +184,7 @@ impl DefaultValue for FieldType { FieldType::Enum(e) => None, FieldType::Literal(_) => None, FieldType::Class(_) => None, - FieldType::Alias { resolution, .. } => resolution.default_value(error), + FieldType::RecursiveTypeAlias(_) => None, FieldType::List(_) => Some(BamlValueWithFlags::List(get_flags(), Vec::new())), FieldType::Union(items) => items.iter().find_map(|i| i.default_value(error)), FieldType::Primitive(TypeValue::Null) | FieldType::Optional(_) => { diff --git a/engine/baml-lib/jsonish/src/deserializer/coercer/ir_ref/coerce_alias.rs b/engine/baml-lib/jsonish/src/deserializer/coercer/ir_ref/coerce_alias.rs new file mode 100644 index 000000000..352724bd8 --- /dev/null +++ b/engine/baml-lib/jsonish/src/deserializer/coercer/ir_ref/coerce_alias.rs @@ -0,0 +1,44 @@ +use anyhow::Result; +use internal_baml_core::ir::FieldType; + +use crate::deserializer::types::BamlValueWithFlags; + +use super::{ParsingContext, ParsingError, TypeCoercer}; + +pub fn coerce_alias( + ctx: &ParsingContext, + target: &FieldType, + value: Option<&crate::jsonish::Value>, +) -> Result<BamlValueWithFlags, ParsingError> { + assert!(matches!(target, FieldType::RecursiveTypeAlias(_))); + log::debug!( + "scope: {scope} :: coercing to: {name} (current: {current})", + name = target.to_string(), + scope = ctx.display_scope(), + current = value.map(|v| v.r#type()).unwrap_or("<null>".into()) + ); + + let FieldType::RecursiveTypeAlias(alias) = target else { + unreachable!("coerce_alias"); + }; + + // See coerce_class.rs + let mut nested_ctx = None; + if let Some(v) = value { + let cls_value_pair = (alias.to_string(), v.to_owned()); + if ctx.visited.contains(&cls_value_pair) { + return Err(ctx.error_circular_reference(alias, v)); + } + nested_ctx = Some(ctx.visit_class_value_pair(cls_value_pair)); + } + let ctx = nested_ctx.as_ref().unwrap_or(ctx); + + ctx.of + .find_recursive_alias_target(alias) + .map_err(|e| ParsingError { + reason: format!("Failed to find recursive alias target: {e}"), + scope: ctx.scope.clone(), + causes: Vec::new(), + })? + .coerce(ctx, target, value) +} diff --git a/engine/baml-lib/jsonish/src/deserializer/coercer/ir_ref/mod.rs b/engine/baml-lib/jsonish/src/deserializer/coercer/ir_ref/mod.rs index a16b9ed1a..88a3cfde3 100644 --- a/engine/baml-lib/jsonish/src/deserializer/coercer/ir_ref/mod.rs +++ b/engine/baml-lib/jsonish/src/deserializer/coercer/ir_ref/mod.rs @@ -1,6 +1,9 @@ +pub mod coerce_alias; mod coerce_class; pub mod coerce_enum; +use core::panic; + use anyhow::Result; use internal_baml_core::ir::FieldType; @@ -11,6 +14,7 @@ use super::{ParsingContext, ParsingError}; pub(super) enum IrRef<'a> { Enum(&'a String), Class(&'a String), + RecursiveAlias(&'a String), } impl TypeCoercer for IrRef<'_> { @@ -29,6 +33,10 @@ impl TypeCoercer for IrRef<'_> { Ok(c) => c.coerce(ctx, target, value), Err(e) => Err(ctx.error_internal(e.to_string())), }, + IrRef::RecursiveAlias(a) => match ctx.of.find_recursive_alias_target(a.as_str()) { + Ok(a) => a.coerce(ctx, target, value), + Err(e) => Err(ctx.error_internal(e.to_string())), + }, } } } diff --git a/engine/baml-lib/jsonish/src/deserializer/types.rs b/engine/baml-lib/jsonish/src/deserializer/types.rs index 1303ce7bc..fae0ee606 100644 --- a/engine/baml-lib/jsonish/src/deserializer/types.rs +++ b/engine/baml-lib/jsonish/src/deserializer/types.rs @@ -33,6 +33,22 @@ pub enum BamlValueWithFlags { } impl BamlValueWithFlags { + pub fn is_composite(&self) -> bool { + match self { + BamlValueWithFlags::String(_) => false, + BamlValueWithFlags::Int(_) => false, + BamlValueWithFlags::Float(_) => false, + BamlValueWithFlags::Bool(_) => false, + BamlValueWithFlags::Null(_) => false, + BamlValueWithFlags::Enum(_, value_with_flags) => false, + + BamlValueWithFlags::List(deserializer_conditions, vec) => true, + BamlValueWithFlags::Map(deserializer_conditions, index_map) => true, + BamlValueWithFlags::Class(_, deserializer_conditions, index_map) => true, + BamlValueWithFlags::Media(value_with_flags) => true, + } + } + pub fn score(&self) -> i32 { match self { BamlValueWithFlags::String(f) => f.score(), diff --git a/engine/baml-lib/jsonish/src/tests/mod.rs b/engine/baml-lib/jsonish/src/tests/mod.rs index 1e36255b1..00da9d619 100644 --- a/engine/baml-lib/jsonish/src/tests/mod.rs +++ b/engine/baml-lib/jsonish/src/tests/mod.rs @@ -4,6 +4,7 @@ use internal_baml_jinja::types::{Class, Enum, Name, OutputFormatContent}; #[macro_use] pub mod macros; +mod test_aliases; mod test_basics; mod test_class; mod test_class_2; @@ -16,7 +17,7 @@ mod test_maps; mod test_partials; mod test_unions; -use indexmap::IndexSet; +use indexmap::{IndexMap, IndexSet}; use std::{ collections::{HashMap, HashSet}, path::PathBuf, @@ -56,12 +57,14 @@ fn render_output_format( output: &FieldType, env_values: &EvaluationContext<'_>, ) -> Result<OutputFormatContent> { - let (enums, classes, recursive_classes) = relevant_data_models(ir, output, env_values)?; + let (enums, classes, recursive_classes, structural_recursive_aliases) = + relevant_data_models(ir, output, env_values)?; Ok(OutputFormatContent::target(output.clone()) .enums(enums) .classes(classes) .recursive_classes(recursive_classes) + .structural_recursive_aliases(structural_recursive_aliases) .build()) } @@ -126,11 +129,17 @@ fn relevant_data_models<'a>( ir: &'a IntermediateRepr, output: &'a FieldType, env_values: &EvaluationContext<'_>, -) -> Result<(Vec<Enum>, Vec<Class>, IndexSet<String>)> { +) -> Result<( + Vec<Enum>, + Vec<Class>, + IndexSet<String>, + IndexMap<String, FieldType>, +)> { let mut checked_types: HashSet<String> = HashSet::new(); let mut enums = Vec::new(); let mut classes: Vec<Class> = Vec::new(); let mut recursive_classes = IndexSet::new(); + let mut structural_recursive_aliases = IndexMap::new(); let mut start: Vec<baml_types::FieldType> = vec![output.clone()]; while let Some(output) = start.pop() { @@ -230,7 +239,16 @@ fn relevant_data_models<'a>( }); } } - (FieldType::Alias { resolution, .. }, _) => start.push(*resolution.to_owned()), + (FieldType::RecursiveTypeAlias(name), _) => { + // TODO: Same O(n) problem as above. + for cycle in ir.structural_recursive_alias_cycles() { + if cycle.contains_key(name) { + for (alias, target) in cycle.iter() { + structural_recursive_aliases.insert(alias.to_owned(), target.clone()); + } + } + } + } (FieldType::Literal(_), _) => {} (FieldType::Primitive(_), _constraints) => {} (FieldType::Constrained { .. }, _) => { @@ -239,7 +257,12 @@ fn relevant_data_models<'a>( } } - Ok((enums, classes, recursive_classes)) + Ok(( + enums, + classes, + recursive_classes, + structural_recursive_aliases, + )) } const EMPTY_FILE: &str = r#" diff --git a/engine/baml-lib/jsonish/src/tests/test_aliases.rs b/engine/baml-lib/jsonish/src/tests/test_aliases.rs new file mode 100644 index 000000000..639bf949b --- /dev/null +++ b/engine/baml-lib/jsonish/src/tests/test_aliases.rs @@ -0,0 +1,221 @@ +use baml_types::LiteralValue; + +use super::*; + +test_deserializer!( + test_simple_recursive_alias_list, + r#" +type A = A[] + "#, + "[[], [], [[]]]", + FieldType::RecursiveTypeAlias("A".into()), + [[], [], [[]]] +); + +test_deserializer!( + test_simple_recursive_alias_map, + r#" +type A = map<string, A> + "#, + r#"{"one": {"two": {}}, "three": {"four": {}}}"#, + FieldType::RecursiveTypeAlias("A".into()), + { + "one": {"two": {}}, + "three": {"four": {}} + } +); + +test_deserializer!( + test_recursive_alias_cycle, + r#" +type A = B +type B = C +type C = A[] + "#, + "[[], [], [[]]]", + FieldType::RecursiveTypeAlias("A".into()), + [[], [], [[]]] +); + +test_deserializer!( + test_json_without_nested_objects, + r#" +type JsonValue = int | float | string | bool | JsonValue[] | map<string, JsonValue> + "#, + r#" + { + "int": 1, + "float": 1.0, + "string": "test", + "bool": true + } + "#, + FieldType::RecursiveTypeAlias("JsonValue".into()), + { + "int": 1, + "float": 1.0, + "string": "test", + "bool": true + } +); + +test_deserializer!( + test_json_with_nested_list, + r#" +type JsonValue = int | string | bool | JsonValue[] | map<string, JsonValue> + "#, + r#" + { + "number": 1, + "string": "test", + "bool": true, + "list": [1, 2, 3] + } + "#, + FieldType::RecursiveTypeAlias("JsonValue".into()), + { + "number": 1, + "string": "test", + "bool": true, + "list": [1, 2, 3] + } +); + +test_deserializer!( + test_json_with_nested_object, + r#" +type JsonValue = int | bool | string | JsonValue[] | map<string, JsonValue> + "#, + r#" + { + "number": 1, + "string": "test", + "bool": true, + "json": { + "number": 1, + "string": "test", + "bool": true + } + } + "#, + FieldType::RecursiveTypeAlias("JsonValue".into()), + { + "number": 1, + "string": "test", + "bool": true, + "json": { + "number": 1, + "string": "test", + "bool": true + } + } +); + +test_deserializer!( + test_full_json_with_nested_objects, + r#" +type JsonValue = JsonValue[] | map<string, JsonValue> | int | bool | string + "#, + r#" + { + "number": 1, + "string": "test", + "bool": true, + "list": [1, 2, 3], + "object": { + "number": 1, + "string": "test", + "bool": true, + "list": [1, 2, 3] + }, + "json": { + "number": 1, + "string": "test", + "bool": true, + "list": [1, 2, 3], + "object": { + "number": 1, + "string": "test", + "bool": true, + "list": [1, 2, 3] + } + } + } + "#, + FieldType::RecursiveTypeAlias("JsonValue".into()), + { + "number": 1, + "string": "test", + "bool": true, + "list": [1, 2, 3], + "object": { + "number": 1, + "string": "test", + "bool": true, + "list": [1, 2, 3] + }, + "json": { + "number": 1, + "string": "test", + "bool": true, + "list": [1, 2, 3], + "object": { + "number": 1, + "string": "test", + "bool": true, + "list": [1, 2, 3] + } + } + } +); + +test_deserializer!( + test_list_of_json_objects, + r#" +type JsonValue = int | string | bool | JsonValue[] | map<string, JsonValue> + "#, + r#" + [ + { + "number": 1, + "string": "test", + "bool": true, + "list": [1, 2, 3] + }, + { + "number": 1, + "string": "test", + "bool": true, + "list": [1, 2, 3] + } + ] + "#, + FieldType::RecursiveTypeAlias("JsonValue".into()), + [ + { + "number": 1, + "string": "test", + "bool": true, + "list": [1, 2, 3] + }, + { + "number": 1, + "string": "test", + "bool": true, + "list": [1, 2, 3] + } + ] +); + +test_deserializer!( + test_nested_list, + r#" +type JsonValue = int | float | bool | string | JsonValue[] | map<string, JsonValue> + "#, + r#" + [[42.1]] + "#, + FieldType::RecursiveTypeAlias("JsonValue".into()), + // [[[[[[[[[[[[[[[[[[[[42]]]]]]]]]]]]]]]]]]]] + [[42.1]] +); diff --git a/engine/baml-lib/jsonish/src/tests/test_constraints.rs b/engine/baml-lib/jsonish/src/tests/test_constraints.rs index f543cd115..728c1bf67 100644 --- a/engine/baml-lib/jsonish/src/tests/test_constraints.rs +++ b/engine/baml-lib/jsonish/src/tests/test_constraints.rs @@ -16,7 +16,7 @@ test_deserializer_with_expected_score!( CLASS_FOO_INT_STRING, r#"{"age": 11, "name": "Greg"}"#, FieldType::Class("Foo".to_string()), - 0 + 1 ); test_deserializer_with_expected_score!( @@ -24,7 +24,7 @@ test_deserializer_with_expected_score!( CLASS_FOO_INT_STRING, r#"{"age": 21, "name": "Grog"}"#, FieldType::Class("Foo".to_string()), - 0 + 1 ); test_failing_deserializer!( @@ -61,7 +61,7 @@ test_deserializer_with_expected_score!( UNION_WITH_CHECKS, r#"{"bar": 5, "things":[]}"#, FieldType::Class("Either".to_string()), - 2 + 3 ); test_deserializer_with_expected_score!( @@ -69,7 +69,7 @@ test_deserializer_with_expected_score!( UNION_WITH_CHECKS, r#"{"bar": 15, "things":[]}"#, FieldType::Class("Either".to_string()), - 2 + 3 ); test_failing_deserializer!( @@ -90,7 +90,7 @@ test_deserializer_with_expected_score!( MAP_WITH_CHECKS, r#"{"foo": {"hello": 10, "there":13}}"#, FieldType::Class("Foo".to_string()), - 1 + 2 ); test_deserializer_with_expected_score!( @@ -98,7 +98,7 @@ test_deserializer_with_expected_score!( MAP_WITH_CHECKS, r#"{"foo": {"hello": 11, "there":13}}"#, FieldType::Class("Foo".to_string()), - 1 + 2 ); const NESTED_CLASS_CONSTRAINTS: &str = r#" @@ -116,7 +116,7 @@ test_deserializer_with_expected_score!( NESTED_CLASS_CONSTRAINTS, r#"{"inner": {"value": 15}}"#, FieldType::Class("Outer".to_string()), - 0 + 1 ); const BLOCK_LEVEL: &str = r#" diff --git a/engine/baml-lib/jsonish/src/tests/test_maps.rs b/engine/baml-lib/jsonish/src/tests/test_maps.rs index 906e4a1c5..5c1b1d641 100644 --- a/engine/baml-lib/jsonish/src/tests/test_maps.rs +++ b/engine/baml-lib/jsonish/src/tests/test_maps.rs @@ -166,6 +166,7 @@ fn test_union_of_map_and_class() { assert!(result.is_ok(), "Failed to parse: {:?}", result); let value = result.unwrap(); + dbg!(&value); assert!(matches!(value, BamlValueWithFlags::Class(_, _, _))); log::trace!("Score: {}", value.score()); diff --git a/engine/baml-lib/parser-database/src/lib.rs b/engine/baml-lib/parser-database/src/lib.rs index 2f54efd1e..c90c141ba 100644 --- a/engine/baml-lib/parser-database/src/lib.rs +++ b/engine/baml-lib/parser-database/src/lib.rs @@ -130,6 +130,11 @@ impl ParserDatabase { } fn finalize_dependencies(&mut self, diag: &mut Diagnostics) { + // Cycles left here after cycle validation are allowed. Basically lists + // and maps can introduce cycles. + self.types.structural_recursive_alias_cycles = + Tarjan::components(&self.types.type_alias_dependencies); + // Resolve type aliases. // Cycles are already validated so this should not stack overflow and // it should find the final type. @@ -243,17 +248,21 @@ impl ParserDatabase { // For aliases just get the resolved identifiers and // push them into the stack. If we find resolved classes we'll - // add their dependencies as well. Note that this is not - // "recursive" per se because type aliases can never "resolve" - // to other type aliases. + // add their dependencies as well. Some(TypeWalker::TypeAlias(walker)) => { - stack.extend(walker.resolved().flat_idns().iter().map(|ident| { + stack.extend(walker.resolved().flat_idns().iter().filter_map(|ident| { // Add the resolved name itself to the deps. collected_deps.insert(ident.name().to_owned()); - // Push the resolved name into the stack in case - // it's a class, we'll have to add its deps as - // well. - ident.name() + // If the type is an alias then don't recurse. + if self + .structural_recursive_alias_cycles() + .iter() + .any(|cycle| cycle.contains(&walker.id)) + { + None + } else { + Some(ident.name()) + } })) } @@ -342,6 +351,26 @@ mod test { Ok(()) } + fn assert_structural_alias_cycles( + baml: &'static str, + expected: &[&[&str]], + ) -> Result<(), Diagnostics> { + let db = parse(baml)?; + + assert_eq!( + db.structural_recursive_alias_cycles() + .iter() + .map(|ids| Vec::from_iter(ids.iter().map(|id| db.ast()[*id].name().to_string()))) + .collect::<Vec<_>>(), + expected + .iter() + .map(|cycle| Vec::from_iter(cycle.iter().map(ToString::to_string))) + .collect::<Vec<_>>() + ); + + Ok(()) + } + #[test] fn find_simple_recursive_class() -> Result<(), Diagnostics> { assert_finite_cycles( @@ -601,6 +630,43 @@ mod test { Ok(()) } + #[test] + fn find_basic_map_structural_cycle() -> Result<(), Diagnostics> { + assert_structural_alias_cycles( + "type RecursiveMap = map<string, RecursiveMap>", + &[&["RecursiveMap"]], + ) + } + + #[test] + fn find_basic_list_structural_cycle() -> Result<(), Diagnostics> { + assert_structural_alias_cycles("type A = A[]", &[&["A"]]) + } + + #[test] + fn find_long_list_structural_cycle() -> Result<(), Diagnostics> { + assert_structural_alias_cycles( + r#" + type A = B + type B = C + type C = A[] + "#, + &[&["A", "B", "C"]], + ) + } + + #[test] + fn find_intricate_structural_cycle() -> Result<(), Diagnostics> { + assert_structural_alias_cycles( + r#" + type JsonValue = string | int | float | bool | null | JsonArray | JsonObject + type JsonArray = JsonValue[] + type JsonObject = map<string, JsonValue> + "#, + &[&["JsonValue", "JsonArray", "JsonObject"]], + ) + } + #[test] fn merged_alias_attrs() -> Result<(), Diagnostics> { #[rustfmt::skip] @@ -615,4 +681,30 @@ mod test { Ok(()) } + + // Resolution of aliases here at the parser database level doesn't matter + // as much because there's no notion of "classes" or "enums", it's just + // "symbols". But the resolve type function should not stack overflow + // anyway. + #[test] + fn resolve_simple_structural_recursive_alias() -> Result<(), Diagnostics> { + #[rustfmt::skip] + let db = parse(r#" + type A = A[] + "#)?; + + let resolved = db.resolved_type_alias_by_name("A").unwrap(); + + let FieldType::List(_, inner, ..) = resolved else { + panic!("expected a list type, got {resolved:?}"); + }; + + let FieldType::Symbol(_, ident, _) = &**inner else { + panic!("expected a symbol type, got {inner:?}"); + }; + + assert_eq!(ident.name(), "A"); + + Ok(()) + } } diff --git a/engine/baml-lib/parser-database/src/tarjan.rs b/engine/baml-lib/parser-database/src/tarjan.rs index 58eea7ae1..a80a6a52d 100644 --- a/engine/baml-lib/parser-database/src/tarjan.rs +++ b/engine/baml-lib/parser-database/src/tarjan.rs @@ -6,6 +6,7 @@ use std::{ cmp, collections::{HashMap, HashSet}, + fmt::Debug, hash::Hash, }; @@ -126,8 +127,15 @@ impl<'g, V: Eq + Ord + Hash + Copy> Tarjan<'g, V> { self.index += 1; self.stack.push(node_id); + // TODO: @antoniosarosi: HashSet is random, won't always iterate in the + // same order. Fix this with IndexSet or something, we really don't want + // to sort this every single time. Also order only matters for tests, we + // can do `if cfg!(test)` or something. + let mut successors = Vec::from_iter(&self.graph[&node_id]); + successors.sort(); + // Visit neighbors to find strongly connected components. - for successor_id in &self.graph[&node_id] { + for successor_id in successors { // Grab owned state to circumvent borrow checker. let mut successor = self.state[successor_id]; if successor.index == Self::UNVISITED { diff --git a/engine/baml-lib/parser-database/src/types/mod.rs b/engine/baml-lib/parser-database/src/types/mod.rs index 3fe3f46b8..1978fd078 100644 --- a/engine/baml-lib/parser-database/src/types/mod.rs +++ b/engine/baml-lib/parser-database/src/types/mod.rs @@ -276,6 +276,13 @@ pub(super) struct Types { /// Merge-Find Set or something like that. pub(super) finite_recursive_cycles: Vec<Vec<ast::TypeExpId>>, + /// Contains recursive type aliases. + /// + /// Recursive type aliases are a little bit trickier than recursive classes + /// because the termination condition is tied to lists and maps only. Nulls + /// and unions won't allow type alias cycles to be resolved. + pub(super) structural_recursive_alias_cycles: Vec<Vec<ast::TypeAliasId>>, + pub(super) function: HashMap<ast::ValExpId, FunctionType>, pub(super) client_properties: HashMap<ast::ValExpId, ClientProperties>, @@ -409,8 +416,22 @@ pub fn resolve_type_alias(field_type: &FieldType, db: &ParserDatabase) -> FieldT let mut resolved = match db.types.resolved_type_aliases.get(alias_id) { // Check if we can avoid deeper recursion. Some(already_resolved) => already_resolved.to_owned(), - // No luck, recurse. - None => resolve_type_alias(&db.ast[*alias_id].value, db), + + // No luck, check if the type is resolvable. + None => { + // TODO: O(n) + if db + .structural_recursive_alias_cycles() + .iter() + .any(|cycle| cycle.contains(alias_id)) + { + // Not resolvable, part of a cycle. + field_type.to_owned() + } else { + // Maybe resolvable, recurse deeper. + resolve_type_alias(&db.ast[*alias_id].value, db) + } + } }; // Sync arity. Basically stuff like: diff --git a/engine/baml-lib/parser-database/src/walkers/mod.rs b/engine/baml-lib/parser-database/src/walkers/mod.rs index ae705b111..a6154a1cf 100644 --- a/engine/baml-lib/parser-database/src/walkers/mod.rs +++ b/engine/baml-lib/parser-database/src/walkers/mod.rs @@ -22,7 +22,9 @@ pub use configuration::*; use either::Either; pub use field::*; pub use function::FunctionWalker; -use internal_baml_schema_ast::ast::{FieldType, Identifier, TopId, TypeExpId, WithName}; +use internal_baml_schema_ast::ast::{ + FieldType, Identifier, TopId, TypeAliasId, TypeExpId, WithName, +}; pub use r#class::*; pub use r#enum::*; pub use template_string::TemplateStringWalker; @@ -142,6 +144,14 @@ impl<'db> crate::ParserDatabase { &self.types.finite_recursive_cycles } + /// Set of all aliases that are part of a structural cycle. + /// + /// A structural cycle is created through a map or list, which introduce one + /// level of indirection. + pub fn structural_recursive_alias_cycles(&self) -> &[Vec<TypeAliasId>] { + &self.types.structural_recursive_alias_cycles + } + /// Returns the resolved aliases map. pub fn resolved_type_alias_by_name(&self, alias: &str) -> Option<&FieldType> { match self.find_type_by_str(alias) { @@ -209,6 +219,17 @@ impl<'db> crate::ParserDatabase { }) } + /// Walk all the type aliases in the AST. + pub fn walk_type_aliases(&self) -> impl Iterator<Item = TypeAliasWalker<'_>> { + self.ast() + .iter_tops() + .filter_map(|(top_id, _)| top_id.as_type_alias_id()) + .map(move |top_id| Walker { + db: self, + id: top_id, + }) + } + /// Walk all template strings in the schema. pub fn walk_templates(&self) -> impl Iterator<Item = TemplateStringWalker<'_>> { self.ast() diff --git a/engine/baml-lib/schema-ast/src/ast.rs b/engine/baml-lib/schema-ast/src/ast.rs index 24b0b8a4f..d34b3ecad 100644 --- a/engine/baml-lib/schema-ast/src/ast.rs +++ b/engine/baml-lib/schema-ast/src/ast.rs @@ -191,6 +191,14 @@ impl TopId { } } + /// Try to interpret the top as a type alias. + pub fn as_type_alias_id(self) -> Option<TypeAliasId> { + match self { + TopId::TypeAlias(id) => Some(id), + _ => None, + } + } + /// Try to interpret the top as a function. pub fn as_function_id(self) -> Option<ValExpId> { match self { diff --git a/engine/baml-runtime/src/internal/prompt_renderer/render_output_format.rs b/engine/baml-runtime/src/internal/prompt_renderer/render_output_format.rs index 1e8e8c57f..069ced74e 100644 --- a/engine/baml-runtime/src/internal/prompt_renderer/render_output_format.rs +++ b/engine/baml-runtime/src/internal/prompt_renderer/render_output_format.rs @@ -2,7 +2,7 @@ use std::collections::HashSet; use anyhow::Result; use baml_types::BamlValue; -use indexmap::IndexSet; +use indexmap::{IndexMap, IndexSet}; use internal_baml_core::ir::{ repr::IntermediateRepr, ClassWalker, EnumWalker, FieldType, IRHelper, }; @@ -18,12 +18,14 @@ pub fn render_output_format( ctx: &RuntimeContext, output: &FieldType, ) -> Result<OutputFormatContent> { - let (enums, classes, recursive_classes) = relevant_data_models(ir, output, ctx)?; + let (enums, classes, recursive_classes, structural_recursive_aliases) = + relevant_data_models(ir, output, ctx)?; Ok(OutputFormatContent::target(output.clone()) .enums(enums) .classes(classes) .recursive_classes(recursive_classes) + .structural_recursive_aliases(structural_recursive_aliases) .build()) } @@ -210,11 +212,17 @@ fn relevant_data_models<'a>( ir: &'a IntermediateRepr, output: &'a FieldType, ctx: &RuntimeContext, -) -> Result<(Vec<Enum>, Vec<Class>, IndexSet<String>)> { +) -> Result<( + Vec<Enum>, + Vec<Class>, + IndexSet<String>, + IndexMap<String, FieldType>, +)> { let mut checked_types = HashSet::new(); let mut enums = Vec::new(); let mut classes = Vec::new(); let mut recursive_classes = IndexSet::new(); + let mut structural_recursive_aliases = IndexMap::new(); let mut start: Vec<baml_types::FieldType> = vec![output.clone()]; let eval_ctx = ctx.eval_ctx(false); @@ -376,8 +384,15 @@ fn relevant_data_models<'a>( recursive_classes.insert(cls.to_owned()); } } - (FieldType::Alias { resolution, .. }, _) => { - start.push(*resolution.clone()); + (FieldType::RecursiveTypeAlias(name), _) => { + // TODO: Same O(n) problem as above. + for cycle in ir.structural_recursive_alias_cycles() { + if cycle.contains_key(name) { + for (alias, target) in cycle.iter() { + structural_recursive_aliases.insert(alias.to_owned(), target.clone()); + } + } + } } (FieldType::Literal(_), _) => {} (FieldType::Primitive(_), _) => {} @@ -387,7 +402,12 @@ fn relevant_data_models<'a>( } } - Ok((enums, classes, recursive_classes)) + Ok(( + enums, + classes, + recursive_classes, + structural_recursive_aliases, + )) } #[cfg(test)] diff --git a/engine/baml-runtime/tests/test_runtime.rs b/engine/baml-runtime/tests/test_runtime.rs index 79efe7151..97e9d07ac 100644 --- a/engine/baml-runtime/tests/test_runtime.rs +++ b/engine/baml-runtime/tests/test_runtime.rs @@ -552,4 +552,52 @@ test RunFoo2Test { Ok(()) } + + #[test] + fn test_recursive_alias_cycle() -> anyhow::Result<()> { + let runtime = make_test_runtime( + r##" +type RecAliasOne = RecAliasTwo +type RecAliasTwo = RecAliasThree +type RecAliasThree = RecAliasOne[] + +function RecursiveAliasCycle(input: RecAliasOne) -> RecAliasOne { + client "openai/gpt-4o" + prompt r#" + Return the given value: + + {{ input }} + + {{ ctx.output_format }} + "# +} + +test RecursiveAliasCycle { + functions [RecursiveAliasCycle] + args { + input [ + [] + [] + [[], []] + ] + } +} + "##, + )?; + + let ctx = runtime + .create_ctx_manager(BamlValue::String("test".to_string()), None) + .create_ctx_with_default(); + + let function_name = "RecursiveAliasCycle"; + let test_name = "RecursiveAliasCycle"; + let params = runtime.get_test_params(function_name, test_name, &ctx, true)?; + let render_prompt_future = + runtime + .internal() + .render_prompt(function_name, &ctx, ¶ms, None); + let (prompt, scope, _) = runtime.async_runtime.block_on(render_prompt_future)?; + + Ok(()) + } } diff --git a/engine/baml-schema-wasm/src/runtime_wasm/mod.rs b/engine/baml-schema-wasm/src/runtime_wasm/mod.rs index f21641107..614b19fbc 100644 --- a/engine/baml-schema-wasm/src/runtime_wasm/mod.rs +++ b/engine/baml-schema-wasm/src/runtime_wasm/mod.rs @@ -876,9 +876,7 @@ fn get_dummy_value( baml_runtime::FieldType::Literal(_) => None, baml_runtime::FieldType::Enum(_) => None, baml_runtime::FieldType::Class(_) => None, - baml_runtime::FieldType::Alias { resolution, .. } => { - get_dummy_value(indent, allow_multiline, &resolution) - } + baml_runtime::FieldType::RecursiveTypeAlias(_) => None, baml_runtime::FieldType::List(item) => { let dummy = get_dummy_value(indent + 1, allow_multiline, item); // Repeat it 2 times diff --git a/engine/language_client_codegen/src/openapi.rs b/engine/language_client_codegen/src/openapi.rs index 4dc6009a1..9fb2cca91 100644 --- a/engine/language_client_codegen/src/openapi.rs +++ b/engine/language_client_codegen/src/openapi.rs @@ -539,7 +539,15 @@ impl<'ir> ToTypeReferenceInTypeDefinition<'ir> for FieldType { r#ref: format!("#/components/schemas/{}", name), }, }, - FieldType::Alias { resolution, .. } => resolution.to_type_spec(_ir)?, + FieldType::RecursiveTypeAlias(_) => TypeSpecWithMeta { + meta: TypeMetadata { + title: None, + r#enum: None, + r#const: None, + nullable: false, + }, + type_spec: TypeSpec::AnyValue { any_of: vec![] }, + }, FieldType::Literal(v) => TypeSpecWithMeta { meta: TypeMetadata { title: None, @@ -565,8 +573,14 @@ impl<'ir> ToTypeReferenceInTypeDefinition<'ir> for FieldType { }), }, FieldType::Map(key, value) => { - if !matches!(**key, FieldType::Primitive(TypeValue::String)) { - anyhow::bail!("BAML<->OpenAPI only supports string keys in maps") + if !matches!( + **key, + FieldType::Primitive(TypeValue::String) + | FieldType::Enum(_) + | FieldType::Literal(LiteralValue::String(_)) + | FieldType::Union(_) + ) { + anyhow::bail!(format!("BAML<->OpenAPI only supports strings, enums and literal strings as map keys but got {key}")) } TypeSpecWithMeta { meta: TypeMetadata { @@ -705,6 +719,10 @@ enum TypeSpec { #[serde(rename = "oneOf", alias = "oneOf")] one_of: Vec<TypeSpecWithMeta>, }, + AnyValue { + #[serde(rename = "anyOf", alias = "anyOf")] + any_of: Vec<TypeSpecWithMeta>, + }, } #[derive(Clone, Debug, Serialize)] diff --git a/engine/language_client_codegen/src/python/generate_types.rs b/engine/language_client_codegen/src/python/generate_types.rs index 882110a51..428b25d70 100644 --- a/engine/language_client_codegen/src/python/generate_types.rs +++ b/engine/language_client_codegen/src/python/generate_types.rs @@ -7,7 +7,7 @@ use crate::{field_type_attributes, type_check_attributes, TypeCheckAttributes}; use super::python_language_features::ToPython; use internal_baml_core::ir::{ - repr::{Docstring, IntermediateRepr}, + repr::{Docstring, IntermediateRepr, Walker}, ClassWalker, EnumWalker, FieldType, IRHelper, }; @@ -16,6 +16,7 @@ use internal_baml_core::ir::{ pub(crate) struct PythonTypes<'ir> { enums: Vec<PythonEnum<'ir>>, classes: Vec<PythonClass<'ir>>, + structural_recursive_alias_cycles: Vec<PythonTypeAlias<'ir>>, } #[derive(askama::Template)] @@ -41,6 +42,11 @@ struct PythonClass<'ir> { dynamic: bool, } +struct PythonTypeAlias<'ir> { + name: Cow<'ir, str>, + target: String, +} + #[derive(askama::Template)] #[template(path = "partial_types.py.j2", escape = "none")] pub(crate) struct PythonStreamTypes<'ir> { @@ -66,6 +72,10 @@ impl<'ir> TryFrom<(&'ir IntermediateRepr, &'_ crate::GeneratorArgs)> for PythonT Ok(PythonTypes { enums: ir.walk_enums().map(PythonEnum::from).collect::<Vec<_>>(), classes: ir.walk_classes().map(PythonClass::from).collect::<Vec<_>>(), + structural_recursive_alias_cycles: ir + .walk_alias_cycles() + .map(PythonTypeAlias::from) + .collect::<Vec<_>>(), }) } } @@ -126,6 +136,21 @@ impl<'ir> From<ClassWalker<'ir>> for PythonClass<'ir> { } } +// TODO: Define AliasWalker to simplify type. +impl<'ir> From<Walker<'ir, (&'ir String, &'ir FieldType)>> for PythonTypeAlias<'ir> { + fn from( + Walker { + db, + item: (name, target), + }: Walker<(&'ir String, &'ir FieldType)>, + ) -> Self { + PythonTypeAlias { + name: Cow::Borrowed(name), + target: target.to_type_ref(db), + } + } +} + impl<'ir> TryFrom<(&'ir IntermediateRepr, &'_ crate::GeneratorArgs)> for PythonStreamTypes<'ir> { type Error = anyhow::Error; @@ -219,7 +244,7 @@ impl ToTypeReferenceInTypeDefinition for FieldType { format!("\"{name}\"") } } - FieldType::Alias { resolution, .. } => resolution.to_type_ref(ir), + FieldType::RecursiveTypeAlias(name) => format!("\"{name}\""), FieldType::Literal(value) => to_python_literal(value), FieldType::Class(name) => format!("\"{name}\""), FieldType::List(inner) => format!("List[{}]", inner.to_type_ref(ir)), @@ -275,7 +300,13 @@ impl ToTypeReferenceInTypeDefinition for FieldType { format!("Optional[types.{name}]") } } - FieldType::Alias { resolution, .. } => resolution.to_partial_type_ref(ir, wrapped), + FieldType::RecursiveTypeAlias(name) => { + if wrapped { + format!("\"{name}\"") + } else { + format!("Optional[\"{name}\"]") + } + } FieldType::Literal(value) => to_python_literal(value), FieldType::List(inner) => format!("List[{}]", inner.to_partial_type_ref(ir, true)), FieldType::Map(key, value) => { diff --git a/engine/language_client_codegen/src/python/mod.rs b/engine/language_client_codegen/src/python/mod.rs index 082fc5d3d..06fbbe7d8 100644 --- a/engine/language_client_codegen/src/python/mod.rs +++ b/engine/language_client_codegen/src/python/mod.rs @@ -201,7 +201,7 @@ impl ToTypeReferenceInClientDefinition for FieldType { } } FieldType::Literal(value) => to_python_literal(value), - FieldType::Alias { resolution, .. } => resolution.to_type_ref(ir, _with_checked), + FieldType::RecursiveTypeAlias(name) => format!("types.{name}"), FieldType::Class(name) => format!("types.{name}"), FieldType::List(inner) => format!("List[{}]", inner.to_type_ref(ir, _with_checked)), FieldType::Map(key, value) => { @@ -256,7 +256,7 @@ impl ToTypeReferenceInClientDefinition for FieldType { } } FieldType::Class(name) => format!("partial_types.{name}"), - FieldType::Alias { resolution, .. } => resolution.to_partial_type_ref(ir, with_checked), + FieldType::RecursiveTypeAlias(name) => format!("types.{name}"), FieldType::Literal(value) => to_python_literal(value), FieldType::List(inner) => { format!("List[{}]", inner.to_partial_type_ref(ir, with_checked)) diff --git a/engine/language_client_codegen/src/python/templates/types.py.j2 b/engine/language_client_codegen/src/python/templates/types.py.j2 index 86b776db3..bbc7a7e9a 100644 --- a/engine/language_client_codegen/src/python/templates/types.py.j2 +++ b/engine/language_client_codegen/src/python/templates/types.py.j2 @@ -2,7 +2,7 @@ import baml_py from enum import Enum from pydantic import BaseModel, ConfigDict -from typing import Dict, Generic, List, Literal, Optional, TypeVar, Union +from typing import Dict, Generic, List, Literal, Optional, TypeVar, Union, TypeAlias T = TypeVar('T') @@ -59,3 +59,8 @@ class {{cls.name}}(BaseModel): {%- endif %} {%- endfor %} {% endfor %} + +{#- Type Aliases -#} +{% for alias in structural_recursive_alias_cycles %} +{{alias.name}}: TypeAlias = {{alias.target}} +{% endfor %} diff --git a/engine/language_client_codegen/src/ruby/field_type.rs b/engine/language_client_codegen/src/ruby/field_type.rs index c17c7e538..c6cdba590 100644 --- a/engine/language_client_codegen/src/ruby/field_type.rs +++ b/engine/language_client_codegen/src/ruby/field_type.rs @@ -9,7 +9,9 @@ impl ToRuby for FieldType { match self { FieldType::Class(name) => format!("Baml::Types::{}", name.clone()), FieldType::Enum(name) => format!("T.any(Baml::Types::{}, String)", name.clone()), - FieldType::Alias { resolution, .. } => resolution.to_ruby(), + // Sorbet does not support recursive type aliases. + // https://sorbet.org/docs/type-aliases + FieldType::RecursiveTypeAlias(_name) => "T.anything".to_string(), // TODO: Temporary solution until we figure out Ruby literals. FieldType::Literal(value) => value.literal_base_type().to_ruby(), // https://sorbet.org/docs/stdlib-generics diff --git a/engine/language_client_codegen/src/ruby/generate_types.rs b/engine/language_client_codegen/src/ruby/generate_types.rs index b69a44359..495b7e924 100644 --- a/engine/language_client_codegen/src/ruby/generate_types.rs +++ b/engine/language_client_codegen/src/ruby/generate_types.rs @@ -168,7 +168,8 @@ impl ToTypeReferenceInTypeDefinition for FieldType { match self { FieldType::Class(name) => format!("Baml::PartialTypes::{}", name.clone()), FieldType::Enum(name) => format!("T.nilable(Baml::Types::{})", name.clone()), - FieldType::Alias { resolution, .. } => resolution.to_partial_type_ref(), + // TODO: Can we define recursive aliases in Ruby with Sorbet? + FieldType::RecursiveTypeAlias(_name) => "T.anything".to_string(), // TODO: Temporary solution until we figure out Ruby literals. FieldType::Literal(value) => value.literal_base_type().to_partial_type_ref(), // https://sorbet.org/docs/stdlib-generics diff --git a/engine/language_client_codegen/src/typescript/generate_types.rs b/engine/language_client_codegen/src/typescript/generate_types.rs index 0022f110a..195764bc5 100644 --- a/engine/language_client_codegen/src/typescript/generate_types.rs +++ b/engine/language_client_codegen/src/typescript/generate_types.rs @@ -4,8 +4,8 @@ use anyhow::Result; use itertools::Itertools; use internal_baml_core::ir::{ - repr::{Docstring, IntermediateRepr}, - ClassWalker, EnumWalker, + repr::{Docstring, IntermediateRepr, Walker}, + ClassWalker, EnumWalker, FieldType, }; use crate::{type_check_attributes, GeneratorArgs, TypeCheckAttributes}; @@ -24,6 +24,7 @@ pub(crate) struct TypeBuilder<'ir> { pub(crate) struct TypescriptTypes<'ir> { enums: Vec<TypescriptEnum<'ir>>, classes: Vec<TypescriptClass<'ir>>, + structural_recursive_alias_cycles: Vec<TypescriptTypeAlias<'ir>>, } struct TypescriptEnum<'ir> { @@ -40,6 +41,11 @@ pub struct TypescriptClass<'ir> { pub docstring: Option<String>, } +struct TypescriptTypeAlias<'ir> { + name: Cow<'ir, str>, + target: String, +} + impl<'ir> TryFrom<(&'ir IntermediateRepr, &'ir GeneratorArgs)> for TypescriptTypes<'ir> { type Error = anyhow::Error; @@ -55,6 +61,10 @@ impl<'ir> TryFrom<(&'ir IntermediateRepr, &'ir GeneratorArgs)> for TypescriptTyp .walk_classes() .map(|e| Into::<TypescriptClass>::into(&e)) .collect::<Vec<_>>(), + structural_recursive_alias_cycles: ir + .walk_alias_cycles() + .map(TypescriptTypeAlias::from) + .collect::<Vec<_>>(), }) } } @@ -132,6 +142,21 @@ impl<'ir> From<&ClassWalker<'ir>> for TypescriptClass<'ir> { } } +// TODO: Define AliasWalker to simplify type. +impl<'ir> From<Walker<'ir, (&'ir String, &'ir FieldType)>> for TypescriptTypeAlias<'ir> { + fn from( + Walker { + db, + item: (name, target), + }: Walker<(&'ir String, &'ir FieldType)>, + ) -> Self { + Self { + name: Cow::Borrowed(name), + target: target.to_type_ref(db), + } + } +} + pub fn type_name_for_checks(checks: &TypeCheckAttributes) -> String { checks .0 diff --git a/engine/language_client_codegen/src/typescript/mod.rs b/engine/language_client_codegen/src/typescript/mod.rs index 4f8d61a77..e3178534f 100644 --- a/engine/language_client_codegen/src/typescript/mod.rs +++ b/engine/language_client_codegen/src/typescript/mod.rs @@ -267,7 +267,7 @@ impl ToTypeReferenceInClientDefinition for FieldType { } } FieldType::Class(name) => name.to_string(), - FieldType::Alias { resolution, .. } => resolution.to_type_ref(ir), + FieldType::RecursiveTypeAlias(name) => name.to_owned(), FieldType::List(inner) => match inner.as_ref() { FieldType::Union(_) | FieldType::Optional(_) => { format!("({})[]", inner.to_type_ref(ir)) diff --git a/engine/language_client_codegen/src/typescript/templates/types.ts.j2 b/engine/language_client_codegen/src/typescript/templates/types.ts.j2 index 91ab34165..308967604 100644 --- a/engine/language_client_codegen/src/typescript/templates/types.ts.j2 +++ b/engine/language_client_codegen/src/typescript/templates/types.ts.j2 @@ -52,3 +52,8 @@ export interface {{cls.name}} { {%- endif %} } {% endfor %} + +{#- Type Aliases -#} +{% for alias in structural_recursive_alias_cycles %} +type {{alias.name}} = {{alias.target}} +{% endfor %} diff --git a/integ-tests/baml_src/test-files/functions/output/type-aliases.baml b/integ-tests/baml_src/test-files/functions/output/type-aliases.baml index 5da6d06ef..2e407303d 100644 --- a/integ-tests/baml_src/test-files/functions/output/type-aliases.baml +++ b/integ-tests/baml_src/test-files/functions/output/type-aliases.baml @@ -77,4 +77,60 @@ function AliasWithMultipleAttrs(money: MultipleAttrs) -> MultipleAttrs { {{ ctx.output_format }} "# -} \ No newline at end of file +} + +type RecursiveMapAlias = map<string, RecursiveMapAlias> + +function SimpleRecursiveMapAlias(input: RecursiveMapAlias) -> RecursiveMapAlias { + client "openai/gpt-4o" + prompt r#" + Return the given value: + + {{ input }} + + {{ ctx.output_format }} + "# +} + +type RecursiveListAlias = RecursiveListAlias[] + +function SimpleRecursiveListAlias(input: RecursiveListAlias) -> RecursiveListAlias { + client "openai/gpt-4o" + prompt r#" + Return the given JSON array: + + {{ input }} + + {{ ctx.output_format }} + "# +} + +type RecAliasOne = RecAliasTwo +type RecAliasTwo = RecAliasThree +type RecAliasThree = RecAliasOne[] + +function RecursiveAliasCycle(input: RecAliasOne) -> RecAliasOne { + client "openai/gpt-4o" + prompt r#" + Return the given JSON array: + + {{ input }} + + {{ ctx.output_format }} + "# +} + +type JsonValue = int | string | bool | float | JsonObject | JsonArray +type JsonObject = map<string, JsonValue> +type JsonArray = JsonValue[] + +function JsonTypeAliasCycle(input: JsonValue) -> JsonValue { + client "openai/gpt-4o" + prompt r#" + Return the given input back: + + {{ input }} + + {{ ctx.output_format }} + "# +} diff --git a/integ-tests/python/baml_client/async_client.py b/integ-tests/python/baml_client/async_client.py index 1bb0c3836..f14ad456d 100644 --- a/integ-tests/python/baml_client/async_client.py +++ b/integ-tests/python/baml_client/async_client.py @@ -1453,6 +1453,29 @@ async def InOutSingleLiteralStringMapKey( ) return cast(Dict[Literal["key"], str], raw.cast_to(types, types)) + async def JsonTypeAliasCycle( + self, + input: types.JsonValue, + baml_options: BamlCallOptions = {}, + ) -> types.JsonValue: + __tb__ = baml_options.get("tb", None) + if __tb__ is not None: + tb = __tb__._tb # type: ignore (we know how to use this private attribute) + else: + tb = None + __cr__ = baml_options.get("client_registry", None) + + raw = await self.__runtime.call_function( + "JsonTypeAliasCycle", + { + "input": input, + }, + self.__ctx_manager.get(), + tb, + __cr__, + ) + return cast(types.JsonValue, raw.cast_to(types, types)) + async def LiteralUnionsTest( self, input: str, @@ -1867,6 +1890,29 @@ async def PromptTestStreaming( ) return cast(str, raw.cast_to(types, types)) + async def RecursiveAliasCycle( + self, + input: types.RecAliasOne, + baml_options: BamlCallOptions = {}, + ) -> types.RecAliasOne: + __tb__ = baml_options.get("tb", None) + if __tb__ is not None: + tb = __tb__._tb # type: ignore (we know how to use this private attribute) + else: + tb = None + __cr__ = baml_options.get("client_registry", None) + + raw = await self.__runtime.call_function( + "RecursiveAliasCycle", + { + "input": input, + }, + self.__ctx_manager.get(), + tb, + __cr__, + ) + return cast(types.RecAliasOne, raw.cast_to(types, types)) + async def RecursiveClassWithAliasIndirection( self, cls: types.NodeWithAliasIndirection, @@ -1982,6 +2028,52 @@ async def SchemaDescriptions( ) return cast(types.Schema, raw.cast_to(types, types)) + async def SimpleRecursiveListAlias( + self, + input: types.RecursiveListAlias, + baml_options: BamlCallOptions = {}, + ) -> types.RecursiveListAlias: + __tb__ = baml_options.get("tb", None) + if __tb__ is not None: + tb = __tb__._tb # type: ignore (we know how to use this private attribute) + else: + tb = None + __cr__ = baml_options.get("client_registry", None) + + raw = await self.__runtime.call_function( + "SimpleRecursiveListAlias", + { + "input": input, + }, + self.__ctx_manager.get(), + tb, + __cr__, + ) + return cast(types.RecursiveListAlias, raw.cast_to(types, types)) + + async def SimpleRecursiveMapAlias( + self, + input: types.RecursiveMapAlias, + baml_options: BamlCallOptions = {}, + ) -> types.RecursiveMapAlias: + __tb__ = baml_options.get("tb", None) + if __tb__ is not None: + tb = __tb__._tb # type: ignore (we know how to use this private attribute) + else: + tb = None + __cr__ = baml_options.get("client_registry", None) + + raw = await self.__runtime.call_function( + "SimpleRecursiveMapAlias", + { + "input": input, + }, + self.__ctx_manager.get(), + tb, + __cr__, + ) + return cast(types.RecursiveMapAlias, raw.cast_to(types, types)) + async def StreamBigNumbers( self, digits: int, @@ -4819,6 +4911,36 @@ def InOutSingleLiteralStringMapKey( self.__ctx_manager.get(), ) + def JsonTypeAliasCycle( + self, + input: types.JsonValue, + baml_options: BamlCallOptions = {}, + ) -> baml_py.BamlStream[types.JsonValue, types.JsonValue]: + __tb__ = baml_options.get("tb", None) + if __tb__ is not None: + tb = __tb__._tb # type: ignore (we know how to use this private attribute) + else: + tb = None + __cr__ = baml_options.get("client_registry", None) + + raw = self.__runtime.stream_function( + "JsonTypeAliasCycle", + { + "input": input, + }, + None, + self.__ctx_manager.get(), + tb, + __cr__, + ) + + return baml_py.BamlStream[types.JsonValue, types.JsonValue]( + raw, + lambda x: cast(types.JsonValue, x.cast_to(types, partial_types)), + lambda x: cast(types.JsonValue, x.cast_to(types, types)), + self.__ctx_manager.get(), + ) + def LiteralUnionsTest( self, input: str, @@ -5357,6 +5479,36 @@ def PromptTestStreaming( self.__ctx_manager.get(), ) + def RecursiveAliasCycle( + self, + input: types.RecAliasOne, + baml_options: BamlCallOptions = {}, + ) -> baml_py.BamlStream[types.RecAliasOne, types.RecAliasOne]: + __tb__ = baml_options.get("tb", None) + if __tb__ is not None: + tb = __tb__._tb # type: ignore (we know how to use this private attribute) + else: + tb = None + __cr__ = baml_options.get("client_registry", None) + + raw = self.__runtime.stream_function( + "RecursiveAliasCycle", + { + "input": input, + }, + None, + self.__ctx_manager.get(), + tb, + __cr__, + ) + + return baml_py.BamlStream[types.RecAliasOne, types.RecAliasOne]( + raw, + lambda x: cast(types.RecAliasOne, x.cast_to(types, partial_types)), + lambda x: cast(types.RecAliasOne, x.cast_to(types, types)), + self.__ctx_manager.get(), + ) + def RecursiveClassWithAliasIndirection( self, cls: types.NodeWithAliasIndirection, @@ -5507,6 +5659,66 @@ def SchemaDescriptions( self.__ctx_manager.get(), ) + def SimpleRecursiveListAlias( + self, + input: types.RecursiveListAlias, + baml_options: BamlCallOptions = {}, + ) -> baml_py.BamlStream[types.RecursiveListAlias, types.RecursiveListAlias]: + __tb__ = baml_options.get("tb", None) + if __tb__ is not None: + tb = __tb__._tb # type: ignore (we know how to use this private attribute) + else: + tb = None + __cr__ = baml_options.get("client_registry", None) + + raw = self.__runtime.stream_function( + "SimpleRecursiveListAlias", + { + "input": input, + }, + None, + self.__ctx_manager.get(), + tb, + __cr__, + ) + + return baml_py.BamlStream[types.RecursiveListAlias, types.RecursiveListAlias]( + raw, + lambda x: cast(types.RecursiveListAlias, x.cast_to(types, partial_types)), + lambda x: cast(types.RecursiveListAlias, x.cast_to(types, types)), + self.__ctx_manager.get(), + ) + + def SimpleRecursiveMapAlias( + self, + input: types.RecursiveMapAlias, + baml_options: BamlCallOptions = {}, + ) -> baml_py.BamlStream[types.RecursiveMapAlias, types.RecursiveMapAlias]: + __tb__ = baml_options.get("tb", None) + if __tb__ is not None: + tb = __tb__._tb # type: ignore (we know how to use this private attribute) + else: + tb = None + __cr__ = baml_options.get("client_registry", None) + + raw = self.__runtime.stream_function( + "SimpleRecursiveMapAlias", + { + "input": input, + }, + None, + self.__ctx_manager.get(), + tb, + __cr__, + ) + + return baml_py.BamlStream[types.RecursiveMapAlias, types.RecursiveMapAlias]( + raw, + lambda x: cast(types.RecursiveMapAlias, x.cast_to(types, partial_types)), + lambda x: cast(types.RecursiveMapAlias, x.cast_to(types, types)), + self.__ctx_manager.get(), + ) + def StreamBigNumbers( self, digits: int, diff --git a/integ-tests/python/baml_client/inlinedbaml.py b/integ-tests/python/baml_client/inlinedbaml.py index 43fe97e37..fcb867aef 100644 --- a/integ-tests/python/baml_client/inlinedbaml.py +++ b/integ-tests/python/baml_client/inlinedbaml.py @@ -83,7 +83,7 @@ "test-files/functions/output/recursive-type-aliases.baml": "class LinkedListAliasNode {\n value int\n next LinkedListAliasNode?\n}\n\n// Simple alias that points to recursive type.\ntype LinkedListAlias = LinkedListAliasNode\n\nfunction AliasThatPointsToRecursiveType(list: LinkedListAlias) -> LinkedListAlias {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given linked list back:\n \n {{ list }}\n \n {{ ctx.output_format }}\n \"#\n}\n\n// Class that points to an alias that points to a recursive type.\nclass ClassToRecAlias {\n list LinkedListAlias\n}\n\nfunction ClassThatPointsToRecursiveClassThroughAlias(cls: ClassToRecAlias) -> ClassToRecAlias {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given object back:\n \n {{ cls }}\n \n {{ ctx.output_format }}\n \"#\n}\n\n// This is tricky cause this class should be hoisted, but classes and aliases\n// are two different types in the AST. This test will make sure they can interop.\nclass NodeWithAliasIndirection {\n value int\n next NodeIndirection?\n}\n\ntype NodeIndirection = NodeWithAliasIndirection\n\nfunction RecursiveClassWithAliasIndirection(cls: NodeWithAliasIndirection) -> NodeWithAliasIndirection {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given object back:\n \n {{ cls }}\n \n {{ ctx.output_format }}\n \"#\n}\n", "test-files/functions/output/serialization-error.baml": "class DummyOutput {\n nonce string\n nonce2 string\n @@dynamic\n}\n\nfunction DummyOutputFunction(input: string) -> DummyOutput {\n client GPT35\n prompt #\"\n Say \"hello there\".\n \"#\n}", "test-files/functions/output/string-list.baml": "function FnOutputStringList(input: string) -> string[] {\n client GPT35\n prompt #\"\n Return a list of strings in json format like [\"string1\", \"string2\", \"string3\"].\n\n JSON:\n \"#\n}\n\ntest FnOutputStringList {\n functions [FnOutputStringList]\n args {\n input \"example input\"\n }\n}\n", - "test-files/functions/output/type-aliases.baml": "type Primitive = int | string | bool | float\n\ntype List = string[]\n\ntype Graph = map<string, string[]>\n\ntype Combination = Primitive | List | Graph\n\nfunction PrimitiveAlias(p: Primitive) -> Primitive {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given value back: {{ p }}\n \"#\n}\n\nfunction MapAlias(m: Graph) -> Graph {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given Graph back:\n\n {{ m }}\n\n {{ ctx.output_format }}\n \"#\n}\n\nfunction NestedAlias(c: Combination) -> Combination {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given value back:\n\n {{ c }}\n\n {{ ctx.output_format }}\n \"#\n}\n\n// Test attribute merging.\ntype Currency = int @check(gt_ten, {{ this > 10 }})\ntype Amount = Currency @assert({{ this > 0 }})\n\nclass MergeAttrs {\n amount Amount @description(\"In USD\")\n}\n\n// This should be allowed.\ntype MultipleAttrs = int @assert({{ this > 0 }}) @check(gt_ten, {{ this > 10 }})\n\nfunction MergeAliasAttributes(money: int) -> MergeAttrs {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given integer in the specified format:\n\n {{ money }}\n\n {{ ctx.output_format }}\n \"#\n}\n\nfunction ReturnAliasWithMergedAttributes(money: Amount) -> Amount {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given integer without additional context:\n\n {{ money }}\n\n {{ ctx.output_format }}\n \"#\n}\n\nfunction AliasWithMultipleAttrs(money: MultipleAttrs) -> MultipleAttrs {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given integer without additional context:\n\n {{ money }}\n\n {{ ctx.output_format }}\n \"#\n}", + "test-files/functions/output/type-aliases.baml": "type Primitive = int | string | bool | float\n\ntype List = string[]\n\ntype Graph = map<string, string[]>\n\ntype Combination = Primitive | List | Graph\n\nfunction PrimitiveAlias(p: Primitive) -> Primitive {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given value back: {{ p }}\n \"#\n}\n\nfunction MapAlias(m: Graph) -> Graph {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given Graph back:\n\n {{ m }}\n\n {{ ctx.output_format }}\n \"#\n}\n\nfunction NestedAlias(c: Combination) -> Combination {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given value back:\n\n {{ c }}\n\n {{ ctx.output_format }}\n \"#\n}\n\n// Test attribute merging.\ntype Currency = int @check(gt_ten, {{ this > 10 }})\ntype Amount = Currency @assert({{ this > 0 }})\n\nclass MergeAttrs {\n amount Amount @description(\"In USD\")\n}\n\n// This should be allowed.\ntype MultipleAttrs = int @assert({{ this > 0 }}) @check(gt_ten, {{ this > 10 }})\n\nfunction MergeAliasAttributes(money: int) -> MergeAttrs {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given integer in the specified format:\n\n {{ money }}\n\n {{ ctx.output_format }}\n \"#\n}\n\nfunction ReturnAliasWithMergedAttributes(money: Amount) -> Amount {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given integer without additional context:\n\n {{ money }}\n\n {{ ctx.output_format }}\n \"#\n}\n\nfunction AliasWithMultipleAttrs(money: MultipleAttrs) -> MultipleAttrs {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given integer without additional context:\n\n {{ money }}\n\n {{ ctx.output_format }}\n \"#\n}\n\ntype RecursiveMapAlias = map<string, RecursiveMapAlias>\n\nfunction SimpleRecursiveMapAlias(input: RecursiveMapAlias) -> RecursiveMapAlias {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given value:\n\n {{ input }}\n\n {{ ctx.output_format }}\n \"#\n}\n\ntype RecursiveListAlias = RecursiveListAlias[]\n\nfunction SimpleRecursiveListAlias(input: RecursiveListAlias) -> RecursiveListAlias {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given JSON array:\n\n {{ input }}\n\n {{ ctx.output_format }}\n \"#\n}\n\ntype RecAliasOne = RecAliasTwo\ntype RecAliasTwo = RecAliasThree\ntype RecAliasThree = RecAliasOne[]\n\nfunction RecursiveAliasCycle(input: RecAliasOne) -> RecAliasOne {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given JSON array:\n\n {{ input }}\n\n {{ ctx.output_format }}\n \"#\n}\n\ntype JsonValue = int | string | bool | float | JsonObject | JsonArray\ntype JsonObject = map<string, JsonValue>\ntype JsonArray = JsonValue[]\n\nfunction JsonTypeAliasCycle(input: JsonValue) -> JsonValue {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given input back:\n\n {{ input }}\n\n {{ ctx.output_format }}\n \"#\n}\n", "test-files/functions/output/unions.baml": "class UnionTest_ReturnType {\n prop1 string | bool\n prop2 (float | bool)[]\n prop3 (bool[] | int[])\n}\n\nfunction UnionTest_Function(input: string | bool) -> UnionTest_ReturnType {\n client GPT35\n prompt #\"\n Return a JSON blob with this schema: \n {{ctx.output_format}}\n\n JSON:\n \"#\n}\n\ntest UnionTest_Function {\n functions [UnionTest_Function]\n args {\n input \"example input\"\n }\n}\n", "test-files/functions/prompts/no-chat-messages.baml": "\n\nfunction PromptTestClaude(input: string) -> string {\n client Sonnet\n prompt #\"\n Tell me a haiku about {{ input }}\n \"#\n}\n\n\nfunction PromptTestStreaming(input: string) -> string {\n client GPT35\n prompt #\"\n Tell me a short story about {{ input }}\n \"#\n}\n\ntest TestName {\n functions [PromptTestStreaming]\n args {\n input #\"\n hello world\n \"#\n }\n}\n", "test-files/functions/prompts/with-chat-messages.baml": "\nfunction PromptTestOpenAIChat(input: string) -> string {\n client GPT35\n prompt #\"\n {{ _.role(\"system\") }}\n You are an assistant that always responds in a very excited way with emojis and also outputs this word 4 times after giving a response: {{ input }}\n \n {{ _.role(\"user\") }}\n Tell me a haiku about {{ input }}\n \"#\n}\n\nfunction PromptTestOpenAIChatNoSystem(input: string) -> string {\n client GPT35\n prompt #\"\n You are an assistant that always responds in a very excited way with emojis and also outputs this word 4 times after giving a response: {{ input }}\n \n {{ _.role(\"user\") }}\n Tell me a haiku about {{ input }}\n \"#\n}\n\nfunction PromptTestClaudeChat(input: string) -> string {\n client Claude\n prompt #\"\n {{ _.role(\"system\") }}\n You are an assistant that always responds in a very excited way with emojis and also outputs this word 4 times after giving a response: {{ input }}\n \n {{ _.role(\"user\") }}\n Tell me a haiku about {{ input }}\n \"#\n}\n\nfunction PromptTestClaudeChatNoSystem(input: string) -> string {\n client Claude\n prompt #\"\n You are an assistant that always responds in a very excited way with emojis and also outputs this word 4 times after giving a response: {{ input }}\n \n {{ _.role(\"user\") }}\n Tell me a haiku about {{ input }}\n \"#\n}\n\ntest TestSystemAndNonSystemChat1 {\n functions [PromptTestClaude, PromptTestOpenAI, PromptTestOpenAIChat, PromptTestOpenAIChatNoSystem, PromptTestClaudeChat, PromptTestClaudeChatNoSystem]\n args {\n input \"cats\"\n }\n}\n\ntest TestSystemAndNonSystemChat2 {\n functions [PromptTestClaude, PromptTestOpenAI, PromptTestOpenAIChat, PromptTestOpenAIChatNoSystem, PromptTestClaudeChat, PromptTestClaudeChatNoSystem]\n args {\n input \"lion\"\n }\n}", diff --git a/integ-tests/python/baml_client/sync_client.py b/integ-tests/python/baml_client/sync_client.py index 1202e8360..e60b62b97 100644 --- a/integ-tests/python/baml_client/sync_client.py +++ b/integ-tests/python/baml_client/sync_client.py @@ -1450,6 +1450,29 @@ def InOutSingleLiteralStringMapKey( ) return cast(Dict[Literal["key"], str], raw.cast_to(types, types)) + def JsonTypeAliasCycle( + self, + input: types.JsonValue, + baml_options: BamlCallOptions = {}, + ) -> types.JsonValue: + __tb__ = baml_options.get("tb", None) + if __tb__ is not None: + tb = __tb__._tb # type: ignore (we know how to use this private attribute) + else: + tb = None + __cr__ = baml_options.get("client_registry", None) + + raw = self.__runtime.call_function_sync( + "JsonTypeAliasCycle", + { + "input": input, + }, + self.__ctx_manager.get(), + tb, + __cr__, + ) + return cast(types.JsonValue, raw.cast_to(types, types)) + def LiteralUnionsTest( self, input: str, @@ -1864,6 +1887,29 @@ def PromptTestStreaming( ) return cast(str, raw.cast_to(types, types)) + def RecursiveAliasCycle( + self, + input: types.RecAliasOne, + baml_options: BamlCallOptions = {}, + ) -> types.RecAliasOne: + __tb__ = baml_options.get("tb", None) + if __tb__ is not None: + tb = __tb__._tb # type: ignore (we know how to use this private attribute) + else: + tb = None + __cr__ = baml_options.get("client_registry", None) + + raw = self.__runtime.call_function_sync( + "RecursiveAliasCycle", + { + "input": input, + }, + self.__ctx_manager.get(), + tb, + __cr__, + ) + return cast(types.RecAliasOne, raw.cast_to(types, types)) + def RecursiveClassWithAliasIndirection( self, cls: types.NodeWithAliasIndirection, @@ -1979,6 +2025,52 @@ def SchemaDescriptions( ) return cast(types.Schema, raw.cast_to(types, types)) + def SimpleRecursiveListAlias( + self, + input: types.RecursiveListAlias, + baml_options: BamlCallOptions = {}, + ) -> types.RecursiveListAlias: + __tb__ = baml_options.get("tb", None) + if __tb__ is not None: + tb = __tb__._tb # type: ignore (we know how to use this private attribute) + else: + tb = None + __cr__ = baml_options.get("client_registry", None) + + raw = self.__runtime.call_function_sync( + "SimpleRecursiveListAlias", + { + "input": input, + }, + self.__ctx_manager.get(), + tb, + __cr__, + ) + return cast(types.RecursiveListAlias, raw.cast_to(types, types)) + + def SimpleRecursiveMapAlias( + self, + input: types.RecursiveMapAlias, + baml_options: BamlCallOptions = {}, + ) -> types.RecursiveMapAlias: + __tb__ = baml_options.get("tb", None) + if __tb__ is not None: + tb = __tb__._tb # type: ignore (we know how to use this private attribute) + else: + tb = None + __cr__ = baml_options.get("client_registry", None) + + raw = self.__runtime.call_function_sync( + "SimpleRecursiveMapAlias", + { + "input": input, + }, + self.__ctx_manager.get(), + tb, + __cr__, + ) + return cast(types.RecursiveMapAlias, raw.cast_to(types, types)) + def StreamBigNumbers( self, digits: int, @@ -4817,6 +4909,36 @@ def InOutSingleLiteralStringMapKey( self.__ctx_manager.get(), ) + def JsonTypeAliasCycle( + self, + input: types.JsonValue, + baml_options: BamlCallOptions = {}, + ) -> baml_py.BamlSyncStream[types.JsonValue, types.JsonValue]: + __tb__ = baml_options.get("tb", None) + if __tb__ is not None: + tb = __tb__._tb # type: ignore (we know how to use this private attribute) + else: + tb = None + __cr__ = baml_options.get("client_registry", None) + + raw = self.__runtime.stream_function_sync( + "JsonTypeAliasCycle", + { + "input": input, + }, + None, + self.__ctx_manager.get(), + tb, + __cr__, + ) + + return baml_py.BamlSyncStream[types.JsonValue, types.JsonValue]( + raw, + lambda x: cast(types.JsonValue, x.cast_to(types, partial_types)), + lambda x: cast(types.JsonValue, x.cast_to(types, types)), + self.__ctx_manager.get(), + ) + def LiteralUnionsTest( self, input: str, @@ -5355,6 +5477,36 @@ def PromptTestStreaming( self.__ctx_manager.get(), ) + def RecursiveAliasCycle( + self, + input: types.RecAliasOne, + baml_options: BamlCallOptions = {}, + ) -> baml_py.BamlSyncStream[types.RecAliasOne, types.RecAliasOne]: + __tb__ = baml_options.get("tb", None) + if __tb__ is not None: + tb = __tb__._tb # type: ignore (we know how to use this private attribute) + else: + tb = None + __cr__ = baml_options.get("client_registry", None) + + raw = self.__runtime.stream_function_sync( + "RecursiveAliasCycle", + { + "input": input, + }, + None, + self.__ctx_manager.get(), + tb, + __cr__, + ) + + return baml_py.BamlSyncStream[types.RecAliasOne, types.RecAliasOne]( + raw, + lambda x: cast(types.RecAliasOne, x.cast_to(types, partial_types)), + lambda x: cast(types.RecAliasOne, x.cast_to(types, types)), + self.__ctx_manager.get(), + ) + def RecursiveClassWithAliasIndirection( self, cls: types.NodeWithAliasIndirection, @@ -5505,6 +5657,66 @@ def SchemaDescriptions( self.__ctx_manager.get(), ) + def SimpleRecursiveListAlias( + self, + input: types.RecursiveListAlias, + baml_options: BamlCallOptions = {}, + ) -> baml_py.BamlSyncStream[types.RecursiveListAlias, types.RecursiveListAlias]: + __tb__ = baml_options.get("tb", None) + if __tb__ is not None: + tb = __tb__._tb # type: ignore (we know how to use this private attribute) + else: + tb = None + __cr__ = baml_options.get("client_registry", None) + + raw = self.__runtime.stream_function_sync( + "SimpleRecursiveListAlias", + { + "input": input, + }, + None, + self.__ctx_manager.get(), + tb, + __cr__, + ) + + return baml_py.BamlSyncStream[types.RecursiveListAlias, types.RecursiveListAlias]( + raw, + lambda x: cast(types.RecursiveListAlias, x.cast_to(types, partial_types)), + lambda x: cast(types.RecursiveListAlias, x.cast_to(types, types)), + self.__ctx_manager.get(), + ) + + def SimpleRecursiveMapAlias( + self, + input: types.RecursiveMapAlias, + baml_options: BamlCallOptions = {}, + ) -> baml_py.BamlSyncStream[types.RecursiveMapAlias, types.RecursiveMapAlias]: + __tb__ = baml_options.get("tb", None) + if __tb__ is not None: + tb = __tb__._tb # type: ignore (we know how to use this private attribute) + else: + tb = None + __cr__ = baml_options.get("client_registry", None) + + raw = self.__runtime.stream_function_sync( + "SimpleRecursiveMapAlias", + { + "input": input, + }, + None, + self.__ctx_manager.get(), + tb, + __cr__, + ) + + return baml_py.BamlSyncStream[types.RecursiveMapAlias, types.RecursiveMapAlias]( + raw, + lambda x: cast(types.RecursiveMapAlias, x.cast_to(types, partial_types)), + lambda x: cast(types.RecursiveMapAlias, x.cast_to(types, types)), + self.__ctx_manager.get(), + ) + def StreamBigNumbers( self, digits: int, diff --git a/integ-tests/python/baml_client/types.py b/integ-tests/python/baml_client/types.py index 1b64063b6..195eb2369 100644 --- a/integ-tests/python/baml_client/types.py +++ b/integ-tests/python/baml_client/types.py @@ -16,7 +16,7 @@ import baml_py from enum import Enum from pydantic import BaseModel, ConfigDict -from typing import Dict, Generic, List, Literal, Optional, TypeVar, Union +from typing import Dict, Generic, List, Literal, Optional, TypeVar, Union, TypeAlias T = TypeVar('T') @@ -478,3 +478,19 @@ class UnionTest_ReturnType(BaseModel): class WithReasoning(BaseModel): value: str reasoning: str + +RecursiveMapAlias: TypeAlias = Dict[str, "RecursiveMapAlias"] + +RecursiveListAlias: TypeAlias = List["RecursiveListAlias"] + +RecAliasOne: TypeAlias = "RecAliasTwo" + +RecAliasTwo: TypeAlias = "RecAliasThree" + +RecAliasThree: TypeAlias = List["RecAliasOne"] + +JsonValue: TypeAlias = Union[int, str, bool, float, "JsonObject", "JsonArray"] + +JsonObject: TypeAlias = Dict[str, "JsonValue"] + +JsonArray: TypeAlias = List["JsonValue"] diff --git a/integ-tests/python/tests/test_functions.py b/integ-tests/python/tests/test_functions.py index 11d92c800..ead15c024 100644 --- a/integ-tests/python/tests/test_functions.py +++ b/integ-tests/python/tests/test_functions.py @@ -314,6 +314,47 @@ async def test_alias_with_multiple_attrs(self): assert res.value == 123 assert res.checks["gt_ten"].status == "succeeded" + @pytest.mark.asyncio + async def test_simple_recursive_map_alias(self): + res = await b.SimpleRecursiveMapAlias({"one": {"two": {"three": {}}}}) + assert res == {"one": {"two": {"three": {}}}} + + @pytest.mark.asyncio + async def test_simple_recursive_list_alias(self): + res = await b.SimpleRecursiveListAlias([[], [], [[]]]) + assert res == [[], [], [[]]] + + @pytest.mark.asyncio + async def test_recursive_alias_cycles(self): + res = await b.RecursiveAliasCycle([[], [], [[]]]) + assert res == [[], [], [[]]] + + @pytest.mark.asyncio + async def test_json_type_alias_cycle(self): + data = { + "number": 1, + "string": "test", + "bool": True, + "list": [1, 2, 3], + "object": {"number": 1, "string": "test", "bool": True, "list": [1, 2, 3]}, + "json": { + "number": 1, + "string": "test", + "bool": True, + "list": [1, 2, 3], + "object": { + "number": 1, + "string": "test", + "bool": True, + "list": [1, 2, 3], + }, + }, + } + + res = await b.JsonTypeAliasCycle(data) + assert res == data + assert res["json"]["object"]["list"] == [1, 2, 3] + class MyCustomClass(NamedArgsSingleClass): date: datetime.datetime diff --git a/integ-tests/ruby/baml_client/client.rb b/integ-tests/ruby/baml_client/client.rb index e8cc900c6..fb0bb086d 100644 --- a/integ-tests/ruby/baml_client/client.rb +++ b/integ-tests/ruby/baml_client/client.rb @@ -2002,6 +2002,38 @@ def InOutSingleLiteralStringMapKey( (raw.parsed_using_types(Baml::Types)) end + sig { + params( + varargs: T.untyped, + input: T.anything, + baml_options: T::Hash[Symbol, T.any(Baml::TypeBuilder, Baml::ClientRegistry)] + ).returns(T.anything) + } + def JsonTypeAliasCycle( + *varargs, + input:, + baml_options: {} + ) + if varargs.any? + + raise ArgumentError.new("JsonTypeAliasCycle may only be called with keyword arguments") + end + if (baml_options.keys - [:client_registry, :tb]).any? + raise ArgumentError.new("Received unknown keys in baml_options (valid keys: :client_registry, :tb): #{baml_options.keys - [:client_registry, :tb]}") + end + + raw = @runtime.call_function( + "JsonTypeAliasCycle", + { + input: input, + }, + @ctx_manager, + baml_options[:tb]&.instance_variable_get(:@registry), + baml_options[:client_registry], + ) + (raw.parsed_using_types(Baml::Types)) + end + sig { params( varargs: T.untyped, @@ -2578,6 +2610,38 @@ def PromptTestStreaming( (raw.parsed_using_types(Baml::Types)) end + sig { + params( + varargs: T.untyped, + input: T.anything, + baml_options: T::Hash[Symbol, T.any(Baml::TypeBuilder, Baml::ClientRegistry)] + ).returns(T.anything) + } + def RecursiveAliasCycle( + *varargs, + input:, + baml_options: {} + ) + if varargs.any? + + raise ArgumentError.new("RecursiveAliasCycle may only be called with keyword arguments") + end + if (baml_options.keys - [:client_registry, :tb]).any? + raise ArgumentError.new("Received unknown keys in baml_options (valid keys: :client_registry, :tb): #{baml_options.keys - [:client_registry, :tb]}") + end + + raw = @runtime.call_function( + "RecursiveAliasCycle", + { + input: input, + }, + @ctx_manager, + baml_options[:tb]&.instance_variable_get(:@registry), + baml_options[:client_registry], + ) + (raw.parsed_using_types(Baml::Types)) + end + sig { params( varargs: T.untyped, @@ -2738,6 +2802,70 @@ def SchemaDescriptions( (raw.parsed_using_types(Baml::Types)) end + sig { + params( + varargs: T.untyped, + input: T.anything, + baml_options: T::Hash[Symbol, T.any(Baml::TypeBuilder, Baml::ClientRegistry)] + ).returns(T.anything) + } + def SimpleRecursiveListAlias( + *varargs, + input:, + baml_options: {} + ) + if varargs.any? + + raise ArgumentError.new("SimpleRecursiveListAlias may only be called with keyword arguments") + end + if (baml_options.keys - [:client_registry, :tb]).any? + raise ArgumentError.new("Received unknown keys in baml_options (valid keys: :client_registry, :tb): #{baml_options.keys - [:client_registry, :tb]}") + end + + raw = @runtime.call_function( + "SimpleRecursiveListAlias", + { + input: input, + }, + @ctx_manager, + baml_options[:tb]&.instance_variable_get(:@registry), + baml_options[:client_registry], + ) + (raw.parsed_using_types(Baml::Types)) + end + + sig { + params( + varargs: T.untyped, + input: T.anything, + baml_options: T::Hash[Symbol, T.any(Baml::TypeBuilder, Baml::ClientRegistry)] + ).returns(T.anything) + } + def SimpleRecursiveMapAlias( + *varargs, + input:, + baml_options: {} + ) + if varargs.any? + + raise ArgumentError.new("SimpleRecursiveMapAlias may only be called with keyword arguments") + end + if (baml_options.keys - [:client_registry, :tb]).any? + raise ArgumentError.new("Received unknown keys in baml_options (valid keys: :client_registry, :tb): #{baml_options.keys - [:client_registry, :tb]}") + end + + raw = @runtime.call_function( + "SimpleRecursiveMapAlias", + { + input: input, + }, + @ctx_manager, + baml_options[:tb]&.instance_variable_get(:@registry), + baml_options[:client_registry], + ) + (raw.parsed_using_types(Baml::Types)) + end + sig { params( varargs: T.untyped, @@ -6262,6 +6390,41 @@ def InOutSingleLiteralStringMapKey( ) end + sig { + params( + varargs: T.untyped, + input: T.anything, + baml_options: T::Hash[Symbol, T.any(Baml::TypeBuilder, Baml::ClientRegistry)] + ).returns(Baml::BamlStream[T.anything]) + } + def JsonTypeAliasCycle( + *varargs, + input:, + baml_options: {} + ) + if varargs.any? + + raise ArgumentError.new("JsonTypeAliasCycle may only be called with keyword arguments") + end + if (baml_options.keys - [:client_registry, :tb]).any? + raise ArgumentError.new("Received unknown keys in baml_options (valid keys: :client_registry, :tb): #{baml_options.keys - [:client_registry, :tb]}") + end + + raw = @runtime.stream_function( + "JsonTypeAliasCycle", + { + input: input, + }, + @ctx_manager, + baml_options[:tb]&.instance_variable_get(:@registry), + baml_options[:client_registry], + ) + Baml::BamlStream[T.anything, T.anything].new( + ffi_stream: raw, + ctx_manager: @ctx_manager + ) + end + sig { params( varargs: T.untyped, @@ -6892,6 +7055,41 @@ def PromptTestStreaming( ) end + sig { + params( + varargs: T.untyped, + input: T.anything, + baml_options: T::Hash[Symbol, T.any(Baml::TypeBuilder, Baml::ClientRegistry)] + ).returns(Baml::BamlStream[T.anything]) + } + def RecursiveAliasCycle( + *varargs, + input:, + baml_options: {} + ) + if varargs.any? + + raise ArgumentError.new("RecursiveAliasCycle may only be called with keyword arguments") + end + if (baml_options.keys - [:client_registry, :tb]).any? + raise ArgumentError.new("Received unknown keys in baml_options (valid keys: :client_registry, :tb): #{baml_options.keys - [:client_registry, :tb]}") + end + + raw = @runtime.stream_function( + "RecursiveAliasCycle", + { + input: input, + }, + @ctx_manager, + baml_options[:tb]&.instance_variable_get(:@registry), + baml_options[:client_registry], + ) + Baml::BamlStream[T.anything, T.anything].new( + ffi_stream: raw, + ctx_manager: @ctx_manager + ) + end + sig { params( varargs: T.untyped, @@ -7067,6 +7265,76 @@ def SchemaDescriptions( ) end + sig { + params( + varargs: T.untyped, + input: T.anything, + baml_options: T::Hash[Symbol, T.any(Baml::TypeBuilder, Baml::ClientRegistry)] + ).returns(Baml::BamlStream[T.anything]) + } + def SimpleRecursiveListAlias( + *varargs, + input:, + baml_options: {} + ) + if varargs.any? + + raise ArgumentError.new("SimpleRecursiveListAlias may only be called with keyword arguments") + end + if (baml_options.keys - [:client_registry, :tb]).any? + raise ArgumentError.new("Received unknown keys in baml_options (valid keys: :client_registry, :tb): #{baml_options.keys - [:client_registry, :tb]}") + end + + raw = @runtime.stream_function( + "SimpleRecursiveListAlias", + { + input: input, + }, + @ctx_manager, + baml_options[:tb]&.instance_variable_get(:@registry), + baml_options[:client_registry], + ) + Baml::BamlStream[T.anything, T.anything].new( + ffi_stream: raw, + ctx_manager: @ctx_manager + ) + end + + sig { + params( + varargs: T.untyped, + input: T.anything, + baml_options: T::Hash[Symbol, T.any(Baml::TypeBuilder, Baml::ClientRegistry)] + ).returns(Baml::BamlStream[T.anything]) + } + def SimpleRecursiveMapAlias( + *varargs, + input:, + baml_options: {} + ) + if varargs.any? + + raise ArgumentError.new("SimpleRecursiveMapAlias may only be called with keyword arguments") + end + if (baml_options.keys - [:client_registry, :tb]).any? + raise ArgumentError.new("Received unknown keys in baml_options (valid keys: :client_registry, :tb): #{baml_options.keys - [:client_registry, :tb]}") + end + + raw = @runtime.stream_function( + "SimpleRecursiveMapAlias", + { + input: input, + }, + @ctx_manager, + baml_options[:tb]&.instance_variable_get(:@registry), + baml_options[:client_registry], + ) + Baml::BamlStream[T.anything, T.anything].new( + ffi_stream: raw, + ctx_manager: @ctx_manager + ) + end + sig { params( varargs: T.untyped, diff --git a/integ-tests/ruby/baml_client/inlined.rb b/integ-tests/ruby/baml_client/inlined.rb index 0751e9f54..ca979d424 100644 --- a/integ-tests/ruby/baml_client/inlined.rb +++ b/integ-tests/ruby/baml_client/inlined.rb @@ -83,7 +83,7 @@ module Inlined "test-files/functions/output/recursive-type-aliases.baml" => "class LinkedListAliasNode {\n value int\n next LinkedListAliasNode?\n}\n\n// Simple alias that points to recursive type.\ntype LinkedListAlias = LinkedListAliasNode\n\nfunction AliasThatPointsToRecursiveType(list: LinkedListAlias) -> LinkedListAlias {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given linked list back:\n \n {{ list }}\n \n {{ ctx.output_format }}\n \"#\n}\n\n// Class that points to an alias that points to a recursive type.\nclass ClassToRecAlias {\n list LinkedListAlias\n}\n\nfunction ClassThatPointsToRecursiveClassThroughAlias(cls: ClassToRecAlias) -> ClassToRecAlias {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given object back:\n \n {{ cls }}\n \n {{ ctx.output_format }}\n \"#\n}\n\n// This is tricky cause this class should be hoisted, but classes and aliases\n// are two different types in the AST. This test will make sure they can interop.\nclass NodeWithAliasIndirection {\n value int\n next NodeIndirection?\n}\n\ntype NodeIndirection = NodeWithAliasIndirection\n\nfunction RecursiveClassWithAliasIndirection(cls: NodeWithAliasIndirection) -> NodeWithAliasIndirection {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given object back:\n \n {{ cls }}\n \n {{ ctx.output_format }}\n \"#\n}\n", "test-files/functions/output/serialization-error.baml" => "class DummyOutput {\n nonce string\n nonce2 string\n @@dynamic\n}\n\nfunction DummyOutputFunction(input: string) -> DummyOutput {\n client GPT35\n prompt #\"\n Say \"hello there\".\n \"#\n}", "test-files/functions/output/string-list.baml" => "function FnOutputStringList(input: string) -> string[] {\n client GPT35\n prompt #\"\n Return a list of strings in json format like [\"string1\", \"string2\", \"string3\"].\n\n JSON:\n \"#\n}\n\ntest FnOutputStringList {\n functions [FnOutputStringList]\n args {\n input \"example input\"\n }\n}\n", - "test-files/functions/output/type-aliases.baml" => "type Primitive = int | string | bool | float\n\ntype List = string[]\n\ntype Graph = map<string, string[]>\n\ntype Combination = Primitive | List | Graph\n\nfunction PrimitiveAlias(p: Primitive) -> Primitive {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given value back: {{ p }}\n \"#\n}\n\nfunction MapAlias(m: Graph) -> Graph {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given Graph back:\n\n {{ m }}\n\n {{ ctx.output_format }}\n \"#\n}\n\nfunction NestedAlias(c: Combination) -> Combination {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given value back:\n\n {{ c }}\n\n {{ ctx.output_format }}\n \"#\n}\n\n// Test attribute merging.\ntype Currency = int @check(gt_ten, {{ this > 10 }})\ntype Amount = Currency @assert({{ this > 0 }})\n\nclass MergeAttrs {\n amount Amount @description(\"In USD\")\n}\n\n// This should be allowed.\ntype MultipleAttrs = int @assert({{ this > 0 }}) @check(gt_ten, {{ this > 10 }})\n\nfunction MergeAliasAttributes(money: int) -> MergeAttrs {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given integer in the specified format:\n\n {{ money }}\n\n {{ ctx.output_format }}\n \"#\n}\n\nfunction ReturnAliasWithMergedAttributes(money: Amount) -> Amount {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given integer without additional context:\n\n {{ money }}\n\n {{ ctx.output_format }}\n \"#\n}\n\nfunction AliasWithMultipleAttrs(money: MultipleAttrs) -> MultipleAttrs {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given integer without additional context:\n\n {{ money }}\n\n {{ ctx.output_format }}\n \"#\n}", + "test-files/functions/output/type-aliases.baml" => "type Primitive = int | string | bool | float\n\ntype List = string[]\n\ntype Graph = map<string, string[]>\n\ntype Combination = Primitive | List | Graph\n\nfunction PrimitiveAlias(p: Primitive) -> Primitive {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given value back: {{ p }}\n \"#\n}\n\nfunction MapAlias(m: Graph) -> Graph {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given Graph back:\n\n {{ m }}\n\n {{ ctx.output_format }}\n \"#\n}\n\nfunction NestedAlias(c: Combination) -> Combination {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given value back:\n\n {{ c }}\n\n {{ ctx.output_format }}\n \"#\n}\n\n// Test attribute merging.\ntype Currency = int @check(gt_ten, {{ this > 10 }})\ntype Amount = Currency @assert({{ this > 0 }})\n\nclass MergeAttrs {\n amount Amount @description(\"In USD\")\n}\n\n// This should be allowed.\ntype MultipleAttrs = int @assert({{ this > 0 }}) @check(gt_ten, {{ this > 10 }})\n\nfunction MergeAliasAttributes(money: int) -> MergeAttrs {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given integer in the specified format:\n\n {{ money }}\n\n {{ ctx.output_format }}\n \"#\n}\n\nfunction ReturnAliasWithMergedAttributes(money: Amount) -> Amount {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given integer without additional context:\n\n {{ money }}\n\n {{ ctx.output_format }}\n \"#\n}\n\nfunction AliasWithMultipleAttrs(money: MultipleAttrs) -> MultipleAttrs {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given integer without additional context:\n\n {{ money }}\n\n {{ ctx.output_format }}\n \"#\n}\n\ntype RecursiveMapAlias = map<string, RecursiveMapAlias>\n\nfunction SimpleRecursiveMapAlias(input: RecursiveMapAlias) -> RecursiveMapAlias {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given value:\n\n {{ input }}\n\n {{ ctx.output_format }}\n \"#\n}\n\ntype RecursiveListAlias = RecursiveListAlias[]\n\nfunction SimpleRecursiveListAlias(input: RecursiveListAlias) -> RecursiveListAlias {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given JSON array:\n\n {{ input }}\n\n {{ ctx.output_format }}\n \"#\n}\n\ntype RecAliasOne = RecAliasTwo\ntype RecAliasTwo = RecAliasThree\ntype RecAliasThree = RecAliasOne[]\n\nfunction RecursiveAliasCycle(input: RecAliasOne) -> RecAliasOne {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given JSON array:\n\n {{ input }}\n\n {{ ctx.output_format }}\n \"#\n}\n\ntype JsonValue = int | string | bool | float | JsonObject | JsonArray\ntype JsonObject = map<string, JsonValue>\ntype JsonArray = JsonValue[]\n\nfunction JsonTypeAliasCycle(input: JsonValue) -> JsonValue {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given input back:\n\n {{ input }}\n\n {{ ctx.output_format }}\n \"#\n}\n", "test-files/functions/output/unions.baml" => "class UnionTest_ReturnType {\n prop1 string | bool\n prop2 (float | bool)[]\n prop3 (bool[] | int[])\n}\n\nfunction UnionTest_Function(input: string | bool) -> UnionTest_ReturnType {\n client GPT35\n prompt #\"\n Return a JSON blob with this schema: \n {{ctx.output_format}}\n\n JSON:\n \"#\n}\n\ntest UnionTest_Function {\n functions [UnionTest_Function]\n args {\n input \"example input\"\n }\n}\n", "test-files/functions/prompts/no-chat-messages.baml" => "\n\nfunction PromptTestClaude(input: string) -> string {\n client Sonnet\n prompt #\"\n Tell me a haiku about {{ input }}\n \"#\n}\n\n\nfunction PromptTestStreaming(input: string) -> string {\n client GPT35\n prompt #\"\n Tell me a short story about {{ input }}\n \"#\n}\n\ntest TestName {\n functions [PromptTestStreaming]\n args {\n input #\"\n hello world\n \"#\n }\n}\n", "test-files/functions/prompts/with-chat-messages.baml" => "\nfunction PromptTestOpenAIChat(input: string) -> string {\n client GPT35\n prompt #\"\n {{ _.role(\"system\") }}\n You are an assistant that always responds in a very excited way with emojis and also outputs this word 4 times after giving a response: {{ input }}\n \n {{ _.role(\"user\") }}\n Tell me a haiku about {{ input }}\n \"#\n}\n\nfunction PromptTestOpenAIChatNoSystem(input: string) -> string {\n client GPT35\n prompt #\"\n You are an assistant that always responds in a very excited way with emojis and also outputs this word 4 times after giving a response: {{ input }}\n \n {{ _.role(\"user\") }}\n Tell me a haiku about {{ input }}\n \"#\n}\n\nfunction PromptTestClaudeChat(input: string) -> string {\n client Claude\n prompt #\"\n {{ _.role(\"system\") }}\n You are an assistant that always responds in a very excited way with emojis and also outputs this word 4 times after giving a response: {{ input }}\n \n {{ _.role(\"user\") }}\n Tell me a haiku about {{ input }}\n \"#\n}\n\nfunction PromptTestClaudeChatNoSystem(input: string) -> string {\n client Claude\n prompt #\"\n You are an assistant that always responds in a very excited way with emojis and also outputs this word 4 times after giving a response: {{ input }}\n \n {{ _.role(\"user\") }}\n Tell me a haiku about {{ input }}\n \"#\n}\n\ntest TestSystemAndNonSystemChat1 {\n functions [PromptTestClaude, PromptTestOpenAI, PromptTestOpenAIChat, PromptTestOpenAIChatNoSystem, PromptTestClaudeChat, PromptTestClaudeChatNoSystem]\n args {\n input \"cats\"\n }\n}\n\ntest TestSystemAndNonSystemChat2 {\n functions [PromptTestClaude, PromptTestOpenAI, PromptTestOpenAIChat, PromptTestOpenAIChatNoSystem, PromptTestClaudeChat, PromptTestClaudeChatNoSystem]\n args {\n input \"lion\"\n }\n}", diff --git a/integ-tests/typescript/baml_client/async_client.ts b/integ-tests/typescript/baml_client/async_client.ts index bd88336c3..8fdcf1d81 100644 --- a/integ-tests/typescript/baml_client/async_client.ts +++ b/integ-tests/typescript/baml_client/async_client.ts @@ -1568,6 +1568,31 @@ export class BamlAsyncClient { } } + async JsonTypeAliasCycle( + input: JsonValue, + __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } + ): Promise<JsonValue> { + try { + const raw = await this.runtime.callFunction( + "JsonTypeAliasCycle", + { + "input": input + }, + this.ctx_manager.cloneContext(), + __baml_options__?.tb?.__tb(), + __baml_options__?.clientRegistry, + ) + return raw.parsed() as JsonValue + } catch (error: any) { + const bamlError = createBamlValidationError(error); + if (bamlError instanceof BamlValidationError) { + throw bamlError; + } else { + throw error; + } + } + } + async LiteralUnionsTest( input: string, __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } @@ -2018,6 +2043,31 @@ export class BamlAsyncClient { } } + async RecursiveAliasCycle( + input: RecAliasOne, + __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } + ): Promise<RecAliasOne> { + try { + const raw = await this.runtime.callFunction( + "RecursiveAliasCycle", + { + "input": input + }, + this.ctx_manager.cloneContext(), + __baml_options__?.tb?.__tb(), + __baml_options__?.clientRegistry, + ) + return raw.parsed() as RecAliasOne + } catch (error: any) { + const bamlError = createBamlValidationError(error); + if (bamlError instanceof BamlValidationError) { + throw bamlError; + } else { + throw error; + } + } + } + async RecursiveClassWithAliasIndirection( cls: NodeWithAliasIndirection, __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } @@ -2143,6 +2193,56 @@ export class BamlAsyncClient { } } + async SimpleRecursiveListAlias( + input: RecursiveListAlias, + __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } + ): Promise<RecursiveListAlias> { + try { + const raw = await this.runtime.callFunction( + "SimpleRecursiveListAlias", + { + "input": input + }, + this.ctx_manager.cloneContext(), + __baml_options__?.tb?.__tb(), + __baml_options__?.clientRegistry, + ) + return raw.parsed() as RecursiveListAlias + } catch (error: any) { + const bamlError = createBamlValidationError(error); + if (bamlError instanceof BamlValidationError) { + throw bamlError; + } else { + throw error; + } + } + } + + async SimpleRecursiveMapAlias( + input: RecursiveMapAlias, + __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } + ): Promise<RecursiveMapAlias> { + try { + const raw = await this.runtime.callFunction( + "SimpleRecursiveMapAlias", + { + "input": input + }, + this.ctx_manager.cloneContext(), + __baml_options__?.tb?.__tb(), + __baml_options__?.clientRegistry, + ) + return raw.parsed() as RecursiveMapAlias + } catch (error: any) { + const bamlError = createBamlValidationError(error); + if (bamlError instanceof BamlValidationError) { + throw bamlError; + } else { + throw error; + } + } + } + async StreamBigNumbers( digits: number, __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } @@ -5237,6 +5337,39 @@ class BamlStreamClient { } } + JsonTypeAliasCycle( + input: JsonValue, + __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } + ): BamlStream<RecursivePartialNull<JsonValue>, JsonValue> { + try { + const raw = this.runtime.streamFunction( + "JsonTypeAliasCycle", + { + "input": input + }, + undefined, + this.ctx_manager.cloneContext(), + __baml_options__?.tb?.__tb(), + __baml_options__?.clientRegistry, + ) + return new BamlStream<RecursivePartialNull<JsonValue>, JsonValue>( + raw, + (a): a is RecursivePartialNull<JsonValue> => a, + (a): a is JsonValue => a, + this.ctx_manager.cloneContext(), + __baml_options__?.tb?.__tb(), + ) + } catch (error) { + if (error instanceof Error) { + const bamlError = createBamlValidationError(error); + if (bamlError instanceof BamlValidationError) { + throw bamlError; + } + } + throw error; + } + } + LiteralUnionsTest( input: string, __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } @@ -5831,6 +5964,39 @@ class BamlStreamClient { } } + RecursiveAliasCycle( + input: RecAliasOne, + __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } + ): BamlStream<RecursivePartialNull<RecAliasOne>, RecAliasOne> { + try { + const raw = this.runtime.streamFunction( + "RecursiveAliasCycle", + { + "input": input + }, + undefined, + this.ctx_manager.cloneContext(), + __baml_options__?.tb?.__tb(), + __baml_options__?.clientRegistry, + ) + return new BamlStream<RecursivePartialNull<RecAliasOne>, RecAliasOne>( + raw, + (a): a is RecursivePartialNull<RecAliasOne> => a, + (a): a is RecAliasOne => a, + this.ctx_manager.cloneContext(), + __baml_options__?.tb?.__tb(), + ) + } catch (error) { + if (error instanceof Error) { + const bamlError = createBamlValidationError(error); + if (bamlError instanceof BamlValidationError) { + throw bamlError; + } + } + throw error; + } + } + RecursiveClassWithAliasIndirection( cls: NodeWithAliasIndirection, __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } @@ -5996,6 +6162,72 @@ class BamlStreamClient { } } + SimpleRecursiveListAlias( + input: RecursiveListAlias, + __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } + ): BamlStream<RecursivePartialNull<RecursiveListAlias>, RecursiveListAlias> { + try { + const raw = this.runtime.streamFunction( + "SimpleRecursiveListAlias", + { + "input": input + }, + undefined, + this.ctx_manager.cloneContext(), + __baml_options__?.tb?.__tb(), + __baml_options__?.clientRegistry, + ) + return new BamlStream<RecursivePartialNull<RecursiveListAlias>, RecursiveListAlias>( + raw, + (a): a is RecursivePartialNull<RecursiveListAlias> => a, + (a): a is RecursiveListAlias => a, + this.ctx_manager.cloneContext(), + __baml_options__?.tb?.__tb(), + ) + } catch (error) { + if (error instanceof Error) { + const bamlError = createBamlValidationError(error); + if (bamlError instanceof BamlValidationError) { + throw bamlError; + } + } + throw error; + } + } + + SimpleRecursiveMapAlias( + input: RecursiveMapAlias, + __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } + ): BamlStream<RecursivePartialNull<RecursiveMapAlias>, RecursiveMapAlias> { + try { + const raw = this.runtime.streamFunction( + "SimpleRecursiveMapAlias", + { + "input": input + }, + undefined, + this.ctx_manager.cloneContext(), + __baml_options__?.tb?.__tb(), + __baml_options__?.clientRegistry, + ) + return new BamlStream<RecursivePartialNull<RecursiveMapAlias>, RecursiveMapAlias>( + raw, + (a): a is RecursivePartialNull<RecursiveMapAlias> => a, + (a): a is RecursiveMapAlias => a, + this.ctx_manager.cloneContext(), + __baml_options__?.tb?.__tb(), + ) + } catch (error) { + if (error instanceof Error) { + const bamlError = createBamlValidationError(error); + if (bamlError instanceof BamlValidationError) { + throw bamlError; + } + } + throw error; + } + } + StreamBigNumbers( digits: number, __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } diff --git a/integ-tests/typescript/baml_client/inlinedbaml.ts b/integ-tests/typescript/baml_client/inlinedbaml.ts index f03520004..c01a4def0 100644 --- a/integ-tests/typescript/baml_client/inlinedbaml.ts +++ b/integ-tests/typescript/baml_client/inlinedbaml.ts @@ -84,7 +84,7 @@ const fileMap = { "test-files/functions/output/recursive-type-aliases.baml": "class LinkedListAliasNode {\n value int\n next LinkedListAliasNode?\n}\n\n// Simple alias that points to recursive type.\ntype LinkedListAlias = LinkedListAliasNode\n\nfunction AliasThatPointsToRecursiveType(list: LinkedListAlias) -> LinkedListAlias {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given linked list back:\n \n {{ list }}\n \n {{ ctx.output_format }}\n \"#\n}\n\n// Class that points to an alias that points to a recursive type.\nclass ClassToRecAlias {\n list LinkedListAlias\n}\n\nfunction ClassThatPointsToRecursiveClassThroughAlias(cls: ClassToRecAlias) -> ClassToRecAlias {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given object back:\n \n {{ cls }}\n \n {{ ctx.output_format }}\n \"#\n}\n\n// This is tricky cause this class should be hoisted, but classes and aliases\n// are two different types in the AST. This test will make sure they can interop.\nclass NodeWithAliasIndirection {\n value int\n next NodeIndirection?\n}\n\ntype NodeIndirection = NodeWithAliasIndirection\n\nfunction RecursiveClassWithAliasIndirection(cls: NodeWithAliasIndirection) -> NodeWithAliasIndirection {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given object back:\n \n {{ cls }}\n \n {{ ctx.output_format }}\n \"#\n}\n", "test-files/functions/output/serialization-error.baml": "class DummyOutput {\n nonce string\n nonce2 string\n @@dynamic\n}\n\nfunction DummyOutputFunction(input: string) -> DummyOutput {\n client GPT35\n prompt #\"\n Say \"hello there\".\n \"#\n}", "test-files/functions/output/string-list.baml": "function FnOutputStringList(input: string) -> string[] {\n client GPT35\n prompt #\"\n Return a list of strings in json format like [\"string1\", \"string2\", \"string3\"].\n\n JSON:\n \"#\n}\n\ntest FnOutputStringList {\n functions [FnOutputStringList]\n args {\n input \"example input\"\n }\n}\n", - "test-files/functions/output/type-aliases.baml": "type Primitive = int | string | bool | float\n\ntype List = string[]\n\ntype Graph = map<string, string[]>\n\ntype Combination = Primitive | List | Graph\n\nfunction PrimitiveAlias(p: Primitive) -> Primitive {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given value back: {{ p }}\n \"#\n}\n\nfunction MapAlias(m: Graph) -> Graph {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given Graph back:\n\n {{ m }}\n\n {{ ctx.output_format }}\n \"#\n}\n\nfunction NestedAlias(c: Combination) -> Combination {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given value back:\n\n {{ c }}\n\n {{ ctx.output_format }}\n \"#\n}\n\n// Test attribute merging.\ntype Currency = int @check(gt_ten, {{ this > 10 }})\ntype Amount = Currency @assert({{ this > 0 }})\n\nclass MergeAttrs {\n amount Amount @description(\"In USD\")\n}\n\n// This should be allowed.\ntype MultipleAttrs = int @assert({{ this > 0 }}) @check(gt_ten, {{ this > 10 }})\n\nfunction MergeAliasAttributes(money: int) -> MergeAttrs {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given integer in the specified format:\n\n {{ money }}\n\n {{ ctx.output_format }}\n \"#\n}\n\nfunction ReturnAliasWithMergedAttributes(money: Amount) -> Amount {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given integer without additional context:\n\n {{ money }}\n\n {{ ctx.output_format }}\n \"#\n}\n\nfunction AliasWithMultipleAttrs(money: MultipleAttrs) -> MultipleAttrs {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given integer without additional context:\n\n {{ money }}\n\n {{ ctx.output_format }}\n \"#\n}", + "test-files/functions/output/type-aliases.baml": "type Primitive = int | string | bool | float\n\ntype List = string[]\n\ntype Graph = map<string, string[]>\n\ntype Combination = Primitive | List | Graph\n\nfunction PrimitiveAlias(p: Primitive) -> Primitive {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given value back: {{ p }}\n \"#\n}\n\nfunction MapAlias(m: Graph) -> Graph {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given Graph back:\n\n {{ m }}\n\n {{ ctx.output_format }}\n \"#\n}\n\nfunction NestedAlias(c: Combination) -> Combination {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given value back:\n\n {{ c }}\n\n {{ ctx.output_format }}\n \"#\n}\n\n// Test attribute merging.\ntype Currency = int @check(gt_ten, {{ this > 10 }})\ntype Amount = Currency @assert({{ this > 0 }})\n\nclass MergeAttrs {\n amount Amount @description(\"In USD\")\n}\n\n// This should be allowed.\ntype MultipleAttrs = int @assert({{ this > 0 }}) @check(gt_ten, {{ this > 10 }})\n\nfunction MergeAliasAttributes(money: int) -> MergeAttrs {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given integer in the specified format:\n\n {{ money }}\n\n {{ ctx.output_format }}\n \"#\n}\n\nfunction ReturnAliasWithMergedAttributes(money: Amount) -> Amount {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given integer without additional context:\n\n {{ money }}\n\n {{ ctx.output_format }}\n \"#\n}\n\nfunction AliasWithMultipleAttrs(money: MultipleAttrs) -> MultipleAttrs {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given integer without additional context:\n\n {{ money }}\n\n {{ ctx.output_format }}\n \"#\n}\n\ntype RecursiveMapAlias = map<string, RecursiveMapAlias>\n\nfunction SimpleRecursiveMapAlias(input: RecursiveMapAlias) -> RecursiveMapAlias {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given value:\n\n {{ input }}\n\n {{ ctx.output_format }}\n \"#\n}\n\ntype RecursiveListAlias = RecursiveListAlias[]\n\nfunction SimpleRecursiveListAlias(input: RecursiveListAlias) -> RecursiveListAlias {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given JSON array:\n\n {{ input }}\n\n {{ ctx.output_format }}\n \"#\n}\n\ntype RecAliasOne = RecAliasTwo\ntype RecAliasTwo = RecAliasThree\ntype RecAliasThree = RecAliasOne[]\n\nfunction RecursiveAliasCycle(input: RecAliasOne) -> RecAliasOne {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given JSON array:\n\n {{ input }}\n\n {{ ctx.output_format }}\n \"#\n}\n\ntype JsonValue = int | string | bool | float | JsonObject | JsonArray\ntype JsonObject = map<string, JsonValue>\ntype JsonArray = JsonValue[]\n\nfunction JsonTypeAliasCycle(input: JsonValue) -> JsonValue {\n client \"openai/gpt-4o\"\n prompt r#\"\n Return the given input back:\n\n {{ input }}\n\n {{ ctx.output_format }}\n \"#\n}\n", "test-files/functions/output/unions.baml": "class UnionTest_ReturnType {\n prop1 string | bool\n prop2 (float | bool)[]\n prop3 (bool[] | int[])\n}\n\nfunction UnionTest_Function(input: string | bool) -> UnionTest_ReturnType {\n client GPT35\n prompt #\"\n Return a JSON blob with this schema: \n {{ctx.output_format}}\n\n JSON:\n \"#\n}\n\ntest UnionTest_Function {\n functions [UnionTest_Function]\n args {\n input \"example input\"\n }\n}\n", "test-files/functions/prompts/no-chat-messages.baml": "\n\nfunction PromptTestClaude(input: string) -> string {\n client Sonnet\n prompt #\"\n Tell me a haiku about {{ input }}\n \"#\n}\n\n\nfunction PromptTestStreaming(input: string) -> string {\n client GPT35\n prompt #\"\n Tell me a short story about {{ input }}\n \"#\n}\n\ntest TestName {\n functions [PromptTestStreaming]\n args {\n input #\"\n hello world\n \"#\n }\n}\n", "test-files/functions/prompts/with-chat-messages.baml": "\nfunction PromptTestOpenAIChat(input: string) -> string {\n client GPT35\n prompt #\"\n {{ _.role(\"system\") }}\n You are an assistant that always responds in a very excited way with emojis and also outputs this word 4 times after giving a response: {{ input }}\n \n {{ _.role(\"user\") }}\n Tell me a haiku about {{ input }}\n \"#\n}\n\nfunction PromptTestOpenAIChatNoSystem(input: string) -> string {\n client GPT35\n prompt #\"\n You are an assistant that always responds in a very excited way with emojis and also outputs this word 4 times after giving a response: {{ input }}\n \n {{ _.role(\"user\") }}\n Tell me a haiku about {{ input }}\n \"#\n}\n\nfunction PromptTestClaudeChat(input: string) -> string {\n client Claude\n prompt #\"\n {{ _.role(\"system\") }}\n You are an assistant that always responds in a very excited way with emojis and also outputs this word 4 times after giving a response: {{ input }}\n \n {{ _.role(\"user\") }}\n Tell me a haiku about {{ input }}\n \"#\n}\n\nfunction PromptTestClaudeChatNoSystem(input: string) -> string {\n client Claude\n prompt #\"\n You are an assistant that always responds in a very excited way with emojis and also outputs this word 4 times after giving a response: {{ input }}\n \n {{ _.role(\"user\") }}\n Tell me a haiku about {{ input }}\n \"#\n}\n\ntest TestSystemAndNonSystemChat1 {\n functions [PromptTestClaude, PromptTestOpenAI, PromptTestOpenAIChat, PromptTestOpenAIChatNoSystem, PromptTestClaudeChat, PromptTestClaudeChatNoSystem]\n args {\n input \"cats\"\n }\n}\n\ntest TestSystemAndNonSystemChat2 {\n functions [PromptTestClaude, PromptTestOpenAI, PromptTestOpenAIChat, PromptTestOpenAIChatNoSystem, PromptTestClaudeChat, PromptTestClaudeChatNoSystem]\n args {\n input \"lion\"\n }\n}", diff --git a/integ-tests/typescript/baml_client/sync_client.ts b/integ-tests/typescript/baml_client/sync_client.ts index 35306dac1..b5fa48d63 100644 --- a/integ-tests/typescript/baml_client/sync_client.ts +++ b/integ-tests/typescript/baml_client/sync_client.ts @@ -1568,6 +1568,31 @@ export class BamlSyncClient { } } + JsonTypeAliasCycle( + input: JsonValue, + __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } + ): JsonValue { + try { + const raw = this.runtime.callFunctionSync( + "JsonTypeAliasCycle", + { + "input": input + }, + this.ctx_manager.cloneContext(), + __baml_options__?.tb?.__tb(), + __baml_options__?.clientRegistry, + ) + return raw.parsed() as JsonValue + } catch (error: any) { + const bamlError = createBamlValidationError(error); + if (bamlError instanceof BamlValidationError) { + throw bamlError; + } else { + throw error; + } + } + } + LiteralUnionsTest( input: string, __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } @@ -2018,6 +2043,31 @@ export class BamlSyncClient { } } + RecursiveAliasCycle( + input: RecAliasOne, + __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } + ): RecAliasOne { + try { + const raw = this.runtime.callFunctionSync( + "RecursiveAliasCycle", + { + "input": input + }, + this.ctx_manager.cloneContext(), + __baml_options__?.tb?.__tb(), + __baml_options__?.clientRegistry, + ) + return raw.parsed() as RecAliasOne + } catch (error: any) { + const bamlError = createBamlValidationError(error); + if (bamlError instanceof BamlValidationError) { + throw bamlError; + } else { + throw error; + } + } + } + RecursiveClassWithAliasIndirection( cls: NodeWithAliasIndirection, __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } @@ -2143,6 +2193,56 @@ export class BamlSyncClient { } } + SimpleRecursiveListAlias( + input: RecursiveListAlias, + __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } + ): RecursiveListAlias { + try { + const raw = this.runtime.callFunctionSync( + "SimpleRecursiveListAlias", + { + "input": input + }, + this.ctx_manager.cloneContext(), + __baml_options__?.tb?.__tb(), + __baml_options__?.clientRegistry, + ) + return raw.parsed() as RecursiveListAlias + } catch (error: any) { + const bamlError = createBamlValidationError(error); + if (bamlError instanceof BamlValidationError) { + throw bamlError; + } else { + throw error; + } + } + } + + SimpleRecursiveMapAlias( + input: RecursiveMapAlias, + __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } + ): RecursiveMapAlias { + try { + const raw = this.runtime.callFunctionSync( + "SimpleRecursiveMapAlias", + { + "input": input + }, + this.ctx_manager.cloneContext(), + __baml_options__?.tb?.__tb(), + __baml_options__?.clientRegistry, + ) + return raw.parsed() as RecursiveMapAlias + } catch (error: any) { + const bamlError = createBamlValidationError(error); + if (bamlError instanceof BamlValidationError) { + throw bamlError; + } else { + throw error; + } + } + } + StreamBigNumbers( digits: number, __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } diff --git a/integ-tests/typescript/baml_client/types.ts b/integ-tests/typescript/baml_client/types.ts index 17a6fb1d9..8f9bb995a 100644 --- a/integ-tests/typescript/baml_client/types.ts +++ b/integ-tests/typescript/baml_client/types.ts @@ -630,3 +630,19 @@ export interface WithReasoning { reasoning: string } + +type RecursiveMapAlias = Record<string, RecursiveMapAlias> + +type RecursiveListAlias = RecursiveListAlias[] + +type RecAliasOne = RecAliasTwo + +type RecAliasTwo = RecAliasThree + +type RecAliasThree = RecAliasOne[] + +type JsonValue = number | string | boolean | number | JsonObject | JsonArray + +type JsonObject = Record<string, JsonValue> + +type JsonArray = JsonValue[] diff --git a/integ-tests/typescript/test-report.html b/integ-tests/typescript/test-report.html index ea171ca6e..87108c133 100644 --- a/integ-tests/typescript/test-report.html +++ b/integ-tests/typescript/test-report.html @@ -257,9 +257,9 @@ font-size: 1rem; padding: 0 0.5rem; } -</style></head><body><div class="jesthtml-content"><header><h1 id="title">Test Report</h1></header><div id="metadata-container"><div id="timestamp">Started: 2024-12-09 16:43:58</div><div id="summary"><div id="suite-summary"><div class="summary-total">Suites (1)</div><div class="summary-passed summary-empty">0 passed</div><div class="summary-failed ">1 failed</div><div class="summary-pending summary-empty">0 pending</div></div><div id="test-summary"><div class="summary-total">Tests (73)</div><div class="summary-passed ">71 passed</div><div class="summary-failed ">2 failed</div><div class="summary-pending summary-empty">0 pending</div></div></div></div><div id="suite-1" class="suite-container"><input id="collapsible-0" type="checkbox" class="toggle" checked="checked"/><label for="collapsible-0"><div class="suite-info"><div class="suite-path">/workspaces/baml/integ-tests/typescript/tests/integ-tests.test.ts</div><div class="suite-time warn">120.513s</div></div></label><div class="suite-tests"><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single bool</div><div class="test-status">passed</div><div class="test-duration">0.64s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single string list</div><div class="test-status">passed</div><div class="test-duration">0.718s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">return literal union</div><div class="test-status">passed</div><div class="test-duration">0.462s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single class</div><div class="test-status">passed</div><div class="test-duration">0.469s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">multiple classes</div><div class="test-status">passed</div><div class="test-duration">0.464s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single enum list</div><div class="test-status">passed</div><div class="test-duration">0.443s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single float</div><div class="test-status">passed</div><div class="test-duration">0.408s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single int</div><div class="test-status">passed</div><div class="test-duration">0.398s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single literal int</div><div class="test-status">passed</div><div class="test-duration">0.428s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single literal bool</div><div class="test-status">passed</div><div class="test-duration">0.401s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single literal string</div><div class="test-status">passed</div><div class="test-duration">0.33s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single class with literal prop</div><div class="test-status">passed</div><div class="test-duration">0.489s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single class with literal union prop</div><div class="test-status">passed</div><div class="test-duration">1.094s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single optional string</div><div class="test-status">passed</div><div class="test-duration">0.378s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single map string to string</div><div class="test-status">passed</div><div class="test-duration">0.676s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single map string to class</div><div class="test-status">passed</div><div class="test-duration">0.819s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single map string to map</div><div class="test-status">passed</div><div class="test-duration">0.505s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">enum key in map</div><div class="test-status">passed</div><div class="test-duration">0.775s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">literal string union key in map</div><div class="test-status">passed</div><div class="test-duration">0.519s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single literal string key in map</div><div class="test-status">passed</div><div class="test-duration">0.603s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">primitive union alias</div><div class="test-status">passed</div><div class="test-duration">0.413s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">map alias</div><div class="test-status">passed</div><div class="test-duration">0.869s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">alias union</div><div class="test-status">passed</div><div class="test-duration">1.331s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">alias pointing to recursive class</div><div class="test-status">passed</div><div class="test-duration">0.609s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">class pointing to alias that points to recursive class</div><div class="test-status">passed</div><div class="test-duration">0.932s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">recursive class with alias indirection</div><div class="test-status">passed</div><div class="test-duration">1.223s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work for all outputs</div><div class="test-status">passed</div><div class="test-duration">5.646s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">works with retries1</div><div class="test-status">passed</div><div class="test-duration">1.425s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">works with retries2</div><div class="test-status">passed</div><div class="test-duration">2.348s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">works with fallbacks</div><div class="test-status">passed</div><div class="test-duration">2.297s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with image from url</div><div class="test-status">passed</div><div class="test-duration">1.386s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with image from base 64</div><div class="test-status">passed</div><div class="test-duration">1.239s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with audio base 64</div><div class="test-status">passed</div><div class="test-duration">1.734s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with audio from url</div><div class="test-status">passed</div><div class="test-duration">1.844s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support streaming in OpenAI</div><div class="test-status">passed</div><div class="test-duration">2.423s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support streaming in Gemini</div><div class="test-status">passed</div><div class="test-duration">6.634s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support AWS</div><div class="test-status">passed</div><div class="test-duration">1.69s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support streaming in AWS</div><div class="test-status">passed</div><div class="test-duration">1.518s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should allow overriding the region</div><div class="test-status">passed</div><div class="test-duration">0.062s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support OpenAI shorthand</div><div class="test-status">passed</div><div class="test-duration">8.762s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support OpenAI shorthand streaming</div><div class="test-status">passed</div><div class="test-duration">19.248s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support anthropic shorthand</div><div class="test-status">passed</div><div class="test-duration">2.567s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support anthropic shorthand streaming</div><div class="test-status">passed</div><div class="test-duration">4.385s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support streaming without iterating</div><div class="test-status">passed</div><div class="test-duration">2.164s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support streaming in Claude</div><div class="test-status">passed</div><div class="test-duration">2.722s</div></div></div><div class="test-result failed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support vertex</div><div class="test-status">failed</div><div class="test-duration">0.003s</div></div><div class="failureMessages"> <pre class="failureMsg">Error: BamlError: Failed to read service account file: +</style></head><body><div class="jesthtml-content"><header><h1 id="title">Test Report</h1></header><div id="metadata-container"><div id="timestamp">Started: 2024-12-18 17:20:38</div><div id="summary"><div id="suite-summary"><div class="summary-total">Suites (1)</div><div class="summary-passed summary-empty">0 passed</div><div class="summary-failed ">1 failed</div><div class="summary-pending summary-empty">0 pending</div></div><div id="test-summary"><div class="summary-total">Tests (80)</div><div class="summary-passed ">78 passed</div><div class="summary-failed ">2 failed</div><div class="summary-pending summary-empty">0 pending</div></div></div></div><div id="suite-1" class="suite-container"><input id="collapsible-0" type="checkbox" class="toggle" checked="checked"/><label for="collapsible-0"><div class="suite-info"><div class="suite-path">/workspaces/baml/integ-tests/typescript/tests/integ-tests.test.ts</div><div class="suite-time warn">127.082s</div></div></label><div class="suite-tests"><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single bool</div><div class="test-status">passed</div><div class="test-duration">0.39s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single string list</div><div class="test-status">passed</div><div class="test-duration">0.484s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">return literal union</div><div class="test-status">passed</div><div class="test-duration">0.354s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single class</div><div class="test-status">passed</div><div class="test-duration">0.41s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">multiple classes</div><div class="test-status">passed</div><div class="test-duration">0.399s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single enum list</div><div class="test-status">passed</div><div class="test-duration">0.392s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single float</div><div class="test-status">passed</div><div class="test-duration">0.356s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single int</div><div class="test-status">passed</div><div class="test-duration">0.336s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single literal int</div><div class="test-status">passed</div><div class="test-duration">0.314s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single literal bool</div><div class="test-status">passed</div><div class="test-duration">0.388s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single literal string</div><div class="test-status">passed</div><div class="test-duration">0.403s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single class with literal prop</div><div class="test-status">passed</div><div class="test-duration">0.764s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single class with literal union prop</div><div class="test-status">passed</div><div class="test-duration">0.512s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single optional string</div><div class="test-status">passed</div><div class="test-duration">0.93s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single map string to string</div><div class="test-status">passed</div><div class="test-duration">0.644s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single map string to class</div><div class="test-status">passed</div><div class="test-duration">0.687s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single map string to map</div><div class="test-status">passed</div><div class="test-duration">0.529s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">enum key in map</div><div class="test-status">passed</div><div class="test-duration">0.911s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">literal string union key in map</div><div class="test-status">passed</div><div class="test-duration">0.788s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">single literal string key in map</div><div class="test-status">passed</div><div class="test-duration">0.611s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">primitive union alias</div><div class="test-status">passed</div><div class="test-duration">0.657s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">map alias</div><div class="test-status">passed</div><div class="test-duration">1.155s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">alias union</div><div class="test-status">passed</div><div class="test-duration">1.45s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">alias pointing to recursive class</div><div class="test-status">passed</div><div class="test-duration">0.753s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">class pointing to alias that points to recursive class</div><div class="test-status">passed</div><div class="test-duration">1.114s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">recursive class with alias indirection</div><div class="test-status">passed</div><div class="test-duration">0.865s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">merge alias attributes</div><div class="test-status">passed</div><div class="test-duration">0.558s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">return alias with merged attrs</div><div class="test-status">passed</div><div class="test-duration">0.749s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">alias with multiple attrs</div><div class="test-status">passed</div><div class="test-duration">0.673s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">simple recursive map alias</div><div class="test-status">passed</div><div class="test-duration">0.701s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">simple recursive map alias</div><div class="test-status">passed</div><div class="test-duration">0.745s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">recursive alias cycles</div><div class="test-status">passed</div><div class="test-duration">0.498s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests > should work for all inputs</div><div class="test-title">json type alias cycle</div><div class="test-status">passed</div><div class="test-duration">2.621s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work for all outputs</div><div class="test-status">passed</div><div class="test-duration">4.553s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">works with retries1</div><div class="test-status">passed</div><div class="test-duration">1.172s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">works with retries2</div><div class="test-status">passed</div><div class="test-duration">2.265s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">works with fallbacks</div><div class="test-status">passed</div><div class="test-duration">1.893s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with image from url</div><div class="test-status">passed</div><div class="test-duration">1.86s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with image from base 64</div><div class="test-status">passed</div><div class="test-duration">1.766s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with audio base 64</div><div class="test-status">passed</div><div class="test-duration">1.889s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with audio from url</div><div class="test-status">passed</div><div class="test-duration">2.357s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support streaming in OpenAI</div><div class="test-status">passed</div><div class="test-duration">2.328s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support streaming in Gemini</div><div class="test-status">passed</div><div class="test-duration">8.829s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support AWS</div><div class="test-status">passed</div><div class="test-duration">1.549s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support streaming in AWS</div><div class="test-status">passed</div><div class="test-duration">1.623s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should allow overriding the region</div><div class="test-status">passed</div><div class="test-duration">0.047s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support OpenAI shorthand</div><div class="test-status">passed</div><div class="test-duration">18.752s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support OpenAI shorthand streaming</div><div class="test-status">passed</div><div class="test-duration">13.463s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support anthropic shorthand</div><div class="test-status">passed</div><div class="test-duration">3.58s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support anthropic shorthand streaming</div><div class="test-status">passed</div><div class="test-duration">3.213s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support streaming without iterating</div><div class="test-status">passed</div><div class="test-duration">4.054s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support streaming in Claude</div><div class="test-status">passed</div><div class="test-duration">1.013s</div></div></div><div class="test-result failed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should support vertex</div><div class="test-status">failed</div><div class="test-duration">0.002s</div></div><div class="failureMessages"> <pre class="failureMsg">Error: BamlError: Failed to read service account file: Caused by: - No such file or directory (os error 2)</pre></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">supports tracing sync</div><div class="test-status">passed</div><div class="test-duration">0.023s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">supports tracing async</div><div class="test-status">passed</div><div class="test-duration">4.933s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic types single</div><div class="test-status">passed</div><div class="test-duration">1.024s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic types enum</div><div class="test-status">passed</div><div class="test-duration">1.11s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic literals</div><div class="test-status">passed</div><div class="test-duration">0.932s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic types class</div><div class="test-status">passed</div><div class="test-duration">1.335s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic inputs class</div><div class="test-status">passed</div><div class="test-duration">0.515s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic inputs list</div><div class="test-status">passed</div><div class="test-duration">0.608s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic output map</div><div class="test-status">passed</div><div class="test-duration">1.073s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic output union</div><div class="test-status">passed</div><div class="test-duration">1.795s</div></div></div><div class="test-result failed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with nested classes</div><div class="test-status">failed</div><div class="test-duration">0.109s</div></div><div class="failureMessages"> <pre class="failureMsg">Error: BamlError: BamlClientError: Something went wrong with the LLM client: reqwest::Error { kind: Request, url: Url { scheme: "http", cannot_be_a_base: false, username: "", password: None, host: Some(Domain("localhost")), port: Some(11434), path: "/v1/chat/completions", query: None, fragment: None }, source: hyper_util::client::legacy::Error(Connect, ConnectError("tcp connect error", Os { code: 111, kind: ConnectionRefused, message: "Connection refused" })) } + No such file or directory (os error 2)</pre></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">supports tracing sync</div><div class="test-status">passed</div><div class="test-duration">0.015s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">supports tracing async</div><div class="test-status">passed</div><div class="test-duration">3.008s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic types single</div><div class="test-status">passed</div><div class="test-duration">1.334s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic types enum</div><div class="test-status">passed</div><div class="test-duration">0.981s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic literals</div><div class="test-status">passed</div><div class="test-duration">1.422s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic types class</div><div class="test-status">passed</div><div class="test-duration">0.984s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic inputs class</div><div class="test-status">passed</div><div class="test-duration">0.553s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic inputs list</div><div class="test-status">passed</div><div class="test-duration">0.654s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic output map</div><div class="test-status">passed</div><div class="test-duration">0.672s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic output union</div><div class="test-status">passed</div><div class="test-duration">1.875s</div></div></div><div class="test-result failed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with nested classes</div><div class="test-status">failed</div><div class="test-duration">0.107s</div></div><div class="failureMessages"> <pre class="failureMsg">Error: BamlError: BamlClientError: Something went wrong with the LLM client: reqwest::Error { kind: Request, url: Url { scheme: "http", cannot_be_a_base: false, username: "", password: None, host: Some(Domain("localhost")), port: Some(11434), path: "/v1/chat/completions", query: None, fragment: None }, source: hyper_util::client::legacy::Error(Connect, ConnectError("tcp connect error", Os { code: 111, kind: ConnectionRefused, message: "Connection refused" })) } at BamlStream.parsed [as getFinalResponse] (/workspaces/baml/engine/language_client_typescript/stream.js:58:39) - at Object.<anonymous> (/workspaces/baml/integ-tests/typescript/tests/integ-tests.test.ts:635:19)</pre></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic client</div><div class="test-status">passed</div><div class="test-duration">0.493s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with 'onLogEvent'</div><div class="test-status">passed</div><div class="test-duration">2.269s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with a sync client</div><div class="test-status">passed</div><div class="test-duration">0.701s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should raise an error when appropriate</div><div class="test-status">passed</div><div class="test-duration">1.082s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should raise a BAMLValidationError</div><div class="test-status">passed</div><div class="test-duration">0.66s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should reset environment variables correctly</div><div class="test-status">passed</div><div class="test-duration">2.453s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should use aliases when serializing input objects - classes</div><div class="test-status">passed</div><div class="test-duration">0.94s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should use aliases when serializing, but still have original keys in jinja</div><div class="test-status">passed</div><div class="test-duration">0.802s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should use aliases when serializing input objects - enums</div><div class="test-status">passed</div><div class="test-duration">0.408s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should use aliases when serializing input objects - lists</div><div class="test-status">passed</div><div class="test-duration">0.513s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">constraints: should handle checks in return types</div><div class="test-status">passed</div><div class="test-duration">0.638s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">constraints: should handle checks in returned unions</div><div class="test-status">passed</div><div class="test-duration">0.709s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">constraints: should handle block-level checks</div><div class="test-status">passed</div><div class="test-duration">2.75s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">constraints: should handle nested-block-level checks</div><div class="test-status">passed</div><div class="test-duration">0.612s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">simple recursive type</div><div class="test-status">passed</div><div class="test-duration">2.458s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">mutually recursive type</div><div class="test-status">passed</div><div class="test-duration">2.048s</div></div></div></div></div></div></body></html> \ No newline at end of file + at Object.<anonymous> (/workspaces/baml/integ-tests/typescript/tests/integ-tests.test.ts:688:19)</pre></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with dynamic client</div><div class="test-status">passed</div><div class="test-duration">0.39s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with 'onLogEvent'</div><div class="test-status">passed</div><div class="test-duration">1.623s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should work with a sync client</div><div class="test-status">passed</div><div class="test-duration">0.539s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should raise an error when appropriate</div><div class="test-status">passed</div><div class="test-duration">0.971s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should raise a BAMLValidationError</div><div class="test-status">passed</div><div class="test-duration">0.419s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should reset environment variables correctly</div><div class="test-status">passed</div><div class="test-duration">1.657s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should use aliases when serializing input objects - classes</div><div class="test-status">passed</div><div class="test-duration">1.005s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should use aliases when serializing, but still have original keys in jinja</div><div class="test-status">passed</div><div class="test-duration">0.851s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should use aliases when serializing input objects - enums</div><div class="test-status">passed</div><div class="test-duration">0.369s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">should use aliases when serializing input objects - lists</div><div class="test-status">passed</div><div class="test-duration">0.374s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">constraints: should handle checks in return types</div><div class="test-status">passed</div><div class="test-duration">0.737s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">constraints: should handle checks in returned unions</div><div class="test-status">passed</div><div class="test-duration">0.788s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">constraints: should handle block-level checks</div><div class="test-status">passed</div><div class="test-duration">0.531s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">constraints: should handle nested-block-level checks</div><div class="test-status">passed</div><div class="test-duration">0.605s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">simple recursive type</div><div class="test-status">passed</div><div class="test-duration">2.579s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">Integ tests</div><div class="test-title">mutually recursive type</div><div class="test-status">passed</div><div class="test-duration">2.009s</div></div></div></div></div></div></body></html> \ No newline at end of file diff --git a/integ-tests/typescript/tests/integ-tests.test.ts b/integ-tests/typescript/tests/integ-tests.test.ts index 8eaeb690b..3ba5b9745 100644 --- a/integ-tests/typescript/tests/integ-tests.test.ts +++ b/integ-tests/typescript/tests/integ-tests.test.ts @@ -181,6 +181,59 @@ describe('Integ tests', () => { const res = await b.RecursiveClassWithAliasIndirection({ value: 1, next: { value: 2, next: null } }) expect(res).toEqual({ value: 1, next: { value: 2, next: null } }) }) + + it('merge alias attributes', async () => { + const res = await b.MergeAliasAttributes(123) + expect(res.amount.value).toEqual(123) + expect(res.amount.checks['gt_ten'].status).toEqual('succeeded') + }) + + it('return alias with merged attrs', async () => { + const res = await b.ReturnAliasWithMergedAttributes(123) + expect(res.value).toEqual(123) + expect(res.checks['gt_ten'].status).toEqual('succeeded') + }) + + it('alias with multiple attrs', async () => { + const res = await b.AliasWithMultipleAttrs(123) + expect(res.value).toEqual(123) + expect(res.checks['gt_ten'].status).toEqual('succeeded') + }) + + it('simple recursive map alias', async () => { + const res = await b.SimpleRecursiveMapAlias({ one: { two: { three: {} } } }) + expect(res).toEqual({ one: { two: { three: {} } } }) + }) + + it('simple recursive map alias', async () => { + const res = await b.SimpleRecursiveListAlias([[], [], [[]]]) + expect(res).toEqual([[], [], [[]]]) + }) + + it('recursive alias cycles', async () => { + const res = await b.RecursiveAliasCycle([[], [], [[]]]) + expect(res).toEqual([[], [], [[]]]) + }) + + it('json type alias cycle', async () => { + const data = { + number: 1, + string: 'test', + bool: true, + list: [1, 2, 3], + object: { number: 1, string: 'test', bool: true, list: [1, 2, 3] }, + json: { + number: 1, + string: 'test', + bool: true, + list: [1, 2, 3], + object: { number: 1, string: 'test', bool: true, list: [1, 2, 3] }, + }, + } + const res = await b.JsonTypeAliasCycle(data) + expect(res).toEqual(data) + expect(res.json.object.list).toEqual([1, 2, 3]) + }) }) it('should work for all outputs', async () => { From eafc2bc988cd6cecf4456e5830f2aaa2a4c776f7 Mon Sep 17 00:00:00 2001 From: Antonio Sarosi <sarosiantonio@gmail.com> Date: Wed, 18 Dec 2024 20:42:18 +0100 Subject: [PATCH 47/51] Refactor `is_composite` --- .../jsonish/src/deserializer/types.rs | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/engine/baml-lib/jsonish/src/deserializer/types.rs b/engine/baml-lib/jsonish/src/deserializer/types.rs index fae0ee606..b614b915e 100644 --- a/engine/baml-lib/jsonish/src/deserializer/types.rs +++ b/engine/baml-lib/jsonish/src/deserializer/types.rs @@ -35,17 +35,17 @@ pub enum BamlValueWithFlags { impl BamlValueWithFlags { pub fn is_composite(&self) -> bool { match self { - BamlValueWithFlags::String(_) => false, - BamlValueWithFlags::Int(_) => false, - BamlValueWithFlags::Float(_) => false, - BamlValueWithFlags::Bool(_) => false, - BamlValueWithFlags::Null(_) => false, - BamlValueWithFlags::Enum(_, value_with_flags) => false, + BamlValueWithFlags::String(_) + | BamlValueWithFlags::Int(_) + | BamlValueWithFlags::Float(_) + | BamlValueWithFlags::Bool(_) + | BamlValueWithFlags::Null(_) + | BamlValueWithFlags::Enum(_, _) => false, - BamlValueWithFlags::List(deserializer_conditions, vec) => true, - BamlValueWithFlags::Map(deserializer_conditions, index_map) => true, - BamlValueWithFlags::Class(_, deserializer_conditions, index_map) => true, - BamlValueWithFlags::Media(value_with_flags) => true, + BamlValueWithFlags::List(_, _) + | BamlValueWithFlags::Map(_, _) + | BamlValueWithFlags::Class(_, _, _) + | BamlValueWithFlags::Media(_) => true, } } From 7f92daeb5a3a167d0117731cf368c87a9afa2776 Mon Sep 17 00:00:00 2001 From: Antonio Sarosi <sarosiantonio@gmail.com> Date: Wed, 18 Dec 2024 20:48:02 +0100 Subject: [PATCH 48/51] Update inlined files --- integ-tests/python/baml_client/inlinedbaml.py | 4 ++-- integ-tests/ruby/baml_client/inlined.rb | 4 ++-- integ-tests/typescript/baml_client/inlinedbaml.ts | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/integ-tests/python/baml_client/inlinedbaml.py b/integ-tests/python/baml_client/inlinedbaml.py index fcb867aef..ab7ac32e3 100644 --- a/integ-tests/python/baml_client/inlinedbaml.py +++ b/integ-tests/python/baml_client/inlinedbaml.py @@ -25,7 +25,7 @@ "fiddle-examples/extract-receipt-info.baml": "class ReceiptItem {\n name string\n description string?\n quantity int\n price float\n}\n\nclass ReceiptInfo {\n items ReceiptItem[]\n total_cost float?\n venue \"barisa\" | \"ox_burger\"\n}\n\nfunction ExtractReceiptInfo(email: string, reason: \"curiosity\" | \"personal_finance\") -> ReceiptInfo {\n client GPT4o\n prompt #\"\n Given the receipt below:\n\n ```\n {{email}}\n ```\n\n {{ ctx.output_format }}\n \"#\n}\n\n", "fiddle-examples/images/image.baml": "function DescribeImage(img: image) -> string {\n client GPT4o\n prompt #\"\n {{ _.role(\"user\") }}\n\n\n Describe the image below in 20 words:\n {{ img }}\n \"#\n\n}\n\nclass FakeImage {\n url string\n}\n\nclass ClassWithImage {\n myImage image\n param2 string\n fake_image FakeImage\n}\n\n// chat role user present\nfunction DescribeImage2(classWithImage: ClassWithImage, img2: image) -> string { \n client GPT4Turbo\n prompt #\"\n {{ _.role(\"user\") }}\n You should return 2 answers that answer the following commands.\n\n 1. Describe this in 5 words:\n {{ classWithImage.myImage }}\n\n 2. Also tell me what's happening here in one sentence:\n {{ img2 }}\n \"#\n}\n\n// no chat role\nfunction DescribeImage3(classWithImage: ClassWithImage, img2: image) -> string {\n client GPT4Turbo\n prompt #\"\n Describe this in 5 words:\n {{ classWithImage.myImage }}\n\n Tell me also what's happening here in one sentence and relate it to the word {{ classWithImage.param2 }}:\n {{ img2 }}\n \"#\n}\n\n\n// system prompt and chat prompt\nfunction DescribeImage4(classWithImage: ClassWithImage, img2: image) -> string {\n client GPT4Turbo\n prompt #\"\n {{ _.role(\"system\")}}\n\n Describe this in 5 words:\n {{ classWithImage.myImage }}\n\n Tell me also what's happening here in one sentence and relate it to the word {{ classWithImage.param2 }}:\n {{ img2 }}\n \"#\n}\n\ntest TestName {\n functions [DescribeImage]\n args {\n img { url \"https://imgs.xkcd.com/comics/standards.png\"}\n }\n}\n", "fiddle-examples/symbol-tuning.baml": "enum Category3 {\n Refund @alias(\"k1\")\n @description(\"Customer wants to refund a product\")\n\n CancelOrder @alias(\"k2\")\n @description(\"Customer wants to cancel an order\")\n\n TechnicalSupport @alias(\"k3\")\n @description(\"Customer needs help with a technical issue unrelated to account creation or login\")\n\n AccountIssue @alias(\"k4\")\n @description(\"Specifically relates to account-login or account-creation\")\n\n Question @alias(\"k5\")\n @description(\"Customer has a question\")\n}\n\nfunction ClassifyMessage3(input: string) -> Category {\n client GPT4\n\n prompt #\"\n Classify the following INPUT into ONE\n of the following categories:\n\n INPUT: {{ input }}\n\n {{ ctx.output_format }}\n\n Response:\n \"#\n}", - "generators.baml": "generator lang_python {\n output_type python/pydantic\n output_dir \"../python\"\n version \"0.70.1\"\n}\n\ngenerator lang_typescript {\n output_type typescript\n output_dir \"../typescript\"\n version \"0.70.1\"\n}\n\ngenerator lang_ruby {\n output_type ruby/sorbet\n output_dir \"../ruby\"\n version \"0.70.1\"\n}\n\n// generator openapi {\n// output_type rest/openapi\n// output_dir \"../openapi\"\n// version \"0.70.1\"\n// on_generate \"rm .gitignore\"\n// }\n", + "generators.baml": "generator lang_python {\n output_type python/pydantic\n output_dir \"../python\"\n version \"0.70.5\"\n}\n\ngenerator lang_typescript {\n output_type typescript\n output_dir \"../typescript\"\n version \"0.70.5\"\n}\n\ngenerator lang_ruby {\n output_type ruby/sorbet\n output_dir \"../ruby\"\n version \"0.70.5\"\n}\n\n// generator openapi {\n// output_type rest/openapi\n// output_dir \"../openapi\"\n// version \"0.70.5\"\n// on_generate \"rm .gitignore\"\n// }\n", "test-files/aliases/aliased-inputs.baml": "\nclass InputClass {\n key string @alias(\"color\")\n key2 string\n}\n\n\nclass InputClassNested {\n key string\n nested InputClass @alias(\"interesting-key\")\n}\n \n\nfunction AliasedInputClass(input: InputClass) -> string {\n client GPT35\n prompt #\"\n\n {{input}}\n\n This is a test. What's the name of the first json key above? Remember, tell me the key, not value.\n \"#\n}\n \nfunction AliasedInputClass2(input: InputClass) -> string {\n client GPT35\n prompt #\"\n\n {# making sure we can still access the original key #}\n {%if input.key == \"tiger\"%}\n Repeat this value back to me, and nothing else: {{input.key}}\n {%endif%}\n \"#\n}\n \n function AliasedInputClassNested(input: InputClassNested) -> string {\n client GPT35\n prompt #\"\n {{ _.role(\"user\")}}\n\n {{input}}\n\n This is a test. What's the name of the second json key above? Remember, tell me the key, not value.\n \"#\n }\n\n\nenum AliasedEnum {\n KEY_ONE @alias(\"tiger\")\n KEY_TWO\n}\n\nfunction AliasedInputEnum(input: AliasedEnum) -> string {\n client GPT4o\n prompt #\"\n {{ _.role(\"user\")}}\n\n\n Write out this word only in your response, in lowercase:\n ---\n {{input}}\n ---\n Answer:\n \"#\n}\n\n\nfunction AliasedInputList(input: AliasedEnum[]) -> string {\n client GPT35\n prompt #\"\n {{ _.role(\"user\")}}\n Given this array:\n ---\n {{input}}\n ---\n\n Return the first element in the array:\n \"#\n}\n\n", "test-files/aliases/classes.baml": "class TestClassAlias {\n key string @alias(\"key-dash\") @description(#\"\n This is a description for key\n af asdf\n \"#)\n key2 string @alias(\"key21\")\n key3 string @alias(\"key with space\")\n key4 string //unaliased\n key5 string @alias(\"key.with.punctuation/123\")\n}\n\nfunction FnTestClassAlias(input: string) -> TestClassAlias {\n client GPT35\n prompt #\"\n {{ctx.output_format}}\n \"#\n}\n\ntest FnTestClassAlias {\n functions [FnTestClassAlias]\n args {\n input \"example input\"\n }\n}\n", "test-files/aliases/enums.baml": "enum TestEnum {\n A @alias(\"k1\") @description(#\"\n User is angry\n \"#)\n B @alias(\"k22\") @description(#\"\n User is happy\n \"#)\n // tests whether k1 doesnt incorrectly get matched with k11\n C @alias(\"k11\") @description(#\"\n User is sad\n \"#)\n D @alias(\"k44\") @description(\n User is confused\n )\n E @description(\n User is excited\n )\n F @alias(\"k5\") // only alias\n \n G @alias(\"k6\") @description(#\"\n User is bored\n With a long description\n \"#)\n \n @@alias(\"Category\")\n}\n\nfunction FnTestAliasedEnumOutput(input: string) -> TestEnum {\n client GPT35\n prompt #\"\n Classify the user input into the following category\n \n {{ ctx.output_format }}\n\n {{ _.role('user') }}\n {{input}}\n\n {{ _.role('assistant') }}\n Category ID:\n \"#\n}\n\ntest FnTestAliasedEnumOutput {\n functions [FnTestAliasedEnumOutput]\n args {\n input \"mehhhhh\"\n }\n}", @@ -88,7 +88,7 @@ "test-files/functions/prompts/no-chat-messages.baml": "\n\nfunction PromptTestClaude(input: string) -> string {\n client Sonnet\n prompt #\"\n Tell me a haiku about {{ input }}\n \"#\n}\n\n\nfunction PromptTestStreaming(input: string) -> string {\n client GPT35\n prompt #\"\n Tell me a short story about {{ input }}\n \"#\n}\n\ntest TestName {\n functions [PromptTestStreaming]\n args {\n input #\"\n hello world\n \"#\n }\n}\n", "test-files/functions/prompts/with-chat-messages.baml": "\nfunction PromptTestOpenAIChat(input: string) -> string {\n client GPT35\n prompt #\"\n {{ _.role(\"system\") }}\n You are an assistant that always responds in a very excited way with emojis and also outputs this word 4 times after giving a response: {{ input }}\n \n {{ _.role(\"user\") }}\n Tell me a haiku about {{ input }}\n \"#\n}\n\nfunction PromptTestOpenAIChatNoSystem(input: string) -> string {\n client GPT35\n prompt #\"\n You are an assistant that always responds in a very excited way with emojis and also outputs this word 4 times after giving a response: {{ input }}\n \n {{ _.role(\"user\") }}\n Tell me a haiku about {{ input }}\n \"#\n}\n\nfunction PromptTestClaudeChat(input: string) -> string {\n client Claude\n prompt #\"\n {{ _.role(\"system\") }}\n You are an assistant that always responds in a very excited way with emojis and also outputs this word 4 times after giving a response: {{ input }}\n \n {{ _.role(\"user\") }}\n Tell me a haiku about {{ input }}\n \"#\n}\n\nfunction PromptTestClaudeChatNoSystem(input: string) -> string {\n client Claude\n prompt #\"\n You are an assistant that always responds in a very excited way with emojis and also outputs this word 4 times after giving a response: {{ input }}\n \n {{ _.role(\"user\") }}\n Tell me a haiku about {{ input }}\n \"#\n}\n\ntest TestSystemAndNonSystemChat1 {\n functions [PromptTestClaude, PromptTestOpenAI, PromptTestOpenAIChat, PromptTestOpenAIChatNoSystem, PromptTestClaudeChat, PromptTestClaudeChatNoSystem]\n args {\n input \"cats\"\n }\n}\n\ntest TestSystemAndNonSystemChat2 {\n functions [PromptTestClaude, PromptTestOpenAI, PromptTestOpenAIChat, PromptTestOpenAIChatNoSystem, PromptTestClaudeChat, PromptTestClaudeChatNoSystem]\n args {\n input \"lion\"\n }\n}", "test-files/functions/v2/basic.baml": "\n\nfunction ExtractResume2(resume: string) -> Resume {\n client GPT4\n prompt #\"\n {{ _.role('system') }}\n\n Extract the following information from the resume:\n\n Resume:\n <<<<\n {{ resume }}\n <<<<\n\n Output JSON schema:\n {{ ctx.output_format }}\n\n JSON:\n \"#\n}\n\n\nclass WithReasoning {\n value string\n reasoning string @description(#\"\n Why the value is a good fit.\n \"#)\n}\n\n\nclass SearchParams {\n dateRange int? @description(#\"\n In ISO duration format, e.g. P1Y2M10D.\n \"#)\n location string[]\n jobTitle WithReasoning? @description(#\"\n An exact job title, not a general category.\n \"#)\n company WithReasoning? @description(#\"\n The exact name of the company, not a product or service.\n \"#)\n description WithReasoning[] @description(#\"\n Any specific projects or features the user is looking for.\n \"#)\n tags (Tag | string)[]\n}\n\nenum Tag {\n Security\n AI\n Blockchain\n}\n\nfunction GetQuery(query: string) -> SearchParams {\n client GPT4\n prompt #\"\n Extract the following information from the query:\n\n Query:\n <<<<\n {{ query }}\n <<<<\n\n OUTPUT_JSON_SCHEMA:\n {{ ctx.output_format }}\n\n Before OUTPUT_JSON_SCHEMA, list 5 intentions the user may have.\n --- EXAMPLES ---\n 1. <intent>\n 2. <intent>\n 3. <intent>\n 4. <intent>\n 5. <intent>\n\n {\n ... // OUTPUT_JSON_SCHEMA\n }\n \"#\n}\n\nclass RaysData {\n dataType DataType\n value Resume | Event\n}\n\nenum DataType {\n Resume\n Event\n}\n\nclass Event {\n title string\n date string\n location string\n description string\n}\n\nfunction GetDataType(text: string) -> RaysData {\n client GPT4\n prompt #\"\n Extract the relevant info.\n\n Text:\n <<<<\n {{ text }}\n <<<<\n\n Output JSON schema:\n {{ ctx.output_format }}\n\n JSON:\n \"#\n}", - "test-files/providers/providers.baml": "function TestAnthropic(input: string) -> string {\n client Claude\n prompt #\"\n Write a nice haiku about {{ input }}\n \"#\n}\n\nfunction PromptTestOpenAI(input: string) -> string {\n client GPT35\n prompt #\"\n Write a nice haiku about {{ input }}\n \"#\n}\n\nfunction TestOpenAILegacyProvider(input: string) -> string {\n client GPT35LegacyProvider\n prompt #\"\n Write a nice haiku about {{ input }}\n \"#\n}\n\nfunction TestAzure(input: string) -> string {\n client GPT35Azure\n prompt #\"\n Write a nice haiku about {{ input }}\n \"#\n}\n\nfunction TestOllama(input: string) -> string {\n client Ollama\n prompt #\"\n Write a nice haiku about {{ input }}\n \"#\n}\n\nfunction TestGemini(input: string) -> string {\n client Gemini\n prompt #\"\n Write a nice short story about {{ input }}\n \"#\n}\n\nfunction TestVertex(input: string) -> string {\n client Vertex\n prompt #\"\n Write a nice short story about {{ input }}\n \"#\n\n}\n\nfunction TestAws(input: string) -> string {\n client AwsBedrock\n prompt #\"\n Write a nice short story about {{ input }}\n \"#\n}\n\nfunction TestAwsInvalidRegion(input: string) -> string {\n client AwsBedrockInvalidRegion\n prompt #\"\n Write a nice short story about {{ input }}\n \"#\n}\n\nfunction TestOpenAIShorthand(input: string) -> string {\n client \"openai/gpt-4o-mini\"\n prompt #\"\n Write a nice short story about {{ input }}\n \"#\n}\n\nfunction TestAnthropicShorthand(input: string) -> string {\n client \"anthropic/claude-3-haiku-20240307\"\n prompt #\"\n Write a nice short story about {{ input }}\n \"#\n}\n\ntest TestProvider {\n functions [\n TestAnthropic, TestVertex, PromptTestOpenAI, TestAzure, TestOllama, TestGemini, TestAws,\n TestAwsInvalidRegion,\n TestOpenAIShorthand,\n TestAnthropicShorthand\n ]\n args {\n input \"Donkey kong and peanut butter\"\n }\n}\n\n\nfunction TestCaching(input: string, not_cached: string) -> string {\n client ClaudeWithCaching\n prompt #\"\n {{ _.role('system', cache_control={\"type\": \"ephemeral\"}) }}\n Generate the following story\n {{ input }}\n\n {# Haiku require 2048 tokens to cache -#}\n {{ input }}\n\n {{ _.role('user') }}\n {{ not_cached }}\n \"#\n}\n\ntest TestName {\n functions [TestCaching]\n args {\n input #\"\nIn a near-future society where dreams have become a tradable commodity and shared experience, a lonely and socially awkward teenager named Alex discovers they possess a rare and powerful ability to not only view but also manipulate the dreams of others. Initially thrilled by this newfound power, Alex begins subtly altering the dreams of classmates and family members, helping them overcome fears, boost confidence, or experience fantastical adventures. As Alex's skills grow, so does their influence. They start selling premium dream experiences on the black market, crafting intricate and addictive dreamscapes for wealthy clients. However, the line between dream and reality begins to blur for those exposed to Alex's creations. Some clients struggle to differentiate between their true memories and the artificial ones implanted by Alex's dream manipulation.\n\nComplications arise when a mysterious government agency takes notice of Alex's unique abilities. They offer Alex a chance to use their gift for \"the greater good,\" hinting at applications in therapy, criminal rehabilitation, and even national security. Simultaneously, an underground resistance movement reaches out, warning Alex about the dangers of dream manipulation and the potential for mass control and exploitation. Caught between these opposing forces, Alex must navigate a complex web of ethical dilemmas. They grapple with questions of free will, the nature of consciousness, and the responsibility that comes with having power over people's minds. As the consequences of their actions spiral outward, affecting the lives of loved ones and strangers alike, Alex is forced to confront the true nature of their ability and decide how—or if—it should be used.\n\nThe story explores themes of identity, the subconscious mind, the ethics of technology, and the power of imagination. It delves into the potential consequences of a world where our most private thoughts and experiences are no longer truly our own, and examines the fine line between helping others and manipulating them for personal gain or a perceived greater good. The narrative further expands on the societal implications of such abilities, questioning the moral boundaries of altering consciousness and the potential for abuse in a world where dreams can be commodified. It challenges the reader to consider the impact of technology on personal autonomy and the ethical responsibilities of those who wield such power.\n\nAs Alex's journey unfolds, they encounter various individuals whose lives have been touched by their dream manipulations, each presenting a unique perspective on the ethical quandaries at hand. From a classmate who gains newfound confidence to a wealthy client who becomes addicted to the dreamscapes, the ripple effects of Alex's actions are profound and far-reaching. The government agency's interest in Alex's abilities raises questions about the potential for state control and surveillance, while the resistance movement highlights the dangers of unchecked power and the importance of safeguarding individual freedoms.\n\nUltimately, Alex's story is one of self-discovery and moral reckoning, as they must decide whether to embrace their abilities for personal gain, align with the government's vision of a controlled utopia, or join the resistance in their fight for freedom and autonomy. The narrative invites readers to reflect on the nature of reality, the boundaries of human experience, and the ethical implications of a world where dreams are no longer private sanctuaries but shared and manipulated commodities. It also explores the psychological impact on Alex, who must deal with the burden of knowing the intimate fears and desires of others, and the isolation that comes from being unable to share their own dreams without altering them.\n\nThe story further examines the technological advancements that have made dream manipulation possible, questioning the role of innovation in society and the potential for both progress and peril. It considers the societal divide between those who can afford to buy enhanced dream experiences and those who cannot, highlighting issues of inequality and access. As Alex becomes more entangled in the web of their own making, they must confront the possibility that their actions could lead to unintended consequences, not just for themselves but for the fabric of society as a whole.\n\nIn the end, Alex's journey is a cautionary tale about the power of dreams and the responsibilities that come with wielding such influence. It serves as a reminder of the importance of ethical considerations in the face of technological advancement and the need to balance innovation with humanity. The story leaves readers pondering the true cost of a world where dreams are no longer sacred, and the potential for both wonder and danger in the uncharted territories of the mind. But it's also a story about the power of imagination and the potential for change, even in a world where our deepest thoughts are no longer our own. And it's a story about the power of choice, and the importance of fighting for the freedom to dream.\n\nIn conclusion, this story is a reflection on the power of dreams and the responsibilities that come with wielding such influence. It serves as a reminder of the importance of ethical considerations in the face of technological advancement and the need to balance innovation with humanity. The story leaves readers pondering the true cost of a world where dreams are no longer sacred, and the potential for both wonder and danger in the uncharted territories of the mind. But it's also a story about the power of imagination and the potential for change, even in a world where our deepest thoughts are no longer our own. And it's a story about the power of choice, and the importance of fighting for the freedom to dream.\n \"#\n not_cached #\"\n hello world\n \"#\n }\n}\n", + "test-files/providers/providers.baml": "function TestAnthropic(input: string) -> string {\n client Claude\n prompt #\"\n Write a nice haiku about {{ input }}\n \"#\n}\n\nfunction PromptTestOpenAI(input: string) -> string {\n client GPT35\n prompt #\"\n Write a nice haiku about {{ input }}\n \"#\n}\n\nfunction TestOpenAILegacyProvider(input: string) -> string {\n client GPT35LegacyProvider\n prompt #\"\n Write a nice haiku about {{ input }}\n \"#\n}\n\nfunction TestAzure(input: string) -> string {\n client GPT35Azure\n prompt #\"\n Write a nice haiku about {{ input }}\n \"#\n}\n\nclient GPT35AzureFailed {\n provider azure-openai\n options {\n resource_name \"west-us-azure-baml-incorrect-suffix\"\n deployment_id \"gpt-35-turbo-default\"\n api_key env.AZURE_OPENAI_API_KEY\n }\n}\nfunction TestAzureFailure(input: string) -> string {\n client GPT35AzureFailed\n prompt #\"\n Write a nice haiku about {{ input }}\n \"#\n}\n\nfunction TestOllama(input: string) -> string {\n client Ollama\n prompt #\"\n Write a nice haiku about {{ input }}\n \"#\n}\n\nfunction TestGemini(input: string) -> string {\n client Gemini\n prompt #\"\n Write a nice short story about {{ input }}\n \"#\n}\n\nfunction TestVertex(input: string) -> string {\n client Vertex\n prompt #\"\n Write a nice short story about {{ input }}\n \"#\n\n}\n\nfunction TestAws(input: string) -> string {\n client AwsBedrock\n prompt #\"\n Write a nice short story about {{ input }}\n \"#\n}\n\nfunction TestAwsInvalidRegion(input: string) -> string {\n client AwsBedrockInvalidRegion\n prompt #\"\n Write a nice short story about {{ input }}\n \"#\n}\n\nfunction TestOpenAIShorthand(input: string) -> string {\n client \"openai/gpt-4o-mini\"\n prompt #\"\n Write a nice short story about {{ input }}\n \"#\n}\n\nfunction TestAnthropicShorthand(input: string) -> string {\n client \"anthropic/claude-3-haiku-20240307\"\n prompt #\"\n Write a nice short story about {{ input }}\n \"#\n}\n\ntest TestProvider {\n functions [\n TestAnthropic, TestVertex, PromptTestOpenAI, TestAzure, TestOllama, TestGemini, TestAws,\n TestAwsInvalidRegion,\n TestOpenAIShorthand,\n TestAnthropicShorthand\n ]\n args {\n input \"Donkey kong and peanut butter\"\n }\n}\n\n\nfunction TestCaching(input: string, not_cached: string) -> string {\n client ClaudeWithCaching\n prompt #\"\n {{ _.role('system', cache_control={\"type\": \"ephemeral\"}) }}\n Generate the following story\n {{ input }}\n\n {# Haiku require 2048 tokens to cache -#}\n {{ input }}\n\n {{ _.role('user') }}\n {{ not_cached }}\n \"#\n}\n\ntest TestName {\n functions [TestCaching]\n args {\n input #\"\nIn a near-future society where dreams have become a tradable commodity and shared experience, a lonely and socially awkward teenager named Alex discovers they possess a rare and powerful ability to not only view but also manipulate the dreams of others. Initially thrilled by this newfound power, Alex begins subtly altering the dreams of classmates and family members, helping them overcome fears, boost confidence, or experience fantastical adventures. As Alex's skills grow, so does their influence. They start selling premium dream experiences on the black market, crafting intricate and addictive dreamscapes for wealthy clients. However, the line between dream and reality begins to blur for those exposed to Alex's creations. Some clients struggle to differentiate between their true memories and the artificial ones implanted by Alex's dream manipulation.\n\nComplications arise when a mysterious government agency takes notice of Alex's unique abilities. They offer Alex a chance to use their gift for \"the greater good,\" hinting at applications in therapy, criminal rehabilitation, and even national security. Simultaneously, an underground resistance movement reaches out, warning Alex about the dangers of dream manipulation and the potential for mass control and exploitation. Caught between these opposing forces, Alex must navigate a complex web of ethical dilemmas. They grapple with questions of free will, the nature of consciousness, and the responsibility that comes with having power over people's minds. As the consequences of their actions spiral outward, affecting the lives of loved ones and strangers alike, Alex is forced to confront the true nature of their ability and decide how—or if—it should be used.\n\nThe story explores themes of identity, the subconscious mind, the ethics of technology, and the power of imagination. It delves into the potential consequences of a world where our most private thoughts and experiences are no longer truly our own, and examines the fine line between helping others and manipulating them for personal gain or a perceived greater good. The narrative further expands on the societal implications of such abilities, questioning the moral boundaries of altering consciousness and the potential for abuse in a world where dreams can be commodified. It challenges the reader to consider the impact of technology on personal autonomy and the ethical responsibilities of those who wield such power.\n\nAs Alex's journey unfolds, they encounter various individuals whose lives have been touched by their dream manipulations, each presenting a unique perspective on the ethical quandaries at hand. From a classmate who gains newfound confidence to a wealthy client who becomes addicted to the dreamscapes, the ripple effects of Alex's actions are profound and far-reaching. The government agency's interest in Alex's abilities raises questions about the potential for state control and surveillance, while the resistance movement highlights the dangers of unchecked power and the importance of safeguarding individual freedoms.\n\nUltimately, Alex's story is one of self-discovery and moral reckoning, as they must decide whether to embrace their abilities for personal gain, align with the government's vision of a controlled utopia, or join the resistance in their fight for freedom and autonomy. The narrative invites readers to reflect on the nature of reality, the boundaries of human experience, and the ethical implications of a world where dreams are no longer private sanctuaries but shared and manipulated commodities. It also explores the psychological impact on Alex, who must deal with the burden of knowing the intimate fears and desires of others, and the isolation that comes from being unable to share their own dreams without altering them.\n\nThe story further examines the technological advancements that have made dream manipulation possible, questioning the role of innovation in society and the potential for both progress and peril. It considers the societal divide between those who can afford to buy enhanced dream experiences and those who cannot, highlighting issues of inequality and access. As Alex becomes more entangled in the web of their own making, they must confront the possibility that their actions could lead to unintended consequences, not just for themselves but for the fabric of society as a whole.\n\nIn the end, Alex's journey is a cautionary tale about the power of dreams and the responsibilities that come with wielding such influence. It serves as a reminder of the importance of ethical considerations in the face of technological advancement and the need to balance innovation with humanity. The story leaves readers pondering the true cost of a world where dreams are no longer sacred, and the potential for both wonder and danger in the uncharted territories of the mind. But it's also a story about the power of imagination and the potential for change, even in a world where our deepest thoughts are no longer our own. And it's a story about the power of choice, and the importance of fighting for the freedom to dream.\n\nIn conclusion, this story is a reflection on the power of dreams and the responsibilities that come with wielding such influence. It serves as a reminder of the importance of ethical considerations in the face of technological advancement and the need to balance innovation with humanity. The story leaves readers pondering the true cost of a world where dreams are no longer sacred, and the potential for both wonder and danger in the uncharted territories of the mind. But it's also a story about the power of imagination and the potential for change, even in a world where our deepest thoughts are no longer our own. And it's a story about the power of choice, and the importance of fighting for the freedom to dream.\n \"#\n not_cached #\"\n hello world\n \"#\n }\n}\n", "test-files/strategies/fallback-shorthand.baml": "\nclient<llm> FallbackToShorthand {\n provider fallback\n options {\n strategy [\n \"openai/does-not-exist\",\n \"openai/gpt-4o-mini\"\n ]\n }\n}\n\n\nfunction TestFallbackToShorthand(input: string) -> string {\n client FallbackToShorthand\n // TODO make it return the client name instead\n prompt #\"\n Say a haiku about {{input}}.\n \"#\n}\n\ntest TestProvider_FallbackToShorthand {\n functions [\n TestFallbackToShorthand\n ]\n args {\n input \"Donkey kong and peanut butter\"\n }\n}\n", "test-files/strategies/fallback.baml": "// Happy path fallbacks.\nclient<llm> FaultyClient {\n provider openai\n options {\n model unknown-model\n api_key env.OPENAI_API_KEY\n }\n}\n\n\nclient<llm> FallbackClient {\n provider fallback\n options {\n // first 2 clients are expected to fail.\n strategy [\n FaultyClient,\n RetryClientConstant,\n GPT35\n Gemini\n\n ]\n }\n}\n\nfunction TestFallbackClient() -> string {\n client FallbackClient\n // TODO make it return the client name instead\n prompt #\"\n Say a haiku about mexico.\n \"#\n}\n\n// Fallbacks should fail gracefully.\nclient<llm> FaultyAzureClient {\n provider azure-openai\n options {\n model unknown-model\n resource_name \"unknown-resource-id\"\n deployment_id \"unknown-deployment-id\"\n }\n}\n\nclient<llm> SingleFallbackClient {\n provider fallback\n options {\n // first 2 clients are expected to fail.\n strategy [\n FaultyAzureClient\n ]\n }\n}\n\nfunction TestSingleFallbackClient() -> string {\n client SingleFallbackClient\n // TODO make it return the client name instead\n prompt #\"\n Say a haiku about mexico.\n \"#\n}\n", "test-files/strategies/retry.baml": "\nretry_policy Exponential {\n max_retries 3\n strategy {\n type exponential_backoff\n }\n}\n\nretry_policy Constant {\n max_retries 3\n strategy {\n type constant_delay\n delay_ms 100\n }\n}\n\nclient<llm> RetryClientConstant {\n provider openai\n retry_policy Constant\n options {\n model \"gpt-3.5-turbo\"\n api_key \"blah\"\n }\n}\n\nclient<llm> RetryClientExponential {\n provider openai\n retry_policy Exponential\n options {\n model \"gpt-3.5-turbo\"\n api_key \"blahh\"\n }\n}\n\nfunction TestRetryConstant() -> string {\n client RetryClientConstant\n prompt #\"\n Say a haiku\n \"#\n}\n\nfunction TestRetryExponential() -> string {\n client RetryClientExponential\n prompt #\"\n Say a haiku\n \"#\n}\n", diff --git a/integ-tests/ruby/baml_client/inlined.rb b/integ-tests/ruby/baml_client/inlined.rb index ca979d424..dbfdbdd4c 100644 --- a/integ-tests/ruby/baml_client/inlined.rb +++ b/integ-tests/ruby/baml_client/inlined.rb @@ -25,7 +25,7 @@ module Inlined "fiddle-examples/extract-receipt-info.baml" => "class ReceiptItem {\n name string\n description string?\n quantity int\n price float\n}\n\nclass ReceiptInfo {\n items ReceiptItem[]\n total_cost float?\n venue \"barisa\" | \"ox_burger\"\n}\n\nfunction ExtractReceiptInfo(email: string, reason: \"curiosity\" | \"personal_finance\") -> ReceiptInfo {\n client GPT4o\n prompt #\"\n Given the receipt below:\n\n ```\n {{email}}\n ```\n\n {{ ctx.output_format }}\n \"#\n}\n\n", "fiddle-examples/images/image.baml" => "function DescribeImage(img: image) -> string {\n client GPT4o\n prompt #\"\n {{ _.role(\"user\") }}\n\n\n Describe the image below in 20 words:\n {{ img }}\n \"#\n\n}\n\nclass FakeImage {\n url string\n}\n\nclass ClassWithImage {\n myImage image\n param2 string\n fake_image FakeImage\n}\n\n// chat role user present\nfunction DescribeImage2(classWithImage: ClassWithImage, img2: image) -> string { \n client GPT4Turbo\n prompt #\"\n {{ _.role(\"user\") }}\n You should return 2 answers that answer the following commands.\n\n 1. Describe this in 5 words:\n {{ classWithImage.myImage }}\n\n 2. Also tell me what's happening here in one sentence:\n {{ img2 }}\n \"#\n}\n\n// no chat role\nfunction DescribeImage3(classWithImage: ClassWithImage, img2: image) -> string {\n client GPT4Turbo\n prompt #\"\n Describe this in 5 words:\n {{ classWithImage.myImage }}\n\n Tell me also what's happening here in one sentence and relate it to the word {{ classWithImage.param2 }}:\n {{ img2 }}\n \"#\n}\n\n\n// system prompt and chat prompt\nfunction DescribeImage4(classWithImage: ClassWithImage, img2: image) -> string {\n client GPT4Turbo\n prompt #\"\n {{ _.role(\"system\")}}\n\n Describe this in 5 words:\n {{ classWithImage.myImage }}\n\n Tell me also what's happening here in one sentence and relate it to the word {{ classWithImage.param2 }}:\n {{ img2 }}\n \"#\n}\n\ntest TestName {\n functions [DescribeImage]\n args {\n img { url \"https://imgs.xkcd.com/comics/standards.png\"}\n }\n}\n", "fiddle-examples/symbol-tuning.baml" => "enum Category3 {\n Refund @alias(\"k1\")\n @description(\"Customer wants to refund a product\")\n\n CancelOrder @alias(\"k2\")\n @description(\"Customer wants to cancel an order\")\n\n TechnicalSupport @alias(\"k3\")\n @description(\"Customer needs help with a technical issue unrelated to account creation or login\")\n\n AccountIssue @alias(\"k4\")\n @description(\"Specifically relates to account-login or account-creation\")\n\n Question @alias(\"k5\")\n @description(\"Customer has a question\")\n}\n\nfunction ClassifyMessage3(input: string) -> Category {\n client GPT4\n\n prompt #\"\n Classify the following INPUT into ONE\n of the following categories:\n\n INPUT: {{ input }}\n\n {{ ctx.output_format }}\n\n Response:\n \"#\n}", - "generators.baml" => "generator lang_python {\n output_type python/pydantic\n output_dir \"../python\"\n version \"0.70.1\"\n}\n\ngenerator lang_typescript {\n output_type typescript\n output_dir \"../typescript\"\n version \"0.70.1\"\n}\n\ngenerator lang_ruby {\n output_type ruby/sorbet\n output_dir \"../ruby\"\n version \"0.70.1\"\n}\n\n// generator openapi {\n// output_type rest/openapi\n// output_dir \"../openapi\"\n// version \"0.70.1\"\n// on_generate \"rm .gitignore\"\n// }\n", + "generators.baml" => "generator lang_python {\n output_type python/pydantic\n output_dir \"../python\"\n version \"0.70.5\"\n}\n\ngenerator lang_typescript {\n output_type typescript\n output_dir \"../typescript\"\n version \"0.70.5\"\n}\n\ngenerator lang_ruby {\n output_type ruby/sorbet\n output_dir \"../ruby\"\n version \"0.70.5\"\n}\n\n// generator openapi {\n// output_type rest/openapi\n// output_dir \"../openapi\"\n// version \"0.70.5\"\n// on_generate \"rm .gitignore\"\n// }\n", "test-files/aliases/aliased-inputs.baml" => "\nclass InputClass {\n key string @alias(\"color\")\n key2 string\n}\n\n\nclass InputClassNested {\n key string\n nested InputClass @alias(\"interesting-key\")\n}\n \n\nfunction AliasedInputClass(input: InputClass) -> string {\n client GPT35\n prompt #\"\n\n {{input}}\n\n This is a test. What's the name of the first json key above? Remember, tell me the key, not value.\n \"#\n}\n \nfunction AliasedInputClass2(input: InputClass) -> string {\n client GPT35\n prompt #\"\n\n {# making sure we can still access the original key #}\n {%if input.key == \"tiger\"%}\n Repeat this value back to me, and nothing else: {{input.key}}\n {%endif%}\n \"#\n}\n \n function AliasedInputClassNested(input: InputClassNested) -> string {\n client GPT35\n prompt #\"\n {{ _.role(\"user\")}}\n\n {{input}}\n\n This is a test. What's the name of the second json key above? Remember, tell me the key, not value.\n \"#\n }\n\n\nenum AliasedEnum {\n KEY_ONE @alias(\"tiger\")\n KEY_TWO\n}\n\nfunction AliasedInputEnum(input: AliasedEnum) -> string {\n client GPT4o\n prompt #\"\n {{ _.role(\"user\")}}\n\n\n Write out this word only in your response, in lowercase:\n ---\n {{input}}\n ---\n Answer:\n \"#\n}\n\n\nfunction AliasedInputList(input: AliasedEnum[]) -> string {\n client GPT35\n prompt #\"\n {{ _.role(\"user\")}}\n Given this array:\n ---\n {{input}}\n ---\n\n Return the first element in the array:\n \"#\n}\n\n", "test-files/aliases/classes.baml" => "class TestClassAlias {\n key string @alias(\"key-dash\") @description(#\"\n This is a description for key\n af asdf\n \"#)\n key2 string @alias(\"key21\")\n key3 string @alias(\"key with space\")\n key4 string //unaliased\n key5 string @alias(\"key.with.punctuation/123\")\n}\n\nfunction FnTestClassAlias(input: string) -> TestClassAlias {\n client GPT35\n prompt #\"\n {{ctx.output_format}}\n \"#\n}\n\ntest FnTestClassAlias {\n functions [FnTestClassAlias]\n args {\n input \"example input\"\n }\n}\n", "test-files/aliases/enums.baml" => "enum TestEnum {\n A @alias(\"k1\") @description(#\"\n User is angry\n \"#)\n B @alias(\"k22\") @description(#\"\n User is happy\n \"#)\n // tests whether k1 doesnt incorrectly get matched with k11\n C @alias(\"k11\") @description(#\"\n User is sad\n \"#)\n D @alias(\"k44\") @description(\n User is confused\n )\n E @description(\n User is excited\n )\n F @alias(\"k5\") // only alias\n \n G @alias(\"k6\") @description(#\"\n User is bored\n With a long description\n \"#)\n \n @@alias(\"Category\")\n}\n\nfunction FnTestAliasedEnumOutput(input: string) -> TestEnum {\n client GPT35\n prompt #\"\n Classify the user input into the following category\n \n {{ ctx.output_format }}\n\n {{ _.role('user') }}\n {{input}}\n\n {{ _.role('assistant') }}\n Category ID:\n \"#\n}\n\ntest FnTestAliasedEnumOutput {\n functions [FnTestAliasedEnumOutput]\n args {\n input \"mehhhhh\"\n }\n}", @@ -88,7 +88,7 @@ module Inlined "test-files/functions/prompts/no-chat-messages.baml" => "\n\nfunction PromptTestClaude(input: string) -> string {\n client Sonnet\n prompt #\"\n Tell me a haiku about {{ input }}\n \"#\n}\n\n\nfunction PromptTestStreaming(input: string) -> string {\n client GPT35\n prompt #\"\n Tell me a short story about {{ input }}\n \"#\n}\n\ntest TestName {\n functions [PromptTestStreaming]\n args {\n input #\"\n hello world\n \"#\n }\n}\n", "test-files/functions/prompts/with-chat-messages.baml" => "\nfunction PromptTestOpenAIChat(input: string) -> string {\n client GPT35\n prompt #\"\n {{ _.role(\"system\") }}\n You are an assistant that always responds in a very excited way with emojis and also outputs this word 4 times after giving a response: {{ input }}\n \n {{ _.role(\"user\") }}\n Tell me a haiku about {{ input }}\n \"#\n}\n\nfunction PromptTestOpenAIChatNoSystem(input: string) -> string {\n client GPT35\n prompt #\"\n You are an assistant that always responds in a very excited way with emojis and also outputs this word 4 times after giving a response: {{ input }}\n \n {{ _.role(\"user\") }}\n Tell me a haiku about {{ input }}\n \"#\n}\n\nfunction PromptTestClaudeChat(input: string) -> string {\n client Claude\n prompt #\"\n {{ _.role(\"system\") }}\n You are an assistant that always responds in a very excited way with emojis and also outputs this word 4 times after giving a response: {{ input }}\n \n {{ _.role(\"user\") }}\n Tell me a haiku about {{ input }}\n \"#\n}\n\nfunction PromptTestClaudeChatNoSystem(input: string) -> string {\n client Claude\n prompt #\"\n You are an assistant that always responds in a very excited way with emojis and also outputs this word 4 times after giving a response: {{ input }}\n \n {{ _.role(\"user\") }}\n Tell me a haiku about {{ input }}\n \"#\n}\n\ntest TestSystemAndNonSystemChat1 {\n functions [PromptTestClaude, PromptTestOpenAI, PromptTestOpenAIChat, PromptTestOpenAIChatNoSystem, PromptTestClaudeChat, PromptTestClaudeChatNoSystem]\n args {\n input \"cats\"\n }\n}\n\ntest TestSystemAndNonSystemChat2 {\n functions [PromptTestClaude, PromptTestOpenAI, PromptTestOpenAIChat, PromptTestOpenAIChatNoSystem, PromptTestClaudeChat, PromptTestClaudeChatNoSystem]\n args {\n input \"lion\"\n }\n}", "test-files/functions/v2/basic.baml" => "\n\nfunction ExtractResume2(resume: string) -> Resume {\n client GPT4\n prompt #\"\n {{ _.role('system') }}\n\n Extract the following information from the resume:\n\n Resume:\n <<<<\n {{ resume }}\n <<<<\n\n Output JSON schema:\n {{ ctx.output_format }}\n\n JSON:\n \"#\n}\n\n\nclass WithReasoning {\n value string\n reasoning string @description(#\"\n Why the value is a good fit.\n \"#)\n}\n\n\nclass SearchParams {\n dateRange int? @description(#\"\n In ISO duration format, e.g. P1Y2M10D.\n \"#)\n location string[]\n jobTitle WithReasoning? @description(#\"\n An exact job title, not a general category.\n \"#)\n company WithReasoning? @description(#\"\n The exact name of the company, not a product or service.\n \"#)\n description WithReasoning[] @description(#\"\n Any specific projects or features the user is looking for.\n \"#)\n tags (Tag | string)[]\n}\n\nenum Tag {\n Security\n AI\n Blockchain\n}\n\nfunction GetQuery(query: string) -> SearchParams {\n client GPT4\n prompt #\"\n Extract the following information from the query:\n\n Query:\n <<<<\n {{ query }}\n <<<<\n\n OUTPUT_JSON_SCHEMA:\n {{ ctx.output_format }}\n\n Before OUTPUT_JSON_SCHEMA, list 5 intentions the user may have.\n --- EXAMPLES ---\n 1. <intent>\n 2. <intent>\n 3. <intent>\n 4. <intent>\n 5. <intent>\n\n {\n ... // OUTPUT_JSON_SCHEMA\n }\n \"#\n}\n\nclass RaysData {\n dataType DataType\n value Resume | Event\n}\n\nenum DataType {\n Resume\n Event\n}\n\nclass Event {\n title string\n date string\n location string\n description string\n}\n\nfunction GetDataType(text: string) -> RaysData {\n client GPT4\n prompt #\"\n Extract the relevant info.\n\n Text:\n <<<<\n {{ text }}\n <<<<\n\n Output JSON schema:\n {{ ctx.output_format }}\n\n JSON:\n \"#\n}", - "test-files/providers/providers.baml" => "function TestAnthropic(input: string) -> string {\n client Claude\n prompt #\"\n Write a nice haiku about {{ input }}\n \"#\n}\n\nfunction PromptTestOpenAI(input: string) -> string {\n client GPT35\n prompt #\"\n Write a nice haiku about {{ input }}\n \"#\n}\n\nfunction TestOpenAILegacyProvider(input: string) -> string {\n client GPT35LegacyProvider\n prompt #\"\n Write a nice haiku about {{ input }}\n \"#\n}\n\nfunction TestAzure(input: string) -> string {\n client GPT35Azure\n prompt #\"\n Write a nice haiku about {{ input }}\n \"#\n}\n\nfunction TestOllama(input: string) -> string {\n client Ollama\n prompt #\"\n Write a nice haiku about {{ input }}\n \"#\n}\n\nfunction TestGemini(input: string) -> string {\n client Gemini\n prompt #\"\n Write a nice short story about {{ input }}\n \"#\n}\n\nfunction TestVertex(input: string) -> string {\n client Vertex\n prompt #\"\n Write a nice short story about {{ input }}\n \"#\n\n}\n\nfunction TestAws(input: string) -> string {\n client AwsBedrock\n prompt #\"\n Write a nice short story about {{ input }}\n \"#\n}\n\nfunction TestAwsInvalidRegion(input: string) -> string {\n client AwsBedrockInvalidRegion\n prompt #\"\n Write a nice short story about {{ input }}\n \"#\n}\n\nfunction TestOpenAIShorthand(input: string) -> string {\n client \"openai/gpt-4o-mini\"\n prompt #\"\n Write a nice short story about {{ input }}\n \"#\n}\n\nfunction TestAnthropicShorthand(input: string) -> string {\n client \"anthropic/claude-3-haiku-20240307\"\n prompt #\"\n Write a nice short story about {{ input }}\n \"#\n}\n\ntest TestProvider {\n functions [\n TestAnthropic, TestVertex, PromptTestOpenAI, TestAzure, TestOllama, TestGemini, TestAws,\n TestAwsInvalidRegion,\n TestOpenAIShorthand,\n TestAnthropicShorthand\n ]\n args {\n input \"Donkey kong and peanut butter\"\n }\n}\n\n\nfunction TestCaching(input: string, not_cached: string) -> string {\n client ClaudeWithCaching\n prompt #\"\n {{ _.role('system', cache_control={\"type\": \"ephemeral\"}) }}\n Generate the following story\n {{ input }}\n\n {# Haiku require 2048 tokens to cache -#}\n {{ input }}\n\n {{ _.role('user') }}\n {{ not_cached }}\n \"#\n}\n\ntest TestName {\n functions [TestCaching]\n args {\n input #\"\nIn a near-future society where dreams have become a tradable commodity and shared experience, a lonely and socially awkward teenager named Alex discovers they possess a rare and powerful ability to not only view but also manipulate the dreams of others. Initially thrilled by this newfound power, Alex begins subtly altering the dreams of classmates and family members, helping them overcome fears, boost confidence, or experience fantastical adventures. As Alex's skills grow, so does their influence. They start selling premium dream experiences on the black market, crafting intricate and addictive dreamscapes for wealthy clients. However, the line between dream and reality begins to blur for those exposed to Alex's creations. Some clients struggle to differentiate between their true memories and the artificial ones implanted by Alex's dream manipulation.\n\nComplications arise when a mysterious government agency takes notice of Alex's unique abilities. They offer Alex a chance to use their gift for \"the greater good,\" hinting at applications in therapy, criminal rehabilitation, and even national security. Simultaneously, an underground resistance movement reaches out, warning Alex about the dangers of dream manipulation and the potential for mass control and exploitation. Caught between these opposing forces, Alex must navigate a complex web of ethical dilemmas. They grapple with questions of free will, the nature of consciousness, and the responsibility that comes with having power over people's minds. As the consequences of their actions spiral outward, affecting the lives of loved ones and strangers alike, Alex is forced to confront the true nature of their ability and decide how—or if—it should be used.\n\nThe story explores themes of identity, the subconscious mind, the ethics of technology, and the power of imagination. It delves into the potential consequences of a world where our most private thoughts and experiences are no longer truly our own, and examines the fine line between helping others and manipulating them for personal gain or a perceived greater good. The narrative further expands on the societal implications of such abilities, questioning the moral boundaries of altering consciousness and the potential for abuse in a world where dreams can be commodified. It challenges the reader to consider the impact of technology on personal autonomy and the ethical responsibilities of those who wield such power.\n\nAs Alex's journey unfolds, they encounter various individuals whose lives have been touched by their dream manipulations, each presenting a unique perspective on the ethical quandaries at hand. From a classmate who gains newfound confidence to a wealthy client who becomes addicted to the dreamscapes, the ripple effects of Alex's actions are profound and far-reaching. The government agency's interest in Alex's abilities raises questions about the potential for state control and surveillance, while the resistance movement highlights the dangers of unchecked power and the importance of safeguarding individual freedoms.\n\nUltimately, Alex's story is one of self-discovery and moral reckoning, as they must decide whether to embrace their abilities for personal gain, align with the government's vision of a controlled utopia, or join the resistance in their fight for freedom and autonomy. The narrative invites readers to reflect on the nature of reality, the boundaries of human experience, and the ethical implications of a world where dreams are no longer private sanctuaries but shared and manipulated commodities. It also explores the psychological impact on Alex, who must deal with the burden of knowing the intimate fears and desires of others, and the isolation that comes from being unable to share their own dreams without altering them.\n\nThe story further examines the technological advancements that have made dream manipulation possible, questioning the role of innovation in society and the potential for both progress and peril. It considers the societal divide between those who can afford to buy enhanced dream experiences and those who cannot, highlighting issues of inequality and access. As Alex becomes more entangled in the web of their own making, they must confront the possibility that their actions could lead to unintended consequences, not just for themselves but for the fabric of society as a whole.\n\nIn the end, Alex's journey is a cautionary tale about the power of dreams and the responsibilities that come with wielding such influence. It serves as a reminder of the importance of ethical considerations in the face of technological advancement and the need to balance innovation with humanity. The story leaves readers pondering the true cost of a world where dreams are no longer sacred, and the potential for both wonder and danger in the uncharted territories of the mind. But it's also a story about the power of imagination and the potential for change, even in a world where our deepest thoughts are no longer our own. And it's a story about the power of choice, and the importance of fighting for the freedom to dream.\n\nIn conclusion, this story is a reflection on the power of dreams and the responsibilities that come with wielding such influence. It serves as a reminder of the importance of ethical considerations in the face of technological advancement and the need to balance innovation with humanity. The story leaves readers pondering the true cost of a world where dreams are no longer sacred, and the potential for both wonder and danger in the uncharted territories of the mind. But it's also a story about the power of imagination and the potential for change, even in a world where our deepest thoughts are no longer our own. And it's a story about the power of choice, and the importance of fighting for the freedom to dream.\n \"#\n not_cached #\"\n hello world\n \"#\n }\n}\n", + "test-files/providers/providers.baml" => "function TestAnthropic(input: string) -> string {\n client Claude\n prompt #\"\n Write a nice haiku about {{ input }}\n \"#\n}\n\nfunction PromptTestOpenAI(input: string) -> string {\n client GPT35\n prompt #\"\n Write a nice haiku about {{ input }}\n \"#\n}\n\nfunction TestOpenAILegacyProvider(input: string) -> string {\n client GPT35LegacyProvider\n prompt #\"\n Write a nice haiku about {{ input }}\n \"#\n}\n\nfunction TestAzure(input: string) -> string {\n client GPT35Azure\n prompt #\"\n Write a nice haiku about {{ input }}\n \"#\n}\n\nclient GPT35AzureFailed {\n provider azure-openai\n options {\n resource_name \"west-us-azure-baml-incorrect-suffix\"\n deployment_id \"gpt-35-turbo-default\"\n api_key env.AZURE_OPENAI_API_KEY\n }\n}\nfunction TestAzureFailure(input: string) -> string {\n client GPT35AzureFailed\n prompt #\"\n Write a nice haiku about {{ input }}\n \"#\n}\n\nfunction TestOllama(input: string) -> string {\n client Ollama\n prompt #\"\n Write a nice haiku about {{ input }}\n \"#\n}\n\nfunction TestGemini(input: string) -> string {\n client Gemini\n prompt #\"\n Write a nice short story about {{ input }}\n \"#\n}\n\nfunction TestVertex(input: string) -> string {\n client Vertex\n prompt #\"\n Write a nice short story about {{ input }}\n \"#\n\n}\n\nfunction TestAws(input: string) -> string {\n client AwsBedrock\n prompt #\"\n Write a nice short story about {{ input }}\n \"#\n}\n\nfunction TestAwsInvalidRegion(input: string) -> string {\n client AwsBedrockInvalidRegion\n prompt #\"\n Write a nice short story about {{ input }}\n \"#\n}\n\nfunction TestOpenAIShorthand(input: string) -> string {\n client \"openai/gpt-4o-mini\"\n prompt #\"\n Write a nice short story about {{ input }}\n \"#\n}\n\nfunction TestAnthropicShorthand(input: string) -> string {\n client \"anthropic/claude-3-haiku-20240307\"\n prompt #\"\n Write a nice short story about {{ input }}\n \"#\n}\n\ntest TestProvider {\n functions [\n TestAnthropic, TestVertex, PromptTestOpenAI, TestAzure, TestOllama, TestGemini, TestAws,\n TestAwsInvalidRegion,\n TestOpenAIShorthand,\n TestAnthropicShorthand\n ]\n args {\n input \"Donkey kong and peanut butter\"\n }\n}\n\n\nfunction TestCaching(input: string, not_cached: string) -> string {\n client ClaudeWithCaching\n prompt #\"\n {{ _.role('system', cache_control={\"type\": \"ephemeral\"}) }}\n Generate the following story\n {{ input }}\n\n {# Haiku require 2048 tokens to cache -#}\n {{ input }}\n\n {{ _.role('user') }}\n {{ not_cached }}\n \"#\n}\n\ntest TestName {\n functions [TestCaching]\n args {\n input #\"\nIn a near-future society where dreams have become a tradable commodity and shared experience, a lonely and socially awkward teenager named Alex discovers they possess a rare and powerful ability to not only view but also manipulate the dreams of others. Initially thrilled by this newfound power, Alex begins subtly altering the dreams of classmates and family members, helping them overcome fears, boost confidence, or experience fantastical adventures. As Alex's skills grow, so does their influence. They start selling premium dream experiences on the black market, crafting intricate and addictive dreamscapes for wealthy clients. However, the line between dream and reality begins to blur for those exposed to Alex's creations. Some clients struggle to differentiate between their true memories and the artificial ones implanted by Alex's dream manipulation.\n\nComplications arise when a mysterious government agency takes notice of Alex's unique abilities. They offer Alex a chance to use their gift for \"the greater good,\" hinting at applications in therapy, criminal rehabilitation, and even national security. Simultaneously, an underground resistance movement reaches out, warning Alex about the dangers of dream manipulation and the potential for mass control and exploitation. Caught between these opposing forces, Alex must navigate a complex web of ethical dilemmas. They grapple with questions of free will, the nature of consciousness, and the responsibility that comes with having power over people's minds. As the consequences of their actions spiral outward, affecting the lives of loved ones and strangers alike, Alex is forced to confront the true nature of their ability and decide how—or if—it should be used.\n\nThe story explores themes of identity, the subconscious mind, the ethics of technology, and the power of imagination. It delves into the potential consequences of a world where our most private thoughts and experiences are no longer truly our own, and examines the fine line between helping others and manipulating them for personal gain or a perceived greater good. The narrative further expands on the societal implications of such abilities, questioning the moral boundaries of altering consciousness and the potential for abuse in a world where dreams can be commodified. It challenges the reader to consider the impact of technology on personal autonomy and the ethical responsibilities of those who wield such power.\n\nAs Alex's journey unfolds, they encounter various individuals whose lives have been touched by their dream manipulations, each presenting a unique perspective on the ethical quandaries at hand. From a classmate who gains newfound confidence to a wealthy client who becomes addicted to the dreamscapes, the ripple effects of Alex's actions are profound and far-reaching. The government agency's interest in Alex's abilities raises questions about the potential for state control and surveillance, while the resistance movement highlights the dangers of unchecked power and the importance of safeguarding individual freedoms.\n\nUltimately, Alex's story is one of self-discovery and moral reckoning, as they must decide whether to embrace their abilities for personal gain, align with the government's vision of a controlled utopia, or join the resistance in their fight for freedom and autonomy. The narrative invites readers to reflect on the nature of reality, the boundaries of human experience, and the ethical implications of a world where dreams are no longer private sanctuaries but shared and manipulated commodities. It also explores the psychological impact on Alex, who must deal with the burden of knowing the intimate fears and desires of others, and the isolation that comes from being unable to share their own dreams without altering them.\n\nThe story further examines the technological advancements that have made dream manipulation possible, questioning the role of innovation in society and the potential for both progress and peril. It considers the societal divide between those who can afford to buy enhanced dream experiences and those who cannot, highlighting issues of inequality and access. As Alex becomes more entangled in the web of their own making, they must confront the possibility that their actions could lead to unintended consequences, not just for themselves but for the fabric of society as a whole.\n\nIn the end, Alex's journey is a cautionary tale about the power of dreams and the responsibilities that come with wielding such influence. It serves as a reminder of the importance of ethical considerations in the face of technological advancement and the need to balance innovation with humanity. The story leaves readers pondering the true cost of a world where dreams are no longer sacred, and the potential for both wonder and danger in the uncharted territories of the mind. But it's also a story about the power of imagination and the potential for change, even in a world where our deepest thoughts are no longer our own. And it's a story about the power of choice, and the importance of fighting for the freedom to dream.\n\nIn conclusion, this story is a reflection on the power of dreams and the responsibilities that come with wielding such influence. It serves as a reminder of the importance of ethical considerations in the face of technological advancement and the need to balance innovation with humanity. The story leaves readers pondering the true cost of a world where dreams are no longer sacred, and the potential for both wonder and danger in the uncharted territories of the mind. But it's also a story about the power of imagination and the potential for change, even in a world where our deepest thoughts are no longer our own. And it's a story about the power of choice, and the importance of fighting for the freedom to dream.\n \"#\n not_cached #\"\n hello world\n \"#\n }\n}\n", "test-files/strategies/fallback-shorthand.baml" => "\nclient<llm> FallbackToShorthand {\n provider fallback\n options {\n strategy [\n \"openai/does-not-exist\",\n \"openai/gpt-4o-mini\"\n ]\n }\n}\n\n\nfunction TestFallbackToShorthand(input: string) -> string {\n client FallbackToShorthand\n // TODO make it return the client name instead\n prompt #\"\n Say a haiku about {{input}}.\n \"#\n}\n\ntest TestProvider_FallbackToShorthand {\n functions [\n TestFallbackToShorthand\n ]\n args {\n input \"Donkey kong and peanut butter\"\n }\n}\n", "test-files/strategies/fallback.baml" => "// Happy path fallbacks.\nclient<llm> FaultyClient {\n provider openai\n options {\n model unknown-model\n api_key env.OPENAI_API_KEY\n }\n}\n\n\nclient<llm> FallbackClient {\n provider fallback\n options {\n // first 2 clients are expected to fail.\n strategy [\n FaultyClient,\n RetryClientConstant,\n GPT35\n Gemini\n\n ]\n }\n}\n\nfunction TestFallbackClient() -> string {\n client FallbackClient\n // TODO make it return the client name instead\n prompt #\"\n Say a haiku about mexico.\n \"#\n}\n\n// Fallbacks should fail gracefully.\nclient<llm> FaultyAzureClient {\n provider azure-openai\n options {\n model unknown-model\n resource_name \"unknown-resource-id\"\n deployment_id \"unknown-deployment-id\"\n }\n}\n\nclient<llm> SingleFallbackClient {\n provider fallback\n options {\n // first 2 clients are expected to fail.\n strategy [\n FaultyAzureClient\n ]\n }\n}\n\nfunction TestSingleFallbackClient() -> string {\n client SingleFallbackClient\n // TODO make it return the client name instead\n prompt #\"\n Say a haiku about mexico.\n \"#\n}\n", "test-files/strategies/retry.baml" => "\nretry_policy Exponential {\n max_retries 3\n strategy {\n type exponential_backoff\n }\n}\n\nretry_policy Constant {\n max_retries 3\n strategy {\n type constant_delay\n delay_ms 100\n }\n}\n\nclient<llm> RetryClientConstant {\n provider openai\n retry_policy Constant\n options {\n model \"gpt-3.5-turbo\"\n api_key \"blah\"\n }\n}\n\nclient<llm> RetryClientExponential {\n provider openai\n retry_policy Exponential\n options {\n model \"gpt-3.5-turbo\"\n api_key \"blahh\"\n }\n}\n\nfunction TestRetryConstant() -> string {\n client RetryClientConstant\n prompt #\"\n Say a haiku\n \"#\n}\n\nfunction TestRetryExponential() -> string {\n client RetryClientExponential\n prompt #\"\n Say a haiku\n \"#\n}\n", diff --git a/integ-tests/typescript/baml_client/inlinedbaml.ts b/integ-tests/typescript/baml_client/inlinedbaml.ts index c01a4def0..68093601d 100644 --- a/integ-tests/typescript/baml_client/inlinedbaml.ts +++ b/integ-tests/typescript/baml_client/inlinedbaml.ts @@ -26,7 +26,7 @@ const fileMap = { "fiddle-examples/extract-receipt-info.baml": "class ReceiptItem {\n name string\n description string?\n quantity int\n price float\n}\n\nclass ReceiptInfo {\n items ReceiptItem[]\n total_cost float?\n venue \"barisa\" | \"ox_burger\"\n}\n\nfunction ExtractReceiptInfo(email: string, reason: \"curiosity\" | \"personal_finance\") -> ReceiptInfo {\n client GPT4o\n prompt #\"\n Given the receipt below:\n\n ```\n {{email}}\n ```\n\n {{ ctx.output_format }}\n \"#\n}\n\n", "fiddle-examples/images/image.baml": "function DescribeImage(img: image) -> string {\n client GPT4o\n prompt #\"\n {{ _.role(\"user\") }}\n\n\n Describe the image below in 20 words:\n {{ img }}\n \"#\n\n}\n\nclass FakeImage {\n url string\n}\n\nclass ClassWithImage {\n myImage image\n param2 string\n fake_image FakeImage\n}\n\n// chat role user present\nfunction DescribeImage2(classWithImage: ClassWithImage, img2: image) -> string { \n client GPT4Turbo\n prompt #\"\n {{ _.role(\"user\") }}\n You should return 2 answers that answer the following commands.\n\n 1. Describe this in 5 words:\n {{ classWithImage.myImage }}\n\n 2. Also tell me what's happening here in one sentence:\n {{ img2 }}\n \"#\n}\n\n// no chat role\nfunction DescribeImage3(classWithImage: ClassWithImage, img2: image) -> string {\n client GPT4Turbo\n prompt #\"\n Describe this in 5 words:\n {{ classWithImage.myImage }}\n\n Tell me also what's happening here in one sentence and relate it to the word {{ classWithImage.param2 }}:\n {{ img2 }}\n \"#\n}\n\n\n// system prompt and chat prompt\nfunction DescribeImage4(classWithImage: ClassWithImage, img2: image) -> string {\n client GPT4Turbo\n prompt #\"\n {{ _.role(\"system\")}}\n\n Describe this in 5 words:\n {{ classWithImage.myImage }}\n\n Tell me also what's happening here in one sentence and relate it to the word {{ classWithImage.param2 }}:\n {{ img2 }}\n \"#\n}\n\ntest TestName {\n functions [DescribeImage]\n args {\n img { url \"https://imgs.xkcd.com/comics/standards.png\"}\n }\n}\n", "fiddle-examples/symbol-tuning.baml": "enum Category3 {\n Refund @alias(\"k1\")\n @description(\"Customer wants to refund a product\")\n\n CancelOrder @alias(\"k2\")\n @description(\"Customer wants to cancel an order\")\n\n TechnicalSupport @alias(\"k3\")\n @description(\"Customer needs help with a technical issue unrelated to account creation or login\")\n\n AccountIssue @alias(\"k4\")\n @description(\"Specifically relates to account-login or account-creation\")\n\n Question @alias(\"k5\")\n @description(\"Customer has a question\")\n}\n\nfunction ClassifyMessage3(input: string) -> Category {\n client GPT4\n\n prompt #\"\n Classify the following INPUT into ONE\n of the following categories:\n\n INPUT: {{ input }}\n\n {{ ctx.output_format }}\n\n Response:\n \"#\n}", - "generators.baml": "generator lang_python {\n output_type python/pydantic\n output_dir \"../python\"\n version \"0.70.1\"\n}\n\ngenerator lang_typescript {\n output_type typescript\n output_dir \"../typescript\"\n version \"0.70.1\"\n}\n\ngenerator lang_ruby {\n output_type ruby/sorbet\n output_dir \"../ruby\"\n version \"0.70.1\"\n}\n\n// generator openapi {\n// output_type rest/openapi\n// output_dir \"../openapi\"\n// version \"0.70.1\"\n// on_generate \"rm .gitignore\"\n// }\n", + "generators.baml": "generator lang_python {\n output_type python/pydantic\n output_dir \"../python\"\n version \"0.70.5\"\n}\n\ngenerator lang_typescript {\n output_type typescript\n output_dir \"../typescript\"\n version \"0.70.5\"\n}\n\ngenerator lang_ruby {\n output_type ruby/sorbet\n output_dir \"../ruby\"\n version \"0.70.5\"\n}\n\n// generator openapi {\n// output_type rest/openapi\n// output_dir \"../openapi\"\n// version \"0.70.5\"\n// on_generate \"rm .gitignore\"\n// }\n", "test-files/aliases/aliased-inputs.baml": "\nclass InputClass {\n key string @alias(\"color\")\n key2 string\n}\n\n\nclass InputClassNested {\n key string\n nested InputClass @alias(\"interesting-key\")\n}\n \n\nfunction AliasedInputClass(input: InputClass) -> string {\n client GPT35\n prompt #\"\n\n {{input}}\n\n This is a test. What's the name of the first json key above? Remember, tell me the key, not value.\n \"#\n}\n \nfunction AliasedInputClass2(input: InputClass) -> string {\n client GPT35\n prompt #\"\n\n {# making sure we can still access the original key #}\n {%if input.key == \"tiger\"%}\n Repeat this value back to me, and nothing else: {{input.key}}\n {%endif%}\n \"#\n}\n \n function AliasedInputClassNested(input: InputClassNested) -> string {\n client GPT35\n prompt #\"\n {{ _.role(\"user\")}}\n\n {{input}}\n\n This is a test. What's the name of the second json key above? Remember, tell me the key, not value.\n \"#\n }\n\n\nenum AliasedEnum {\n KEY_ONE @alias(\"tiger\")\n KEY_TWO\n}\n\nfunction AliasedInputEnum(input: AliasedEnum) -> string {\n client GPT4o\n prompt #\"\n {{ _.role(\"user\")}}\n\n\n Write out this word only in your response, in lowercase:\n ---\n {{input}}\n ---\n Answer:\n \"#\n}\n\n\nfunction AliasedInputList(input: AliasedEnum[]) -> string {\n client GPT35\n prompt #\"\n {{ _.role(\"user\")}}\n Given this array:\n ---\n {{input}}\n ---\n\n Return the first element in the array:\n \"#\n}\n\n", "test-files/aliases/classes.baml": "class TestClassAlias {\n key string @alias(\"key-dash\") @description(#\"\n This is a description for key\n af asdf\n \"#)\n key2 string @alias(\"key21\")\n key3 string @alias(\"key with space\")\n key4 string //unaliased\n key5 string @alias(\"key.with.punctuation/123\")\n}\n\nfunction FnTestClassAlias(input: string) -> TestClassAlias {\n client GPT35\n prompt #\"\n {{ctx.output_format}}\n \"#\n}\n\ntest FnTestClassAlias {\n functions [FnTestClassAlias]\n args {\n input \"example input\"\n }\n}\n", "test-files/aliases/enums.baml": "enum TestEnum {\n A @alias(\"k1\") @description(#\"\n User is angry\n \"#)\n B @alias(\"k22\") @description(#\"\n User is happy\n \"#)\n // tests whether k1 doesnt incorrectly get matched with k11\n C @alias(\"k11\") @description(#\"\n User is sad\n \"#)\n D @alias(\"k44\") @description(\n User is confused\n )\n E @description(\n User is excited\n )\n F @alias(\"k5\") // only alias\n \n G @alias(\"k6\") @description(#\"\n User is bored\n With a long description\n \"#)\n \n @@alias(\"Category\")\n}\n\nfunction FnTestAliasedEnumOutput(input: string) -> TestEnum {\n client GPT35\n prompt #\"\n Classify the user input into the following category\n \n {{ ctx.output_format }}\n\n {{ _.role('user') }}\n {{input}}\n\n {{ _.role('assistant') }}\n Category ID:\n \"#\n}\n\ntest FnTestAliasedEnumOutput {\n functions [FnTestAliasedEnumOutput]\n args {\n input \"mehhhhh\"\n }\n}", @@ -89,7 +89,7 @@ const fileMap = { "test-files/functions/prompts/no-chat-messages.baml": "\n\nfunction PromptTestClaude(input: string) -> string {\n client Sonnet\n prompt #\"\n Tell me a haiku about {{ input }}\n \"#\n}\n\n\nfunction PromptTestStreaming(input: string) -> string {\n client GPT35\n prompt #\"\n Tell me a short story about {{ input }}\n \"#\n}\n\ntest TestName {\n functions [PromptTestStreaming]\n args {\n input #\"\n hello world\n \"#\n }\n}\n", "test-files/functions/prompts/with-chat-messages.baml": "\nfunction PromptTestOpenAIChat(input: string) -> string {\n client GPT35\n prompt #\"\n {{ _.role(\"system\") }}\n You are an assistant that always responds in a very excited way with emojis and also outputs this word 4 times after giving a response: {{ input }}\n \n {{ _.role(\"user\") }}\n Tell me a haiku about {{ input }}\n \"#\n}\n\nfunction PromptTestOpenAIChatNoSystem(input: string) -> string {\n client GPT35\n prompt #\"\n You are an assistant that always responds in a very excited way with emojis and also outputs this word 4 times after giving a response: {{ input }}\n \n {{ _.role(\"user\") }}\n Tell me a haiku about {{ input }}\n \"#\n}\n\nfunction PromptTestClaudeChat(input: string) -> string {\n client Claude\n prompt #\"\n {{ _.role(\"system\") }}\n You are an assistant that always responds in a very excited way with emojis and also outputs this word 4 times after giving a response: {{ input }}\n \n {{ _.role(\"user\") }}\n Tell me a haiku about {{ input }}\n \"#\n}\n\nfunction PromptTestClaudeChatNoSystem(input: string) -> string {\n client Claude\n prompt #\"\n You are an assistant that always responds in a very excited way with emojis and also outputs this word 4 times after giving a response: {{ input }}\n \n {{ _.role(\"user\") }}\n Tell me a haiku about {{ input }}\n \"#\n}\n\ntest TestSystemAndNonSystemChat1 {\n functions [PromptTestClaude, PromptTestOpenAI, PromptTestOpenAIChat, PromptTestOpenAIChatNoSystem, PromptTestClaudeChat, PromptTestClaudeChatNoSystem]\n args {\n input \"cats\"\n }\n}\n\ntest TestSystemAndNonSystemChat2 {\n functions [PromptTestClaude, PromptTestOpenAI, PromptTestOpenAIChat, PromptTestOpenAIChatNoSystem, PromptTestClaudeChat, PromptTestClaudeChatNoSystem]\n args {\n input \"lion\"\n }\n}", "test-files/functions/v2/basic.baml": "\n\nfunction ExtractResume2(resume: string) -> Resume {\n client GPT4\n prompt #\"\n {{ _.role('system') }}\n\n Extract the following information from the resume:\n\n Resume:\n <<<<\n {{ resume }}\n <<<<\n\n Output JSON schema:\n {{ ctx.output_format }}\n\n JSON:\n \"#\n}\n\n\nclass WithReasoning {\n value string\n reasoning string @description(#\"\n Why the value is a good fit.\n \"#)\n}\n\n\nclass SearchParams {\n dateRange int? @description(#\"\n In ISO duration format, e.g. P1Y2M10D.\n \"#)\n location string[]\n jobTitle WithReasoning? @description(#\"\n An exact job title, not a general category.\n \"#)\n company WithReasoning? @description(#\"\n The exact name of the company, not a product or service.\n \"#)\n description WithReasoning[] @description(#\"\n Any specific projects or features the user is looking for.\n \"#)\n tags (Tag | string)[]\n}\n\nenum Tag {\n Security\n AI\n Blockchain\n}\n\nfunction GetQuery(query: string) -> SearchParams {\n client GPT4\n prompt #\"\n Extract the following information from the query:\n\n Query:\n <<<<\n {{ query }}\n <<<<\n\n OUTPUT_JSON_SCHEMA:\n {{ ctx.output_format }}\n\n Before OUTPUT_JSON_SCHEMA, list 5 intentions the user may have.\n --- EXAMPLES ---\n 1. <intent>\n 2. <intent>\n 3. <intent>\n 4. <intent>\n 5. <intent>\n\n {\n ... // OUTPUT_JSON_SCHEMA\n }\n \"#\n}\n\nclass RaysData {\n dataType DataType\n value Resume | Event\n}\n\nenum DataType {\n Resume\n Event\n}\n\nclass Event {\n title string\n date string\n location string\n description string\n}\n\nfunction GetDataType(text: string) -> RaysData {\n client GPT4\n prompt #\"\n Extract the relevant info.\n\n Text:\n <<<<\n {{ text }}\n <<<<\n\n Output JSON schema:\n {{ ctx.output_format }}\n\n JSON:\n \"#\n}", - "test-files/providers/providers.baml": "function TestAnthropic(input: string) -> string {\n client Claude\n prompt #\"\n Write a nice haiku about {{ input }}\n \"#\n}\n\nfunction PromptTestOpenAI(input: string) -> string {\n client GPT35\n prompt #\"\n Write a nice haiku about {{ input }}\n \"#\n}\n\nfunction TestOpenAILegacyProvider(input: string) -> string {\n client GPT35LegacyProvider\n prompt #\"\n Write a nice haiku about {{ input }}\n \"#\n}\n\nfunction TestAzure(input: string) -> string {\n client GPT35Azure\n prompt #\"\n Write a nice haiku about {{ input }}\n \"#\n}\n\nfunction TestOllama(input: string) -> string {\n client Ollama\n prompt #\"\n Write a nice haiku about {{ input }}\n \"#\n}\n\nfunction TestGemini(input: string) -> string {\n client Gemini\n prompt #\"\n Write a nice short story about {{ input }}\n \"#\n}\n\nfunction TestVertex(input: string) -> string {\n client Vertex\n prompt #\"\n Write a nice short story about {{ input }}\n \"#\n\n}\n\nfunction TestAws(input: string) -> string {\n client AwsBedrock\n prompt #\"\n Write a nice short story about {{ input }}\n \"#\n}\n\nfunction TestAwsInvalidRegion(input: string) -> string {\n client AwsBedrockInvalidRegion\n prompt #\"\n Write a nice short story about {{ input }}\n \"#\n}\n\nfunction TestOpenAIShorthand(input: string) -> string {\n client \"openai/gpt-4o-mini\"\n prompt #\"\n Write a nice short story about {{ input }}\n \"#\n}\n\nfunction TestAnthropicShorthand(input: string) -> string {\n client \"anthropic/claude-3-haiku-20240307\"\n prompt #\"\n Write a nice short story about {{ input }}\n \"#\n}\n\ntest TestProvider {\n functions [\n TestAnthropic, TestVertex, PromptTestOpenAI, TestAzure, TestOllama, TestGemini, TestAws,\n TestAwsInvalidRegion,\n TestOpenAIShorthand,\n TestAnthropicShorthand\n ]\n args {\n input \"Donkey kong and peanut butter\"\n }\n}\n\n\nfunction TestCaching(input: string, not_cached: string) -> string {\n client ClaudeWithCaching\n prompt #\"\n {{ _.role('system', cache_control={\"type\": \"ephemeral\"}) }}\n Generate the following story\n {{ input }}\n\n {# Haiku require 2048 tokens to cache -#}\n {{ input }}\n\n {{ _.role('user') }}\n {{ not_cached }}\n \"#\n}\n\ntest TestName {\n functions [TestCaching]\n args {\n input #\"\nIn a near-future society where dreams have become a tradable commodity and shared experience, a lonely and socially awkward teenager named Alex discovers they possess a rare and powerful ability to not only view but also manipulate the dreams of others. Initially thrilled by this newfound power, Alex begins subtly altering the dreams of classmates and family members, helping them overcome fears, boost confidence, or experience fantastical adventures. As Alex's skills grow, so does their influence. They start selling premium dream experiences on the black market, crafting intricate and addictive dreamscapes for wealthy clients. However, the line between dream and reality begins to blur for those exposed to Alex's creations. Some clients struggle to differentiate between their true memories and the artificial ones implanted by Alex's dream manipulation.\n\nComplications arise when a mysterious government agency takes notice of Alex's unique abilities. They offer Alex a chance to use their gift for \"the greater good,\" hinting at applications in therapy, criminal rehabilitation, and even national security. Simultaneously, an underground resistance movement reaches out, warning Alex about the dangers of dream manipulation and the potential for mass control and exploitation. Caught between these opposing forces, Alex must navigate a complex web of ethical dilemmas. They grapple with questions of free will, the nature of consciousness, and the responsibility that comes with having power over people's minds. As the consequences of their actions spiral outward, affecting the lives of loved ones and strangers alike, Alex is forced to confront the true nature of their ability and decide how—or if—it should be used.\n\nThe story explores themes of identity, the subconscious mind, the ethics of technology, and the power of imagination. It delves into the potential consequences of a world where our most private thoughts and experiences are no longer truly our own, and examines the fine line between helping others and manipulating them for personal gain or a perceived greater good. The narrative further expands on the societal implications of such abilities, questioning the moral boundaries of altering consciousness and the potential for abuse in a world where dreams can be commodified. It challenges the reader to consider the impact of technology on personal autonomy and the ethical responsibilities of those who wield such power.\n\nAs Alex's journey unfolds, they encounter various individuals whose lives have been touched by their dream manipulations, each presenting a unique perspective on the ethical quandaries at hand. From a classmate who gains newfound confidence to a wealthy client who becomes addicted to the dreamscapes, the ripple effects of Alex's actions are profound and far-reaching. The government agency's interest in Alex's abilities raises questions about the potential for state control and surveillance, while the resistance movement highlights the dangers of unchecked power and the importance of safeguarding individual freedoms.\n\nUltimately, Alex's story is one of self-discovery and moral reckoning, as they must decide whether to embrace their abilities for personal gain, align with the government's vision of a controlled utopia, or join the resistance in their fight for freedom and autonomy. The narrative invites readers to reflect on the nature of reality, the boundaries of human experience, and the ethical implications of a world where dreams are no longer private sanctuaries but shared and manipulated commodities. It also explores the psychological impact on Alex, who must deal with the burden of knowing the intimate fears and desires of others, and the isolation that comes from being unable to share their own dreams without altering them.\n\nThe story further examines the technological advancements that have made dream manipulation possible, questioning the role of innovation in society and the potential for both progress and peril. It considers the societal divide between those who can afford to buy enhanced dream experiences and those who cannot, highlighting issues of inequality and access. As Alex becomes more entangled in the web of their own making, they must confront the possibility that their actions could lead to unintended consequences, not just for themselves but for the fabric of society as a whole.\n\nIn the end, Alex's journey is a cautionary tale about the power of dreams and the responsibilities that come with wielding such influence. It serves as a reminder of the importance of ethical considerations in the face of technological advancement and the need to balance innovation with humanity. The story leaves readers pondering the true cost of a world where dreams are no longer sacred, and the potential for both wonder and danger in the uncharted territories of the mind. But it's also a story about the power of imagination and the potential for change, even in a world where our deepest thoughts are no longer our own. And it's a story about the power of choice, and the importance of fighting for the freedom to dream.\n\nIn conclusion, this story is a reflection on the power of dreams and the responsibilities that come with wielding such influence. It serves as a reminder of the importance of ethical considerations in the face of technological advancement and the need to balance innovation with humanity. The story leaves readers pondering the true cost of a world where dreams are no longer sacred, and the potential for both wonder and danger in the uncharted territories of the mind. But it's also a story about the power of imagination and the potential for change, even in a world where our deepest thoughts are no longer our own. And it's a story about the power of choice, and the importance of fighting for the freedom to dream.\n \"#\n not_cached #\"\n hello world\n \"#\n }\n}\n", + "test-files/providers/providers.baml": "function TestAnthropic(input: string) -> string {\n client Claude\n prompt #\"\n Write a nice haiku about {{ input }}\n \"#\n}\n\nfunction PromptTestOpenAI(input: string) -> string {\n client GPT35\n prompt #\"\n Write a nice haiku about {{ input }}\n \"#\n}\n\nfunction TestOpenAILegacyProvider(input: string) -> string {\n client GPT35LegacyProvider\n prompt #\"\n Write a nice haiku about {{ input }}\n \"#\n}\n\nfunction TestAzure(input: string) -> string {\n client GPT35Azure\n prompt #\"\n Write a nice haiku about {{ input }}\n \"#\n}\n\nclient GPT35AzureFailed {\n provider azure-openai\n options {\n resource_name \"west-us-azure-baml-incorrect-suffix\"\n deployment_id \"gpt-35-turbo-default\"\n api_key env.AZURE_OPENAI_API_KEY\n }\n}\nfunction TestAzureFailure(input: string) -> string {\n client GPT35AzureFailed\n prompt #\"\n Write a nice haiku about {{ input }}\n \"#\n}\n\nfunction TestOllama(input: string) -> string {\n client Ollama\n prompt #\"\n Write a nice haiku about {{ input }}\n \"#\n}\n\nfunction TestGemini(input: string) -> string {\n client Gemini\n prompt #\"\n Write a nice short story about {{ input }}\n \"#\n}\n\nfunction TestVertex(input: string) -> string {\n client Vertex\n prompt #\"\n Write a nice short story about {{ input }}\n \"#\n\n}\n\nfunction TestAws(input: string) -> string {\n client AwsBedrock\n prompt #\"\n Write a nice short story about {{ input }}\n \"#\n}\n\nfunction TestAwsInvalidRegion(input: string) -> string {\n client AwsBedrockInvalidRegion\n prompt #\"\n Write a nice short story about {{ input }}\n \"#\n}\n\nfunction TestOpenAIShorthand(input: string) -> string {\n client \"openai/gpt-4o-mini\"\n prompt #\"\n Write a nice short story about {{ input }}\n \"#\n}\n\nfunction TestAnthropicShorthand(input: string) -> string {\n client \"anthropic/claude-3-haiku-20240307\"\n prompt #\"\n Write a nice short story about {{ input }}\n \"#\n}\n\ntest TestProvider {\n functions [\n TestAnthropic, TestVertex, PromptTestOpenAI, TestAzure, TestOllama, TestGemini, TestAws,\n TestAwsInvalidRegion,\n TestOpenAIShorthand,\n TestAnthropicShorthand\n ]\n args {\n input \"Donkey kong and peanut butter\"\n }\n}\n\n\nfunction TestCaching(input: string, not_cached: string) -> string {\n client ClaudeWithCaching\n prompt #\"\n {{ _.role('system', cache_control={\"type\": \"ephemeral\"}) }}\n Generate the following story\n {{ input }}\n\n {# Haiku require 2048 tokens to cache -#}\n {{ input }}\n\n {{ _.role('user') }}\n {{ not_cached }}\n \"#\n}\n\ntest TestName {\n functions [TestCaching]\n args {\n input #\"\nIn a near-future society where dreams have become a tradable commodity and shared experience, a lonely and socially awkward teenager named Alex discovers they possess a rare and powerful ability to not only view but also manipulate the dreams of others. Initially thrilled by this newfound power, Alex begins subtly altering the dreams of classmates and family members, helping them overcome fears, boost confidence, or experience fantastical adventures. As Alex's skills grow, so does their influence. They start selling premium dream experiences on the black market, crafting intricate and addictive dreamscapes for wealthy clients. However, the line between dream and reality begins to blur for those exposed to Alex's creations. Some clients struggle to differentiate between their true memories and the artificial ones implanted by Alex's dream manipulation.\n\nComplications arise when a mysterious government agency takes notice of Alex's unique abilities. They offer Alex a chance to use their gift for \"the greater good,\" hinting at applications in therapy, criminal rehabilitation, and even national security. Simultaneously, an underground resistance movement reaches out, warning Alex about the dangers of dream manipulation and the potential for mass control and exploitation. Caught between these opposing forces, Alex must navigate a complex web of ethical dilemmas. They grapple with questions of free will, the nature of consciousness, and the responsibility that comes with having power over people's minds. As the consequences of their actions spiral outward, affecting the lives of loved ones and strangers alike, Alex is forced to confront the true nature of their ability and decide how—or if—it should be used.\n\nThe story explores themes of identity, the subconscious mind, the ethics of technology, and the power of imagination. It delves into the potential consequences of a world where our most private thoughts and experiences are no longer truly our own, and examines the fine line between helping others and manipulating them for personal gain or a perceived greater good. The narrative further expands on the societal implications of such abilities, questioning the moral boundaries of altering consciousness and the potential for abuse in a world where dreams can be commodified. It challenges the reader to consider the impact of technology on personal autonomy and the ethical responsibilities of those who wield such power.\n\nAs Alex's journey unfolds, they encounter various individuals whose lives have been touched by their dream manipulations, each presenting a unique perspective on the ethical quandaries at hand. From a classmate who gains newfound confidence to a wealthy client who becomes addicted to the dreamscapes, the ripple effects of Alex's actions are profound and far-reaching. The government agency's interest in Alex's abilities raises questions about the potential for state control and surveillance, while the resistance movement highlights the dangers of unchecked power and the importance of safeguarding individual freedoms.\n\nUltimately, Alex's story is one of self-discovery and moral reckoning, as they must decide whether to embrace their abilities for personal gain, align with the government's vision of a controlled utopia, or join the resistance in their fight for freedom and autonomy. The narrative invites readers to reflect on the nature of reality, the boundaries of human experience, and the ethical implications of a world where dreams are no longer private sanctuaries but shared and manipulated commodities. It also explores the psychological impact on Alex, who must deal with the burden of knowing the intimate fears and desires of others, and the isolation that comes from being unable to share their own dreams without altering them.\n\nThe story further examines the technological advancements that have made dream manipulation possible, questioning the role of innovation in society and the potential for both progress and peril. It considers the societal divide between those who can afford to buy enhanced dream experiences and those who cannot, highlighting issues of inequality and access. As Alex becomes more entangled in the web of their own making, they must confront the possibility that their actions could lead to unintended consequences, not just for themselves but for the fabric of society as a whole.\n\nIn the end, Alex's journey is a cautionary tale about the power of dreams and the responsibilities that come with wielding such influence. It serves as a reminder of the importance of ethical considerations in the face of technological advancement and the need to balance innovation with humanity. The story leaves readers pondering the true cost of a world where dreams are no longer sacred, and the potential for both wonder and danger in the uncharted territories of the mind. But it's also a story about the power of imagination and the potential for change, even in a world where our deepest thoughts are no longer our own. And it's a story about the power of choice, and the importance of fighting for the freedom to dream.\n\nIn conclusion, this story is a reflection on the power of dreams and the responsibilities that come with wielding such influence. It serves as a reminder of the importance of ethical considerations in the face of technological advancement and the need to balance innovation with humanity. The story leaves readers pondering the true cost of a world where dreams are no longer sacred, and the potential for both wonder and danger in the uncharted territories of the mind. But it's also a story about the power of imagination and the potential for change, even in a world where our deepest thoughts are no longer our own. And it's a story about the power of choice, and the importance of fighting for the freedom to dream.\n \"#\n not_cached #\"\n hello world\n \"#\n }\n}\n", "test-files/strategies/fallback-shorthand.baml": "\nclient<llm> FallbackToShorthand {\n provider fallback\n options {\n strategy [\n \"openai/does-not-exist\",\n \"openai/gpt-4o-mini\"\n ]\n }\n}\n\n\nfunction TestFallbackToShorthand(input: string) -> string {\n client FallbackToShorthand\n // TODO make it return the client name instead\n prompt #\"\n Say a haiku about {{input}}.\n \"#\n}\n\ntest TestProvider_FallbackToShorthand {\n functions [\n TestFallbackToShorthand\n ]\n args {\n input \"Donkey kong and peanut butter\"\n }\n}\n", "test-files/strategies/fallback.baml": "// Happy path fallbacks.\nclient<llm> FaultyClient {\n provider openai\n options {\n model unknown-model\n api_key env.OPENAI_API_KEY\n }\n}\n\n\nclient<llm> FallbackClient {\n provider fallback\n options {\n // first 2 clients are expected to fail.\n strategy [\n FaultyClient,\n RetryClientConstant,\n GPT35\n Gemini\n\n ]\n }\n}\n\nfunction TestFallbackClient() -> string {\n client FallbackClient\n // TODO make it return the client name instead\n prompt #\"\n Say a haiku about mexico.\n \"#\n}\n\n// Fallbacks should fail gracefully.\nclient<llm> FaultyAzureClient {\n provider azure-openai\n options {\n model unknown-model\n resource_name \"unknown-resource-id\"\n deployment_id \"unknown-deployment-id\"\n }\n}\n\nclient<llm> SingleFallbackClient {\n provider fallback\n options {\n // first 2 clients are expected to fail.\n strategy [\n FaultyAzureClient\n ]\n }\n}\n\nfunction TestSingleFallbackClient() -> string {\n client SingleFallbackClient\n // TODO make it return the client name instead\n prompt #\"\n Say a haiku about mexico.\n \"#\n}\n", "test-files/strategies/retry.baml": "\nretry_policy Exponential {\n max_retries 3\n strategy {\n type exponential_backoff\n }\n}\n\nretry_policy Constant {\n max_retries 3\n strategy {\n type constant_delay\n delay_ms 100\n }\n}\n\nclient<llm> RetryClientConstant {\n provider openai\n retry_policy Constant\n options {\n model \"gpt-3.5-turbo\"\n api_key \"blah\"\n }\n}\n\nclient<llm> RetryClientExponential {\n provider openai\n retry_policy Exponential\n options {\n model \"gpt-3.5-turbo\"\n api_key \"blahh\"\n }\n}\n\nfunction TestRetryConstant() -> string {\n client RetryClientConstant\n prompt #\"\n Say a haiku\n \"#\n}\n\nfunction TestRetryExponential() -> string {\n client RetryClientExponential\n prompt #\"\n Say a haiku\n \"#\n}\n", From 22539472381a09aa6ea927feb4ddbb5e498461ff Mon Sep 17 00:00:00 2001 From: Antonio Sarosi <sarosiantonio@gmail.com> Date: Wed, 18 Dec 2024 21:21:43 +0100 Subject: [PATCH 49/51] Fix second unreachable --- .../class/invalid_type_aliases.baml | 9 +++ .../baml-lib/parser-database/src/types/mod.rs | 6 +- .../tests/test_file_manager.rs | 74 ++++++++++++++++++- 3 files changed, 87 insertions(+), 2 deletions(-) diff --git a/engine/baml-lib/baml/tests/validation_files/class/invalid_type_aliases.baml b/engine/baml-lib/baml/tests/validation_files/class/invalid_type_aliases.baml index 0a13664ba..e1d584c98 100644 --- a/engine/baml-lib/baml/tests/validation_files/class/invalid_type_aliases.baml +++ b/engine/baml-lib/baml/tests/validation_files/class/invalid_type_aliases.baml @@ -11,6 +11,9 @@ typpe Two = float // Unknown identifier. type Three = i +// Unknown identifier in union. +type Four = int | string | b + // error: Error validating: Unexpected keyword used in assignment: typpe // --> class/invalid_type_aliases.baml:9 // | @@ -29,3 +32,9 @@ type Three = i // 11 | // Unknown identifier. // 12 | type Three = i // | +// error: Error validating: Type alias points to unknown identifier `b` +// --> class/invalid_type_aliases.baml:15 +// | +// 14 | // Unknown identifier in union. +// 15 | type Four = int | string | b +// | diff --git a/engine/baml-lib/parser-database/src/types/mod.rs b/engine/baml-lib/parser-database/src/types/mod.rs index 1978fd078..59f2e73e7 100644 --- a/engine/baml-lib/parser-database/src/types/mod.rs +++ b/engine/baml-lib/parser-database/src/types/mod.rs @@ -518,7 +518,11 @@ fn visit_type_alias<'db>( }; let Some(top_id) = ctx.names.tops.get(&string_id) else { - unreachable!("Alias name `{ident}` is not registered in the context"); + ctx.push_error(DatamodelError::new_validation_error( + &format!("Type alias points to unknown identifier `{ident}`"), + item.span().clone(), + )); + return; }; // Add alias to the graph. diff --git a/engine/baml-schema-wasm/tests/test_file_manager.rs b/engine/baml-schema-wasm/tests/test_file_manager.rs index bdcb841c2..6069e6091 100644 --- a/engine/baml-schema-wasm/tests/test_file_manager.rs +++ b/engine/baml-schema-wasm/tests/test_file_manager.rs @@ -231,7 +231,7 @@ test Two { } #[wasm_bindgen_test] - fn test_type_alias() { + fn test_type_alias_pointing_to_unknown_identifier() { wasm_logger::init(wasm_logger::Config::new(log::Level::Info)); let sample_baml_content = r##" type Foo = i @@ -265,4 +265,76 @@ test Two { // .unwrap() // ); } + + #[wasm_bindgen_test] + fn test_type_alias_pointing_to_union_with_unknown_identifier() { + wasm_logger::init(wasm_logger::Config::new(log::Level::Info)); + let sample_baml_content = r##" + type Foo = int | i + "##; + let mut files = HashMap::new(); + files.insert("error.baml".to_string(), sample_baml_content.to_string()); + let files_js = to_value(&files).unwrap(); + let project = WasmProject::new("baml_src", files_js) + .map_err(JsValue::from) + .unwrap(); + + let env_vars = [("OPENAI_API_KEY", "12345")] + .iter() + .cloned() + .collect::<HashMap<_, _>>(); + let env_vars_js = to_value(&env_vars).unwrap(); + + let Err(js_error) = project.runtime(env_vars_js) else { + panic!("Expected error, got Ok"); + }; + + assert!(js_error.is_object()); + + // TODO: Don't know how to build Object + // assert_eq!( + // js_error, + // serde_wasm_bindgen::to_value::<HashMap<String, Vec<String>>>(&HashMap::from_iter([( + // "all_files".to_string(), + // vec!["error.baml".to_string()] + // )])) + // .unwrap() + // ); + } + + #[wasm_bindgen_test] + fn test_type_alias_pointing_to_union_with_unknown_identifier_in_union() { + wasm_logger::init(wasm_logger::Config::new(log::Level::Info)); + let sample_baml_content = r##" + type Four = int | string | b + "##; + let mut files = HashMap::new(); + files.insert("error.baml".to_string(), sample_baml_content.to_string()); + let files_js = to_value(&files).unwrap(); + let project = WasmProject::new("baml_src", files_js) + .map_err(JsValue::from) + .unwrap(); + + let env_vars = [("OPENAI_API_KEY", "12345")] + .iter() + .cloned() + .collect::<HashMap<_, _>>(); + let env_vars_js = to_value(&env_vars).unwrap(); + + let Err(js_error) = project.runtime(env_vars_js) else { + panic!("Expected error, got Ok"); + }; + + assert!(js_error.is_object()); + + // TODO: Don't know how to build Object + // assert_eq!( + // js_error, + // serde_wasm_bindgen::to_value::<HashMap<String, Vec<String>>>(&HashMap::from_iter([( + // "all_files".to_string(), + // vec!["error.baml".to_string()] + // )])) + // .unwrap() + // ); + } } From 2a433cc866431e33853da8d606a818069f6068e6 Mon Sep 17 00:00:00 2001 From: Antonio Sarosi <sarosiantonio@gmail.com> Date: Wed, 25 Dec 2024 19:54:55 +0100 Subject: [PATCH 50/51] Fix string to float conversion when parsing to `float | string` unions --- .../deserializer/coercer/coerce_primitive.rs | 279 +++++++++--------- .../src/deserializer/coercer/field_type.rs | 17 +- .../src/deserializer/deserialize_flags.rs | 5 + .../jsonish/src/deserializer/score.rs | 1 + .../jsonish/src/tests/test_aliases.rs | 60 ++++ .../baml-lib/jsonish/src/tests/test_basics.rs | 8 + .../baml-lib/jsonish/src/tests/test_unions.rs | 11 + 7 files changed, 228 insertions(+), 153 deletions(-) diff --git a/engine/baml-lib/jsonish/src/deserializer/coercer/coerce_primitive.rs b/engine/baml-lib/jsonish/src/deserializer/coercer/coerce_primitive.rs index 801ec9735..ebac33720 100644 --- a/engine/baml-lib/jsonish/src/deserializer/coercer/coerce_primitive.rs +++ b/engine/baml-lib/jsonish/src/deserializer/coercer/coerce_primitive.rs @@ -64,18 +64,16 @@ fn coerce_string( target: &FieldType, value: Option<&crate::jsonish::Value>, ) -> Result<BamlValueWithFlags, ParsingError> { - if let Some(value) = value { - match value { - crate::jsonish::Value::String(s) => { - Ok(BamlValueWithFlags::String(s.to_string().into())) - } - crate::jsonish::Value::Null => Err(ctx.error_unexpected_null(target)), - v => Ok(BamlValueWithFlags::String( - (v.to_string(), Flag::JsonToString(v.clone())).into(), - )), - } - } else { - Err(ctx.error_unexpected_null(target)) + let Some(value) = value else { + return Err(ctx.error_unexpected_null(target)); + }; + + match value { + crate::jsonish::Value::String(s) => Ok(BamlValueWithFlags::String(s.to_string().into())), + crate::jsonish::Value::Null => Err(ctx.error_unexpected_null(target)), + v => Ok(BamlValueWithFlags::String( + (v.to_string(), Flag::JsonToString(v.clone())).into(), + )), } } @@ -84,54 +82,54 @@ pub(super) fn coerce_int( target: &FieldType, value: Option<&crate::jsonish::Value>, ) -> Result<BamlValueWithFlags, ParsingError> { - if let Some(value) = value { - match value { - crate::jsonish::Value::Number(n) => { - if let Some(n) = n.as_i64() { - Ok(BamlValueWithFlags::Int(n.into())) - } else if let Some(n) = n.as_u64() { - Ok(BamlValueWithFlags::Int((n as i64).into())) - } else if let Some(n) = n.as_f64() { - Ok(BamlValueWithFlags::Int( - ((n.round() as i64), Flag::FloatToInt(n)).into(), - )) - } else { - Err(ctx.error_unexpected_type(target, value)) - } - } - crate::jsonish::Value::String(s) => { - let s = s.trim(); - // Trim trailing commas - let s = s.trim_end_matches(','); - if let Ok(n) = s.parse::<i64>() { - Ok(BamlValueWithFlags::Int(n.into())) - } else if let Ok(n) = s.parse::<u64>() { - Ok(BamlValueWithFlags::Int((n as i64).into())) - } else if let Ok(n) = s.parse::<f64>() { - Ok(BamlValueWithFlags::Int( - ((n.round() as i64), Flag::FloatToInt(n)).into(), - )) - } else if let Some(frac) = float_from_maybe_fraction(s) { - Ok(BamlValueWithFlags::Int( - ((frac.round() as i64), Flag::FloatToInt(frac)).into(), - )) - } else if let Some(frac) = float_from_comma_separated(s) { - Ok(BamlValueWithFlags::Int( - ((frac.round() as i64), Flag::FloatToInt(frac)).into(), - )) - } else { - Err(ctx.error_unexpected_type(target, value)) - } + let Some(value) = value else { + return Err(ctx.error_unexpected_null(target)); + }; + + match value { + crate::jsonish::Value::Number(n) => { + if let Some(n) = n.as_i64() { + Ok(BamlValueWithFlags::Int(n.into())) + } else if let Some(n) = n.as_u64() { + Ok(BamlValueWithFlags::Int((n as i64).into())) + } else if let Some(n) = n.as_f64() { + Ok(BamlValueWithFlags::Int( + ((n.round() as i64), Flag::FloatToInt(n)).into(), + )) + } else { + Err(ctx.error_unexpected_type(target, value)) } - crate::jsonish::Value::Array(items) => { - coerce_array_to_singular(ctx, target, &items.iter().collect::<Vec<_>>(), &|value| { - coerce_int(ctx, target, Some(value)) - }) + } + crate::jsonish::Value::String(s) => { + let s = s.trim(); + // Trim trailing commas + let s = s.trim_end_matches(','); + if let Ok(n) = s.parse::<i64>() { + Ok(BamlValueWithFlags::Int(n.into())) + } else if let Ok(n) = s.parse::<u64>() { + Ok(BamlValueWithFlags::Int((n as i64).into())) + } else if let Ok(n) = s.parse::<f64>() { + Ok(BamlValueWithFlags::Int( + ((n.round() as i64), Flag::FloatToInt(n)).into(), + )) + } else if let Some(frac) = float_from_maybe_fraction(s) { + Ok(BamlValueWithFlags::Int( + ((frac.round() as i64), Flag::FloatToInt(frac)).into(), + )) + } else if let Some(frac) = float_from_comma_separated(s) { + Ok(BamlValueWithFlags::Int( + ((frac.round() as i64), Flag::FloatToInt(frac)).into(), + )) + } else { + Err(ctx.error_unexpected_type(target, value)) } - _ => Err(ctx.error_unexpected_type(target, value)), } - } else { - Err(ctx.error_unexpected_null(target)) + crate::jsonish::Value::Array(items) => { + coerce_array_to_singular(ctx, target, &items.iter().collect::<Vec<_>>(), &|value| { + coerce_int(ctx, target, Some(value)) + }) + } + _ => Err(ctx.error_unexpected_type(target, value)), } } @@ -172,46 +170,53 @@ fn coerce_float( target: &FieldType, value: Option<&crate::jsonish::Value>, ) -> Result<BamlValueWithFlags, ParsingError> { - if let Some(value) = value { - match value { - crate::jsonish::Value::Number(n) => { - if let Some(n) = n.as_f64() { - Ok(BamlValueWithFlags::Float(n.into())) - } else if let Some(n) = n.as_i64() { - Ok(BamlValueWithFlags::Float((n as f64).into())) - } else if let Some(n) = n.as_u64() { - Ok(BamlValueWithFlags::Float((n as f64).into())) - } else { - Err(ctx.error_unexpected_type(target, value)) - } - } - crate::jsonish::Value::String(s) => { - let s = s.trim(); - // Trim trailing commas - let s = s.trim_end_matches(','); - if let Ok(n) = s.parse::<f64>() { - Ok(BamlValueWithFlags::Float(n.into())) - } else if let Ok(n) = s.parse::<i64>() { - Ok(BamlValueWithFlags::Float((n as f64).into())) - } else if let Ok(n) = s.parse::<u64>() { - Ok(BamlValueWithFlags::Float((n as f64).into())) - } else if let Some(frac) = float_from_maybe_fraction(s) { - Ok(BamlValueWithFlags::Float(frac.into())) - } else if let Some(frac) = float_from_comma_separated(s) { - Ok(BamlValueWithFlags::Float(frac.into())) - } else { - Err(ctx.error_unexpected_type(target, value)) - } + let Some(value) = value else { + return Err(ctx.error_unexpected_null(target)); + }; + + match value { + crate::jsonish::Value::Number(n) => { + if let Some(n) = n.as_f64() { + Ok(BamlValueWithFlags::Float(n.into())) + } else if let Some(n) = n.as_i64() { + Ok(BamlValueWithFlags::Float((n as f64).into())) + } else if let Some(n) = n.as_u64() { + Ok(BamlValueWithFlags::Float((n as f64).into())) + } else { + Err(ctx.error_unexpected_type(target, value)) } - crate::jsonish::Value::Array(items) => { - coerce_array_to_singular(ctx, target, &items.iter().collect::<Vec<_>>(), &|value| { - coerce_float(ctx, target, Some(value)) - }) + } + crate::jsonish::Value::String(s) => { + let s = s.trim(); + // Trim trailing commas + let s = s.trim_end_matches(','); + if let Ok(n) = s.parse::<f64>() { + Ok(BamlValueWithFlags::Float(n.into())) + } else if let Ok(n) = s.parse::<i64>() { + Ok(BamlValueWithFlags::Float((n as f64).into())) + } else if let Ok(n) = s.parse::<u64>() { + Ok(BamlValueWithFlags::Float((n as f64).into())) + } else if let Some(frac) = float_from_maybe_fraction(s) { + Ok(BamlValueWithFlags::Float(frac.into())) + } else if let Some(frac) = float_from_comma_separated(s) { + let mut baml_value = BamlValueWithFlags::Float(frac.into()); + // Add flag here to penalize strings like + // "1 cup unsalted butter, room temperature". + // If we're trying to parse this to a float it should work + // anyway but unions like "float | string" should still coerce + // this to a string. + baml_value.add_flag(Flag::StringToFloat(s.to_string())); + Ok(baml_value) + } else { + Err(ctx.error_unexpected_type(target, value)) } - _ => Err(ctx.error_unexpected_type(target, value)), } - } else { - Err(ctx.error_unexpected_null(target)) + crate::jsonish::Value::Array(items) => { + coerce_array_to_singular(ctx, target, &items.iter().collect::<Vec<_>>(), &|value| { + coerce_float(ctx, target, Some(value)) + }) + } + _ => Err(ctx.error_unexpected_type(target, value)), } } @@ -220,51 +225,51 @@ pub(super) fn coerce_bool( target: &FieldType, value: Option<&crate::jsonish::Value>, ) -> Result<BamlValueWithFlags, ParsingError> { - if let Some(value) = value { - match value { - crate::jsonish::Value::Boolean(b) => Ok(BamlValueWithFlags::Bool((*b).into())), - crate::jsonish::Value::String(s) => match s.to_lowercase().as_str() { - "true" => Ok(BamlValueWithFlags::Bool( - (true, Flag::StringToBool(s.clone())).into(), - )), - "false" => Ok(BamlValueWithFlags::Bool( - (false, Flag::StringToBool(s.clone())).into(), - )), - _ => { - match super::match_string::match_string( - ctx, - target, - Some(value), - &[ - ("true", vec!["true".into(), "True".into(), "TRUE".into()]), - ( - "false", - vec!["false".into(), "False".into(), "FALSE".into()], - ), - ], - ) { - Ok(val) => match val.value().as_str() { - "true" => Ok(BamlValueWithFlags::Bool( - (true, Flag::StringToBool(val.value().clone())).into(), - )), - "false" => Ok(BamlValueWithFlags::Bool( - (false, Flag::StringToBool(val.value().clone())).into(), - )), - _ => Err(ctx.error_unexpected_type(target, value)), - }, - Err(_) => Err(ctx.error_unexpected_type(target, value)), - } + let Some(value) = value else { + return Err(ctx.error_unexpected_null(target)); + }; + + match value { + crate::jsonish::Value::Boolean(b) => Ok(BamlValueWithFlags::Bool((*b).into())), + crate::jsonish::Value::String(s) => match s.to_lowercase().as_str() { + "true" => Ok(BamlValueWithFlags::Bool( + (true, Flag::StringToBool(s.clone())).into(), + )), + "false" => Ok(BamlValueWithFlags::Bool( + (false, Flag::StringToBool(s.clone())).into(), + )), + _ => { + match super::match_string::match_string( + ctx, + target, + Some(value), + &[ + ("true", vec!["true".into(), "True".into(), "TRUE".into()]), + ( + "false", + vec!["false".into(), "False".into(), "FALSE".into()], + ), + ], + ) { + Ok(val) => match val.value().as_str() { + "true" => Ok(BamlValueWithFlags::Bool( + (true, Flag::StringToBool(val.value().clone())).into(), + )), + "false" => Ok(BamlValueWithFlags::Bool( + (false, Flag::StringToBool(val.value().clone())).into(), + )), + _ => Err(ctx.error_unexpected_type(target, value)), + }, + Err(_) => Err(ctx.error_unexpected_type(target, value)), } - }, - crate::jsonish::Value::Array(items) => { - coerce_array_to_singular(ctx, target, &items.iter().collect::<Vec<_>>(), &|value| { - coerce_bool(ctx, target, Some(value)) - }) } - _ => Err(ctx.error_unexpected_type(target, value)), + }, + crate::jsonish::Value::Array(items) => { + coerce_array_to_singular(ctx, target, &items.iter().collect::<Vec<_>>(), &|value| { + coerce_bool(ctx, target, Some(value)) + }) } - } else { - Err(ctx.error_unexpected_null(target)) + _ => Err(ctx.error_unexpected_type(target, value)), } } @@ -337,7 +342,7 @@ mod tests { ("12,111,123,", Some(12111123.0)), ]; - for &(input, expected) in &test_cases { + for (input, expected) in test_cases { let result = float_from_comma_separated(input); assert_eq!( result, expected, diff --git a/engine/baml-lib/jsonish/src/deserializer/coercer/field_type.rs b/engine/baml-lib/jsonish/src/deserializer/coercer/field_type.rs index df7cf8048..26d973dd3 100644 --- a/engine/baml-lib/jsonish/src/deserializer/coercer/field_type.rs +++ b/engine/baml-lib/jsonish/src/deserializer/coercer/field_type.rs @@ -18,8 +18,6 @@ use super::{ ParsingContext, ParsingError, }; -static mut LIMIT: usize = 0; - impl TypeCoercer for FieldType { fn coerce( &self, @@ -27,12 +25,7 @@ impl TypeCoercer for FieldType { target: &FieldType, value: Option<&crate::jsonish::Value>, ) -> Result<BamlValueWithFlags, ParsingError> { - unsafe { - LIMIT += 1; - eprintln!("LIMIT: {}", LIMIT); - } - - let ret_v = match value { + match value { Some(crate::jsonish::Value::AnyOf(candidates, primitive)) => { log::debug!( "scope: {scope} :: coercing to: {name} (current: {current})", @@ -117,15 +110,7 @@ impl TypeCoercer for FieldType { Ok(coerced_value) } }, - }; - - unsafe { - LIMIT -= 1; } - - eprintln!("ret_v: {:?}", ret_v); - - ret_v } } diff --git a/engine/baml-lib/jsonish/src/deserializer/deserialize_flags.rs b/engine/baml-lib/jsonish/src/deserializer/deserialize_flags.rs index 5aab40624..106981ebc 100644 --- a/engine/baml-lib/jsonish/src/deserializer/deserialize_flags.rs +++ b/engine/baml-lib/jsonish/src/deserializer/deserialize_flags.rs @@ -38,6 +38,7 @@ pub enum Flag { StringToBool(String), StringToNull(String), StringToChar(String), + StringToFloat(String), // Number -> X convertions. FloatToInt(f64), @@ -91,6 +92,7 @@ impl DeserializerConditions { Flag::StringToBool(_) => None, Flag::StringToNull(_) => None, Flag::StringToChar(_) => None, + Flag::StringToFloat(_) => None, Flag::FloatToInt(_) => None, Flag::NoFields(_) => None, Flag::UnionMatch(_idx, _) => None, @@ -235,6 +237,9 @@ impl std::fmt::Display for Flag { Flag::StringToChar(value) => { write!(f, "String to char: {}", value)?; } + Flag::StringToFloat(value) => { + write!(f, "String to float: {}", value)?; + } Flag::FloatToInt(value) => { write!(f, "Float to int: {}", value)?; } diff --git a/engine/baml-lib/jsonish/src/deserializer/score.rs b/engine/baml-lib/jsonish/src/deserializer/score.rs index f198702bd..7fbfefd8a 100644 --- a/engine/baml-lib/jsonish/src/deserializer/score.rs +++ b/engine/baml-lib/jsonish/src/deserializer/score.rs @@ -64,6 +64,7 @@ impl WithScore for Flag { Flag::StringToBool(_) => 1, Flag::StringToNull(_) => 1, Flag::StringToChar(_) => 1, + Flag::StringToFloat(_) => 1, Flag::FloatToInt(_) => 1, Flag::NoFields(_) => 1, // No scores for contraints diff --git a/engine/baml-lib/jsonish/src/tests/test_aliases.rs b/engine/baml-lib/jsonish/src/tests/test_aliases.rs index f3f553a53..9570be34f 100644 --- a/engine/baml-lib/jsonish/src/tests/test_aliases.rs +++ b/engine/baml-lib/jsonish/src/tests/test_aliases.rs @@ -251,3 +251,63 @@ type JsonObject = map<string, JsonValue> } } ); + +test_deserializer!( + test_ambiguous_int_string_json_type, + r#" +type JsonValue = int | float | bool | string | null | JsonValue[] | map<string, JsonValue> + "#, + r#" + { + "recipe": { + "name": "Chocolate Chip Cookies", + "servings": 24, + "ingredients": [ + "2 1/4 cups all-purpose flour", "1/2 teaspoon baking soda", + "1 cup unsalted butter, room temperature", + "1/2 cup granulated sugar", + "1 cup packed light-brown sugar", + "1 teaspoon salt", "2 teaspoons pure vanilla extract", + "2 large eggs", "2 cups semisweet and/or milk chocolate chips" + ], + "instructions": [ + "Preheat oven to 350°F (180°C).", + "In a small bowl, whisk together flour and baking soda; set aside.", + "In a large bowl, cream butter and sugars until light and fluffy.", + "Add salt, vanilla, and eggs; mix well.", + "Gradually stir in flour mixture.", + "Fold in chocolate chips.", + "Drop by rounded tablespoons onto ungreased baking sheets.", + "Bake for 10-12 minutes or until golden brown.", + "Cool on wire racks." + ] + } + } + "#, + FieldType::RecursiveTypeAlias("JsonValue".into()), + { + "recipe": { + "name": "Chocolate Chip Cookies", + "servings": 24, + "ingredients": [ + "2 1/4 cups all-purpose flour", "1/2 teaspoon baking soda", + "1 cup unsalted butter, room temperature", + "1/2 cup granulated sugar", + "1 cup packed light-brown sugar", + "1 teaspoon salt", "2 teaspoons pure vanilla extract", + "2 large eggs", "2 cups semisweet and/or milk chocolate chips" + ], + "instructions": [ + "Preheat oven to 350°F (180°C).", + "In a small bowl, whisk together flour and baking soda; set aside.", + "In a large bowl, cream butter and sugars until light and fluffy.", + "Add salt, vanilla, and eggs; mix well.", + "Gradually stir in flour mixture.", + "Fold in chocolate chips.", + "Drop by rounded tablespoons onto ungreased baking sheets.", + "Bake for 10-12 minutes or until golden brown.", + "Cool on wire racks." + ] + } + } +); diff --git a/engine/baml-lib/jsonish/src/tests/test_basics.rs b/engine/baml-lib/jsonish/src/tests/test_basics.rs index ea88ae476..a79698b18 100644 --- a/engine/baml-lib/jsonish/src/tests/test_basics.rs +++ b/engine/baml-lib/jsonish/src/tests/test_basics.rs @@ -146,6 +146,14 @@ test_deserializer!( [1., 2., 3.] ); +test_deserializer!( + test_string_to_float_from_comma_separated, + "", + "1 cup unsalted butter, room temperature", + FieldType::Primitive(TypeValue::Float), + 1.0 +); + test_deserializer!( test_object, r#" diff --git a/engine/baml-lib/jsonish/src/tests/test_unions.rs b/engine/baml-lib/jsonish/src/tests/test_unions.rs index c1f7173c9..b8619022b 100644 --- a/engine/baml-lib/jsonish/src/tests/test_unions.rs +++ b/engine/baml-lib/jsonish/src/tests/test_unions.rs @@ -277,3 +277,14 @@ test_deserializer!( FieldType::Class("ContactInfo".to_string()), {"primary": {"value": "help@boundaryml.com"}} ); + +test_deserializer!( + test_ignore_float_in_string_if_string_in_union, + "", + "1 cup unsalted butter, room temperature", + FieldType::Union(vec![ + FieldType::Primitive(TypeValue::Float), + FieldType::Primitive(TypeValue::String), + ]), + "1 cup unsalted butter, room temperature" +); From 53c7b71a72adcc09f25d396200754af597cc7186 Mon Sep 17 00:00:00 2001 From: Antonio Sarosi <sarosiantonio@gmail.com> Date: Fri, 27 Dec 2024 18:18:18 +0100 Subject: [PATCH 51/51] Add test for `int | string` --- engine/baml-lib/jsonish/src/tests/test_unions.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/engine/baml-lib/jsonish/src/tests/test_unions.rs b/engine/baml-lib/jsonish/src/tests/test_unions.rs index b8619022b..dbdacbf7d 100644 --- a/engine/baml-lib/jsonish/src/tests/test_unions.rs +++ b/engine/baml-lib/jsonish/src/tests/test_unions.rs @@ -288,3 +288,14 @@ test_deserializer!( ]), "1 cup unsalted butter, room temperature" ); + +test_deserializer!( + test_ignore_int_if_string_in_union, + "", + "1 cup unsalted butter, room temperature", + FieldType::Union(vec![ + FieldType::Primitive(TypeValue::Int), + FieldType::Primitive(TypeValue::String), + ]), + "1 cup unsalted butter, room temperature" +);