From 39e0271f605234535cc53470a6aedff07aaa0c6c Mon Sep 17 00:00:00 2001 From: Antonio Sarosi Date: Tue, 19 Nov 2024 03:11:33 +0000 Subject: [PATCH] Enums & Literals as map keys (#1178) Issue #1050 Added support for enums and literal strings in map keys: ```baml enum MapKey { A B C } class Fields { // Enum as key e map // Single literal as key l1 map<"literal", string> // Union of literals as keys l2 map<"one" | "two" | ("three" | "four"), string> } ``` Literal integers are more complicated since they require maps to support int keys. See #1180. ---- > [!IMPORTANT] > Add support for enums and literal strings as map keys in BAML, with updated validation, coercion logic, and tests. > > - **Behavior**: > - Support for enums and literal strings as map keys added in `mod.rs` and `types.rs`. > - Validation logic updated to allow enums and literal strings as map keys. > - Coercion logic updated to handle enums and literal strings as map keys. > - **Tests**: > - Added tests in `map_enums_and_literals.baml` and `map_types.baml` to verify new map key functionality. > - Updated `test_functions.py` and `integ-tests.test.ts` to include cases for enum and literal string map keys. > - **Misc**: > - Updated error messages in `error.rs` to reflect new map key types. > - Minor updates in `async_client.py`, `sync_client.py`, and `client.rb` to support new map key types. > > This description was created by [Ellipsis](https://www.ellipsis.dev?ref=BoundaryML%2Fbaml&utm_source=github&utm_medium=referral) for c7742fda1a00d97274340b2d67d4d51002e72ed4. It will automatically update as commits are pushed. --- .../baml-core/src/ir/ir_helpers/mod.rs | 8 +- .../validation_pipeline/validations/types.rs | 54 ++++- .../class/map_enums_and_literals.baml | 36 ++++ .../validation_files/class/map_types.baml | 22 +- .../validation_files/class/secure_types.baml | 16 +- engine/baml-lib/diagnostics/src/error.rs | 7 + .../src/deserializer/coercer/coerce_map.rs | 83 ++++++-- .../jsonish/src/deserializer/coercer/mod.rs | 6 +- .../src/attributes/constraint.rs | 2 +- .../src/ruby/field_type.rs | 38 ++-- .../src/ruby/generate_types.rs | 73 ++++--- .../src/typescript/mod.rs | 29 ++- .../functions/output/map-enum-key.baml | 14 ++ .../output/map-literal-union-key.baml | 26 +++ .../python/baml_client/async_client.py | 161 ++++++++++++++ integ-tests/python/baml_client/inlinedbaml.py | 2 + integ-tests/python/baml_client/sync_client.py | 161 ++++++++++++++ .../python/baml_client/type_builder.py | 2 +- integ-tests/python/baml_client/types.py | 6 + integ-tests/python/poetry.lock | 23 +- integ-tests/python/pyproject.toml | 1 + integ-tests/python/tests/test_functions.py | 18 ++ integ-tests/ruby/baml_client/client.rb | 201 ++++++++++++++++++ integ-tests/ruby/baml_client/inlined.rb | 2 + integ-tests/ruby/baml_client/type-registry.rb | 2 +- integ-tests/ruby/baml_client/types.rb | 7 + integ-tests/ruby/test_functions.rb | 11 + .../typescript/baml_client/async_client.ts | 176 ++++++++++++++- .../typescript/baml_client/inlinedbaml.ts | 2 + .../typescript/baml_client/sync_client.ts | 77 ++++++- .../typescript/baml_client/type_builder.ts | 2 +- integ-tests/typescript/baml_client/types.ts | 6 + integ-tests/typescript/test-report.html | 13 +- .../typescript/tests/integ-tests.test.ts | 23 +- .../baml-schema-wasm-node/package-lock.json | 13 ++ 35 files changed, 1210 insertions(+), 113 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 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/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..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,7 @@ -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::{ Argument, Attribute, Expression, FieldArity, FieldType, Identifier, WithName, WithSpan, @@ -56,12 +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, ..) => {} - key_type => { - ctx.push_error(DatamodelError::new_validation_error( - "Maps may only have strings as keys", - key_type.span().clone(), + + // Enum key. + FieldType::Symbol(FieldArity::Required, identifier, _) + if ctx + .db + .find_type(identifier) + .is_some_and(|t| matches!(t, Either::Right(_))) => {} + + // 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 new file mode 100644 index 000000000..5b9c8612e --- /dev/null +++ b/engine/baml-lib/baml/tests/validation_files/class/map_enums_and_literals.baml @@ -0,0 +1,36 @@ +enum MapKey { + A + B + C +} + +class Fields { + e map + l1 map<"literal", string> + l2 map<"one" | "two" | ("three" | "four"), string> +} + +function InOutEnumKey(i1: map, i2: map) -> map { + client "openai/gpt-4o" + prompt #" + Merge these: {{i1}} {{i2}} + + {{ ctx.output_format }} + "# +} + +function InOutLiteralStringUnionMapKey( + 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 }} + "# +} 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..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 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 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 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 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 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 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 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 as keys +// error: Error validating: Maps may only have strings, enums or literal strings as keys // --> class/map_types.baml:27 // | // 26 | diff --git a/engine/baml-lib/baml/tests/validation_files/class/secure_types.baml b/engine/baml-lib/baml/tests/validation_files/class/secure_types.baml index 081573f8a..8e6f1a97b 100644 --- a/engine/baml-lib/baml/tests/validation_files/class/secure_types.baml +++ b/engine/baml-lib/baml/tests/validation_files/class/secure_types.baml @@ -25,7 +25,7 @@ class ComplexTypes { // 2 | class ComplexTypes { // 3 | a map // | -// error: Error validating: Maps may only have strings as keys +// error: Error validating: Maps may only have strings, enums or literal strings as keys // --> class/secure_types.baml:3 // | // 2 | class ComplexTypes { @@ -43,7 +43,7 @@ class ComplexTypes { // 3 | a map // 4 | b (int, map, (char | float)[][] | long_word_123.foobar[]) // | -// error: Error validating: Maps may only have strings as keys +// error: Error validating: Maps may only have strings, enums or literal strings as keys // --> class/secure_types.baml:4 // | // 3 | a map @@ -73,7 +73,7 @@ class ComplexTypes { // 5 | c apple123_456_pie | (stringer, bool[], (int | char))[] // 6 | d map // | -// error: Error validating: Maps may only have strings as keys +// error: Error validating: Maps may only have strings, enums or literal strings as keys // --> class/secure_types.baml:6 // | // 5 | c apple123_456_pie | (stringer, bool[], (int | char))[] @@ -121,7 +121,7 @@ class ComplexTypes { // 9 | g (int, (float, char, bool), string[]) | tuple_inside_tuple[] // 10 | h (((int | string)[]) | map) // | -// error: Error validating: Maps may only have strings as keys +// error: Error validating: Maps may only have strings, enums or literal strings as keys // --> class/secure_types.baml:10 // | // 9 | g (int, (float, char, bool), string[]) | tuple_inside_tuple[] @@ -181,13 +181,13 @@ class ComplexTypes { // 12 | j ((char, int[][], (bool | string[][])) | double[][][][], (float, int)[]) // 13 | k map | map // | -// error: Error validating: Maps may only have strings as keys +// error: Error validating: Maps may only have strings, enums or literal strings as keys // --> class/secure_types.baml:13 // | // 12 | j ((char, int[][], (bool | string[][])) | double[][][][], (float, int)[]) // 13 | k map | map // | -// error: Error validating: Maps may only have strings as keys +// error: Error validating: Maps may only have strings, enums or literal strings as keys // --> class/secure_types.baml:13 // | // 12 | j ((char, int[][], (bool | string[][])) | double[][][][], (float, int)[]) @@ -247,13 +247,13 @@ class ComplexTypes { // 15 | m (tuple_1, tuple_2 | tuple_3, (tuple_4, tuple_5))[] // 16 | n map> // | -// error: Error validating: Maps may only have strings as keys +// error: Error validating: Maps may only have strings, enums or literal strings as keys // --> class/secure_types.baml:16 // | // 15 | m (tuple_1, tuple_2 | tuple_3, (tuple_4, tuple_5))[] // 16 | n map> // | -// error: Error validating: Maps may only have strings as keys +// error: Error validating: Maps may only have strings, enums or literal strings as keys // --> class/secure_types.baml:16 // | // 15 | m (tuple_1, tuple_2 | tuple_3, (tuple_4, tuple_5))[] 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 ff66bb498..48d4d10ec 100644 --- a/engine/baml-lib/jsonish/src/deserializer/coercer/coerce_map.rs +++ b/engine/baml-lib/jsonish/src/deserializer/coercer/coerce_map.rs @@ -1,17 +1,22 @@ +use std::collections::VecDeque; + use anyhow::Result; -use crate::deserializer::{ - deserialize_flags::{DeserializerConditions, Flag}, - types::BamlValueWithFlags, +use crate::{ + deserializer::{ + deserialize_flags::{DeserializerConditions, Flag}, + types::BamlValueWithFlags, + }, + jsonish, }; -use baml_types::{BamlMap, FieldType, TypeValue}; +use baml_types::{BamlMap, FieldType, LiteralValue, TypeValue}; use super::{ParsingContext, ParsingError, TypeCoercer}; pub(super) fn coerce_map( ctx: &ParsingContext, map_target: &FieldType, - value: Option<&crate::jsonish::Value>, + value: Option<&jsonish::Value>, ) -> Result { log::debug!( "scope: {scope} :: coercing to: {name} (current: {current})", @@ -28,22 +33,74 @@ pub(super) fn coerce_map( return Err(ctx.error_unexpected_type(map_target, value)); }; - if !matches!(**key_type, FieldType::Primitive(TypeValue::String)) { - return Err(ctx.error_map_must_have_string_key(key_type)); + // TODO: Do we actually need to check the key type here in the coercion + // logic? Can the user pass a "type" here at runtime? Can we pass the wrong + // type from our own code or is this guaranteed to be a valid map key type? + // If we can determine that the type is always valid then we can get rid of + // this logic and skip the loops & allocs in the the union branch. + match key_type.as_ref() { + // String, enum or just one literal string, OK. + FieldType::Primitive(TypeValue::String) + | FieldType::Enum(_) + | FieldType::Literal(LiteralValue::String(_)) => {} + + // 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(); flags.add_flag(Flag::ObjectToMap(value.clone())); match &value { - crate::jsonish::Value::Object(obj) => { + jsonish::Value::Object(obj) => { let mut items = BamlMap::new(); - for (key, value) in obj.iter() { - match value_type.coerce(&ctx.enter_scope(key), value_type, Some(value)) { - Ok(v) => { - items.insert(key.clone(), (DeserializerConditions::new(), v)); + for (idx, (key, value)) in obj.iter().enumerate() { + let coerced_value = + match value_type.coerce(&ctx.enter_scope(key), value_type, Some(value)) { + Ok(v) => v, + Err(e) => { + flags.add_flag(Flag::MapValueParseError(key.clone(), e)); + // Could not coerce value, nothing else to do here. + continue; + } + }; + + // Keys are just strings but since we suport enums and literals + // we have to check that the key we are reading is actually a + // valid enum member or expected literal value. The coercion + // logic already does that so we'll just coerce the key. + // + // TODO: Is it necessary to check that values match here? This + // is also checked at `coerce_arg` in + // baml-lib/baml-core/src/ir/ir_helpers/to_baml_arg.rs + let key_as_jsonish = jsonish::Value::String(key.to_owned()); + match key_type.coerce(ctx, &key_type, Some(&key_as_jsonish)) { + Ok(_) => { + // Hack to avoid cloning the key twice. + let jsonish::Value::String(owned_key) = key_as_jsonish else { + unreachable!("key_as_jsonish is defined as jsonish::Value::String"); + }; + + // Both the value and the key were successfully + // coerced, add the key to the map. + items.insert(owned_key, (DeserializerConditions::new(), coerced_value)); } - Err(e) => flags.add_flag(Flag::MapValueParseError(key.clone(), e)), + // Couldn't coerce key, this is either not a valid enum + // variant or it doesn't match any of the literal values + // expected. + Err(e) => flags.add_flag(Flag::MapKeyParseError(idx, e)), } } Ok(BamlValueWithFlags::Map(flags, items)) diff --git a/engine/baml-lib/jsonish/src/deserializer/coercer/mod.rs b/engine/baml-lib/jsonish/src/deserializer/coercer/mod.rs index 5c94d3eb3..a4ed76707 100644 --- a/engine/baml-lib/jsonish/src/deserializer/coercer/mod.rs +++ b/engine/baml-lib/jsonish/src/deserializer/coercer/mod.rs @@ -139,9 +139,11 @@ 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 for keys, but got {}", key_type), + reason: format!( + "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/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..cb2115910 100644 --- a/engine/language_client_codegen/src/ruby/field_type.rs +++ b/engine/language_client_codegen/src/ruby/field_type.rs @@ -1,5 +1,4 @@ - -use baml_types::{BamlMediaType, FieldType, TypeValue}; +use baml_types::{BamlMediaType, FieldType, LiteralValue, TypeValue}; use crate::field_type_attributes; @@ -14,10 +13,18 @@ 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::Literal(LiteralValue::String(_)) + | FieldType::Union(_) => 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 +34,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 +54,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 1e776bb8d..55419ba0f 100644 --- a/engine/language_client_codegen/src/ruby/generate_types.rs +++ b/engine/language_client_codegen/src/ruby/generate_types.rs @@ -2,12 +2,16 @@ 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}; use super::ruby_language_features::ToRuby; -use internal_baml_core::ir::{repr::{Docstring, IntermediateRepr}, ClassWalker, EnumWalker, FieldType}; +use internal_baml_core::ir::{ + repr::{Docstring, IntermediateRepr}, + ClassWalker, EnumWalker, FieldType, +}; #[derive(askama::Template)] #[template(path = "types.rb.j2", escape = "none")] @@ -74,7 +78,12 @@ impl<'ir> From> for RubyEnum<'ir> { .iter() .map(|v| v.0.elem.0.as_str()) .collect(), - docstring: e.item.elem.docstring.as_ref().map(|d| render_docstring(d, true)) + docstring: e + .item + .elem + .docstring + .as_ref() + .map(|d| render_docstring(d, true)), } } } @@ -89,13 +98,20 @@ 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(), - f.elem.docstring.as_ref().map(|d| render_docstring(d, true)) - )) + .map(|f| { + ( + Cow::Borrowed(f.elem.name.as_str()), + f.elem.r#type.elem.to_type_ref(), + f.elem.docstring.as_ref().map(|d| render_docstring(d, true)), + ) + }) .collect(), - docstring: c.item.elem.docstring.as_ref().map(|d| render_docstring(d, false)), + docstring: c + .item + .elem + .docstring + .as_ref() + .map(|d| render_docstring(d, false)), } } } @@ -123,11 +139,16 @@ impl<'ir> From> for PartialRubyStruct<'ir> { ( f.elem.name.as_str(), f.elem.r#type.elem.to_partial_type_ref(), - f.elem.docstring.as_ref().map(|d| render_docstring(d, true)) + f.elem.docstring.as_ref().map(|d| render_docstring(d, true)), ) }) .collect(), - docstring: c.item.elem.docstring.as_ref().map(|d| render_docstring(d, false)), + docstring: c + .item + .elem + .docstring + .as_ref() + .map(|d| render_docstring(d, false)), } } } @@ -151,13 +172,17 @@ 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::Literal(LiteralValue::String(_)) + | FieldType::Union(_) => 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 @@ -178,16 +203,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..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::{ @@ -273,7 +274,17 @@ 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(_) + | FieldType::Union(_) + | FieldType::Literal(LiteralValue::String(_)) => { + 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 +306,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/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..c52f074a9 --- /dev/null +++ b/integ-tests/baml_src/test-files/functions/output/map-literal-union-key.baml @@ -0,0 +1,26 @@ +function InOutLiteralStringUnionMapKey( + 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 }} + "# +} + +function InOutSingleLiteralStringMapKey(m: map<"key", string>) -> map<"key", string> { + client "openai/gpt-4o" + prompt #" + Return the same map you were given: + + {{m}} + + {{ 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..d49f1ebd7 100644 --- a/integ-tests/python/baml_client/async_client.py +++ b/integ-tests/python/baml_client/async_client.py @@ -1292,6 +1292,75 @@ 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 InOutLiteralStringUnionMapKey( + 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( + "InOutLiteralStringUnionMapKey", + { + "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 InOutSingleLiteralStringMapKey( + self, + m: Dict[Literal["key"], str], + baml_options: BamlCallOptions = {}, + ) -> Dict[Literal["key"], 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( + "InOutSingleLiteralStringMapKey", + { + "m": m, + }, + self.__ctx_manager.get(), + tb, + __cr__, + ) + return cast(Dict[Literal["key"], str], raw.cast_to(types, types)) + async def LiteralUnionsTest( self, input: str, @@ -4306,6 +4375,98 @@ 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 InOutLiteralStringUnionMapKey( + 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( + "InOutLiteralStringUnionMapKey", + { + "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 InOutSingleLiteralStringMapKey( + self, + m: Dict[Literal["key"], str], + baml_options: BamlCallOptions = {}, + ) -> baml_py.BamlStream[Dict[Literal["key"], Optional[str]], Dict[Literal["key"], 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( + "InOutSingleLiteralStringMapKey", + { + "m": m, + }, + None, + self.__ctx_manager.get(), + tb, + __cr__, + ) + + return baml_py.BamlStream[Dict[Literal["key"], Optional[str]], Dict[Literal["key"], str]]( + raw, + lambda x: cast(Dict[Literal["key"], Optional[str]], x.cast_to(types, partial_types)), + lambda x: cast(Dict[Literal["key"], 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 6d3df6eb1..4f7d1acb2 100644 --- a/integ-tests/python/baml_client/inlinedbaml.py +++ b/integ-tests/python/baml_client/inlinedbaml.py @@ -74,6 +74,8 @@ "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/map-literal-union-key.baml": "function InOutLiteralStringUnionMapKey(\n i1: map<\"one\" | \"two\" | (\"three\" | \"four\"), string>, \n i2: map<\"one\" | \"two\" | (\"three\" | \"four\"), string>\n) -> map<\"one\" | \"two\" | (\"three\" | \"four\"), string> {\n client \"openai/gpt-4o\"\n prompt #\"\n Merge these:\n \n {{i1}}\n \n {{i2}}\n\n {{ ctx.output_format }}\n \"#\n}\n\nfunction InOutSingleLiteralStringMapKey(m: map<\"key\", string>) -> map<\"key\", string> {\n client \"openai/gpt-4o\"\n prompt #\"\n Return the same map you were given:\n \n {{m}}\n\n {{ ctx.output_format }}\n \"#\n}\n", "test-files/functions/output/mutually-recursive-classes.baml": "class Tree {\n data int\n children Forest\n}\n\nclass Forest {\n trees Tree[]\n}\n\nclass BinaryNode {\n data int\n left BinaryNode?\n right BinaryNode?\n}\n\nfunction BuildTree(input: BinaryNode) -> Tree {\n client GPT35\n prompt #\"\n Given the input binary tree, transform it into a generic tree using the given schema.\n\n INPUT:\n {{ input }}\n\n {{ ctx.output_format }} \n \"#\n}\n\ntest TestTree {\n functions [BuildTree]\n args {\n input {\n data 2\n left {\n data 1\n left null\n right null\n }\n right {\n data 3\n left null\n right null\n }\n }\n }\n}", "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..107be357c 100644 --- a/integ-tests/python/baml_client/sync_client.py +++ b/integ-tests/python/baml_client/sync_client.py @@ -1289,6 +1289,75 @@ 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 InOutLiteralStringUnionMapKey( + 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( + "InOutLiteralStringUnionMapKey", + { + "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 InOutSingleLiteralStringMapKey( + self, + m: Dict[Literal["key"], str], + baml_options: BamlCallOptions = {}, + ) -> Dict[Literal["key"], 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( + "InOutSingleLiteralStringMapKey", + { + "m": m, + }, + self.__ctx_manager.get(), + tb, + __cr__, + ) + return cast(Dict[Literal["key"], str], raw.cast_to(types, types)) + def LiteralUnionsTest( self, input: str, @@ -4304,6 +4373,98 @@ 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 InOutLiteralStringUnionMapKey( + 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( + "InOutLiteralStringUnionMapKey", + { + "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 InOutSingleLiteralStringMapKey( + self, + m: Dict[Literal["key"], str], + baml_options: BamlCallOptions = {}, + ) -> baml_py.BamlSyncStream[Dict[Literal["key"], Optional[str]], Dict[Literal["key"], 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( + "InOutSingleLiteralStringMapKey", + { + "m": m, + }, + None, + self.__ctx_manager.get(), + tb, + __cr__, + ) + + return baml_py.BamlSyncStream[Dict[Literal["key"], Optional[str]], Dict[Literal["key"], str]]( + raw, + lambda x: cast(Dict[Literal["key"], Optional[str]], x.cast_to(types, partial_types)), + lambda x: cast(Dict[Literal["key"], 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 6ed466630..ebe52a38f 100644 --- a/integ-tests/python/baml_client/types.py +++ b/integ-tests/python/baml_client/types.py @@ -109,6 +109,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/poetry.lock b/integ-tests/python/poetry.lock index 7936a1106..b13247d60 100644 --- a/integ-tests/python/poetry.lock +++ b/integ-tests/python/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand. [[package]] name = "annotated-types" @@ -239,6 +239,25 @@ files = [ {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, ] +[[package]] +name = "patchelf" +version = "0.17.2.1" +description = "A small utility to modify the dynamic linker and RPATH of ELF executables." +optional = false +python-versions = "*" +files = [ + {file = "patchelf-0.17.2.1-py2.py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:fc329da0e8f628bd836dfb8eaf523547e342351fa8f739bf2b3fe4a6db5a297c"}, + {file = "patchelf-0.17.2.1-py2.py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.musllinux_1_1_armv7l.whl", hash = "sha256:ccb266a94edf016efe80151172c26cff8c2ec120a57a1665d257b0442784195d"}, + {file = "patchelf-0.17.2.1-py2.py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.musllinux_1_1_ppc64le.whl", hash = "sha256:f47b5bdd6885cfb20abdd14c707d26eb6f499a7f52e911865548d4aa43385502"}, + {file = "patchelf-0.17.2.1-py2.py3-none-manylinux_2_17_s390x.manylinux2014_s390x.musllinux_1_1_s390x.whl", hash = "sha256:a9e6ebb0874a11f7ed56d2380bfaa95f00612b23b15f896583da30c2059fcfa8"}, + {file = "patchelf-0.17.2.1-py2.py3-none-manylinux_2_5_i686.manylinux1_i686.musllinux_1_1_i686.whl", hash = "sha256:3c8d58f0e4c1929b1c7c45ba8da5a84a8f1aa6a82a46e1cfb2e44a4d40f350e5"}, + {file = "patchelf-0.17.2.1-py2.py3-none-manylinux_2_5_x86_64.manylinux1_x86_64.musllinux_1_1_x86_64.whl", hash = "sha256:d1a9bc0d4fd80c038523ebdc451a1cce75237cfcc52dbd1aca224578001d5927"}, + {file = "patchelf-0.17.2.1.tar.gz", hash = "sha256:a6eb0dd452ce4127d0d5e1eb26515e39186fa609364274bc1b0b77539cfa7031"}, +] + +[package.extras] +test = ["importlib-metadata", "pytest"] + [[package]] name = "pluggy" version = "1.5.0" @@ -532,4 +551,4 @@ zstd = ["zstandard (>=0.18.0)"] [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "324e77023537e384bec377bf8662498f9f2763d87bf28faa0e8c884d27f9a326" +content-hash = "afb27b68ef8ac9c50a89641e92f903500bd10d8f549ccbb78eccc51651846bf8" diff --git a/integ-tests/python/pyproject.toml b/integ-tests/python/pyproject.toml index c4d2c2768..e5b00cbbf 100644 --- a/integ-tests/python/pyproject.toml +++ b/integ-tests/python/pyproject.toml @@ -21,6 +21,7 @@ pydantic = "^2.7.1" python-dotenv = "^1.0.1" assertpy = "^1.1" requests = "^2.32.3" +patchelf = "^0.17.2.1" [tool.poetry.group.dev.dependencies] types-assertpy = "^1.1.0.20240712" diff --git a/integ-tests/python/tests/test_functions.py b/integ-tests/python/tests/test_functions.py index e88acfc6b..d4fd427f2 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,23 @@ 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" + + @pytest.mark.asyncio + async def test_literal_string_union_key_in_map(self): + res = await b.InOutLiteralStringUnionMapKey({"one": "1"}, {"two": "2"}) + assert res["one"] == "1" + assert res["two"] == "2" + + @pytest.mark.asyncio + async def test_single_literal_string_key_in_map(self): + res = await b.InOutSingleLiteralStringMapKey({"key": "1"}) + assert res["key"] == "1" + 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..49a987d31 100644 --- a/integ-tests/ruby/baml_client/client.rb +++ b/integ-tests/ruby/baml_client/client.rb @@ -1778,6 +1778,102 @@ 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, + 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 InOutLiteralStringUnionMapKey( + *varargs, + i1:,i2:, + baml_options: {} + ) + if varargs.any? + + raise ArgumentError.new("InOutLiteralStringUnionMapKey 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( + "InOutLiteralStringUnionMapKey", + { + 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, + m: T::Hash[String, String], + baml_options: T::Hash[Symbol, T.any(Baml::TypeBuilder, Baml::ClientRegistry)] + ).returns(T::Hash[String, String]) + } + def InOutSingleLiteralStringMapKey( + *varargs, + m:, + baml_options: {} + ) + if varargs.any? + + raise ArgumentError.new("InOutSingleLiteralStringMapKey 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( + "InOutSingleLiteralStringMapKey", + { + m: m, + }, + @ctx_manager, + baml_options[:tb]&.instance_variable_get(:@registry), + baml_options[:client_registry], + ) + (raw.parsed_using_types(Baml::Types)) + end + sig { params( varargs: T.untyped, @@ -5601,6 +5697,111 @@ 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, + 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 InOutLiteralStringUnionMapKey( + *varargs, + i1:,i2:, + baml_options: {} + ) + if varargs.any? + + raise ArgumentError.new("InOutLiteralStringUnionMapKey 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( + "InOutLiteralStringUnionMapKey", + { + 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, + m: T::Hash[String, String], + baml_options: T::Hash[Symbol, T.any(Baml::TypeBuilder, Baml::ClientRegistry)] + ).returns(Baml::BamlStream[T::Hash[String, String]]) + } + def InOutSingleLiteralStringMapKey( + *varargs, + m:, + baml_options: {} + ) + if varargs.any? + + raise ArgumentError.new("InOutSingleLiteralStringMapKey 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( + "InOutSingleLiteralStringMapKey", + { + m: m, + }, + @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 539a9df4b..8eac818cc 100644 --- a/integ-tests/ruby/baml_client/inlined.rb +++ b/integ-tests/ruby/baml_client/inlined.rb @@ -74,6 +74,8 @@ 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/map-literal-union-key.baml" => "function InOutLiteralStringUnionMapKey(\n i1: map<\"one\" | \"two\" | (\"three\" | \"four\"), string>, \n i2: map<\"one\" | \"two\" | (\"three\" | \"four\"), string>\n) -> map<\"one\" | \"two\" | (\"three\" | \"four\"), string> {\n client \"openai/gpt-4o\"\n prompt #\"\n Merge these:\n \n {{i1}}\n \n {{i2}}\n\n {{ ctx.output_format }}\n \"#\n}\n\nfunction InOutSingleLiteralStringMapKey(m: map<\"key\", string>) -> map<\"key\", string> {\n client \"openai/gpt-4o\"\n prompt #\"\n Return the same map you were given:\n \n {{m}}\n\n {{ ctx.output_format }}\n \"#\n}\n", "test-files/functions/output/mutually-recursive-classes.baml" => "class Tree {\n data int\n children Forest\n}\n\nclass Forest {\n trees Tree[]\n}\n\nclass BinaryNode {\n data int\n left BinaryNode?\n right BinaryNode?\n}\n\nfunction BuildTree(input: BinaryNode) -> Tree {\n client GPT35\n prompt #\"\n Given the input binary tree, transform it into a generic tree using the given schema.\n\n INPUT:\n {{ input }}\n\n {{ ctx.output_format }} \n \"#\n}\n\ntest TestTree {\n functions [BuildTree]\n args {\n input {\n data 2\n left {\n data 1\n left null\n right null\n }\n right {\n data 3\n left null\n right null\n }\n }\n }\n}", "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 0a7095dfb..9c6d2c6d6 100644 --- a/integ-tests/ruby/baml_client/types.rb +++ b/integ-tests/ruby/baml_client/types.rb @@ -93,6 +93,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..01a4f4995 100644 --- a/integ-tests/ruby/test_functions.rb +++ b/integ-tests/ruby/test_functions.rb @@ -67,6 +67,17 @@ 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" + + res = b.InOutLiteralStringUnionMapKey(i1: {"one" => "1"}, i2: {"two" => "2"}) + assert_equal res['one'], "1" + assert_equal res['two'], "2" + + res = b.InOutSingleLiteralStringMapKey(m: {"key" => "1"}) + assert_equal res['key'], "1" 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..ae277c2a6 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,81 @@ 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 InOutLiteralStringUnionMapKey( + i1: Partial>,i2: Partial>, + __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } + ): Promise>> { + try { + const raw = await this.runtime.callFunction( + "InOutLiteralStringUnionMapKey", + { + "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 InOutSingleLiteralStringMapKey( + m: Partial>, + __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } + ): Promise>> { + try { + const raw = await this.runtime.callFunction( + "InOutSingleLiteralStringMapKey", + { + "m": m + }, + 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 +4756,105 @@ 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; + } + } + + InOutLiteralStringUnionMapKey( + i1: Partial>,i2: Partial>, + __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } + ): BamlStream>>, Partial>> { + try { + const raw = this.runtime.streamFunction( + "InOutLiteralStringUnionMapKey", + { + "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; + } + } + + InOutSingleLiteralStringMapKey( + m: Partial>, + __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } + ): BamlStream>>, Partial>> { + try { + const raw = this.runtime.streamFunction( + "InOutSingleLiteralStringMapKey", + { + "m": m + }, + 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 2e6da1391..439752eaa 100644 --- a/integ-tests/typescript/baml_client/inlinedbaml.ts +++ b/integ-tests/typescript/baml_client/inlinedbaml.ts @@ -75,6 +75,8 @@ 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/map-literal-union-key.baml": "function InOutLiteralStringUnionMapKey(\n i1: map<\"one\" | \"two\" | (\"three\" | \"four\"), string>, \n i2: map<\"one\" | \"two\" | (\"three\" | \"four\"), string>\n) -> map<\"one\" | \"two\" | (\"three\" | \"four\"), string> {\n client \"openai/gpt-4o\"\n prompt #\"\n Merge these:\n \n {{i1}}\n \n {{i2}}\n\n {{ ctx.output_format }}\n \"#\n}\n\nfunction InOutSingleLiteralStringMapKey(m: map<\"key\", string>) -> map<\"key\", string> {\n client \"openai/gpt-4o\"\n prompt #\"\n Return the same map you were given:\n \n {{m}}\n\n {{ ctx.output_format }}\n \"#\n}\n", "test-files/functions/output/mutually-recursive-classes.baml": "class Tree {\n data int\n children Forest\n}\n\nclass Forest {\n trees Tree[]\n}\n\nclass BinaryNode {\n data int\n left BinaryNode?\n right BinaryNode?\n}\n\nfunction BuildTree(input: BinaryNode) -> Tree {\n client GPT35\n prompt #\"\n Given the input binary tree, transform it into a generic tree using the given schema.\n\n INPUT:\n {{ input }}\n\n {{ ctx.output_format }} \n \"#\n}\n\ntest TestTree {\n functions [BuildTree]\n args {\n input {\n data 2\n left {\n data 1\n left null\n right null\n }\n right {\n data 3\n left null\n right null\n }\n }\n }\n}", "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..2cc0e6f65 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,81 @@ 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; + } + } + } + + InOutLiteralStringUnionMapKey( + i1: Partial>,i2: Partial>, + __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } + ): Partial> { + try { + const raw = this.runtime.callFunctionSync( + "InOutLiteralStringUnionMapKey", + { + "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; + } + } + } + + InOutSingleLiteralStringMapKey( + m: Partial>, + __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } + ): Partial> { + try { + const raw = this.runtime.callFunctionSync( + "InOutSingleLiteralStringMapKey", + { + "m": m + }, + 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 efe1447e9..72928abe8 100644 --- a/integ-tests/typescript/baml_client/types.ts +++ b/integ-tests/typescript/baml_client/types.ts @@ -111,6 +111,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 91771e435..b6e28a9e4 100644 --- a/integ-tests/typescript/test-report.html +++ b/integ-tests/typescript/test-report.html @@ -257,13 +257,6 @@ font-size: 1rem; padding: 0 0.5rem; } -

Test Report

Started: 2024-11-15 19:32:20
Suites (1)
0 passed
1 failed
0 pending
Tests (64)
62 passed
2 failed
0 pending
Integ tests > should work for all inputs
single bool
passed
0.823s
Integ tests > should work for all inputs
single string list
passed
1.004s
Integ tests > should work for all inputs
return literal union
failed
0.973s
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 (/Users/greghale/code/baml/engine/language_client_typescript/index.js:33:28)
-    at from (/Users/greghale/code/baml/engine/language_client_typescript/index.js:58:32)
-    at BamlAsyncClient.LiteralUnionsTest (/Users/greghale/code/baml/integ-tests/typescript/baml_client/async_client.ts:1412:50)
-    at Object.<anonymous> (/Users/greghale/code/baml/integ-tests/typescript/tests/integ-tests.test.ts:42:19)
Integ tests > should work for all inputs
single class
passed
0.97s
Integ tests > should work for all inputs
multiple classes
passed
0.706s
Integ tests > should work for all inputs
single enum list
passed
0.733s
Integ tests > should work for all inputs
single float
passed
0.949s
Integ tests > should work for all inputs
single int
passed
1.186s
Integ tests > should work for all inputs
single literal int
passed
0.789s
Integ tests > should work for all inputs
single literal bool
passed
0.692s
Integ tests > should work for all inputs
single literal string
passed
0.663s
Integ tests > should work for all inputs
single class with literal prop
passed
1.023s
Integ tests > should work for all inputs
single class with literal union prop
passed
0.943s
Integ tests > should work for all inputs
single optional string
passed
0.874s
Integ tests > should work for all inputs
single map string to string
passed
0.962s
Integ tests > should work for all inputs
single map string to class
passed
0.807s
Integ tests > should work for all inputs
single map string to map
passed
0.808s
Integ tests
should work for all outputs
passed
10.556s
Integ tests
works with retries1
passed
2.638s
Integ tests
works with retries2
passed
4.042s
Integ tests
works with fallbacks
passed
3.456s
Integ tests
should work with image from url
passed
1.682s
Integ tests
should work with image from base 64
passed
1.445s
Integ tests
should work with audio base 64
passed
1.619s
Integ tests
should work with audio from url
passed
1.988s
Integ tests
should support streaming in OpenAI
passed
3.366s
Integ tests
should support streaming in Gemini
passed
8.699s
Integ tests
should support AWS
passed
2.087s
Integ tests
should support streaming in AWS
passed
1.629s
Integ tests
should allow overriding the region
passed
0.012s
Integ tests
should support OpenAI shorthand
passed
14.804s
Integ tests
should support OpenAI shorthand streaming
passed
10.288s
Integ tests
should support anthropic shorthand
passed
3.471s
Integ tests
should support anthropic shorthand streaming
passed
2.778s
Integ tests
should support streaming without iterating
passed
2.774s
Integ tests
should support streaming in Claude
passed
1.523s
Integ tests
should support vertex
passed
9.822s
Integ tests
supports tracing sync
passed
0.012s
Integ tests
supports tracing async
passed
4.65s
Integ tests
should work with dynamic types single
passed
1.482s
Integ tests
should work with dynamic types enum
passed
1.53s
Integ tests
should work with dynamic literals
passed
1.363s
Integ tests
should work with dynamic types class
passed
1.566s
Integ tests
should work with dynamic inputs class
passed
0.912s
Integ tests
should work with dynamic inputs list
passed
1.142s
Integ tests
should work with dynamic output map
passed
1.333s
Integ tests
should work with dynamic output union
passed
2.594s
Integ tests
should work with nested classes
failed
0.104s
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: 61, kind: ConnectionRefused, message: "Connection refused" })) }
-    at BamlStream.parsed [as getFinalResponse] (/Users/greghale/code/baml/engine/language_client_typescript/stream.js:58:39)
-    at Object.<anonymous> (/Users/greghale/code/baml/integ-tests/typescript/tests/integ-tests.test.ts:584:19)
Integ tests
should work with dynamic client
passed
0.609s
Integ tests
should work with 'onLogEvent'
passed
4.141s
Integ tests
should work with a sync client
passed
1.264s
Integ tests
should raise an error when appropriate
passed
1.967s
Integ tests
should raise a BAMLValidationError
passed
0.737s
Integ tests
should reset environment variables correctly
passed
1.529s
Integ tests
should use aliases when serializing input objects - classes
passed
1.814s
Integ tests
should use aliases when serializing, but still have original keys in jinja
passed
1.783s
Integ tests
should use aliases when serializing input objects - enums
passed
0.687s
Integ tests
should use aliases when serializing input objects - lists
passed
0.688s
Integ tests
constraints: should handle checks in return types
passed
1.15s
Integ tests
constraints: should handle checks in returned unions
passed
1.245s
Integ tests
constraints: should handle block-level checks
passed
0.916s
Integ tests
constraints: should handle nested-block-level checks
passed
0.911s
Integ tests
simple recursive type
passed
3.379s
Integ tests
mutually recursive type
passed
3.077s
\ No newline at end of file +

