From 73e5bb391151b81054a74938f43ddb89211b6380 Mon Sep 17 00:00:00 2001 From: Antonio Sarosi Date: Sat, 16 Nov 2024 01:34:54 +0000 Subject: [PATCH 1/6] Enums as map keys --- .../baml-core/src/ir/ir_helpers/mod.rs | 8 +- .../validation_pipeline/validations/types.rs | 12 +- .../class/map_enums_and_literals.baml | 18 + .../validation_files/class/map_types.baml | 16 +- .../src/deserializer/coercer/coerce_map.rs | 5 +- .../jsonish/src/deserializer/coercer/mod.rs | 5 +- .../src/attributes/constraint.rs | 2 +- .../src/ruby/field_type.rs | 34 +- .../src/ruby/generate_types.rs | 37 +- .../src/typescript/mod.rs | 24 +- .../functions/output/map-enum-key.baml | 14 + .../python/baml_client/async_client.py | 54 ++ integ-tests/python/baml_client/inlinedbaml.py | 1 + integ-tests/python/baml_client/sync_client.py | 54 ++ .../python/baml_client/type_builder.py | 2 +- integ-tests/python/baml_client/types.py | 6 + integ-tests/python/tests/test_functions.py | 7 + integ-tests/ruby/baml_client/client.rb | 67 ++ integ-tests/ruby/baml_client/inlined.rb | 1 + integ-tests/ruby/baml_client/type-registry.rb | 2 +- integ-tests/ruby/baml_client/types.rb | 7 + integ-tests/ruby/test_functions.rb | 4 + .../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 | 778 +----------------- .../typescript/tests/integ-tests.test.ts | 12 +- 29 files changed, 423 insertions(+), 843 deletions(-) create mode 100644 engine/baml-lib/baml/tests/validation_files/class/map_enums_and_literals.baml create mode 100644 integ-tests/baml_src/test-files/functions/output/map-enum-key.baml 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 9234d9c31..50907b5d7 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 @@ -278,7 +278,13 @@ impl IRHelper for IntermediateRepr { match maybe_item_type { Some(item_type) => { let map_type = FieldType::Map( - Box::new(FieldType::Primitive(TypeValue::String)), + Box::new(match &field_type { + FieldType::Map(key, _) => match key.as_ref() { + FieldType::Enum(name) => FieldType::Enum(name.clone()), + _ => FieldType::string(), + }, + _ => FieldType::string(), + }), Box::new(item_type.clone()), ); diff --git a/engine/baml-lib/baml-core/src/validate/validation_pipeline/validations/types.rs b/engine/baml-lib/baml-core/src/validate/validation_pipeline/validations/types.rs index ce4633b59..093da1e6d 100644 --- a/engine/baml-lib/baml-core/src/validate/validation_pipeline/validations/types.rs +++ b/engine/baml-lib/baml-core/src/validate/validation_pipeline/validations/types.rs @@ -1,4 +1,5 @@ use baml_types::TypeValue; +use either::Either; use internal_baml_diagnostics::{DatamodelError, DatamodelWarning, Span}; use internal_baml_schema_ast::ast::{ Argument, Attribute, Expression, FieldArity, FieldType, Identifier, WithName, WithSpan, @@ -57,10 +58,19 @@ fn validate_type_allowed(ctx: &mut Context<'_>, field_type: &FieldType) { )); } match &kv_types.0 { + // String key. FieldType::Primitive(FieldArity::Required, TypeValue::String, ..) => {} + + // Enum key. + FieldType::Symbol(_, identifier, _) + if ctx + .db + .find_type(identifier) + .is_some_and(|t| matches!(t, Either::Right(_))) => {} + key_type => { ctx.push_error(DatamodelError::new_validation_error( - "Maps may only have strings as keys", + "Maps may only have strings or enums as keys", key_type.span().clone(), )); } diff --git a/engine/baml-lib/baml/tests/validation_files/class/map_enums_and_literals.baml b/engine/baml-lib/baml/tests/validation_files/class/map_enums_and_literals.baml new file mode 100644 index 000000000..61fca1032 --- /dev/null +++ b/engine/baml-lib/baml/tests/validation_files/class/map_enums_and_literals.baml @@ -0,0 +1,18 @@ +enum MapKey { + A + B + C +} + +class Fields { + e map +} + +function InOutEnumKey(i1: map, i2: map) -> map { + client "openai/gpt-4o" + prompt #" + Merge these: {{i1}} {{i2}} + + {{ ctx.output_format }} + "# +} diff --git a/engine/baml-lib/baml/tests/validation_files/class/map_types.baml b/engine/baml-lib/baml/tests/validation_files/class/map_types.baml index 7843d8409..038ed66d0 100644 --- a/engine/baml-lib/baml/tests/validation_files/class/map_types.baml +++ b/engine/baml-lib/baml/tests/validation_files/class/map_types.baml @@ -31,49 +31,49 @@ function InputAndOutput(i1: map, i2: map) -> m "# } -// error: Error validating: Maps may only have strings as keys +// error: Error validating: Maps may only have strings or enums as keys // --> class/map_types.baml:16 // | // 15 | // 16 | b1 map // | -// error: Error validating: Maps may only have strings as keys +// error: Error validating: Maps may only have strings or enums as keys // --> class/map_types.baml:17 // | // 16 | b1 map // 17 | b2 map // | -// error: Error validating: Maps may only have strings as keys +// error: Error validating: Maps may only have strings or enums as keys // --> class/map_types.baml:18 // | // 17 | b2 map // 18 | b3 map // | -// error: Error validating: Maps may only have strings as keys +// error: Error validating: Maps may only have strings or enums as keys // --> class/map_types.baml:19 // | // 18 | b3 map // 19 | b4 map // | -// error: Error validating: Maps may only have strings as keys +// error: Error validating: Maps may only have strings or enums as keys // --> class/map_types.baml:20 // | // 19 | b4 map // 20 | b5 map // | -// error: Error validating: Maps may only have strings as keys +// error: Error validating: Maps may only have strings or enums as keys // --> class/map_types.baml:23 // | // 22 | c1 string | map // 23 | c2 string | map // | -// error: Error validating: Maps may only have strings as keys +// error: Error validating: Maps may only have strings or enums as keys // --> class/map_types.baml:24 // | // 23 | c2 string | map // 24 | c3 string | map // | -// error: Error validating: Maps may only have strings as keys +// error: Error validating: Maps may only have strings or enums as keys // --> class/map_types.baml:27 // | // 26 | diff --git a/engine/baml-lib/jsonish/src/deserializer/coercer/coerce_map.rs b/engine/baml-lib/jsonish/src/deserializer/coercer/coerce_map.rs index ff66bb498..c31bf5f12 100644 --- a/engine/baml-lib/jsonish/src/deserializer/coercer/coerce_map.rs +++ b/engine/baml-lib/jsonish/src/deserializer/coercer/coerce_map.rs @@ -28,7 +28,10 @@ pub(super) fn coerce_map( return Err(ctx.error_unexpected_type(map_target, value)); }; - if !matches!(**key_type, FieldType::Primitive(TypeValue::String)) { + if !matches!( + **key_type, + FieldType::Primitive(TypeValue::String) | FieldType::Enum(_) + ) { return Err(ctx.error_map_must_have_string_key(key_type)); } diff --git a/engine/baml-lib/jsonish/src/deserializer/coercer/mod.rs b/engine/baml-lib/jsonish/src/deserializer/coercer/mod.rs index 5c94d3eb3..dbc01ab2e 100644 --- a/engine/baml-lib/jsonish/src/deserializer/coercer/mod.rs +++ b/engine/baml-lib/jsonish/src/deserializer/coercer/mod.rs @@ -141,7 +141,10 @@ impl ParsingContext<'_> { pub(crate) fn error_map_must_have_string_key(&self, key_type: &FieldType) -> ParsingError { ParsingError { - reason: format!("Maps may only have strings for keys, but got {}", key_type), + reason: format!( + "Maps may only have strings or enums for keys, but got {}", + key_type + ), scope: self.scope.clone(), causes: vec![], } diff --git a/engine/baml-lib/parser-database/src/attributes/constraint.rs b/engine/baml-lib/parser-database/src/attributes/constraint.rs index 5920ea8d9..c54a921d5 100644 --- a/engine/baml-lib/parser-database/src/attributes/constraint.rs +++ b/engine/baml-lib/parser-database/src/attributes/constraint.rs @@ -23,7 +23,7 @@ pub(super) fn visit_constraint_attributes( ctx.push_error(DatamodelError::new_attribute_validation_error( "Internal error - the parser should have ruled out other attribute names.", other_name, - span + span, )); return (); } diff --git a/engine/language_client_codegen/src/ruby/field_type.rs b/engine/language_client_codegen/src/ruby/field_type.rs index 91e2cbb83..751dd80e2 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; @@ -14,10 +13,16 @@ impl ToRuby for FieldType { FieldType::Literal(value) => value.literal_base_type().to_ruby(), // https://sorbet.org/docs/stdlib-generics FieldType::List(inner) => format!("T::Array[{}]", inner.to_ruby()), - FieldType::Map(key, value) => { - format!("T::Hash[{}, {}]", key.to_ruby(), value.to_ruby()) - } - FieldType::Primitive(r#type) => match r#type { + FieldType::Map(key, value) => format!( + "T::Hash[{}, {}]", + match key.as_ref() { + // For enums just default to strings. + FieldType::Enum(_) => FieldType::string().to_ruby(), + _ => key.to_ruby(), + }, + value.to_ruby() + ), + FieldType::Primitive(r#type) => String::from(match r#type { // https://sorbet.org/docs/class-types TypeValue::Bool => "T::Boolean", TypeValue::Float => "Float", @@ -27,8 +32,7 @@ impl ToRuby for FieldType { // TODO: Create Baml::Types::Image TypeValue::Media(BamlMediaType::Image) => "Baml::Image", TypeValue::Media(BamlMediaType::Audio) => "Baml::Audio", - } - .to_string(), + }), FieldType::Union(inner) => format!( // https://sorbet.org/docs/union-types "T.any({})", @@ -48,17 +52,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..a8625eb34 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> 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(), } } @@ -140,13 +145,15 @@ impl ToTypeReferenceInTypeDefinition for FieldType { FieldType::Literal(value) => value.literal_base_type().to_partial_type_ref(), // https://sorbet.org/docs/stdlib-generics FieldType::List(inner) => format!("T::Array[{}]", inner.to_partial_type_ref()), - FieldType::Map(key, value) => { - format!( - "T::Hash[{}, {}]", - key.to_type_ref(), - value.to_partial_type_ref() - ) - } + FieldType::Map(key, value) => format!( + "T::Hash[{}, {}]", + match key.as_ref() { + // For enums just default to strings. + FieldType::Enum(_) => FieldType::string().to_type_ref(), + _ => key.to_type_ref(), + }, + value.to_partial_type_ref() + ), FieldType::Primitive(_) => format!("T.nilable({})", self.to_type_ref()), FieldType::Union(inner) => format!( // https://sorbet.org/docs/union-types @@ -167,16 +174,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..4a48d3822 100644 --- a/engine/language_client_codegen/src/typescript/mod.rs +++ b/engine/language_client_codegen/src/typescript/mod.rs @@ -273,7 +273,13 @@ impl ToTypeReferenceInClientDefinition for FieldType { _ => format!("{}[]", inner.to_type_ref(ir)), }, FieldType::Map(key, value) => { - format!("Record<{}, {}>", key.to_type_ref(ir), value.to_type_ref(ir)) + let k = key.to_type_ref(ir); + let v = value.to_type_ref(ir); + + match key.as_ref() { + FieldType::Enum(_) => format!("Partial>"), + _ => format!("Record<{k}, {v}>"), + } } FieldType::Primitive(r#type) => r#type.to_typescript(), // In typescript we can just use literal values as type defs. @@ -295,17 +301,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), }, } } diff --git a/integ-tests/baml_src/test-files/functions/output/map-enum-key.baml b/integ-tests/baml_src/test-files/functions/output/map-enum-key.baml new file mode 100644 index 000000000..3b9a1110f --- /dev/null +++ b/integ-tests/baml_src/test-files/functions/output/map-enum-key.baml @@ -0,0 +1,14 @@ +enum MapKey { + A + B + C +} + +function InOutEnumMapKey(i1: map, i2: map) -> map { + client "openai/gpt-4o" + prompt #" + Merge these: {{i1}} {{i2}} + + {{ ctx.output_format }} + "# +} diff --git a/integ-tests/python/baml_client/async_client.py b/integ-tests/python/baml_client/async_client.py index 758505b13..312eac098 100644 --- a/integ-tests/python/baml_client/async_client.py +++ b/integ-tests/python/baml_client/async_client.py @@ -1292,6 +1292,29 @@ async def GetQuery( ) return cast(types.SearchParams, raw.cast_to(types, types)) + async def InOutEnumMapKey( + self, + i1: Dict[types.MapKey, str],i2: Dict[types.MapKey, str], + baml_options: BamlCallOptions = {}, + ) -> Dict[types.MapKey, 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( + "InOutEnumMapKey", + { + "i1": i1,"i2": i2, + }, + self.__ctx_manager.get(), + tb, + __cr__, + ) + return cast(Dict[types.MapKey, str], raw.cast_to(types, types)) + async def LiteralUnionsTest( self, input: str, @@ -4306,6 +4329,37 @@ def GetQuery( self.__ctx_manager.get(), ) + def InOutEnumMapKey( + self, + i1: Dict[types.MapKey, str],i2: Dict[types.MapKey, str], + baml_options: BamlCallOptions = {}, + ) -> baml_py.BamlStream[Dict[types.MapKey, Optional[str]], Dict[types.MapKey, 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( + "InOutEnumMapKey", + { + "i1": i1, + "i2": i2, + }, + None, + self.__ctx_manager.get(), + tb, + __cr__, + ) + + return baml_py.BamlStream[Dict[types.MapKey, Optional[str]], Dict[types.MapKey, str]]( + raw, + lambda x: cast(Dict[types.MapKey, Optional[str]], x.cast_to(types, partial_types)), + lambda x: cast(Dict[types.MapKey, str], x.cast_to(types, types)), + self.__ctx_manager.get(), + ) + def LiteralUnionsTest( self, input: str, diff --git a/integ-tests/python/baml_client/inlinedbaml.py b/integ-tests/python/baml_client/inlinedbaml.py index e63fc92fd..09ecd5dea 100644 --- a/integ-tests/python/baml_client/inlinedbaml.py +++ b/integ-tests/python/baml_client/inlinedbaml.py @@ -74,6 +74,7 @@ "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/map-enum-key.baml": "enum MapKey {\n A\n B\n C\n}\n\nfunction InOutEnumMapKey(i1: map, i2: map) -> map {\n client \"openai/gpt-4o\"\n prompt #\"\n Merge these: {{i1}} {{i2}}\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}", "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", diff --git a/integ-tests/python/baml_client/sync_client.py b/integ-tests/python/baml_client/sync_client.py index bb4347c7c..41627662e 100644 --- a/integ-tests/python/baml_client/sync_client.py +++ b/integ-tests/python/baml_client/sync_client.py @@ -1289,6 +1289,29 @@ def GetQuery( ) return cast(types.SearchParams, raw.cast_to(types, types)) + def InOutEnumMapKey( + self, + i1: Dict[types.MapKey, str],i2: Dict[types.MapKey, str], + baml_options: BamlCallOptions = {}, + ) -> Dict[types.MapKey, 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( + "InOutEnumMapKey", + { + "i1": i1,"i2": i2, + }, + self.__ctx_manager.get(), + tb, + __cr__, + ) + return cast(Dict[types.MapKey, str], raw.cast_to(types, types)) + def LiteralUnionsTest( self, input: str, @@ -4304,6 +4327,37 @@ def GetQuery( self.__ctx_manager.get(), ) + def InOutEnumMapKey( + self, + i1: Dict[types.MapKey, str],i2: Dict[types.MapKey, str], + baml_options: BamlCallOptions = {}, + ) -> baml_py.BamlSyncStream[Dict[types.MapKey, Optional[str]], Dict[types.MapKey, 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( + "InOutEnumMapKey", + { + "i1": i1, + "i2": i2, + }, + None, + self.__ctx_manager.get(), + tb, + __cr__, + ) + + return baml_py.BamlSyncStream[Dict[types.MapKey, Optional[str]], Dict[types.MapKey, str]]( + raw, + lambda x: cast(Dict[types.MapKey, Optional[str]], x.cast_to(types, partial_types)), + lambda x: cast(Dict[types.MapKey, str], x.cast_to(types, types)), + self.__ctx_manager.get(), + ) + def LiteralUnionsTest( self, input: str, diff --git a/integ-tests/python/baml_client/type_builder.py b/integ-tests/python/baml_client/type_builder.py index 6ca036a4c..a8ed479ae 100644 --- a/integ-tests/python/baml_client/type_builder.py +++ b/integ-tests/python/baml_client/type_builder.py @@ -22,7 +22,7 @@ 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",] ), enums=set( - ["AliasedEnum","Category","Category2","Category3","Color","DataType","DynEnumOne","DynEnumTwo","EnumInClass","EnumOutput","Hobby","NamedArgsSingleEnum","NamedArgsSingleEnumList","OptionalTest_CategoryType","OrderStatus","Tag","TestEnum",] + ["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 d9b05889b..3b5a7530e 100644 --- a/integ-tests/python/baml_client/types.py +++ b/integ-tests/python/baml_client/types.py @@ -105,6 +105,12 @@ class Hobby(str, Enum): MUSIC = "MUSIC" READING = "READING" +class MapKey(str, Enum): + + A = "A" + B = "B" + C = "C" + class NamedArgsSingleEnum(str, Enum): ONE = "ONE" diff --git a/integ-tests/python/tests/test_functions.py b/integ-tests/python/tests/test_functions.py index e88acfc6b..effa01d9d 100644 --- a/integ-tests/python/tests/test_functions.py +++ b/integ-tests/python/tests/test_functions.py @@ -38,6 +38,7 @@ all_succeeded, BlockConstraintForParam, NestedBlockConstraintForParam, + MapKey, ) import baml_client.types as types from ..baml_client.tracing import trace, set_tags, flush, on_log_event @@ -230,6 +231,12 @@ async def test_single_map_string_to_map(self): res = await b.TestFnNamedArgsSingleMapStringToMap({"lorem": {"word": "ipsum"}}) assert res["lorem"]["word"] == "ipsum" + @pytest.mark.asyncio + async def test_enum_key_in_map(self): + res = await b.InOutEnumMapKey({MapKey.A: "A"}, {MapKey.B: "B"}) + assert res[MapKey.A] == "A" + assert res[MapKey.B] == "B" + 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 2631807c4..b1eb349ca 100644 --- a/integ-tests/ruby/baml_client/client.rb +++ b/integ-tests/ruby/baml_client/client.rb @@ -1778,6 +1778,38 @@ def GetQuery( (raw.parsed_using_types(Baml::Types)) end + sig { + params( + varargs: T.untyped, + i1: T::Hash[String, String],i2: T::Hash[String, String], + baml_options: T::Hash[Symbol, T.any(Baml::TypeBuilder, Baml::ClientRegistry)] + ).returns(T::Hash[String, String]) + } + def InOutEnumMapKey( + *varargs, + i1:,i2:, + baml_options: {} + ) + if varargs.any? + + raise ArgumentError.new("InOutEnumMapKey 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( + "InOutEnumMapKey", + { + i1: i1,i2: i2, + }, + @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, @@ -5601,6 +5633,41 @@ def GetQuery( ) end + sig { + params( + varargs: T.untyped, + i1: T::Hash[String, String],i2: T::Hash[String, String], + baml_options: T::Hash[Symbol, T.any(Baml::TypeBuilder, Baml::ClientRegistry)] + ).returns(Baml::BamlStream[T::Hash[String, String]]) + } + def InOutEnumMapKey( + *varargs, + i1:,i2:, + baml_options: {} + ) + if varargs.any? + + raise ArgumentError.new("InOutEnumMapKey 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( + "InOutEnumMapKey", + { + i1: i1,i2: i2, + }, + @ctx_manager, + baml_options[:tb]&.instance_variable_get(:@registry), + baml_options[:client_registry], + ) + Baml::BamlStream[T::Hash[String, T.nilable(String)], T::Hash[String, String]].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 cdd8e6de1..b03c9dbab 100644 --- a/integ-tests/ruby/baml_client/inlined.rb +++ b/integ-tests/ruby/baml_client/inlined.rb @@ -74,6 +74,7 @@ module Inlined "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/map-enum-key.baml" => "enum MapKey {\n A\n B\n C\n}\n\nfunction InOutEnumMapKey(i1: map, i2: map) -> map {\n client \"openai/gpt-4o\"\n prompt #\"\n Merge these: {{i1}} {{i2}}\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}", "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", diff --git a/integ-tests/ruby/baml_client/type-registry.rb b/integ-tests/ruby/baml_client/type-registry.rb index 20c1b7401..f2e87aa08 100644 --- a/integ-tests/ruby/baml_client/type-registry.rb +++ b/integ-tests/ruby/baml_client/type-registry.rb @@ -19,7 +19,7 @@ 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", ] - @enums = Set[ "AliasedEnum", "Category", "Category2", "Category3", "Color", "DataType", "DynEnumOne", "DynEnumTwo", "EnumInClass", "EnumOutput", "Hobby", "NamedArgsSingleEnum", "NamedArgsSingleEnumList", "OptionalTest_CategoryType", "OrderStatus", "Tag", "TestEnum", ] + @enums = Set[ "AliasedEnum", "Category", "Category2", "Category3", "Color", "DataType", "DynEnumOne", "DynEnumTwo", "EnumInClass", "EnumOutput", "Hobby", "MapKey", "NamedArgsSingleEnum", "NamedArgsSingleEnumList", "OptionalTest_CategoryType", "OrderStatus", "Tag", "TestEnum", ] end def string diff --git a/integ-tests/ruby/baml_client/types.rb b/integ-tests/ruby/baml_client/types.rb index f57583469..aab5e73c1 100644 --- a/integ-tests/ruby/baml_client/types.rb +++ b/integ-tests/ruby/baml_client/types.rb @@ -91,6 +91,13 @@ class Hobby < T::Enum READING = new("READING") end end + class MapKey < T::Enum + enums do + A = new("A") + B = new("B") + C = new("C") + end + end class NamedArgsSingleEnum < T::Enum enums do ONE = new("ONE") diff --git a/integ-tests/ruby/test_functions.rb b/integ-tests/ruby/test_functions.rb index 9c3f39e42..484295780 100644 --- a/integ-tests/ruby/test_functions.rb +++ b/integ-tests/ruby/test_functions.rb @@ -67,6 +67,10 @@ res = b.TestFnNamedArgsSingleMapStringToMap(myMap: {"lorem" => {"word" => "ipsum"}}) assert_equal res['lorem']['word'], "ipsum" + + res = b.InOutEnumMapKey(i1: {"A" => "A"}, i2: {"B" => "B"}) + assert_equal res['A'], "A" + assert_equal res['B'], "B" 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 d3face652..2d1fec9c3 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, 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, 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" @@ -1393,6 +1393,31 @@ export class BamlAsyncClient { } } + async InOutEnumMapKey( + i1: Partial>,i2: Partial>, + __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } + ): Promise>> { + try { + const raw = await this.runtime.callFunction( + "InOutEnumMapKey", + { + "i1": i1,"i2": i2 + }, + this.ctx_manager.cloneContext(), + __baml_options__?.tb?.__tb(), + __baml_options__?.clientRegistry, + ) + return raw.parsed() as Partial> + } 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 } @@ -4681,6 +4706,39 @@ class BamlStreamClient { } } + InOutEnumMapKey( + i1: Partial>,i2: Partial>, + __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } + ): BamlStream>>, Partial>> { + try { + const raw = this.runtime.streamFunction( + "InOutEnumMapKey", + { + "i1": i1,"i2": i2 + }, + undefined, + this.ctx_manager.cloneContext(), + __baml_options__?.tb?.__tb(), + __baml_options__?.clientRegistry, + ) + return new BamlStream>>, Partial>>( + raw, + (a): a is RecursivePartialNull>> => a, + (a): a is Partial> => 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 } diff --git a/integ-tests/typescript/baml_client/inlinedbaml.ts b/integ-tests/typescript/baml_client/inlinedbaml.ts index 40e86d3b4..10f3e7345 100644 --- a/integ-tests/typescript/baml_client/inlinedbaml.ts +++ b/integ-tests/typescript/baml_client/inlinedbaml.ts @@ -75,6 +75,7 @@ const fileMap = { "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/map-enum-key.baml": "enum MapKey {\n A\n B\n C\n}\n\nfunction InOutEnumMapKey(i1: map, i2: map) -> map {\n client \"openai/gpt-4o\"\n prompt #\"\n Merge these: {{i1}} {{i2}}\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}", "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", diff --git a/integ-tests/typescript/baml_client/sync_client.ts b/integ-tests/typescript/baml_client/sync_client.ts index 7b352eb58..e8056fd23 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, 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, 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" @@ -1393,6 +1393,31 @@ export class BamlSyncClient { } } + InOutEnumMapKey( + i1: Partial>,i2: Partial>, + __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } + ): Partial> { + try { + const raw = this.runtime.callFunctionSync( + "InOutEnumMapKey", + { + "i1": i1,"i2": i2 + }, + this.ctx_manager.cloneContext(), + __baml_options__?.tb?.__tb(), + __baml_options__?.clientRegistry, + ) + return raw.parsed() as Partial> + } 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 } diff --git a/integ-tests/typescript/baml_client/type_builder.ts b/integ-tests/typescript/baml_client/type_builder.ts index a28ca0da9..db7a96d0a 100644 --- a/integ-tests/typescript/baml_client/type_builder.ts +++ b/integ-tests/typescript/baml_client/type_builder.ts @@ -53,7 +53,7 @@ export default class TypeBuilder { "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", ]), enums: new Set([ - "AliasedEnum","Category","Category2","Category3","Color","DataType","DynEnumOne","DynEnumTwo","EnumInClass","EnumOutput","Hobby","NamedArgsSingleEnum","NamedArgsSingleEnumList","OptionalTest_CategoryType","OrderStatus","Tag","TestEnum", + "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 99eb57853..0fbdaa319 100644 --- a/integ-tests/typescript/baml_client/types.ts +++ b/integ-tests/typescript/baml_client/types.ts @@ -101,6 +101,12 @@ export enum Hobby { READING = "READING", } +export enum MapKey { + A = "A", + B = "B", + C = "C", +} + export enum NamedArgsSingleEnum { ONE = "ONE", TWO = "TWO", diff --git a/integ-tests/typescript/test-report.html b/integ-tests/typescript/test-report.html index 83d516bc4..16b093e45 100644 --- a/integ-tests/typescript/test-report.html +++ b/integ-tests/typescript/test-report.html @@ -257,780 +257,4 @@ font-size: 1rem; padding: 0 0.5rem; } -

Test Report

Started: 2024-11-11 15:23:46
Suites (1)
0 passed
1 failed
0 pending
Tests (64)
63 passed
1 failed
0 pending
Integ tests > should work for all inputs
single bool
passed
0.687s
Integ tests > should work for all inputs
single string list
passed
0.483s
Integ tests > should work for all inputs
return literal union
passed
0.416s
Integ tests > should work for all inputs
single class
passed
0.445s
Integ tests > should work for all inputs
multiple classes
passed
0.69s
Integ tests > should work for all inputs
single enum list
passed
0.389s
Integ tests > should work for all inputs
single float
passed
0.436s
Integ tests > should work for all inputs
single int
passed
0.612s
Integ tests > should work for all inputs
single literal int
passed
0.352s
Integ tests > should work for all inputs
single literal bool
passed
0.352s
Integ tests > should work for all inputs
single literal string
passed
0.43s
Integ tests > should work for all inputs
single class with literal prop
passed
0.81s
Integ tests > should work for all inputs
single class with literal union prop
passed
0.551s
Integ tests > should work for all inputs
single optional string
passed
0.38s
Integ tests > should work for all inputs
single map string to string
passed
0.774s
Integ tests > should work for all inputs
single map string to class
passed
0.948s
Integ tests > should work for all inputs
single map string to map
passed
0.656s
Integ tests
should work for all outputs
passed
4.797s
Integ tests
works with retries1
passed
1.059s
Integ tests
works with retries2
passed
2.064s
Integ tests
works with fallbacks
passed
1.607s
Integ tests
should work with image from url
passed
1.075s
Integ tests
should work with image from base 64
passed
1.103s
Integ tests
should work with audio base 64
passed
0.802s
Integ tests
should work with audio from url
passed
4.315s
Integ tests
should support streaming in OpenAI
passed
3.357s
Integ tests
should support streaming in Gemini
passed
10.198s
Integ tests
should support AWS
passed
4.281s
Integ tests
should support streaming in AWS
failed
1.621s
Error: BamlError: BamlClientError: BamlClientHttpError: LLM call failed: LLMErrorResponse { client: "AwsBedrock", model: Some("anthropic.claude-3-5-sonnet-20240620-v1:0"), prompt: Chat([RenderedChatMessage { role: "user", allow_duplicate_role: false, parts: [Text("Write a nice short story about Dr. Pepper")] }]), request_options: {"api_key": String("")}, start_time: SystemTime { tv_sec: 1731360271, tv_nsec: 598100000 }, latency: 1.61346525s, message: "ServiceError(\n    ServiceError {\n        source: ThrottlingException(\n            ThrottlingException {\n                message: Some(\n                    \"Too many requests, please wait before trying again.\",\n                ),\n                meta: ErrorMetadata {\n                    code: Some(\n                        \"ThrottlingException\",\n                    ),\n                    message: Some(\n                        \"Too many requests, please wait before trying again.\",\n                    ),\n                    extras: Some(\n                        {\n                            \"aws_request_id\": \"94e852da-1f5a-4dd2-80d1-a5603ddf6d75\",\n                        },\n                    ),\n                },\n            },\n        ),\n        raw: Response {\n            status: StatusCode(\n                429,\n            ),\n            headers: Headers {\n                headers: {\n                    \"date\": HeaderValue {\n                        _private: H0(\n                            \"Mon, 11 Nov 2024 21:24:33 GMT\",\n                        ),\n                    },\n                    \"content-type\": HeaderValue {\n                        _private: H0(\n                            \"application/json\",\n                        ),\n                    },\n                    \"content-length\": HeaderValue {\n                        _private: H0(\n                            \"65\",\n                        ),\n                    },\n                    \"x-amzn-requestid\": HeaderValue {\n                        _private: H0(\n                            \"94e852da-1f5a-4dd2-80d1-a5603ddf6d75\",\n                        ),\n                    },\n                    \"x-amzn-errortype\": HeaderValue {\n                        _private: H0(\n                            \"ThrottlingException:http://internal.amazon.com/coral/com.amazon.bedrock/\",\n                        ),\n                    },\n                },\n            },\n            body: SdkBody {\n                inner: Once(\n                    Some(\n                        b\"{\\\"message\\\":\\\"Too many requests, please wait before trying again.\\\"}\",\n                    ),\n                ),\n                retryable: true,\n            },\n            extensions: Extensions {\n                extensions_02x: Extensions,\n                extensions_1x: Extensions,\n            },\n        },\n    },\n)", code: RateLimited }
-    at BamlStream.parsed [as getFinalResponse] (/Users/vbv/repos/gloo-lang/engine/language_client_typescript/stream.js:58:39)
-    at Object.<anonymous> (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:274:19)
Integ tests
should allow overriding the region
passed
0.005s
Integ tests
should support OpenAI shorthand
passed
10.32s
Integ tests
should support OpenAI shorthand streaming
passed
10.353s
Integ tests
should support anthropic shorthand
passed
2.65s
Integ tests
should support anthropic shorthand streaming
passed
2.642s
Integ tests
should support streaming without iterating
passed
2.552s
Integ tests
should support streaming in Claude
passed
1.014s
Integ tests
should support vertex
passed
10.081s
Integ tests
supports tracing sync
passed
0.009s
Integ tests
supports tracing async
passed
2.162s
Integ tests
should work with dynamic types single
passed
0.966s
Integ tests
should work with dynamic types enum
passed
0.759s
Integ tests
should work with dynamic literals
passed
0.896s
Integ tests
should work with dynamic types class
passed
0.858s
Integ tests
should work with dynamic inputs class
passed
0.528s
Integ tests
should work with dynamic inputs list
passed
0.768s
Integ tests
should work with dynamic output map
passed
1.016s
Integ tests
should work with dynamic output union
passed
2.143s
Integ tests
should work with nested classes
passed
1.952s
Integ tests
should work with dynamic client
passed
0.413s
Integ tests
should work with 'onLogEvent'
passed
1.881s
Integ tests
should work with a sync client
passed
0.673s
Integ tests
should raise an error when appropriate
passed
0.747s
Integ tests
should raise a BAMLValidationError
passed
0.573s
Integ tests
should reset environment variables correctly
passed
1.21s
Integ tests
should use aliases when serializing input objects - classes
passed
0.958s
Integ tests
should use aliases when serializing, but still have original keys in jinja
passed
0.794s
Integ tests
should use aliases when serializing input objects - enums
passed
0.337s
Integ tests
should use aliases when serializing input objects - lists
passed
0.45s
Integ tests
constraints: should handle checks in return types
passed
0.699s
Integ tests
constraints: should handle checks in returned unions
passed
0.879s
Integ tests
constraints: should handle block-level checks
passed
0.636s
Integ tests
constraints: should handle nested-block-level checks
passed
0.599s
Integ tests
simple recursive type
passed
2.395s
Integ tests
mutually recursive type
passed
2.263s
Console Log
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:47: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)
calling with class
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:53:15)
got response key
-true
-52
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:176:15)
Expected error Error: BamlError: BamlClientError: BamlClientHttpError: LLM call failed: LLMErrorResponse { client: "RetryClientConstant", model: None, prompt: Chat([RenderedChatMessage { role: "system", allow_duplicate_role: false, parts: [Text("Say a haiku")] }]), request_options: {"model": String("gpt-3.5-turbo")}, start_time: SystemTime { tv_sec: 1731360242, tv_nsec: 596785000 }, latency: 165.278666ms, message: "Request failed: {\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:2710:18)
-    at Object.<anonymous> (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:173:7) {
-  code: 'GenericFailure'
-}
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:185:15)
Expected error Error: BamlError: BamlClientError: BamlClientHttpError: LLM call failed: LLMErrorResponse { client: "RetryClientExponential", model: None, prompt: Chat([RenderedChatMessage { role: "system", allow_duplicate_role: false, parts: [Text("Say a haiku")] }]), request_options: {"model": String("gpt-3.5-turbo")}, start_time: SystemTime { tv_sec: 1731360244, tv_nsec: 672557000 }, latency: 178.301292ms, message: "Request failed: {\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:2735:18)
-    at Object.<anonymous> (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:182:7) {
-  code: 'GenericFailure'
-}
    at log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:344: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:353: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)
hello world
    at log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:347: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:353: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)
dummyFunc returned
    at log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:350: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:353: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)
dummyFunc2 returned
    at log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:365: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:371: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:383:5)
samDummyNested nested1
    at log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:365: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:371: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:383:5)
samDummyNested nested2
    at log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:365: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:371: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:383:5)