Test Report

Started: 2024-11-19 03:07:16
Suites (1)
0 passed
1 failed
0 pending
Tests (67)
66 passed
1 failed
0 pending
Integ tests > should work for all inputs
single bool
passed
0.471s
Integ tests > should work for all inputs
single string list
passed
0.507s
Integ tests > should work for all inputs
return literal union
passed
0.407s
Integ tests > should work for all inputs
single class
passed
0.514s
Integ tests > should work for all inputs
multiple classes
passed
0.514s
Integ tests > should work for all inputs
single enum list
passed
0.71s
Integ tests > should work for all inputs
single float
passed
0.411s
Integ tests > should work for all inputs
single int
passed
0.283s
Integ tests > should work for all inputs
single literal int
passed
0.429s
Integ tests > should work for all inputs
single literal bool
passed
0.862s
Integ tests > should work for all inputs
single literal string
passed
0.571s
Integ tests > should work for all inputs
single class with literal prop
passed
0.576s
Integ tests > should work for all inputs
single class with literal union prop
passed
0.564s
Integ tests > should work for all inputs
single optional string
passed
0.396s
Integ tests > should work for all inputs
single map string to string
passed
0.508s
Integ tests > should work for all inputs
single map string to class
passed
0.721s
Integ tests > should work for all inputs
single map string to map
passed
0.618s
Integ tests > should work for all inputs
enum key in map
passed
0.714s
Integ tests > should work for all inputs
literal string union key in map
passed
0.817s
Integ tests > should work for all inputs
single literal string key in map
passed
0.69s
Integ tests
should work for all outputs
passed
5.774s
Integ tests
works with retries1
passed
0.994s
Integ tests
works with retries2
passed
2.058s
Integ tests
works with fallbacks
passed
2.055s
Integ tests
should work with image from url
passed
1.277s
Integ tests
should work with image from base 64
passed
0.984s
Integ tests
should work with audio base 64
passed
1.632s
Integ tests
should work with audio from url
passed
1.611s
Integ tests
should support streaming in OpenAI
passed
2.018s
Integ tests
should support streaming in Gemini
passed
6.424s
Integ tests
should support AWS
passed
1.619s
Integ tests
should support streaming in AWS
passed
1.527s
Integ tests
should allow overriding the region
passed
0.01s
Integ tests
should support OpenAI shorthand
passed
9.113s
Integ tests
should support OpenAI shorthand streaming
passed
8.598s
Integ tests
should support anthropic shorthand
passed
3.379s
Integ tests
should support anthropic shorthand streaming
passed
2.332s
Integ tests
should support streaming without iterating
passed
3.02s
Integ tests
should support streaming in Claude
passed
1.525s
Integ tests
should support vertex
passed
11.555s
Integ tests
supports tracing sync
passed
0.02s
Integ tests
supports tracing async
passed
2.937s
Integ tests
should work with dynamic types single
passed
1.243s
Integ tests
should work with dynamic types enum
passed
0.731s
Integ tests
should work with dynamic literals
passed
1.094s
Integ tests
should work with dynamic types class
passed
1.041s
Integ tests
should work with dynamic inputs class
passed
0.596s
Integ tests
should work with dynamic inputs list
passed
0.631s
Integ tests
should work with dynamic output map
passed
0.819s
Integ tests
should work with dynamic output union
passed
2.357s
Integ tests
should work with nested classes
failed
0.117s
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:602:19)
Integ tests
should work with dynamic client
passed
0.379s
Integ tests
should work with 'onLogEvent'
passed
2.343s
Integ tests
should work with a sync client
passed
0.498s
Integ tests
should raise an error when appropriate
passed
0.987s
Integ tests
should raise a BAMLValidationError
passed
0.477s
Integ tests
should reset environment variables correctly
passed
1.431s
Integ tests
should use aliases when serializing input objects - classes
passed
0.918s
Integ tests
should use aliases when serializing, but still have original keys in jinja
passed
0.805s
Integ tests
should use aliases when serializing input objects - enums
passed
0.424s
Integ tests
should use aliases when serializing input objects - lists
passed
0.512s
Integ tests
constraints: should handle checks in return types
passed
0.822s
Integ tests
constraints: should handle checks in returned unions
passed
0.827s
Integ tests
constraints: should handle block-level checks
passed
0.602s
Integ tests
constraints: should handle nested-block-level checks
passed
0.78s
Integ tests
simple recursive type
passed
1.427s
Integ tests
mutually recursive type
passed
2.402s
\ 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..3ad7066d1 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,23 @@ 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('literal string union key in map', async () => { + const res = await b.InOutLiteralStringUnionMapKey({ one: '1' }, { two: '2' }) + expect(res).toHaveProperty('one', '1') + expect(res).toHaveProperty('two', '2') + }) + + it('single literal string key in map', async () => { + const res = await b.InOutSingleLiteralStringMapKey({ key: '1' }) + expect(res).toHaveProperty('key', '1') + }) }) it('should work for all outputs', async () => { @@ -619,7 +637,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 +891,5 @@ describe('Integ tests', () => { afterAll(async () => { flush() - }); - + }) }) 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" + } + } +}