samDummyNested nested3
    at log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:376: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:383:5)
dummy hi1
    at log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:365: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:371: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:383:5)
samDummyNested nested1
    at log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:365: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:371: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:383:5)
samDummyNested nested2
    at log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:365: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:371: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:383:5)
samDummyNested nested3
    at log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:376: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:383:5)
dummy hi2
    at log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:365: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:371: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:383:5)
samDummyNested nested1
    at log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:365: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:371: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:383:5)
samDummyNested nested2
    at log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:365: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:371: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:383:5)
samDummyNested nested3
    at log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:376: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:383:5)
dummy hi3
    at log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:390: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:406:5)
hello world
    at log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:365: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:371:22)
samDummyNested nested1
    at log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:365: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:371:22)
samDummyNested nested2
    at log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:365: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:371:22)
samDummyNested nested3
    at log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:376:15)
-    at runNextTicks (node:internal/process/task_queues:60:5)
-    at listOnTimeout (node:internal/timers:538:9)
-    at processTimers (node:internal/timers:512:7)
dummy firstDummyFuncArg
    at log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:365: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:371: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:395: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:389:17)
samDummyNested nested1
    at log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:365: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:371: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:395: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:389:17)
samDummyNested nested2
    at log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:365: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:371: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:395: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:389:17)
samDummyNested nested3
    at log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:376: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:395: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:389:17)
dummy secondDummyFuncArg
    at log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:365: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:371: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:403: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:389:17)
samDummyNested nested1
    at log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:365: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:371: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:403: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:389:17)
samDummyNested nested2
    at log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:365: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:371: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:403: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:389:17)
samDummyNested nested3
    at log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:376: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:403: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:389:17)
dummy thirdDummyFuncArg
    at log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:409: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:415:5)
hello world
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:419:13)
stats {"failed":0,"started":30,"finalized":30,"submitted":30,"sent":30,"done":30}
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:443:13)
[
-  {
-    name: 'Harrison',
-    hair_color: 'BLACK',
-    last_name: null,
-    height: 1.83,
-    hobbies: [ 'SPORTS' ]
-  }
-]
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:512: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)
[
-  [
-    'hair_color',
-    ClassPropertyBuilder { bldr: ClassPropertyBuilder {} }
-  ],
-  [
-    'attributes',
-    ClassPropertyBuilder { bldr: ClassPropertyBuilder {} }
-  ]
-]
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:514: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)
Property: hair_color
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:514: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)
Property: attributes
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:522:13)
final  {
-  hair_color: 'black',
-  attributes: { height: '6 feet', eye_color: 'blue', facial_hair: 'beard' }
-}
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:546: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)
[
-  [
-    'hair_color',
-    ClassPropertyBuilder { bldr: ClassPropertyBuilder {} }
-  ],
-  [
-    'attributes',
-    ClassPropertyBuilder { bldr: ClassPropertyBuilder {} }
-  ],
-  [ 'height', ClassPropertyBuilder { bldr: ClassPropertyBuilder {} } ]
-]
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:548: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)
Property: hair_color
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:548: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)
Property: attributes
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:548: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)
Property: height
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:556:13)
final  {
-  hair_color: 'black',
-  attributes: { eye_color: 'blue', facial_hair: 'beard', age: '30' },
-  height: { feet: 6, inches: null }
-}
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:567:13)
final  {
-  hair_color: 'black',
-  attributes: { eye_color: 'blue', facial_hair: 'beard', age: '30' },
-  height: { meters: 1.8 }
-}
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg { prop1: null, prop2: null }
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg { prop1: null, prop2: null }
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg { prop1: null, prop2: null }
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg { prop1: null, prop2: null }
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg { prop1: null, prop2: null }
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg { prop1: null, prop2: null }
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg { prop1: null, prop2: null }
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg { prop1: null, prop2: null }
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg { prop1: null, prop2: null }
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg { prop1: null, prop2: null }
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg { prop1: null, prop2: null }
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg { prop1: null, prop2: null }
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg { prop1: null, prop2: null }
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg { prop1: null, prop2: null }
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg { prop1: null, prop2: null }
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg { prop1: null, prop2: null }
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg { prop1: null, prop2: null }
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg { prop1: null, prop2: null }
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg { prop1: null, prop2: null }
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg { prop1: null, prop2: null }
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg { prop1: null, prop2: null }
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg { prop1: null, prop2: null }
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg { prop1: null, prop2: null }
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg { prop1: null, prop2: null }
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg { prop1: null, prop2: null }
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg { prop1: null, prop2: null }
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg { prop1: null, prop2: null }
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg { prop1: '', prop2: null }
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg { prop1: 'value', prop2: null }
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg { prop1: 'value1', prop2: null }
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg { prop1: 'value1', prop2: null }
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg { prop1: 'value1', prop2: null }
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg { prop1: 'value1', prop2: null }
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg { prop1: 'value1', prop2: null }
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg { prop1: 'value1', prop2: null }
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg { prop1: 'value1', prop2: null }
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg { prop1: 'value1', prop2: null }
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg { prop1: 'value1', prop2: { prop1: null, prop2: null, inner: null } }
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg { prop1: 'value1', prop2: { prop1: null, prop2: null, inner: null } }
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg { prop1: 'value1', prop2: { prop1: null, prop2: null, inner: null } }
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg { prop1: 'value1', prop2: { prop1: null, prop2: null, inner: null } }
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg { prop1: 'value1', prop2: { prop1: null, prop2: null, inner: null } }
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg { prop1: 'value1', prop2: { prop1: null, prop2: null, inner: null } }
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg { prop1: 'value1', prop2: { prop1: null, prop2: null, inner: null } }
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg { prop1: 'value1', prop2: { prop1: '', prop2: null, inner: null } }
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg {
-  prop1: 'value1',
-  prop2: { prop1: 'value', prop2: null, inner: null }
-}
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg {
-  prop1: 'value1',
-  prop2: { prop1: 'value2', prop2: null, inner: null }
-}
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg {
-  prop1: 'value1',
-  prop2: { prop1: 'value2', prop2: null, inner: null }
-}
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg {
-  prop1: 'value1',
-  prop2: { prop1: 'value2', prop2: null, inner: null }
-}
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg {
-  prop1: 'value1',
-  prop2: { prop1: 'value2', prop2: null, inner: null }
-}
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg {
-  prop1: 'value1',
-  prop2: { prop1: 'value2', prop2: null, inner: null }
-}
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg {
-  prop1: 'value1',
-  prop2: { prop1: 'value2', prop2: null, inner: null }
-}
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg {
-  prop1: 'value1',
-  prop2: { prop1: 'value2', prop2: null, inner: null }
-}
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg {
-  prop1: 'value1',
-  prop2: { prop1: 'value2', prop2: null, inner: null }
-}
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg { prop1: 'value1', prop2: { prop1: 'value2', prop2: '', inner: null } }
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg {
-  prop1: 'value1',
-  prop2: { prop1: 'value2', prop2: 'value', inner: null }
-}
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg {
-  prop1: 'value1',
-  prop2: { prop1: 'value2', prop2: 'value3', inner: null }
-}
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg {
-  prop1: 'value1',
-  prop2: { prop1: 'value2', prop2: 'value3', inner: null }
-}
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg {
-  prop1: 'value1',
-  prop2: { prop1: 'value2', prop2: 'value3', inner: null }
-}
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg {
-  prop1: 'value1',
-  prop2: { prop1: 'value2', prop2: 'value3', inner: null }
-}
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg {
-  prop1: 'value1',
-  prop2: { prop1: 'value2', prop2: 'value3', inner: null }
-}
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg {
-  prop1: 'value1',
-  prop2: { prop1: 'value2', prop2: 'value3', inner: null }
-}
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg {
-  prop1: 'value1',
-  prop2: { prop1: 'value2', prop2: 'value3', inner: null }
-}
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg {
-  prop1: 'value1',
-  prop2: {
-    prop1: 'value2',
-    prop2: 'value3',
-    inner: { prop2: null, prop3: null }
-  }
-}
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg {
-  prop1: 'value1',
-  prop2: {
-    prop1: 'value2',
-    prop2: 'value3',
-    inner: { prop2: null, prop3: null }
-  }
-}
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg {
-  prop1: 'value1',
-  prop2: {
-    prop1: 'value2',
-    prop2: 'value3',
-    inner: { prop2: null, prop3: null }
-  }
-}
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg {
-  prop1: 'value1',
-  prop2: {
-    prop1: 'value2',
-    prop2: 'value3',
-    inner: { prop2: null, prop3: null }
-  }
-}
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg {
-  prop1: 'value1',
-  prop2: {
-    prop1: 'value2',
-    prop2: 'value3',
-    inner: { prop2: null, prop3: null }
-  }
-}
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg {
-  prop1: 'value1',
-  prop2: {
-    prop1: 'value2',
-    prop2: 'value3',
-    inner: { prop2: null, prop3: null }
-  }
-}
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg {
-  prop1: 'value1',
-  prop2: {
-    prop1: 'value2',
-    prop2: 'value3',
-    inner: { prop2: null, prop3: null }
-  }
-}
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg {
-  prop1: 'value1',
-  prop2: {
-    prop1: 'value2',
-    prop2: 'value3',
-    inner: { prop2: null, prop3: null }
-  }
-}
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg {
-  prop1: 'value1',
-  prop2: {
-    prop1: 'value2',
-    prop2: 'value3',
-    inner: { prop2: null, prop3: null }
-  }
-}
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg {
-  prop1: 'value1',
-  prop2: {
-    prop1: 'value2',
-    prop2: 'value3',
-    inner: { prop2: null, prop3: null }
-  }
-}
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg {
-  prop1: 'value1',
-  prop2: {
-    prop1: 'value2',
-    prop2: 'value3',
-    inner: { prop2: 42, prop3: null }
-  }
-}
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg {
-  prop1: 'value1',
-  prop2: {
-    prop1: 'value2',
-    prop2: 'value3',
-    inner: { prop2: 42, prop3: null }
-  }
-}
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg {
-  prop1: 'value1',
-  prop2: {
-    prop1: 'value2',
-    prop2: 'value3',
-    inner: { prop2: 42, prop3: null }
-  }
-}
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg {
-  prop1: 'value1',
-  prop2: {
-    prop1: 'value2',
-    prop2: 'value3',
-    inner: { prop2: 42, prop3: null }
-  }
-}
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg {
-  prop1: 'value1',
-  prop2: {
-    prop1: 'value2',
-    prop2: 'value3',
-    inner: { prop2: 42, prop3: null }
-  }
-}
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg {
-  prop1: 'value1',
-  prop2: {
-    prop1: 'value2',
-    prop2: 'value3',
-    inner: { prop2: null, prop3: null }
-  }
-}
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg {
-  prop1: 'value1',
-  prop2: {
-    prop1: 'value2',
-    prop2: 'value3',
-    inner: { prop2: 42, prop3: null }
-  }
-}
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg {
-  prop1: 'value1',
-  prop2: {
-    prop1: 'value2',
-    prop2: 'value3',
-    inner: { prop2: 42, prop3: null }
-  }
-}
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg {
-  prop1: 'value1',
-  prop2: {
-    prop1: 'value2',
-    prop2: 'value3',
-    inner: { prop2: 42, prop3: null }
-  }
-}
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg {
-  prop1: 'value1',
-  prop2: {
-    prop1: 'value2',
-    prop2: 'value3',
-    inner: { prop2: 42, prop3: null }
-  }
-}
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg {
-  prop1: 'value1',
-  prop2: {
-    prop1: 'value2',
-    prop2: 'value3',
-    inner: { prop2: 42, prop3: null }
-  }
-}
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg {
-  prop1: 'value1',
-  prop2: {
-    prop1: 'value2',
-    prop2: 'value3',
-    inner: { prop2: 42, prop3: null }
-  }
-}
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg {
-  prop1: 'value1',
-  prop2: {
-    prop1: 'value2',
-    prop2: 'value3',
-    inner: { prop2: 42, prop3: 3.14 }
-  }
-}
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg {
-  prop1: 'value1',
-  prop2: {
-    prop1: 'value2',
-    prop2: 'value3',
-    inner: { prop2: 42, prop3: 3.14 }
-  }
-}
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg {
-  prop1: 'value1',
-  prop2: {
-    prop1: 'value2',
-    prop2: 'value3',
-    inner: { prop2: 42, prop3: 3.14 }
-  }
-}
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg {
-  prop1: 'value1',
-  prop2: {
-    prop1: 'value2',
-    prop2: 'value3',
-    inner: { prop2: 42, prop3: 3.14 }
-  }
-}
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg {
-  prop1: 'value1',
-  prop2: {
-    prop1: 'value2',
-    prop2: 'value3',
-    inner: { prop2: 42, prop3: 3.14 }
-  }
-}
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg {
-  prop1: 'value1',
-  prop2: {
-    prop1: 'value2',
-    prop2: 'value3',
-    inner: { prop2: 42, prop3: 3.14 }
-  }
-}
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg {
-  prop1: 'value1',
-  prop2: {
-    prop1: 'value2',
-    prop2: 'value3',
-    inner: { prop2: 42, prop3: 3.14 }
-  }
-}
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg {
-  prop1: 'value1',
-  prop2: {
-    prop1: 'value2',
-    prop2: 'value3',
-    inner: { prop2: 42, prop3: 3.14 }
-  }
-}
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg {
-  prop1: 'value1',
-  prop2: {
-    prop1: 'value2',
-    prop2: 'value3',
-    inner: { prop2: 42, prop3: 3.14 }
-  }
-}
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg {
-  prop1: 'value1',
-  prop2: {
-    prop1: 'value2',
-    prop2: 'value3',
-    inner: { prop2: 42, prop3: 3.14 }
-  }
-}
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg {
-  prop1: 'value1',
-  prop2: {
-    prop1: 'value2',
-    prop2: 'value3',
-    inner: { prop2: 42, prop3: 3.14 }
-  }
-}
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:580:15)
msg {
-  prop1: 'value1',
-  prop2: {
-    prop1: 'value2',
-    prop2: 'value3',
-    inner: { prop2: 42, prop3: 3.14 }
-  }
-}
    at log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:605:15)
-    at callback (/Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:70:17)
onLogEvent {
-  metadata: {
-    eventId: 'fb9e29fa-78ec-42f1-9030-fa5b1008d31c',
-    rootEventId: 'fb9e29fa-78ec-42f1-9030-fa5b1008d31c'
-  },
-  prompt: '[\n' +
-    '  {\n' +
-    '    "role": "system",\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-11T21:25:25.641Z'
-}
    at log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:605:15)
-    at callback (/Users/vbv/repos/gloo-lang/engine/language_client_typescript/async_context_vars.js:70:17)
onLogEvent {
-  metadata: {
-    eventId: 'a00f3238-afe3-4707-b679-fb6d5fbec4ad',
-    rootEventId: 'a00f3238-afe3-4707-b679-fb6d5fbec4ad'
-  },
-  prompt: '[\n' +
-    '  {\n' +
-    '    "role": "system",\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-11T21:25:26.143Z'
-}
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:639:15)
Error: Error: BamlError: BamlClientError: BamlClientHttpError: LLM call failed: LLMErrorResponse { client: "MyClient", model: None, prompt: Chat([RenderedChatMessage { role: "system", 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: 1731360327, tv_nsec: 999678000 }, latency: 150.208542ms, message: "Request failed: {\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:1485:18)
-    at Object.<anonymous> (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:636:7) {
-  code: 'GenericFailure'
-}
    at log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:647:17)
-    at Object.<anonymous> (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:643:5)
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:537:50)
-    at /Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:645:9
-    at Object.<anonymous> (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:643:5) {
-  prompt: '[\x1B[2mchat\x1B[0m] \x1B[43msystem: \x1B[0mSay "hello there".\n',
-  raw_output: 'Hello there! How can I assist you today?'
-}
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:659:17)
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:537:50)
-    at Object.<anonymous> (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:655:7) {
-  prompt: '[\x1B[2mchat\x1B[0m] \x1B[43msystem: \x1B[0mSay "hello there".\n',
-  raw_output: 'Hello there! How can I help you today?'
-}
    at Object.log (/Users/vbv/repos/gloo-lang/integ-tests/typescript/tests/integ-tests.test.ts:759:13)
{"nbc":{"value":{"foo":1,"bar":"hello"},"checks":{"cross_field":{"name":"cross_field","expression":"this.bar|length > this.foo","status":"succeeded"}}}}
\ No newline at end of file +

Test Report

Started: 2024-11-15 23:21:57
Suites (1)
1 passed
0 failed
0 pending
Tests (65)
1 passed
0 failed
64 pending
Integ tests > should work for all inputs
single bool
pending
0s
Integ tests > should work for all inputs
single string list
pending
0s
Integ tests > should work for all inputs
return literal union
pending
0s
Integ tests > should work for all inputs
single class
pending
0s
Integ tests > should work for all inputs
multiple classes
pending
0s
Integ tests > should work for all inputs
single enum list
pending
0s
Integ tests > should work for all inputs
single float
pending
0s
Integ tests > should work for all inputs
single int
pending
0s
Integ tests > should work for all inputs
single literal int
pending
0s
Integ tests > should work for all inputs
single literal bool
pending
0s
Integ tests > should work for all inputs
single literal string
pending
0s
Integ tests > should work for all inputs
single class with literal prop
pending
0s
Integ tests > should work for all inputs
single class with literal union prop
pending
0s
Integ tests > should work for all inputs
single optional string
pending
0s
Integ tests > should work for all inputs
single map string to string
pending
0s
Integ tests > should work for all inputs
single map string to class
pending
0s
Integ tests > should work for all inputs
single map string to map
pending
0s
Integ tests > should work for all inputs
enum key in map
passed
0.66s
Integ tests
should work for all outputs
pending
0s
Integ tests
works with retries1
pending
0s
Integ tests
works with retries2
pending
0s
Integ tests
works with fallbacks
pending
0s
Integ tests
should work with image from url
pending
0s
Integ tests
should work with image from base 64
pending
0s
Integ tests
should work with audio base 64
pending
0s
Integ tests
should work with audio from url
pending
0s
Integ tests
should support streaming in OpenAI
pending
0s
Integ tests
should support streaming in Gemini
pending
0s
Integ tests
should support AWS
pending
0s
Integ tests
should support streaming in AWS
pending
0s
Integ tests
should allow overriding the region
pending
0s
Integ tests
should support OpenAI shorthand
pending
0s
Integ tests
should support OpenAI shorthand streaming
pending
0s
Integ tests
should support anthropic shorthand
pending
0s
Integ tests
should support anthropic shorthand streaming
pending
0s
Integ tests
should support streaming without iterating
pending
0s
Integ tests
should support streaming in Claude
pending
0s
Integ tests
should support vertex
pending
0s
Integ tests
supports tracing sync
pending
0s
Integ tests
supports tracing async
pending
0s
Integ tests
should work with dynamic types single
pending
0s
Integ tests
should work with dynamic types enum
pending
0s
Integ tests
should work with dynamic literals
pending
0s
Integ tests
should work with dynamic types class
pending
0s
Integ tests
should work with dynamic inputs class
pending
0s
Integ tests
should work with dynamic inputs list
pending
0s
Integ tests
should work with dynamic output map
pending
0s
Integ tests
should work with dynamic output union
pending
0s
Integ tests
should work with nested classes
pending
0s
Integ tests
should work with dynamic client
pending
0s
Integ tests
should work with 'onLogEvent'
pending
0s
Integ tests
should work with a sync client
pending
0s
Integ tests
should raise an error when appropriate
pending
0s
Integ tests
should raise a BAMLValidationError
pending
0s
Integ tests
should reset environment variables correctly
pending
0s
Integ tests
should use aliases when serializing input objects - classes
pending
0s
Integ tests
should use aliases when serializing, but still have original keys in jinja
pending
0s
Integ tests
should use aliases when serializing input objects - enums
pending
0s
Integ tests
should use aliases when serializing input objects - lists
pending
0s
Integ tests
constraints: should handle checks in return types
pending
0s
Integ tests
constraints: should handle checks in returned unions
pending
0s
Integ tests
constraints: should handle block-level checks
pending
0s
Integ tests
constraints: should handle nested-block-level checks
pending
0s
Integ tests
simple recursive type
pending
0s
Integ tests
mutually recursive type
pending
0s
\ 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 c6ae77512..3c703404c 100644 --- a/integ-tests/typescript/tests/integ-tests.test.ts +++ b/integ-tests/typescript/tests/integ-tests.test.ts @@ -14,6 +14,7 @@ import { TestClassNested, onLogEvent, AliasedEnum, + MapKey, } from '../baml_client' import { RecursivePartialNull } from '../baml_client/async_client' import { b as b_sync } from '../baml_client/sync_client' @@ -130,6 +131,12 @@ describe('Integ tests', () => { const res = await b.TestFnNamedArgsSingleMapStringToMap({ lorem: { word: 'ipsum' }, dolor: { word: 'sit' } }) expect(res).toHaveProperty('lorem', { word: 'ipsum' }) }) + + it('enum key in map', async () => { + const res = await b.InOutEnumMapKey({ [MapKey.A]: 'A' }, { [MapKey.B]: 'B' }) + expect(res).toHaveProperty(MapKey.A, 'A') + expect(res).toHaveProperty(MapKey.B, 'B') + }) }) it('should work for all outputs', async () => { @@ -619,7 +626,7 @@ describe('Integ tests', () => { it('should raise an error when appropriate', async () => { await expect(async () => { - await b.TestCaching(111 as unknown as string, "fiction") // intentionally passing an int instead of a string + await b.TestCaching(111 as unknown as string, 'fiction') // intentionally passing an int instead of a string }).rejects.toThrow('BamlInvalidArgumentError') await expect(async () => { @@ -873,6 +880,5 @@ describe('Integ tests', () => { afterAll(async () => { flush() - }); - + }) }) From 738dfddf7481235cf53b2a80cff9f5b3349fecff Mon Sep 17 00:00:00 2001 From: Antonio Sarosi Date: Sat, 16 Nov 2024 22:31:12 +0000 Subject: [PATCH 2/6] Literals as map keys --- .../validation_pipeline/validations/types.rs | 46 +++++++++++-- .../class/map_enums_and_literals.baml | 18 +++++ engine/baml-lib/diagnostics/src/error.rs | 7 ++ .../src/deserializer/coercer/coerce_map.rs | 19 +++++- .../jsonish/src/deserializer/coercer/mod.rs | 5 +- .../src/ruby/field_type.rs | 6 +- .../src/ruby/generate_types.rs | 5 +- .../src/typescript/mod.rs | 7 +- .../output/map-literal-union-key.baml | 15 +++++ .../python/baml_client/async_client.py | 54 +++++++++++++++ integ-tests/python/baml_client/inlinedbaml.py | 1 + integ-tests/python/baml_client/sync_client.py | 54 +++++++++++++++ integ-tests/python/tests/test_functions.py | 6 ++ integ-tests/ruby/baml_client/client.rb | 67 +++++++++++++++++++ integ-tests/ruby/baml_client/inlined.rb | 1 + integ-tests/ruby/test_functions.rb | 4 ++ .../typescript/baml_client/async_client.ts | 58 ++++++++++++++++ .../typescript/baml_client/inlinedbaml.ts | 1 + .../typescript/baml_client/sync_client.ts | 25 +++++++ integ-tests/typescript/test-report.html | 17 +++-- .../typescript/tests/integ-tests.test.ts | 6 ++ .../baml-schema-wasm-node/package-lock.json | 13 ++++ 22 files changed, 412 insertions(+), 23 deletions(-) create mode 100644 integ-tests/baml_src/test-files/functions/output/map-literal-union-key.baml create mode 100644 typescript/baml-schema-wasm-node/package-lock.json diff --git a/engine/baml-lib/baml-core/src/validate/validation_pipeline/validations/types.rs b/engine/baml-lib/baml-core/src/validate/validation_pipeline/validations/types.rs index 093da1e6d..4422fcb41 100644 --- a/engine/baml-lib/baml-core/src/validate/validation_pipeline/validations/types.rs +++ b/engine/baml-lib/baml-core/src/validate/validation_pipeline/validations/types.rs @@ -1,4 +1,6 @@ -use baml_types::TypeValue; +use std::collections::VecDeque; + +use baml_types::{LiteralValue, TypeValue}; use either::Either; use internal_baml_diagnostics::{DatamodelError, DatamodelWarning, Span}; use internal_baml_schema_ast::ast::{ @@ -57,21 +59,53 @@ fn validate_type_allowed(ctx: &mut Context<'_>, field_type: &FieldType) { field_type.span().clone(), )); } + match &kv_types.0 { // String key. FieldType::Primitive(FieldArity::Required, TypeValue::String, ..) => {} // Enum key. - FieldType::Symbol(_, identifier, _) + FieldType::Symbol(FieldArity::Required, identifier, _) if ctx .db .find_type(identifier) .is_some_and(|t| matches!(t, Either::Right(_))) => {} - key_type => { - ctx.push_error(DatamodelError::new_validation_error( - "Maps may only have strings or enums as keys", - key_type.span().clone(), + // Literal string key. + FieldType::Literal(FieldArity::Required, LiteralValue::String(_), ..) => {} + + // Literal string union. + FieldType::Union(FieldArity::Required, items, ..) => { + let mut queue = VecDeque::from_iter(items.iter()); + + while let Some(item) = queue.pop_front() { + match item { + // Ok, literal string. + FieldType::Literal( + FieldArity::Required, + LiteralValue::String(_), + .., + ) => {} + + // Nested union, "recurse" but it's iterative. + FieldType::Union(FieldArity::Required, nested, ..) => { + queue.extend(nested.iter()); + } + + other => { + ctx.push_error( + DatamodelError::new_type_not_allowed_as_map_key_error( + other.span().clone(), + ), + ); + } + } + } + } + + other => { + ctx.push_error(DatamodelError::new_type_not_allowed_as_map_key_error( + other.span().clone(), )); } } diff --git a/engine/baml-lib/baml/tests/validation_files/class/map_enums_and_literals.baml b/engine/baml-lib/baml/tests/validation_files/class/map_enums_and_literals.baml index 61fca1032..137a95a40 100644 --- a/engine/baml-lib/baml/tests/validation_files/class/map_enums_and_literals.baml +++ b/engine/baml-lib/baml/tests/validation_files/class/map_enums_and_literals.baml @@ -6,6 +6,8 @@ enum MapKey { class Fields { e map + l1 map<"literal", string> + l2 map<"one" | "two" | ("three" | "four"), string> } function InOutEnumKey(i1: map, i2: map) -> map { @@ -16,3 +18,19 @@ function InOutEnumKey(i1: map, i2: map) -> map, + i2: map<"one" | "two" | ("three" | "four"), string> +) -> map<"one" | "two" | ("three" | "four"), string> { + client "openai/gpt-4o" + prompt #" + Merge these: + + {{i1}} + + {{i2}} + + {{ ctx.output_format }} + "# +} \ No newline at end of file diff --git a/engine/baml-lib/diagnostics/src/error.rs b/engine/baml-lib/diagnostics/src/error.rs index d4d78d3e6..e1f1386ce 100644 --- a/engine/baml-lib/diagnostics/src/error.rs +++ b/engine/baml-lib/diagnostics/src/error.rs @@ -594,6 +594,13 @@ impl DatamodelError { Self::new(msg, span) } + pub fn new_type_not_allowed_as_map_key_error(span: Span) -> DatamodelError { + Self::new_validation_error( + "Maps may only have strings, enums or literal strings as keys", + span, + ) + } + pub fn span(&self) -> &Span { &self.span } diff --git a/engine/baml-lib/jsonish/src/deserializer/coercer/coerce_map.rs b/engine/baml-lib/jsonish/src/deserializer/coercer/coerce_map.rs index c31bf5f12..bf48169cc 100644 --- a/engine/baml-lib/jsonish/src/deserializer/coercer/coerce_map.rs +++ b/engine/baml-lib/jsonish/src/deserializer/coercer/coerce_map.rs @@ -1,10 +1,12 @@ +use std::collections::VecDeque; + use anyhow::Result; use crate::deserializer::{ deserialize_flags::{DeserializerConditions, Flag}, types::BamlValueWithFlags, }; -use baml_types::{BamlMap, FieldType, TypeValue}; +use baml_types::{BamlMap, FieldType, LiteralValue, TypeValue}; use super::{ParsingContext, ParsingError, TypeCoercer}; @@ -30,9 +32,20 @@ pub(super) fn coerce_map( if !matches!( **key_type, - FieldType::Primitive(TypeValue::String) | FieldType::Enum(_) + FieldType::Primitive(TypeValue::String) | FieldType::Enum(_) | FieldType::Union(_) ) { - return Err(ctx.error_map_must_have_string_key(key_type)); + return Err(ctx.error_map_must_have_supported_key(key_type)); + } + + if let FieldType::Union(items) = &**key_type { + let mut queue = VecDeque::from_iter(items.iter()); + while let Some(item) = queue.pop_front() { + match item { + FieldType::Literal(LiteralValue::String(_)) => continue, + FieldType::Union(nested) => queue.extend(nested.iter()), + other => return Err(ctx.error_map_must_have_supported_key(other)), + } + } } let mut flags = DeserializerConditions::new(); diff --git a/engine/baml-lib/jsonish/src/deserializer/coercer/mod.rs b/engine/baml-lib/jsonish/src/deserializer/coercer/mod.rs index dbc01ab2e..a4ed76707 100644 --- a/engine/baml-lib/jsonish/src/deserializer/coercer/mod.rs +++ b/engine/baml-lib/jsonish/src/deserializer/coercer/mod.rs @@ -139,11 +139,10 @@ impl ParsingContext<'_> { } } - pub(crate) fn error_map_must_have_string_key(&self, key_type: &FieldType) -> ParsingError { + pub(crate) fn error_map_must_have_supported_key(&self, key_type: &FieldType) -> ParsingError { ParsingError { reason: format!( - "Maps may only have strings or enums for keys, but got {}", - key_type + "Maps may only have strings, enums or literal strings for keys, but got {key_type}" ), scope: self.scope.clone(), causes: vec![], diff --git a/engine/language_client_codegen/src/ruby/field_type.rs b/engine/language_client_codegen/src/ruby/field_type.rs index 751dd80e2..cb2115910 100644 --- a/engine/language_client_codegen/src/ruby/field_type.rs +++ b/engine/language_client_codegen/src/ruby/field_type.rs @@ -1,4 +1,4 @@ -use baml_types::{BamlMediaType, FieldType, TypeValue}; +use baml_types::{BamlMediaType, FieldType, LiteralValue, TypeValue}; use crate::field_type_attributes; @@ -17,7 +17,9 @@ impl ToRuby for FieldType { "T::Hash[{}, {}]", match key.as_ref() { // For enums just default to strings. - FieldType::Enum(_) => FieldType::string().to_ruby(), + FieldType::Enum(_) + | FieldType::Literal(LiteralValue::String(_)) + | FieldType::Union(_) => FieldType::string().to_ruby(), _ => key.to_ruby(), }, value.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 035bc62e7..55419ba0f 100644 --- a/engine/language_client_codegen/src/ruby/generate_types.rs +++ b/engine/language_client_codegen/src/ruby/generate_types.rs @@ -2,6 +2,7 @@ use std::borrow::Cow; use std::collections::HashSet; use anyhow::Result; +use baml_types::LiteralValue; use itertools::Itertools; use crate::{field_type_attributes, type_check_attributes, TypeCheckAttributes}; @@ -175,7 +176,9 @@ impl ToTypeReferenceInTypeDefinition for FieldType { "T::Hash[{}, {}]", match key.as_ref() { // For enums just default to strings. - FieldType::Enum(_) => FieldType::string().to_type_ref(), + FieldType::Enum(_) + | FieldType::Literal(LiteralValue::String(_)) + | FieldType::Union(_) => FieldType::string().to_type_ref(), _ => key.to_type_ref(), }, value.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 4a48d3822..a17145830 100644 --- a/engine/language_client_codegen/src/typescript/mod.rs +++ b/engine/language_client_codegen/src/typescript/mod.rs @@ -4,6 +4,7 @@ mod typescript_language_features; use std::path::PathBuf; use anyhow::Result; +use baml_types::LiteralValue; use generate_types::type_name_for_checks; use indexmap::IndexMap; use internal_baml_core::{ @@ -277,7 +278,11 @@ impl ToTypeReferenceInClientDefinition for FieldType { let v = value.to_type_ref(ir); match key.as_ref() { - FieldType::Enum(_) => format!("Partial>"), + FieldType::Enum(_) + | FieldType::Union(_) + | FieldType::Literal(LiteralValue::String(_)) => { + format!("Partial>") + } _ => format!("Record<{k}, {v}>"), } } diff --git a/integ-tests/baml_src/test-files/functions/output/map-literal-union-key.baml b/integ-tests/baml_src/test-files/functions/output/map-literal-union-key.baml new file mode 100644 index 000000000..e5a3ec3a1 --- /dev/null +++ b/integ-tests/baml_src/test-files/functions/output/map-literal-union-key.baml @@ -0,0 +1,15 @@ +function InOutLiteralMapKey( + i1: map<"one" | "two" | ("three" | "four"), string>, + i2: map<"one" | "two" | ("three" | "four"), string> +) -> map<"one" | "two" | ("three" | "four"), string> { + client "openai/gpt-4o" + prompt #" + Merge these: + + {{i1}} + + {{i2}} + + {{ 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 312eac098..f2a6bc094 100644 --- a/integ-tests/python/baml_client/async_client.py +++ b/integ-tests/python/baml_client/async_client.py @@ -1315,6 +1315,29 @@ async def InOutEnumMapKey( ) return cast(Dict[types.MapKey, str], raw.cast_to(types, types)) + async def InOutLiteralMapKey( + self, + i1: Dict[Union[Literal["one"], Literal["two"], Union[Literal["three"], Literal["four"]]], str],i2: Dict[Union[Literal["one"], Literal["two"], Union[Literal["three"], Literal["four"]]], str], + baml_options: BamlCallOptions = {}, + ) -> Dict[Union[Literal["one"], Literal["two"], Union[Literal["three"], Literal["four"]]], 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( + "InOutLiteralMapKey", + { + "i1": i1,"i2": i2, + }, + self.__ctx_manager.get(), + tb, + __cr__, + ) + return cast(Dict[Union[Literal["one"], Literal["two"], Union[Literal["three"], Literal["four"]]], str], raw.cast_to(types, types)) + async def LiteralUnionsTest( self, input: str, @@ -4360,6 +4383,37 @@ def InOutEnumMapKey( self.__ctx_manager.get(), ) + def InOutLiteralMapKey( + self, + i1: Dict[Union[Literal["one"], Literal["two"], Union[Literal["three"], Literal["four"]]], str],i2: Dict[Union[Literal["one"], Literal["two"], Union[Literal["three"], Literal["four"]]], str], + baml_options: BamlCallOptions = {}, + ) -> baml_py.BamlStream[Dict[Union[Literal["one"], Literal["two"], Union[Literal["three"], Literal["four"]]], Optional[str]], Dict[Union[Literal["one"], Literal["two"], Union[Literal["three"], Literal["four"]]], 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( + "InOutLiteralMapKey", + { + "i1": i1, + "i2": i2, + }, + None, + self.__ctx_manager.get(), + tb, + __cr__, + ) + + return baml_py.BamlStream[Dict[Union[Literal["one"], Literal["two"], Union[Literal["three"], Literal["four"]]], Optional[str]], Dict[Union[Literal["one"], Literal["two"], Union[Literal["three"], Literal["four"]]], str]]( + raw, + lambda x: cast(Dict[Union[Literal["one"], Literal["two"], Union[Literal["three"], Literal["four"]]], Optional[str]], x.cast_to(types, partial_types)), + lambda x: cast(Dict[Union[Literal["one"], Literal["two"], Union[Literal["three"], Literal["four"]]], str], x.cast_to(types, types)), + self.__ctx_manager.get(), + ) + def LiteralUnionsTest( self, input: str, diff --git a/integ-tests/python/baml_client/inlinedbaml.py b/integ-tests/python/baml_client/inlinedbaml.py index 2df9e10a2..abf91ab29 100644 --- a/integ-tests/python/baml_client/inlinedbaml.py +++ b/integ-tests/python/baml_client/inlinedbaml.py @@ -75,6 +75,7 @@ "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/map-enum-key.baml": "enum MapKey {\n A\n B\n C\n}\n\nfunction InOutEnumMapKey(i1: map, i2: map) -> map {\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 InOutLiteralMapKey(\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}", "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}", "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", diff --git a/integ-tests/python/baml_client/sync_client.py b/integ-tests/python/baml_client/sync_client.py index 41627662e..a0b0d6c66 100644 --- a/integ-tests/python/baml_client/sync_client.py +++ b/integ-tests/python/baml_client/sync_client.py @@ -1312,6 +1312,29 @@ def InOutEnumMapKey( ) return cast(Dict[types.MapKey, str], raw.cast_to(types, types)) + def InOutLiteralMapKey( + self, + i1: Dict[Union[Literal["one"], Literal["two"], Union[Literal["three"], Literal["four"]]], str],i2: Dict[Union[Literal["one"], Literal["two"], Union[Literal["three"], Literal["four"]]], str], + baml_options: BamlCallOptions = {}, + ) -> Dict[Union[Literal["one"], Literal["two"], Union[Literal["three"], Literal["four"]]], 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( + "InOutLiteralMapKey", + { + "i1": i1,"i2": i2, + }, + self.__ctx_manager.get(), + tb, + __cr__, + ) + return cast(Dict[Union[Literal["one"], Literal["two"], Union[Literal["three"], Literal["four"]]], str], raw.cast_to(types, types)) + def LiteralUnionsTest( self, input: str, @@ -4358,6 +4381,37 @@ def InOutEnumMapKey( self.__ctx_manager.get(), ) + def InOutLiteralMapKey( + self, + i1: Dict[Union[Literal["one"], Literal["two"], Union[Literal["three"], Literal["four"]]], str],i2: Dict[Union[Literal["one"], Literal["two"], Union[Literal["three"], Literal["four"]]], str], + baml_options: BamlCallOptions = {}, + ) -> baml_py.BamlSyncStream[Dict[Union[Literal["one"], Literal["two"], Union[Literal["three"], Literal["four"]]], Optional[str]], Dict[Union[Literal["one"], Literal["two"], Union[Literal["three"], Literal["four"]]], 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( + "InOutLiteralMapKey", + { + "i1": i1, + "i2": i2, + }, + None, + self.__ctx_manager.get(), + tb, + __cr__, + ) + + return baml_py.BamlSyncStream[Dict[Union[Literal["one"], Literal["two"], Union[Literal["three"], Literal["four"]]], Optional[str]], Dict[Union[Literal["one"], Literal["two"], Union[Literal["three"], Literal["four"]]], str]]( + raw, + lambda x: cast(Dict[Union[Literal["one"], Literal["two"], Union[Literal["three"], Literal["four"]]], Optional[str]], x.cast_to(types, partial_types)), + lambda x: cast(Dict[Union[Literal["one"], Literal["two"], Union[Literal["three"], Literal["four"]]], str], x.cast_to(types, types)), + self.__ctx_manager.get(), + ) + def LiteralUnionsTest( self, input: str, diff --git a/integ-tests/python/tests/test_functions.py b/integ-tests/python/tests/test_functions.py index effa01d9d..daf78f8a3 100644 --- a/integ-tests/python/tests/test_functions.py +++ b/integ-tests/python/tests/test_functions.py @@ -237,6 +237,12 @@ async def test_enum_key_in_map(self): assert res[MapKey.A] == "A" assert res[MapKey.B] == "B" + @pytest.mark.asyncio + async def test_literal_key_in_map(self): + res = await b.InOutLiteralMapKey({"one": "1"}, {"two": "2"}) + assert res["one"] == "1" + assert res["two"] == "2" + 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 b1eb349ca..5203bb8b5 100644 --- a/integ-tests/ruby/baml_client/client.rb +++ b/integ-tests/ruby/baml_client/client.rb @@ -1810,6 +1810,38 @@ def InOutEnumMapKey( (raw.parsed_using_types(Baml::Types)) end + sig { + params( + varargs: T.untyped, + i1: T::Hash[String, String],i2: T::Hash[String, String], + baml_options: T::Hash[Symbol, T.any(Baml::TypeBuilder, Baml::ClientRegistry)] + ).returns(T::Hash[String, String]) + } + def InOutLiteralMapKey( + *varargs, + i1:,i2:, + baml_options: {} + ) + if varargs.any? + + raise ArgumentError.new("InOutLiteralMapKey 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( + "InOutLiteralMapKey", + { + i1: i1,i2: i2, + }, + @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, @@ -5668,6 +5700,41 @@ def InOutEnumMapKey( ) end + sig { + params( + varargs: T.untyped, + i1: T::Hash[String, String],i2: T::Hash[String, String], + baml_options: T::Hash[Symbol, T.any(Baml::TypeBuilder, Baml::ClientRegistry)] + ).returns(Baml::BamlStream[T::Hash[String, String]]) + } + def InOutLiteralMapKey( + *varargs, + i1:,i2:, + baml_options: {} + ) + if varargs.any? + + raise ArgumentError.new("InOutLiteralMapKey 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( + "InOutLiteralMapKey", + { + i1: i1,i2: i2, + }, + @ctx_manager, + baml_options[:tb]&.instance_variable_get(:@registry), + baml_options[:client_registry], + ) + Baml::BamlStream[T::Hash[String, T.nilable(String)], T::Hash[String, String]].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 4c087f2a0..86750fbda 100644 --- a/integ-tests/ruby/baml_client/inlined.rb +++ b/integ-tests/ruby/baml_client/inlined.rb @@ -75,6 +75,7 @@ module Inlined "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/map-enum-key.baml" => "enum MapKey {\n A\n B\n C\n}\n\nfunction InOutEnumMapKey(i1: map, i2: map) -> map {\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 InOutLiteralMapKey(\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}", "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}", "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", diff --git a/integ-tests/ruby/test_functions.rb b/integ-tests/ruby/test_functions.rb index 484295780..d987038d5 100644 --- a/integ-tests/ruby/test_functions.rb +++ b/integ-tests/ruby/test_functions.rb @@ -71,6 +71,10 @@ res = b.InOutEnumMapKey(i1: {"A" => "A"}, i2: {"B" => "B"}) assert_equal res['A'], "A" assert_equal res['B'], "B" + + res = b.InOutLiteralMapKey(i1: {"one" => "1"}, i2: {"two" => "2"}) + assert_equal res['one'], "1" + assert_equal res['two'], "2" 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 2d1fec9c3..72af00c56 100644 --- a/integ-tests/typescript/baml_client/async_client.ts +++ b/integ-tests/typescript/baml_client/async_client.ts @@ -1418,6 +1418,31 @@ export class BamlAsyncClient { } } + async InOutLiteralMapKey( + i1: Partial>,i2: Partial>, + __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } + ): Promise>> { + try { + const raw = await this.runtime.callFunction( + "InOutLiteralMapKey", + { + "i1": i1,"i2": i2 + }, + this.ctx_manager.cloneContext(), + __baml_options__?.tb?.__tb(), + __baml_options__?.clientRegistry, + ) + return raw.parsed() as Partial> + } 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 } @@ -4739,6 +4764,39 @@ class BamlStreamClient { } } + InOutLiteralMapKey( + i1: Partial>,i2: Partial>, + __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } + ): BamlStream>>, Partial>> { + try { + const raw = this.runtime.streamFunction( + "InOutLiteralMapKey", + { + "i1": i1,"i2": i2 + }, + undefined, + this.ctx_manager.cloneContext(), + __baml_options__?.tb?.__tb(), + __baml_options__?.clientRegistry, + ) + return new BamlStream>>, Partial>>( + raw, + (a): a is RecursivePartialNull>> => a, + (a): a is Partial> => 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 } diff --git a/integ-tests/typescript/baml_client/inlinedbaml.ts b/integ-tests/typescript/baml_client/inlinedbaml.ts index 6d4e15fd5..4b2cb1a14 100644 --- a/integ-tests/typescript/baml_client/inlinedbaml.ts +++ b/integ-tests/typescript/baml_client/inlinedbaml.ts @@ -76,6 +76,7 @@ const fileMap = { "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/map-enum-key.baml": "enum MapKey {\n A\n B\n C\n}\n\nfunction InOutEnumMapKey(i1: map, i2: map) -> map {\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 InOutLiteralMapKey(\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}", "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}", "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", diff --git a/integ-tests/typescript/baml_client/sync_client.ts b/integ-tests/typescript/baml_client/sync_client.ts index e8056fd23..96f8faa23 100644 --- a/integ-tests/typescript/baml_client/sync_client.ts +++ b/integ-tests/typescript/baml_client/sync_client.ts @@ -1418,6 +1418,31 @@ export class BamlSyncClient { } } + InOutLiteralMapKey( + i1: Partial>,i2: Partial>, + __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } + ): Partial> { + try { + const raw = this.runtime.callFunctionSync( + "InOutLiteralMapKey", + { + "i1": i1,"i2": i2 + }, + this.ctx_manager.cloneContext(), + __baml_options__?.tb?.__tb(), + __baml_options__?.clientRegistry, + ) + return raw.parsed() as Partial> + } 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 } diff --git a/integ-tests/typescript/test-report.html b/integ-tests/typescript/test-report.html index 424e1dc45..887f5fa71 100644 --- a/integ-tests/typescript/test-report.html +++ b/integ-tests/typescript/test-report.html @@ -257,13 +257,16 @@ font-size: 1rem; padding: 0 0.5rem; } -

Test Report

Started: 2024-11-16 17:28:07
Suites (1)
0 passed
1 failed
0 pending
Tests (65)
63 passed
2 failed
0 pending
Integ tests > should work for all inputs
single bool
passed
0.48s
Integ tests > should work for all inputs
single string list
passed
0.507s
Integ tests > should work for all inputs
return literal union
failed
0.474s
BamlValidationError: BamlValidationError: Failed to parse LLM response: Failed to coerce value: <root>: Failed to find any (1 | true | "string output") in 3 items
-  - <root>: Expected 1, got Object([("result", String("true"))]).
-  - <root>: Expected true, got Object([("result", String("true"))]).
-  - <root>: Expected "string output", got Object([("result", String("true"))]).
+

Test Report

Started: 2024-11-16 22:07:51
Suites (1)
0 passed
1 failed
0 pending
Tests (66)
64 passed
2 failed
0 pending
Integ tests > should work for all inputs
single bool
passed
0.541s
Integ tests > should work for all inputs
single string list
passed
0.504s
Integ tests > should work for all inputs
return literal union
failed
0.475s
BamlValidationError: BamlValidationError: Failed to parse LLM response: Failed to coerce value: <root>: Failed to find any (1 | true | "string output") in 3 items
+  - <root>: Expected 1, got Object([("Answer", String("true"))]).
+  - <root>: Expected true, got Object([("Answer", String("true"))]).
+  - <root>: Expected "string output", got Object([("Answer", String("true"))]).
     at Function.from (/workspaces/baml/engine/language_client_typescript/index.js:33:28)
     at from (/workspaces/baml/engine/language_client_typescript/index.js:58:32)
-    at BamlAsyncClient.LiteralUnionsTest (/workspaces/baml/integ-tests/typescript/baml_client/async_client.ts:1437:50)
-    at Object.<anonymous> (/workspaces/baml/integ-tests/typescript/tests/integ-tests.test.ts:43:19)
Integ tests > should work for all inputs
single class
passed
0.43s
Integ tests > should work for all inputs
multiple classes
passed
0.532s
Integ tests > should work for all inputs
single enum list
passed
0.398s
Integ tests > should work for all inputs
single float
passed
0.457s
Integ tests > should work for all inputs
single int
passed
0.512s
Integ tests > should work for all inputs
single literal int
passed
0.349s
Integ tests > should work for all inputs
single literal bool
passed
0.447s
Integ tests > should work for all inputs
single literal string
passed
0.379s
Integ tests > should work for all inputs
single class with literal prop
passed
0.589s
Integ tests > should work for all inputs
single class with literal union prop
passed
0.573s
Integ tests > should work for all inputs
single optional string
passed
0.425s
Integ tests > should work for all inputs
single map string to string
passed
0.51s
Integ tests > should work for all inputs
single map string to class
passed
0.719s
Integ tests > should work for all inputs
single map string to map
passed
0.55s
Integ tests > should work for all inputs
enum key in map
passed
0.529s
Integ tests
should work for all outputs
passed
5.297s
Integ tests
works with retries1
passed
1.316s
Integ tests
works with retries2
passed
2.423s
Integ tests
works with fallbacks
passed
1.942s
Integ tests
should work with image from url
passed
1.481s
Integ tests
should work with image from base 64
passed
1.516s
Integ tests
should work with audio base 64
passed
1.636s
Integ tests
should work with audio from url
passed
1.636s
Integ tests
should support streaming in OpenAI
passed
2.636s
Integ tests
should support streaming in Gemini
passed
9.129s
Integ tests
should support AWS
passed
1.752s
Integ tests
should support streaming in AWS
passed
1.517s
Integ tests
should allow overriding the region
passed
0.028s
Integ tests
should support OpenAI shorthand
passed
5.348s
Integ tests
should support OpenAI shorthand streaming
passed
7.156s
Integ tests
should support anthropic shorthand
passed
3.354s
Integ tests
should support anthropic shorthand streaming
passed
2.939s
Integ tests
should support streaming without iterating
passed
2.922s
Integ tests
should support streaming in Claude
passed
1.322s
Integ tests
should support vertex
passed
12.07s
Integ tests
supports tracing sync
passed
0.023s
Integ tests
supports tracing async
passed
2.647s
Integ tests
should work with dynamic types single
passed
0.811s
Integ tests
should work with dynamic types enum
passed
1.227s
Integ tests
should work with dynamic literals
passed
1.023s
Integ tests
should work with dynamic types class
passed
0.817s
Integ tests
should work with dynamic inputs class
passed
0.615s
Integ tests
should work with dynamic inputs list
passed
0.554s
Integ tests
should work with dynamic output map
passed
0.982s
Integ tests
should work with dynamic output union
passed
2.561s
Integ tests
should work with nested classes
failed
0.106s
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 BamlAsyncClient.LiteralUnionsTest (/workspaces/baml/integ-tests/typescript/baml_client/async_client.ts:1462:50)
+    at Object.<anonymous> (/workspaces/baml/integ-tests/typescript/tests/integ-tests.test.ts:43:19)
Integ tests > should work for all inputs
single class
passed
0.461s
Integ tests > should work for all inputs
multiple classes
passed
0.492s
Integ tests > should work for all inputs
single enum list
passed
0.529s
Integ tests > should work for all inputs
single float
passed
0.408s
Integ tests > should work for all inputs
single int
passed
0.408s
Integ tests > should work for all inputs
single literal int
passed
0.41s
Integ tests > should work for all inputs
single literal bool
passed
0.359s
Integ tests > should work for all inputs
single literal string
passed
0.708s
Integ tests > should work for all inputs
single class with literal prop
passed
0.51s
Integ tests > should work for all inputs
single class with literal union prop
passed
0.574s
Integ tests > should work for all inputs
single optional string
passed
0.407s
Integ tests > should work for all inputs
single map string to string
passed
0.495s
Integ tests > should work for all inputs
single map string to class
passed
0.732s
Integ tests > should work for all inputs
single map string to map
passed
1.025s
Integ tests > should work for all inputs
enum key in map
passed
0.512s
Integ tests > should work for all inputs
literal key in map
passed
0.616s
Integ tests
should work for all outputs
passed
5.008s
Integ tests
works with retries1
passed
1.172s
Integ tests
works with retries2
passed
2.417s
Integ tests
works with fallbacks
passed
2.354s
Integ tests
should work with image from url
passed
1.329s
Integ tests
should work with image from base 64
passed
1.24s
Integ tests
should work with audio base 64
passed
1.527s
Integ tests
should work with audio from url
passed
1.635s
Integ tests
should support streaming in OpenAI
passed
3.366s
Integ tests
should support streaming in Gemini
passed
9.635s
Integ tests
should support AWS
passed
1.641s
Integ tests
should support streaming in AWS
passed
1.63s
Integ tests
should allow overriding the region
passed
0.026s
Integ tests
should support OpenAI shorthand
passed
6.843s
Integ tests
should support OpenAI shorthand streaming
passed
6.677s
Integ tests
should support anthropic shorthand
passed
2.748s
Integ tests
should support anthropic shorthand streaming
passed
2.967s
Integ tests
should support streaming without iterating
passed
2.242s
Integ tests
should support streaming in Claude
passed
1.43s
Integ tests
should support vertex
passed
9.94s
Integ tests
supports tracing sync
passed
0.03s
Integ tests
supports tracing async
passed
3.634s
Integ tests
should work with dynamic types single
passed
1.048s
Integ tests
should work with dynamic types enum
passed
0.817s
Integ tests
should work with dynamic literals
passed
0.715s
Integ tests
should work with dynamic types class
passed
1.23s
Integ tests
should work with dynamic inputs class
passed
0.709s
Integ tests
should work with dynamic inputs list
passed
0.619s
Integ tests
should work with dynamic output map
passed
0.672s
Integ tests
should work with dynamic output union
passed
2.189s
Integ tests
should work with nested classes
failed
0.122s
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:591:19)
Integ tests
should work with dynamic client
passed
0.512s
Integ tests
should work with 'onLogEvent'
passed
2.11s
Integ tests
should work with a sync client
passed
0.536s
Integ tests
should raise an error when appropriate
passed
1.025s
Integ tests
should raise a BAMLValidationError
passed
0.511s
Integ tests
should reset environment variables correctly
passed
1.33s
Integ tests
should use aliases when serializing input objects - classes
passed
1.022s
Integ tests
should use aliases when serializing, but still have original keys in jinja
passed
0.801s
Integ tests
should use aliases when serializing input objects - enums
passed
0.394s
Integ tests
should use aliases when serializing input objects - lists
passed
0.442s
Integ tests
constraints: should handle checks in return types
passed
0.719s
Integ tests
constraints: should handle checks in returned unions
passed
0.787s
Integ tests
constraints: should handle block-level checks
passed
0.544s
Integ tests
constraints: should handle nested-block-level checks
passed
0.714s
Integ tests
simple recursive type
passed
2.975s
Integ tests
mutually recursive type
passed
2.971s
\ No newline at end of file + at runNextTicks (node:internal/process/task_queues:60:5) + at listOnTimeout (node:internal/timers:540:9) + at processTimers (node:internal/timers:514:7) + at Object.<anonymous> (/workspaces/baml/integ-tests/typescript/tests/integ-tests.test.ts:597:19)
Integ tests
should work with dynamic client
passed
0.477s
Integ tests
should work with 'onLogEvent'
passed
1.668s
Integ tests
should work with a sync client
passed
0.594s
Integ tests
should raise an error when appropriate
passed
1.027s
Integ tests
should raise a BAMLValidationError
passed
0.61s
Integ tests
should reset environment variables correctly
passed
2.15s
Integ tests
should use aliases when serializing input objects - classes
passed
0.881s
Integ tests
should use aliases when serializing, but still have original keys in jinja
passed
0.859s
Integ tests
should use aliases when serializing input objects - enums
passed
0.715s
Integ tests
should use aliases when serializing input objects - lists
passed
0.512s
Integ tests
constraints: should handle checks in return types
passed
0.65s
Integ tests
constraints: should handle checks in returned unions
passed
0.905s
Integ tests
constraints: should handle block-level checks
passed
0.57s
Integ tests
constraints: should handle nested-block-level checks
passed
0.744s
Integ tests
simple recursive type
passed
2.458s
Integ tests
mutually recursive type
passed
1.885s
\ 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 3c703404c..12754fe9f 100644 --- a/integ-tests/typescript/tests/integ-tests.test.ts +++ b/integ-tests/typescript/tests/integ-tests.test.ts @@ -137,6 +137,12 @@ describe('Integ tests', () => { expect(res).toHaveProperty(MapKey.A, 'A') expect(res).toHaveProperty(MapKey.B, 'B') }) + + it('literal key in map', async () => { + const res = await b.InOutLiteralMapKey({ one: '1' }, { two: '2' }) + expect(res).toHaveProperty('one', '1') + expect(res).toHaveProperty('two', '2') + }) }) it('should work for all outputs', async () => { diff --git a/typescript/baml-schema-wasm-node/package-lock.json b/typescript/baml-schema-wasm-node/package-lock.json new file mode 100644 index 000000000..8f5c98437 --- /dev/null +++ b/typescript/baml-schema-wasm-node/package-lock.json @@ -0,0 +1,13 @@ +{ + "name": "@gloo-ai/baml-schema-wasm-node", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@gloo-ai/baml-schema-wasm-node", + "version": "0.1.0", + "license": "Apache-2.0" + } + } +} From 12dab2782237b3bc7cd36fcff4b5f8d04c6b1bf4 Mon Sep 17 00:00:00 2001 From: Antonio Sarosi Date: Sat, 16 Nov 2024 22:37:29 +0000 Subject: [PATCH 3/6] Update validation file --- .../validation_files/class/map_types.baml | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/engine/baml-lib/baml/tests/validation_files/class/map_types.baml b/engine/baml-lib/baml/tests/validation_files/class/map_types.baml index 038ed66d0..beb856788 100644 --- a/engine/baml-lib/baml/tests/validation_files/class/map_types.baml +++ b/engine/baml-lib/baml/tests/validation_files/class/map_types.baml @@ -31,49 +31,55 @@ function InputAndOutput(i1: map, i2: map) -> m "# } -// error: Error validating: Maps may only have strings or enums as keys +// error: Error validating: Maps may only have strings, enums or literal strings as keys // --> class/map_types.baml:16 // | // 15 | // 16 | b1 map // | -// error: Error validating: Maps may only have strings or enums as keys +// error: Error validating: Maps may only have strings, enums or literal strings as keys // --> class/map_types.baml:17 // | // 16 | b1 map // 17 | b2 map // | -// error: Error validating: Maps may only have strings or enums as keys +// error: Error validating: Maps may only have strings, enums or literal strings as keys // --> class/map_types.baml:18 // | // 17 | b2 map // 18 | b3 map // | -// error: Error validating: Maps may only have strings or enums as keys +// error: Error validating: Maps may only have strings, enums or literal strings as keys // --> class/map_types.baml:19 // | // 18 | b3 map // 19 | b4 map // | -// error: Error validating: Maps may only have strings or enums as keys +// error: Error validating: Maps may only have strings, enums or literal strings as keys // --> class/map_types.baml:20 // | // 19 | b4 map // 20 | b5 map // | -// error: Error validating: Maps may only have strings or enums as keys +// error: Error validating: Maps may only have strings, enums or literal strings as keys +// --> class/map_types.baml:20 +// | +// 19 | b4 map +// 20 | b5 map +// | +// error: Error validating: Maps may only have strings, enums or literal strings as keys // --> class/map_types.baml:23 // | // 22 | c1 string | map // 23 | c2 string | map // | -// error: Error validating: Maps may only have strings or enums as keys +// error: Error validating: Maps may only have strings, enums or literal strings as keys // --> class/map_types.baml:24 // | // 23 | c2 string | map // 24 | c3 string | map // | -// error: Error validating: Maps may only have strings or enums as keys +// error: Error validating: Maps may only have strings, enums or literal strings as keys // --> class/map_types.baml:27 // | // 26 | From d44b23d5017c75b7b105ecd174eb69c156459869 Mon Sep 17 00:00:00 2001 From: Antonio Sarosi Date: Sat, 16 Nov 2024 22:56:30 +0000 Subject: [PATCH 4/6] Refactor `coerce_map` type checking --- .../src/deserializer/coercer/coerce_map.rs | 30 +++++++++++-------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/engine/baml-lib/jsonish/src/deserializer/coercer/coerce_map.rs b/engine/baml-lib/jsonish/src/deserializer/coercer/coerce_map.rs index bf48169cc..dd6290479 100644 --- a/engine/baml-lib/jsonish/src/deserializer/coercer/coerce_map.rs +++ b/engine/baml-lib/jsonish/src/deserializer/coercer/coerce_map.rs @@ -30,22 +30,26 @@ pub(super) fn coerce_map( return Err(ctx.error_unexpected_type(map_target, value)); }; - if !matches!( - **key_type, - FieldType::Primitive(TypeValue::String) | FieldType::Enum(_) | FieldType::Union(_) - ) { - return Err(ctx.error_map_must_have_supported_key(key_type)); - } + match key_type.as_ref() { + // String, enum or just one literal string, OK. + FieldType::Primitive(TypeValue::String) + | FieldType::Enum(_) + | FieldType::Literal(LiteralValue::String(_)) => {} - if let FieldType::Union(items) = &**key_type { - let mut queue = VecDeque::from_iter(items.iter()); - while let Some(item) = queue.pop_front() { - match item { - FieldType::Literal(LiteralValue::String(_)) => continue, - FieldType::Union(nested) => queue.extend(nested.iter()), - other => return Err(ctx.error_map_must_have_supported_key(other)), + // For unions we need to check if all the items are literal strings. + FieldType::Union(items) => { + let mut queue = VecDeque::from_iter(items.iter()); + while let Some(item) = queue.pop_front() { + match item { + FieldType::Literal(LiteralValue::String(_)) => continue, + FieldType::Union(nested) => queue.extend(nested.iter()), + other => return Err(ctx.error_map_must_have_supported_key(other)), + } } } + + // Key type not allowed. + other => return Err(ctx.error_map_must_have_supported_key(other)), } let mut flags = DeserializerConditions::new(); From fc4ed3746c1a971fbdcbce5da19aad59413651e4 Mon Sep 17 00:00:00 2001 From: Antonio Sarosi Date: Mon, 18 Nov 2024 01:34:13 +0000 Subject: [PATCH 5/6] Progress on implementing `map` --- .../src/ir/ir_helpers/to_baml_arg.rs | 57 ++++++++++++- .../validation_pipeline/validations/types.rs | 35 +++++++- engine/baml-lib/baml-types/src/baml_value.rs | 71 +++++++++-------- .../class/map_enum_and_literal_keys.baml | 23 ++++++ .../enums_and_literals_as_map_keys.baml} | 28 ++++--- engine/baml-lib/diagnostics/src/error.rs | 2 +- .../src/deserializer/coercer/coerce_map.rs | 25 +++++- .../jsonish/src/deserializer/coercer/mod.rs | 15 +++- .../src/parse_py_type.rs | 4 + .../output/map-literal-union-key.baml | 20 ++++- .../python/baml_client/async_client.py | 62 ++++++++++++++- integ-tests/python/baml_client/inlinedbaml.py | 2 +- integ-tests/python/baml_client/sync_client.py | 62 ++++++++++++++- integ-tests/python/tests/test_functions.py | 10 ++- integ-tests/ruby/baml_client/client.rb | 79 +++++++++++++++++-- integ-tests/ruby/baml_client/inlined.rb | 2 +- .../typescript/baml_client/async_client.ts | 66 +++++++++++++++- .../typescript/baml_client/inlinedbaml.ts | 2 +- .../typescript/baml_client/sync_client.ts | 29 ++++++- integ-tests/typescript/test-report.html | 16 +--- .../typescript/tests/integ-tests.test.ts | 10 ++- 21 files changed, 523 insertions(+), 97 deletions(-) create mode 100644 engine/baml-lib/baml/tests/validation_files/class/map_enum_and_literal_keys.baml rename engine/baml-lib/baml/tests/validation_files/{class/map_enums_and_literals.baml => functions_v2/enums_and_literals_as_map_keys.baml} (51%) 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..02fcd3d9a 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 @@ -3,7 +3,7 @@ use baml_types::{ TypeValue, }; use core::result::Result; -use std::path::PathBuf; +use std::{collections::VecDeque, path::PathBuf}; use crate::ir::IntermediateRepr; @@ -285,12 +285,53 @@ impl ArgCoercer { (FieldType::Map(k, v), _) => { if let BamlValue::Map(kv) = value { let mut map = BamlMap::new(); + let mut failed_parsing_int_err = None; + + let mut is_union_of_literal_ints = false; + + // TODO: Can we avoid this loop here? Won't hit performance + // by a lot unless the user defines a giant union. + if let FieldType::Union(items) = k.as_ref() { + let mut found_types_other_than_literal_ints = false; + let mut queue = VecDeque::from_iter(items.iter()); + while let Some(item) = queue.pop_front() { + match item { + FieldType::Literal(LiteralValue::Int(_)) => continue, + FieldType::Union(nested) => queue.extend(nested.iter()), + _ => { + found_types_other_than_literal_ints = true; + break; + } + } + } + if !found_types_other_than_literal_ints { + is_union_of_literal_ints = true; + } + } + for (key, value) in kv { scope.push("".to_string()); - let k = self.coerce_arg(ir, k, &BamlValue::String(key.clone()), scope); + + let target_baml_key = if matches!(**k, FieldType::Primitive(TypeValue::Int)) + || is_union_of_literal_ints + { + match key.parse::() { + Ok(i) => BamlValue::Int(i), + Err(e) => { + failed_parsing_int_err = Some(key); + // Stop here and let the code below return + // the error. + break; + } + } + } else { + BamlValue::String(key.clone()) + }; + + let coerced_key = self.coerce_arg(ir, k, &target_baml_key, scope); scope.pop(false); - if k.is_ok() { + if coerced_key.is_ok() { scope.push(key.to_string()); if let Ok(v) = self.coerce_arg(ir, v, value, scope) { map.insert(key.clone(), v); @@ -298,7 +339,15 @@ impl ArgCoercer { scope.pop(false); } } - Ok(BamlValue::Map(map)) + + if let Some(failed_int) = failed_parsing_int_err { + scope.push_error(format!( + "Expected int for map with int keys, got `{failed_int}`" + )); + Err(()) + } else { + Ok(BamlValue::Map(map)) + } } else { scope.push_error(format!("Expected map, got `{}`", value)); Err(()) diff --git a/engine/baml-lib/baml-core/src/validate/validation_pipeline/validations/types.rs b/engine/baml-lib/baml-core/src/validate/validation_pipeline/validations/types.rs index 4422fcb41..75384a5b7 100644 --- a/engine/baml-lib/baml-core/src/validate/validation_pipeline/validations/types.rs +++ b/engine/baml-lib/baml-core/src/validate/validation_pipeline/validations/types.rs @@ -74,10 +74,24 @@ fn validate_type_allowed(ctx: &mut Context<'_>, field_type: &FieldType) { // Literal string key. FieldType::Literal(FieldArity::Required, LiteralValue::String(_), ..) => {} - // Literal string union. + // Literal int key. + FieldType::Literal(FieldArity::Required, LiteralValue::Int(_), ..) => {} + + // Literal union. FieldType::Union(FieldArity::Required, items, ..) => { let mut queue = VecDeque::from_iter(items.iter()); + // Little hack to keep track of data types in the union with + // a single pass and no allocations. Unions that contain + // literals of different types are not allowed as map keys. + // + // TODO: Same code is used at `coerce_map` function in + // baml-lib/jsonish/src/deserializer/coercer/coerce_map.rs + // + // Should figure out how to reuse this. + let mut literal_types_found = [0, 0, 0]; + let [strings, ints, bools] = &mut literal_types_found; + while let Some(item) = queue.pop_front() { match item { // Ok, literal string. @@ -85,7 +99,17 @@ fn validate_type_allowed(ctx: &mut Context<'_>, field_type: &FieldType) { FieldArity::Required, LiteralValue::String(_), .., - ) => {} + ) => *strings += 1, + + // Ok, literal int. + FieldType::Literal(FieldArity::Required, LiteralValue::Int(_), ..) => { + *ints += 1 + } + + // Ok, literal bool. + FieldType::Literal(FieldArity::Required, LiteralValue::Bool(_), ..) => { + *bools += 1 + } // Nested union, "recurse" but it's iterative. FieldType::Union(FieldArity::Required, nested, ..) => { @@ -101,6 +125,13 @@ fn validate_type_allowed(ctx: &mut Context<'_>, field_type: &FieldType) { } } } + + if literal_types_found.iter().filter(|&&t| t > 0).count() > 1 { + ctx.push_error(DatamodelError::new_validation_error( + "Unions in map keys may only contain literals of the same type.", + kv_types.0.span().clone(), + )); + } } other => { diff --git a/engine/baml-lib/baml-types/src/baml_value.rs b/engine/baml-lib/baml-types/src/baml_value.rs index d33eddf25..75c24a715 100644 --- a/engine/baml-lib/baml-types/src/baml_value.rs +++ b/engine/baml-lib/baml-types/src/baml_value.rs @@ -16,6 +16,7 @@ pub enum BamlValue { Int(i64), Float(f64), Bool(bool), + // TODO: Is it possible to support int keys without converting to strings? Map(BamlMap), List(Vec), Media(BamlMedia), @@ -638,7 +639,7 @@ impl Serialize for BamlValueWithMeta> { add_checks(&mut checked_value, cr)?; checked_value.end() } - }, + } BamlValueWithMeta::Null(cr) => serialize_with_checks(&(), cr, serializer), } } @@ -717,21 +718,23 @@ mod tests { #[test] fn test_serialize_class_checks() { - let baml_value: BamlValueWithMeta> = - BamlValueWithMeta::Class( - "Foo".to_string(), - vec![ - ("foo".to_string(), BamlValueWithMeta::Int(1, vec![])), - ("bar".to_string(), BamlValueWithMeta::String("hi".to_string(), vec![])), - ].into_iter().collect(), - vec![ - ResponseCheck { - name: "bar_len_lt_foo".to_string(), - expression: "this.bar|length < this.foo".to_string(), - status: "failed".to_string() - } - ] - ); + let baml_value: BamlValueWithMeta> = BamlValueWithMeta::Class( + "Foo".to_string(), + vec![ + ("foo".to_string(), BamlValueWithMeta::Int(1, vec![])), + ( + "bar".to_string(), + BamlValueWithMeta::String("hi".to_string(), vec![]), + ), + ] + .into_iter() + .collect(), + vec![ResponseCheck { + name: "bar_len_lt_foo".to_string(), + expression: "this.bar|length < this.foo".to_string(), + status: "failed".to_string(), + }], + ); let expected = serde_json::json!({ "value": {"foo": 1, "bar": "hi"}, "checks": { @@ -748,29 +751,30 @@ mod tests { #[test] fn test_serialize_nested_class_checks() { - // Prepare an object for wrapping. - let foo: BamlValueWithMeta> = - BamlValueWithMeta::Class( - "Foo".to_string(), - vec![ - ("foo".to_string(), BamlValueWithMeta::Int(1, vec![])), - ("bar".to_string(), BamlValueWithMeta::String("hi".to_string(), vec![])), - ].into_iter().collect(), - vec![ - ResponseCheck { - name: "bar_len_lt_foo".to_string(), - expression: "this.bar|length < this.foo".to_string(), - status: "failed".to_string() - } - ] - ); + let foo: BamlValueWithMeta> = BamlValueWithMeta::Class( + "Foo".to_string(), + vec![ + ("foo".to_string(), BamlValueWithMeta::Int(1, vec![])), + ( + "bar".to_string(), + BamlValueWithMeta::String("hi".to_string(), vec![]), + ), + ] + .into_iter() + .collect(), + vec![ResponseCheck { + name: "bar_len_lt_foo".to_string(), + expression: "this.bar|length < this.foo".to_string(), + status: "failed".to_string(), + }], + ); // Prepare the top-level value. let baml_value = BamlValueWithMeta::Class( "FooWrapper".to_string(), vec![("foo".to_string(), foo)].into_iter().collect(), - vec![] + vec![], ); let expected = serde_json::json!({ "foo": { @@ -787,5 +791,4 @@ mod tests { let json = serde_json::to_value(baml_value).unwrap(); assert_eq!(json, expected); } - } diff --git a/engine/baml-lib/baml/tests/validation_files/class/map_enum_and_literal_keys.baml b/engine/baml-lib/baml/tests/validation_files/class/map_enum_and_literal_keys.baml new file mode 100644 index 000000000..41ed08bb4 --- /dev/null +++ b/engine/baml-lib/baml/tests/validation_files/class/map_enum_and_literal_keys.baml @@ -0,0 +1,23 @@ +enum MapKey { + A + B + C +} + +class Fields { + e map + l1 map<"literal", string> + l2 map<5, string> + l3 map<"one" | "two" | ("three" | "four"), string> + l4 map<1 | 2 | (3 | 4), string> + + // This one should fail. + mixed_types map<1 | "one", string> +} + +// error: Error validating: Unions in map keys may only contain literals of the same type. +// --> class/map_enum_and_literal_keys.baml:15 +// | +// 14 | // This one should fail. +// 15 | mixed_types map<1 | "one", string> +// | diff --git a/engine/baml-lib/baml/tests/validation_files/class/map_enums_and_literals.baml b/engine/baml-lib/baml/tests/validation_files/functions_v2/enums_and_literals_as_map_keys.baml similarity index 51% rename from engine/baml-lib/baml/tests/validation_files/class/map_enums_and_literals.baml rename to engine/baml-lib/baml/tests/validation_files/functions_v2/enums_and_literals_as_map_keys.baml index 137a95a40..4ba19ecf8 100644 --- a/engine/baml-lib/baml/tests/validation_files/class/map_enums_and_literals.baml +++ b/engine/baml-lib/baml/tests/validation_files/functions_v2/enums_and_literals_as_map_keys.baml @@ -4,13 +4,7 @@ enum MapKey { C } -class Fields { - e map - l1 map<"literal", string> - l2 map<"one" | "two" | ("three" | "four"), string> -} - -function InOutEnumKey(i1: map, i2: map) -> map { +function InOutEnumMapKey(i1: map, i2: map) -> map { client "openai/gpt-4o" prompt #" Merge these: {{i1}} {{i2}} @@ -19,7 +13,7 @@ function InOutEnumKey(i1: map, i2: map) -> map, i2: map<"one" | "two" | ("three" | "four"), string> ) -> map<"one" | "two" | ("three" | "four"), string> { @@ -33,4 +27,20 @@ function InOutLiteralKey( {{ ctx.output_format }} "# -} \ No newline at end of file +} + +function InOutLiteralIntMapKey( + i1: map<1 | 2 | (3 | 4), string>, + i2: map<1 | 2 | (3 | 4), string> +) -> map<1 | 2 | (3 | 4), string> { + client "openai/gpt-4o" + prompt #" + Merge these: + + {{i1}} + + {{i2}} + + {{ ctx.output_format }} + "# +} diff --git a/engine/baml-lib/diagnostics/src/error.rs b/engine/baml-lib/diagnostics/src/error.rs index e1f1386ce..a55b4df65 100644 --- a/engine/baml-lib/diagnostics/src/error.rs +++ b/engine/baml-lib/diagnostics/src/error.rs @@ -596,7 +596,7 @@ impl DatamodelError { pub fn new_type_not_allowed_as_map_key_error(span: Span) -> DatamodelError { Self::new_validation_error( - "Maps may only have strings, enums or literal strings as keys", + "Maps may only have strings, enums or literals as keys", span, ) } diff --git a/engine/baml-lib/jsonish/src/deserializer/coercer/coerce_map.rs b/engine/baml-lib/jsonish/src/deserializer/coercer/coerce_map.rs index dd6290479..4afe37776 100644 --- a/engine/baml-lib/jsonish/src/deserializer/coercer/coerce_map.rs +++ b/engine/baml-lib/jsonish/src/deserializer/coercer/coerce_map.rs @@ -31,21 +31,38 @@ pub(super) fn coerce_map( }; match key_type.as_ref() { - // String, enum or just one literal string, OK. + // String, int, enum or just one literal string or int, OK. FieldType::Primitive(TypeValue::String) + | FieldType::Primitive(TypeValue::Int) | FieldType::Enum(_) - | FieldType::Literal(LiteralValue::String(_)) => {} + | FieldType::Literal(LiteralValue::String(_)) + | FieldType::Literal(LiteralValue::Int(_)) => {} - // For unions we need to check if all the items are literal strings. + // For unions we need to check if all the items are literals of the same + // type. FieldType::Union(items) => { let mut queue = VecDeque::from_iter(items.iter()); + + // Same trick used in `validate_type_allowed` at + // baml-lib/baml-core/src/validate/validation_pipeline/validations/types.rs + // + // TODO: Figure out how to reuse this. + let mut literal_types_found = [0, 0, 0]; + let [strings, ints, bools] = &mut literal_types_found; + while let Some(item) = queue.pop_front() { match item { - FieldType::Literal(LiteralValue::String(_)) => continue, + FieldType::Literal(LiteralValue::String(_)) => *strings += 1, + FieldType::Literal(LiteralValue::Int(_)) => *ints += 1, + FieldType::Literal(LiteralValue::Bool(_)) => *bools += 1, FieldType::Union(nested) => queue.extend(nested.iter()), other => return Err(ctx.error_map_must_have_supported_key(other)), } } + + if literal_types_found.iter().filter(|&&t| t > 0).count() > 1 { + return Err(ctx.error_map_must_have_only_one_type_in_key_union(key_type)); + } } // Key type not allowed. diff --git a/engine/baml-lib/jsonish/src/deserializer/coercer/mod.rs b/engine/baml-lib/jsonish/src/deserializer/coercer/mod.rs index a4ed76707..e56ed9114 100644 --- a/engine/baml-lib/jsonish/src/deserializer/coercer/mod.rs +++ b/engine/baml-lib/jsonish/src/deserializer/coercer/mod.rs @@ -142,7 +142,20 @@ impl ParsingContext<'_> { pub(crate) fn error_map_must_have_supported_key(&self, key_type: &FieldType) -> ParsingError { ParsingError { reason: format!( - "Maps may only have strings, enums or literal strings for keys, but got {key_type}" + "Maps may only have strings, enums or literals as keys, but got {key_type}" + ), + scope: self.scope.clone(), + causes: vec![], + } + } + + pub(crate) fn error_map_must_have_only_one_type_in_key_union( + &self, + key_type: &FieldType, + ) -> ParsingError { + ParsingError { + reason: format!( + "Unions in map keys may only contain literals of the same type, but got {key_type}" ), scope: self.scope.clone(), causes: vec![], diff --git a/engine/language_client_python/src/parse_py_type.rs b/engine/language_client_python/src/parse_py_type.rs index 7a1f5ee45..84aeeb2d6 100644 --- a/engine/language_client_python/src/parse_py_type.rs +++ b/engine/language_client_python/src/parse_py_type.rs @@ -295,6 +295,10 @@ pub fn parse_py_type( Ok(MappedPyType::List(items)) } else if let Ok(kv) = any.extract::>(py) { Ok(MappedPyType::Map(kv)) + } else if let Ok(kv) = any.extract::>(py) { + Ok(MappedPyType::Map( + kv.into_iter().map(|(k, v)| (k.to_string(), v)).collect(), + )) } else if let Ok(b) = any.downcast_bound::(py) { Ok(MappedPyType::Bool(b.is_true())) } else if let Ok(i) = any.extract::(py) { diff --git a/integ-tests/baml_src/test-files/functions/output/map-literal-union-key.baml b/integ-tests/baml_src/test-files/functions/output/map-literal-union-key.baml index e5a3ec3a1..56f166344 100644 --- a/integ-tests/baml_src/test-files/functions/output/map-literal-union-key.baml +++ b/integ-tests/baml_src/test-files/functions/output/map-literal-union-key.baml @@ -1,4 +1,4 @@ -function InOutLiteralMapKey( +function InOutLiteralStringMapKey( i1: map<"one" | "two" | ("three" | "four"), string>, i2: map<"one" | "two" | ("three" | "four"), string> ) -> map<"one" | "two" | ("three" | "four"), string> { @@ -12,4 +12,20 @@ function InOutLiteralMapKey( {{ ctx.output_format }} "# -} \ No newline at end of file +} + +function InOutLiteralIntMapKey( + i1: map<1 | 2 | (3 | 4), string>, + i2: map<1 | 2 | (3 | 4), string> +) -> map<1 | 2 | (3 | 4), string> { + client "openai/gpt-4o" + prompt #" + Merge these: + + {{i1}} + + {{i2}} + + {{ ctx.output_format }} + "# +} diff --git a/integ-tests/python/baml_client/async_client.py b/integ-tests/python/baml_client/async_client.py index f2a6bc094..419151674 100644 --- a/integ-tests/python/baml_client/async_client.py +++ b/integ-tests/python/baml_client/async_client.py @@ -1315,7 +1315,30 @@ async def InOutEnumMapKey( ) return cast(Dict[types.MapKey, str], raw.cast_to(types, types)) - async def InOutLiteralMapKey( + async def InOutLiteralIntMapKey( + self, + i1: Dict[Union[Literal[1], Literal[2], Union[Literal[3], Literal[4]]], str],i2: Dict[Union[Literal[1], Literal[2], Union[Literal[3], Literal[4]]], str], + baml_options: BamlCallOptions = {}, + ) -> Dict[Union[Literal[1], Literal[2], Union[Literal[3], Literal[4]]], 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( + "InOutLiteralIntMapKey", + { + "i1": i1,"i2": i2, + }, + self.__ctx_manager.get(), + tb, + __cr__, + ) + return cast(Dict[Union[Literal[1], Literal[2], Union[Literal[3], Literal[4]]], str], raw.cast_to(types, types)) + + async def InOutLiteralStringMapKey( self, i1: Dict[Union[Literal["one"], Literal["two"], Union[Literal["three"], Literal["four"]]], str],i2: Dict[Union[Literal["one"], Literal["two"], Union[Literal["three"], Literal["four"]]], str], baml_options: BamlCallOptions = {}, @@ -1328,7 +1351,7 @@ async def InOutLiteralMapKey( __cr__ = baml_options.get("client_registry", None) raw = await self.__runtime.call_function( - "InOutLiteralMapKey", + "InOutLiteralStringMapKey", { "i1": i1,"i2": i2, }, @@ -4383,7 +4406,38 @@ def InOutEnumMapKey( self.__ctx_manager.get(), ) - def InOutLiteralMapKey( + def InOutLiteralIntMapKey( + self, + i1: Dict[Union[Literal[1], Literal[2], Union[Literal[3], Literal[4]]], str],i2: Dict[Union[Literal[1], Literal[2], Union[Literal[3], Literal[4]]], str], + baml_options: BamlCallOptions = {}, + ) -> baml_py.BamlStream[Dict[Union[Literal[1], Literal[2], Union[Literal[3], Literal[4]]], Optional[str]], Dict[Union[Literal[1], Literal[2], Union[Literal[3], Literal[4]]], 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( + "InOutLiteralIntMapKey", + { + "i1": i1, + "i2": i2, + }, + None, + self.__ctx_manager.get(), + tb, + __cr__, + ) + + return baml_py.BamlStream[Dict[Union[Literal[1], Literal[2], Union[Literal[3], Literal[4]]], Optional[str]], Dict[Union[Literal[1], Literal[2], Union[Literal[3], Literal[4]]], str]]( + raw, + lambda x: cast(Dict[Union[Literal[1], Literal[2], Union[Literal[3], Literal[4]]], Optional[str]], x.cast_to(types, partial_types)), + lambda x: cast(Dict[Union[Literal[1], Literal[2], Union[Literal[3], Literal[4]]], str], x.cast_to(types, types)), + self.__ctx_manager.get(), + ) + + def InOutLiteralStringMapKey( self, i1: Dict[Union[Literal["one"], Literal["two"], Union[Literal["three"], Literal["four"]]], str],i2: Dict[Union[Literal["one"], Literal["two"], Union[Literal["three"], Literal["four"]]], str], baml_options: BamlCallOptions = {}, @@ -4396,7 +4450,7 @@ def InOutLiteralMapKey( __cr__ = baml_options.get("client_registry", None) raw = self.__runtime.stream_function( - "InOutLiteralMapKey", + "InOutLiteralStringMapKey", { "i1": i1, "i2": i2, diff --git a/integ-tests/python/baml_client/inlinedbaml.py b/integ-tests/python/baml_client/inlinedbaml.py index abf91ab29..59663ffba 100644 --- a/integ-tests/python/baml_client/inlinedbaml.py +++ b/integ-tests/python/baml_client/inlinedbaml.py @@ -75,7 +75,7 @@ "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/map-enum-key.baml": "enum MapKey {\n A\n B\n C\n}\n\nfunction InOutEnumMapKey(i1: map, i2: map) -> map {\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 InOutLiteralMapKey(\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}", + "test-files/functions/output/map-literal-union-key.baml": "function InOutLiteralStringMapKey(\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 InOutLiteralIntMapKey(\n i1: map<1 | 2 | (3 | 4), string>, \n i2: map<1 | 2 | (3 | 4), string>\n) -> map<1 | 2 | (3 | 4), 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", "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}", "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", diff --git a/integ-tests/python/baml_client/sync_client.py b/integ-tests/python/baml_client/sync_client.py index a0b0d6c66..34279b399 100644 --- a/integ-tests/python/baml_client/sync_client.py +++ b/integ-tests/python/baml_client/sync_client.py @@ -1312,7 +1312,30 @@ def InOutEnumMapKey( ) return cast(Dict[types.MapKey, str], raw.cast_to(types, types)) - def InOutLiteralMapKey( + def InOutLiteralIntMapKey( + self, + i1: Dict[Union[Literal[1], Literal[2], Union[Literal[3], Literal[4]]], str],i2: Dict[Union[Literal[1], Literal[2], Union[Literal[3], Literal[4]]], str], + baml_options: BamlCallOptions = {}, + ) -> Dict[Union[Literal[1], Literal[2], Union[Literal[3], Literal[4]]], 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( + "InOutLiteralIntMapKey", + { + "i1": i1,"i2": i2, + }, + self.__ctx_manager.get(), + tb, + __cr__, + ) + return cast(Dict[Union[Literal[1], Literal[2], Union[Literal[3], Literal[4]]], str], raw.cast_to(types, types)) + + def InOutLiteralStringMapKey( self, i1: Dict[Union[Literal["one"], Literal["two"], Union[Literal["three"], Literal["four"]]], str],i2: Dict[Union[Literal["one"], Literal["two"], Union[Literal["three"], Literal["four"]]], str], baml_options: BamlCallOptions = {}, @@ -1325,7 +1348,7 @@ def InOutLiteralMapKey( __cr__ = baml_options.get("client_registry", None) raw = self.__runtime.call_function_sync( - "InOutLiteralMapKey", + "InOutLiteralStringMapKey", { "i1": i1,"i2": i2, }, @@ -4381,7 +4404,38 @@ def InOutEnumMapKey( self.__ctx_manager.get(), ) - def InOutLiteralMapKey( + def InOutLiteralIntMapKey( + self, + i1: Dict[Union[Literal[1], Literal[2], Union[Literal[3], Literal[4]]], str],i2: Dict[Union[Literal[1], Literal[2], Union[Literal[3], Literal[4]]], str], + baml_options: BamlCallOptions = {}, + ) -> baml_py.BamlSyncStream[Dict[Union[Literal[1], Literal[2], Union[Literal[3], Literal[4]]], Optional[str]], Dict[Union[Literal[1], Literal[2], Union[Literal[3], Literal[4]]], 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( + "InOutLiteralIntMapKey", + { + "i1": i1, + "i2": i2, + }, + None, + self.__ctx_manager.get(), + tb, + __cr__, + ) + + return baml_py.BamlSyncStream[Dict[Union[Literal[1], Literal[2], Union[Literal[3], Literal[4]]], Optional[str]], Dict[Union[Literal[1], Literal[2], Union[Literal[3], Literal[4]]], str]]( + raw, + lambda x: cast(Dict[Union[Literal[1], Literal[2], Union[Literal[3], Literal[4]]], Optional[str]], x.cast_to(types, partial_types)), + lambda x: cast(Dict[Union[Literal[1], Literal[2], Union[Literal[3], Literal[4]]], str], x.cast_to(types, types)), + self.__ctx_manager.get(), + ) + + def InOutLiteralStringMapKey( self, i1: Dict[Union[Literal["one"], Literal["two"], Union[Literal["three"], Literal["four"]]], str],i2: Dict[Union[Literal["one"], Literal["two"], Union[Literal["three"], Literal["four"]]], str], baml_options: BamlCallOptions = {}, @@ -4394,7 +4448,7 @@ def InOutLiteralMapKey( __cr__ = baml_options.get("client_registry", None) raw = self.__runtime.stream_function_sync( - "InOutLiteralMapKey", + "InOutLiteralStringMapKey", { "i1": i1, "i2": i2, diff --git a/integ-tests/python/tests/test_functions.py b/integ-tests/python/tests/test_functions.py index daf78f8a3..e468e2afe 100644 --- a/integ-tests/python/tests/test_functions.py +++ b/integ-tests/python/tests/test_functions.py @@ -238,11 +238,17 @@ async def test_enum_key_in_map(self): assert res[MapKey.B] == "B" @pytest.mark.asyncio - async def test_literal_key_in_map(self): - res = await b.InOutLiteralMapKey({"one": "1"}, {"two": "2"}) + async def test_literal_string_key_in_map(self): + res = await b.InOutLiteralStringMapKey({"one": "1"}, {"two": "2"}) assert res["one"] == "1" assert res["two"] == "2" + @pytest.mark.asyncio + async def test_literal_int_key_in_map(self): + res = await b.InOutLiteralIntMapKey({1: "one"}, {2: "two"}) + assert res[1] == "one" + assert res[2] == "two" + 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 5203bb8b5..aed5cb594 100644 --- a/integ-tests/ruby/baml_client/client.rb +++ b/integ-tests/ruby/baml_client/client.rb @@ -1817,21 +1817,53 @@ def InOutEnumMapKey( baml_options: T::Hash[Symbol, T.any(Baml::TypeBuilder, Baml::ClientRegistry)] ).returns(T::Hash[String, String]) } - def InOutLiteralMapKey( + def InOutLiteralIntMapKey( *varargs, i1:,i2:, baml_options: {} ) if varargs.any? - raise ArgumentError.new("InOutLiteralMapKey may only be called with keyword arguments") + raise ArgumentError.new("InOutLiteralIntMapKey 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( - "InOutLiteralMapKey", + "InOutLiteralIntMapKey", + { + i1: i1,i2: i2, + }, + @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, + i1: T::Hash[String, String],i2: T::Hash[String, String], + baml_options: T::Hash[Symbol, T.any(Baml::TypeBuilder, Baml::ClientRegistry)] + ).returns(T::Hash[String, String]) + } + def InOutLiteralStringMapKey( + *varargs, + i1:,i2:, + baml_options: {} + ) + if varargs.any? + + raise ArgumentError.new("InOutLiteralStringMapKey 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( + "InOutLiteralStringMapKey", { i1: i1,i2: i2, }, @@ -5707,21 +5739,56 @@ def InOutEnumMapKey( baml_options: T::Hash[Symbol, T.any(Baml::TypeBuilder, Baml::ClientRegistry)] ).returns(Baml::BamlStream[T::Hash[String, String]]) } - def InOutLiteralMapKey( + def InOutLiteralIntMapKey( + *varargs, + i1:,i2:, + baml_options: {} + ) + if varargs.any? + + raise ArgumentError.new("InOutLiteralIntMapKey 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( + "InOutLiteralIntMapKey", + { + i1: i1,i2: i2, + }, + @ctx_manager, + baml_options[:tb]&.instance_variable_get(:@registry), + baml_options[:client_registry], + ) + Baml::BamlStream[T::Hash[String, T.nilable(String)], T::Hash[String, String]].new( + ffi_stream: raw, + ctx_manager: @ctx_manager + ) + end + + sig { + params( + varargs: T.untyped, + i1: T::Hash[String, String],i2: T::Hash[String, String], + baml_options: T::Hash[Symbol, T.any(Baml::TypeBuilder, Baml::ClientRegistry)] + ).returns(Baml::BamlStream[T::Hash[String, String]]) + } + def InOutLiteralStringMapKey( *varargs, i1:,i2:, baml_options: {} ) if varargs.any? - raise ArgumentError.new("InOutLiteralMapKey may only be called with keyword arguments") + raise ArgumentError.new("InOutLiteralStringMapKey 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( - "InOutLiteralMapKey", + "InOutLiteralStringMapKey", { i1: i1,i2: i2, }, diff --git a/integ-tests/ruby/baml_client/inlined.rb b/integ-tests/ruby/baml_client/inlined.rb index 86750fbda..278def0cb 100644 --- a/integ-tests/ruby/baml_client/inlined.rb +++ b/integ-tests/ruby/baml_client/inlined.rb @@ -75,7 +75,7 @@ module Inlined "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/map-enum-key.baml" => "enum MapKey {\n A\n B\n C\n}\n\nfunction InOutEnumMapKey(i1: map, i2: map) -> map {\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 InOutLiteralMapKey(\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}", + "test-files/functions/output/map-literal-union-key.baml" => "function InOutLiteralStringMapKey(\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 InOutLiteralIntMapKey(\n i1: map<1 | 2 | (3 | 4), string>, \n i2: map<1 | 2 | (3 | 4), string>\n) -> map<1 | 2 | (3 | 4), 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", "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}", "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", diff --git a/integ-tests/typescript/baml_client/async_client.ts b/integ-tests/typescript/baml_client/async_client.ts index 72af00c56..1b7b8d4d3 100644 --- a/integ-tests/typescript/baml_client/async_client.ts +++ b/integ-tests/typescript/baml_client/async_client.ts @@ -1418,13 +1418,38 @@ export class BamlAsyncClient { } } - async InOutLiteralMapKey( + async InOutLiteralIntMapKey( + i1: Partial>,i2: Partial>, + __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } + ): Promise>> { + try { + const raw = await this.runtime.callFunction( + "InOutLiteralIntMapKey", + { + "i1": i1,"i2": i2 + }, + this.ctx_manager.cloneContext(), + __baml_options__?.tb?.__tb(), + __baml_options__?.clientRegistry, + ) + return raw.parsed() as Partial> + } catch (error: any) { + const bamlError = createBamlValidationError(error); + if (bamlError instanceof BamlValidationError) { + throw bamlError; + } else { + throw error; + } + } + } + + async InOutLiteralStringMapKey( i1: Partial>,i2: Partial>, __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } ): Promise>> { try { const raw = await this.runtime.callFunction( - "InOutLiteralMapKey", + "InOutLiteralStringMapKey", { "i1": i1,"i2": i2 }, @@ -4764,13 +4789,46 @@ class BamlStreamClient { } } - InOutLiteralMapKey( + InOutLiteralIntMapKey( + i1: Partial>,i2: Partial>, + __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } + ): BamlStream>>, Partial>> { + try { + const raw = this.runtime.streamFunction( + "InOutLiteralIntMapKey", + { + "i1": i1,"i2": i2 + }, + undefined, + this.ctx_manager.cloneContext(), + __baml_options__?.tb?.__tb(), + __baml_options__?.clientRegistry, + ) + return new BamlStream>>, Partial>>( + raw, + (a): a is RecursivePartialNull>> => a, + (a): a is Partial> => 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; + } + } + + InOutLiteralStringMapKey( i1: Partial>,i2: Partial>, __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } ): BamlStream>>, Partial>> { try { const raw = this.runtime.streamFunction( - "InOutLiteralMapKey", + "InOutLiteralStringMapKey", { "i1": i1,"i2": i2 }, diff --git a/integ-tests/typescript/baml_client/inlinedbaml.ts b/integ-tests/typescript/baml_client/inlinedbaml.ts index 4b2cb1a14..4743c4ef7 100644 --- a/integ-tests/typescript/baml_client/inlinedbaml.ts +++ b/integ-tests/typescript/baml_client/inlinedbaml.ts @@ -76,7 +76,7 @@ const fileMap = { "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/map-enum-key.baml": "enum MapKey {\n A\n B\n C\n}\n\nfunction InOutEnumMapKey(i1: map, i2: map) -> map {\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 InOutLiteralMapKey(\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}", + "test-files/functions/output/map-literal-union-key.baml": "function InOutLiteralStringMapKey(\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 InOutLiteralIntMapKey(\n i1: map<1 | 2 | (3 | 4), string>, \n i2: map<1 | 2 | (3 | 4), string>\n) -> map<1 | 2 | (3 | 4), 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", "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}", "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", diff --git a/integ-tests/typescript/baml_client/sync_client.ts b/integ-tests/typescript/baml_client/sync_client.ts index 96f8faa23..c9b0c35ac 100644 --- a/integ-tests/typescript/baml_client/sync_client.ts +++ b/integ-tests/typescript/baml_client/sync_client.ts @@ -1418,13 +1418,38 @@ export class BamlSyncClient { } } - InOutLiteralMapKey( + InOutLiteralIntMapKey( + i1: Partial>,i2: Partial>, + __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } + ): Partial> { + try { + const raw = this.runtime.callFunctionSync( + "InOutLiteralIntMapKey", + { + "i1": i1,"i2": i2 + }, + this.ctx_manager.cloneContext(), + __baml_options__?.tb?.__tb(), + __baml_options__?.clientRegistry, + ) + return raw.parsed() as Partial> + } catch (error: any) { + const bamlError = createBamlValidationError(error); + if (bamlError instanceof BamlValidationError) { + throw bamlError; + } else { + throw error; + } + } + } + + InOutLiteralStringMapKey( i1: Partial>,i2: Partial>, __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } ): Partial> { try { const raw = this.runtime.callFunctionSync( - "InOutLiteralMapKey", + "InOutLiteralStringMapKey", { "i1": i1,"i2": i2 }, diff --git a/integ-tests/typescript/test-report.html b/integ-tests/typescript/test-report.html index 887f5fa71..506213c13 100644 --- a/integ-tests/typescript/test-report.html +++ b/integ-tests/typescript/test-report.html @@ -257,16 +257,6 @@ font-size: 1rem; padding: 0 0.5rem; } -

Test Report

Started: 2024-11-16 22:07:51
Suites (1)
0 passed
1 failed
0 pending
Tests (66)
64 passed
2 failed
0 pending
Integ tests > should work for all inputs
single bool
passed
0.541s
Integ tests > should work for all inputs
single string list
passed
0.504s
Integ tests > should work for all inputs
return literal union
failed
0.475s
BamlValidationError: BamlValidationError: Failed to parse LLM response: Failed to coerce value: <root>: Failed to find any (1 | true | "string output") in 3 items
-  - <root>: Expected 1, got Object([("Answer", String("true"))]).
-  - <root>: Expected true, got Object([("Answer", String("true"))]).
-  - <root>: Expected "string output", got Object([("Answer", String("true"))]).
-    at Function.from (/workspaces/baml/engine/language_client_typescript/index.js:33:28)
-    at from (/workspaces/baml/engine/language_client_typescript/index.js:58:32)
-    at BamlAsyncClient.LiteralUnionsTest (/workspaces/baml/integ-tests/typescript/baml_client/async_client.ts:1462:50)
-    at Object.<anonymous> (/workspaces/baml/integ-tests/typescript/tests/integ-tests.test.ts:43:19)
Integ tests > should work for all inputs
single class
passed
0.461s
Integ tests > should work for all inputs
multiple classes
passed
0.492s
Integ tests > should work for all inputs
single enum list
passed
0.529s
Integ tests > should work for all inputs
single float
passed
0.408s
Integ tests > should work for all inputs
single int
passed
0.408s
Integ tests > should work for all inputs
single literal int
passed
0.41s
Integ tests > should work for all inputs
single literal bool
passed
0.359s
Integ tests > should work for all inputs
single literal string
passed
0.708s
Integ tests > should work for all inputs
single class with literal prop
passed
0.51s
Integ tests > should work for all inputs
single class with literal union prop
passed
0.574s
Integ tests > should work for all inputs
single optional string
passed
0.407s
Integ tests > should work for all inputs
single map string to string
passed
0.495s
Integ tests > should work for all inputs
single map string to class
passed
0.732s
Integ tests > should work for all inputs
single map string to map
passed
1.025s
Integ tests > should work for all inputs
enum key in map
passed
0.512s
Integ tests > should work for all inputs
literal key in map
passed
0.616s
Integ tests
should work for all outputs
passed
5.008s
Integ tests
works with retries1
passed
1.172s
Integ tests
works with retries2
passed
2.417s
Integ tests
works with fallbacks
passed
2.354s
Integ tests
should work with image from url
passed
1.329s
Integ tests
should work with image from base 64
passed
1.24s
Integ tests
should work with audio base 64
passed
1.527s
Integ tests
should work with audio from url
passed
1.635s
Integ tests
should support streaming in OpenAI
passed
3.366s
Integ tests
should support streaming in Gemini
passed
9.635s
Integ tests
should support AWS
passed
1.641s
Integ tests
should support streaming in AWS
passed
1.63s
Integ tests
should allow overriding the region
passed
0.026s
Integ tests
should support OpenAI shorthand
passed
6.843s
Integ tests
should support OpenAI shorthand streaming
passed
6.677s
Integ tests
should support anthropic shorthand
passed
2.748s
Integ tests
should support anthropic shorthand streaming
passed
2.967s
Integ tests
should support streaming without iterating
passed
2.242s
Integ tests
should support streaming in Claude
passed
1.43s
Integ tests
should support vertex
passed
9.94s
Integ tests
supports tracing sync
passed
0.03s
Integ tests
supports tracing async
passed
3.634s
Integ tests
should work with dynamic types single
passed
1.048s
Integ tests
should work with dynamic types enum
passed
0.817s
Integ tests
should work with dynamic literals
passed
0.715s
Integ tests
should work with dynamic types class
passed
1.23s
Integ tests
should work with dynamic inputs class
passed
0.709s
Integ tests
should work with dynamic inputs list
passed
0.619s
Integ tests
should work with dynamic output map
passed
0.672s
Integ tests
should work with dynamic output union
passed
2.189s
Integ tests
should work with nested classes
failed
0.122s
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 runNextTicks (node:internal/process/task_queues:60:5)
-    at listOnTimeout (node:internal/timers:540:9)
-    at processTimers (node:internal/timers:514:7)
-    at Object.<anonymous> (/workspaces/baml/integ-tests/typescript/tests/integ-tests.test.ts:597:19)
Integ tests
should work with dynamic client
passed
0.477s
Integ tests
should work with 'onLogEvent'
passed
1.668s
Integ tests
should work with a sync client
passed
0.594s
Integ tests
should raise an error when appropriate
passed
1.027s
Integ tests
should raise a BAMLValidationError
passed
0.61s
Integ tests
should reset environment variables correctly
passed
2.15s
Integ tests
should use aliases when serializing input objects - classes
passed
0.881s
Integ tests
should use aliases when serializing, but still have original keys in jinja
passed
0.859s
Integ tests
should use aliases when serializing input objects - enums
passed
0.715s
Integ tests
should use aliases when serializing input objects - lists
passed
0.512s
Integ tests
constraints: should handle checks in return types
passed
0.65s
Integ tests
constraints: should handle checks in returned unions
passed
0.905s
Integ tests
constraints: should handle block-level checks
passed
0.57s
Integ tests
constraints: should handle nested-block-level checks
passed
0.744s
Integ tests
simple recursive type
passed
2.458s
Integ tests
mutually recursive type
passed
1.885s
\ No newline at end of file +

Test Report

Started: 2024-11-17 01:19:05
Suites (1)
0 passed
1 failed
0 pending
Tests (67)
0 passed
1 failed
66 pending
Integ tests > should work for all inputs
single bool
pending
0s
Integ tests > should work for all inputs
single string list
pending
0s
Integ tests > should work for all inputs
return literal union
pending
0s
Integ tests > should work for all inputs
single class
pending
0s
Integ tests > should work for all inputs
multiple classes
pending
0s
Integ tests > should work for all inputs
single enum list
pending
0s
Integ tests > should work for all inputs
single float
pending
0s
Integ tests > should work for all inputs
single int
pending
0s
Integ tests > should work for all inputs
single literal int
pending
0s
Integ tests > should work for all inputs
single literal bool
pending
0s
Integ tests > should work for all inputs
single literal string
pending
0s
Integ tests > should work for all inputs
single class with literal prop
pending
0s
Integ tests > should work for all inputs
single class with literal union prop
pending
0s
Integ tests > should work for all inputs
single optional string
pending
0s
Integ tests > should work for all inputs
single map string to string
pending
0s
Integ tests > should work for all inputs
single map string to class
pending
0s
Integ tests > should work for all inputs
single map string to map
pending
0s
Integ tests > should work for all inputs
enum key in map
pending
0s
Integ tests > should work for all inputs
literal string key in map
pending
0s
Integ tests > should work for all inputs
literal int key in map
failed
0.003s
Error: BamlError: BamlInvalidArgumentError:   Error: i1: <key>: Expected one of [Literal(Int(1)), Literal(Int(2)), Union([Literal(Int(3)), Literal(Int(4))])], got `String("1")`
+  Error: i2: <key>: Expected one of [Literal(Int(1)), Literal(Int(2)), Union([Literal(Int(3)), Literal(Int(4))])], got `String("2")`
+
Integ tests
should work for all outputs
pending
0s
Integ tests
works with retries1
pending
0s
Integ tests
works with retries2
pending
0s
Integ tests
works with fallbacks
pending
0s
Integ tests
should work with image from url
pending
0s
Integ tests
should work with image from base 64
pending
0s
Integ tests
should work with audio base 64
pending
0s
Integ tests
should work with audio from url
pending
0s
Integ tests
should support streaming in OpenAI
pending
0s
Integ tests
should support streaming in Gemini
pending
0s
Integ tests
should support AWS
pending
0s
Integ tests
should support streaming in AWS
pending
0s
Integ tests
should allow overriding the region
pending
0s
Integ tests
should support OpenAI shorthand
pending
0s
Integ tests
should support OpenAI shorthand streaming
pending
0s
Integ tests
should support anthropic shorthand
pending
0s
Integ tests
should support anthropic shorthand streaming
pending
0s
Integ tests
should support streaming without iterating
pending
0s
Integ tests
should support streaming in Claude
pending
0s
Integ tests
should support vertex
pending
0s
Integ tests
supports tracing sync
pending
0s
Integ tests
supports tracing async
pending
0s
Integ tests
should work with dynamic types single
pending
0s
Integ tests
should work with dynamic types enum
pending
0s
Integ tests
should work with dynamic literals
pending
0s
Integ tests
should work with dynamic types class
pending
0s
Integ tests
should work with dynamic inputs class
pending
0s
Integ tests
should work with dynamic inputs list
pending
0s
Integ tests
should work with dynamic output map
pending
0s
Integ tests
should work with dynamic output union
pending
0s
Integ tests
should work with nested classes
pending
0s
Integ tests
should work with dynamic client
pending
0s
Integ tests
should work with 'onLogEvent'
pending
0s
Integ tests
should work with a sync client
pending
0s
Integ tests
should raise an error when appropriate
pending
0s
Integ tests
should raise a BAMLValidationError
pending
0s
Integ tests
should reset environment variables correctly
pending
0s
Integ tests
should use aliases when serializing input objects - classes
pending
0s
Integ tests
should use aliases when serializing, but still have original keys in jinja
pending
0s
Integ tests
should use aliases when serializing input objects - enums
pending
0s
Integ tests
should use aliases when serializing input objects - lists
pending
0s
Integ tests
constraints: should handle checks in return types
pending
0s
Integ tests
constraints: should handle checks in returned unions
pending
0s
Integ tests
constraints: should handle block-level checks
pending
0s
Integ tests
constraints: should handle nested-block-level checks
pending
0s
Integ tests
simple recursive type
pending
0s
Integ tests
mutually recursive type
pending
0s
\ 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 12754fe9f..8d9d47dca 100644 --- a/integ-tests/typescript/tests/integ-tests.test.ts +++ b/integ-tests/typescript/tests/integ-tests.test.ts @@ -138,11 +138,17 @@ describe('Integ tests', () => { expect(res).toHaveProperty(MapKey.B, 'B') }) - it('literal key in map', async () => { - const res = await b.InOutLiteralMapKey({ one: '1' }, { two: '2' }) + it('literal string key in map', async () => { + const res = await b.InOutLiteralStringMapKey({ one: '1' }, { two: '2' }) expect(res).toHaveProperty('one', '1') expect(res).toHaveProperty('two', '2') }) + + it('literal int key in map', async () => { + const res = await b.InOutLiteralIntMapKey({ 1: 'one' }, { 2: 'two' }) + expect(res[1]).toEqual('one') + expect(res[2]).toEqual('two') + }) }) it('should work for all outputs', async () => { From 9334a4e2de269b345dbbcb2a160672b29777c9d0 Mon Sep 17 00:00:00 2001 From: Antonio Sarosi Date: Tue, 19 Nov 2024 00:22:58 +0000 Subject: [PATCH 6/6] `BamlMapKey` enum --- .../baml-core/src/ir/ir_helpers/mod.rs | 35 ++++++++------ .../src/ir/ir_helpers/to_baml_arg.rs | 24 +++++++--- engine/baml-lib/baml-core/src/ir/walker.rs | 7 ++- engine/baml-lib/baml-types/src/baml_value.rs | 46 ++++++++++++++++--- engine/baml-lib/baml-types/src/lib.rs | 2 +- engine/baml-lib/baml-types/src/minijinja.rs | 12 ++++- 6 files changed, 93 insertions(+), 33 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..0cc9cf79c 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 @@ -14,8 +14,8 @@ use crate::{ }; use anyhow::Result; use baml_types::{ - BamlMap, BamlValue, BamlValueWithMeta, Constraint, ConstraintLevel, FieldType, LiteralValue, - TypeValue, + BamlMap, BamlMapKey, BamlValue, BamlValueWithMeta, Constraint, ConstraintLevel, FieldType, + LiteralValue, TypeValue, }; pub use to_baml_arg::ArgCoercer; @@ -186,7 +186,7 @@ impl IRHelper for IntermediateRepr { if let Ok(baml_arg) = coerce_settings.coerce_arg(self, param_type, param_value, &mut scope) { - baml_arg_map.insert(param_name.to_string(), baml_arg); + baml_arg_map.insert(BamlMapKey::string(param_name), baml_arg); } } else { // Check if the parameter is optional. @@ -291,14 +291,15 @@ 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> = - pairs + let mapped_fields: BamlMap> = + pairs .into_iter() .map(|(key, val)| { - let sub_value = self.distribute_type(val, item_type.clone())?; + let sub_value = + self.distribute_type(val, item_type.clone())?; Ok((key, sub_value)) }) - .collect::>>>()?; + .collect::>()?; Ok(BamlValueWithMeta::Map(mapped_fields, field_type)) } } @@ -515,7 +516,11 @@ mod tests { } fn mk_map_1() -> BamlValue { - BamlValue::Map(vec![("a".to_string(), mk_int(1))].into_iter().collect()) + BamlValue::Map( + vec![(BamlMapKey::string("a"), mk_int(1))] + .into_iter() + .collect(), + ) } fn mk_ir() -> IntermediateRepr { @@ -557,7 +562,7 @@ mod tests { #[test] fn infer_map_map() { let my_map_map = BamlValue::Map( - vec![("map_a".to_string(), mk_map_1())] + vec![(BamlMapKey::string("map_a"), mk_map_1())] .into_iter() .collect(), ); @@ -629,12 +634,12 @@ mod tests { let map_1 = BamlValue::Map( vec![ ( - "1_string".to_string(), + BamlMapKey::string("1_string"), BamlValue::String("1_string_value".to_string()), ), - ("1_int".to_string(), mk_int(1)), + (BamlMapKey::string("1_int"), mk_int(1)), ( - "1_foo".to_string(), + BamlMapKey::string("1_foo"), BamlValue::Class( "Foo".to_string(), vec![ @@ -719,9 +724,9 @@ mod tests { // The compound value we want to test. let map = BamlValue::Map( vec![ - ("a".to_string(), list_1.clone()), - ("b".to_string(), list_1), - ("c".to_string(), list_2), + (BamlMapKey::string("a"), list_1.clone()), + (BamlMapKey::string("b"), list_1), + (BamlMapKey::string("c"), list_2), ] .into_iter() .collect(), 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 02fcd3d9a..83054ec59 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 @@ -1,6 +1,6 @@ use baml_types::{ - BamlMap, BamlValue, BamlValueWithMeta, Constraint, ConstraintLevel, FieldType, LiteralValue, - TypeValue, + BamlMap, BamlMapKey, BamlValue, BamlValueWithMeta, Constraint, ConstraintLevel, FieldType, + LiteralValue, TypeValue, }; use core::result::Result; use std::{collections::VecDeque, path::PathBuf}; @@ -84,7 +84,10 @@ impl ArgCoercer { }; for key in kv.keys() { - if !vec!["file", "media_type"].contains(&key.as_str()) { + if !["file", "media_type"] + .map(BamlMapKey::string) + .contains(&key) + { scope.push_error(format!( "Invalid property `{}` on file {}: `media_type` is the only supported property", key, @@ -118,7 +121,7 @@ impl ArgCoercer { None => None, }; for key in kv.keys() { - if !vec!["url", "media_type"].contains(&key.as_str()) { + if !["url", "media_type"].map(BamlMapKey::string).contains(&key) { scope.push_error(format!( "Invalid property `{}` on url {}: `media_type` is the only supported property", key, @@ -143,7 +146,10 @@ impl ArgCoercer { None => None, }; for key in kv.keys() { - if !vec!["base64", "media_type"].contains(&key.as_str()) { + if !["base64", "media_type"] + .map(BamlMapKey::string) + .contains(&key) + { scope.push_error(format!( "Invalid property `{}` on base64 {}: `media_type` is the only supported property", key, @@ -315,7 +321,11 @@ impl ArgCoercer { let target_baml_key = if matches!(**k, FieldType::Primitive(TypeValue::Int)) || is_union_of_literal_ints { - match key.parse::() { + let BamlMapKey::String(str_int) = key else { + todo!(); + }; + + match str_int.parse::() { Ok(i) => BamlValue::Int(i), Err(e) => { failed_parsing_int_err = Some(key); @@ -325,7 +335,7 @@ impl ArgCoercer { } } } else { - BamlValue::String(key.clone()) + BamlValue::String(key.to_string()) }; let coerced_key = self.coerce_arg(ir, k, &target_baml_key, scope); diff --git a/engine/baml-lib/baml-core/src/ir/walker.rs b/engine/baml-lib/baml-core/src/ir/walker.rs index 034bf2e3a..248b4c7b6 100644 --- a/engine/baml-lib/baml-core/src/ir/walker.rs +++ b/engine/baml-lib/baml-core/src/ir/walker.rs @@ -1,5 +1,5 @@ use anyhow::Result; -use baml_types::BamlValue; +use baml_types::{BamlMapKey, BamlValue}; use indexmap::IndexMap; use internal_baml_parser_database::RetryPolicyStrategy; @@ -237,7 +237,10 @@ impl Expression { Expression::Map(m) => { let mut map = baml_types::BamlMap::new(); for (k, v) in m { - map.insert(k.as_string_value(env_values)?, v.normalize(env_values)?); + map.insert( + BamlMapKey::String(k.as_string_value(env_values)?), + v.normalize(env_values)?, + ); } Ok(BamlValue::Map(map)) } diff --git a/engine/baml-lib/baml-types/src/baml_value.rs b/engine/baml-lib/baml-types/src/baml_value.rs index 75c24a715..e3dddc536 100644 --- a/engine/baml-lib/baml-types/src/baml_value.rs +++ b/engine/baml-lib/baml-types/src/baml_value.rs @@ -1,23 +1,57 @@ use std::collections::HashMap; +use std::fmt::Display; use std::{ collections::{HashSet, VecDeque}, fmt, }; +use indexmap::Equivalent; use serde::ser::SerializeMap; use serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer}; use crate::media::BamlMediaType; use crate::{BamlMap, BamlMedia, ResponseCheck}; +/// Supported map keys. +/// +/// We only support strings and integers as map keys. +#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub enum BamlMapKey { + String(String), + Int(i64), +} + +impl BamlMapKey { + pub fn string(s: &str) -> Self { + Self::String(s.into()) + } +} + +impl Display for BamlMapKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + BamlMapKey::String(s) => write!(f, "{s}"), + BamlMapKey::Int(i) => write!(f, "{i}"), + } + } +} + +impl Equivalent for str { + fn equivalent(&self, key: &BamlMapKey) -> bool { + match key { + BamlMapKey::String(s) => self == s, + _ => false, + } + } +} + #[derive(Clone, Debug, PartialEq)] pub enum BamlValue { String(String), Int(i64), Float(f64), Bool(bool), - // TODO: Is it possible to support int keys without converting to strings? - Map(BamlMap), + Map(BamlMap), List(Vec), Media(BamlMedia), Enum(String, String), @@ -135,13 +169,13 @@ impl BamlValue { matches!(self, BamlValue::Map(_)) } - pub fn as_map(&self) -> Option<&BamlMap> { + pub fn as_map(&self) -> Option<&BamlMap> { match self { BamlValue::Map(m) => Some(m), _ => None, } } - pub fn as_map_owned(self) -> Option> { + pub fn as_map_owned(self) -> Option> { match self { BamlValue::Map(m) => Some(m), _ => None, @@ -361,7 +395,7 @@ pub enum BamlValueWithMeta { Int(i64, T), Float(f64, T), Bool(bool, T), - Map(BamlMap>, T), + Map(BamlMap>, T), List(Vec>, T), Media(BamlMedia, T), Enum(String, String, T), @@ -458,7 +492,7 @@ impl BamlValueWithMeta { BamlValue::Class(_, items) => Map( items .iter() - .map(|(k, v)| (k.clone(), Self::with_default_meta(v))) + .map(|(k, v)| (BamlMapKey::String(k.clone()), Self::with_default_meta(v))) .collect(), T::default(), ), diff --git a/engine/baml-lib/baml-types/src/lib.rs b/engine/baml-lib/baml-types/src/lib.rs index fb721ff8d..20ace3081 100644 --- a/engine/baml-lib/baml-types/src/lib.rs +++ b/engine/baml-lib/baml-types/src/lib.rs @@ -7,7 +7,7 @@ mod baml_value; mod field_type; mod generator; -pub use baml_value::{BamlValue, BamlValueWithMeta}; +pub use baml_value::{BamlMapKey, BamlValue, BamlValueWithMeta}; pub use constraint::*; pub use field_type::{FieldType, LiteralValue, TypeValue}; pub use generator::{GeneratorDefaultClientMode, GeneratorOutputType}; diff --git a/engine/baml-lib/baml-types/src/minijinja.rs b/engine/baml-lib/baml-types/src/minijinja.rs index 36aa7a5a4..4c34e497f 100644 --- a/engine/baml-lib/baml-types/src/minijinja.rs +++ b/engine/baml-lib/baml-types/src/minijinja.rs @@ -1,5 +1,5 @@ +use crate::{baml_value::BamlMapKey, BamlMedia, BamlValue}; use std::fmt; -use crate::{BamlMedia, BamlValue}; /// A wrapper around a jinja expression. The inner `String` should not contain /// the interpolation brackets `{{ }}`; it should be a bare expression like @@ -7,7 +7,6 @@ use crate::{BamlMedia, BamlValue}; #[derive(Clone, Debug, PartialEq, serde::Serialize)] pub struct JinjaExpression(pub String); - impl fmt::Display for JinjaExpression { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Display::fmt(&self.0, f) @@ -56,6 +55,15 @@ impl From for minijinja::Value { } } +impl From for minijinja::Value { + fn from(key: BamlMapKey) -> Self { + match key { + BamlMapKey::String(s) => minijinja::Value::from(s), + BamlMapKey::Int(n) => minijinja::Value::from(n), + } + } +} + const MAGIC_MEDIA_DELIMITER: &'static str = "BAML_MEDIA_MAGIC_STRING_DELIMITER"; impl std::fmt::Display for MinijinjaBamlMedia {