diff --git a/engine/language_client_codegen/src/ruby/field_type.rs b/engine/language_client_codegen/src/ruby/field_type.rs index 691eabff3..9dc429d9d 100644 --- a/engine/language_client_codegen/src/ruby/field_type.rs +++ b/engine/language_client_codegen/src/ruby/field_type.rs @@ -6,7 +6,7 @@ impl ToRuby for FieldType { fn to_ruby(&self) -> String { match self { FieldType::Class(name) => format!("Baml::Types::{}", name.clone()), - FieldType::Enum(name) => format!("Baml::Types::{}", name.clone()), + FieldType::Enum(name) => format!("T.any(Baml::Types::{}, String)", name.clone()), // https://sorbet.org/docs/stdlib-generics FieldType::List(inner) => format!("T::Array[{}]", inner.to_ruby()), FieldType::Map(key, value) => { diff --git a/integ-tests/baml_src/test-files/dynamic/dynamic.baml b/integ-tests/baml_src/test-files/dynamic/dynamic.baml index 36ad77b5e..eb425f03c 100644 --- a/integ-tests/baml_src/test-files/dynamic/dynamic.baml +++ b/integ-tests/baml_src/test-files/dynamic/dynamic.baml @@ -81,3 +81,13 @@ function MyFunc(input: string) -> DynamicOutput { "# } +function ClassifyDynEnumTwo(input: string) -> DynEnumTwo { + client GPT35 + prompt #" + Given a string, extract info using the schema: + + {{ input}} + + {{ ctx.output_format }} + "# +} \ No newline at end of file diff --git a/integ-tests/openapi/baml_client/openapi.yaml b/integ-tests/openapi/baml_client/openapi.yaml index aa854e441..d2261ec45 100644 --- a/integ-tests/openapi/baml_client/openapi.yaml +++ b/integ-tests/openapi/baml_client/openapi.yaml @@ -45,6 +45,19 @@ paths: title: AudioInputResponse type: string operationId: AudioInput + /call/ClassifyDynEnumTwo: + post: + requestBody: + $ref: '#/components/requestBodies/ClassifyDynEnumTwo' + responses: + '200': + description: Successful operation + content: + application/json: + schema: + title: ClassifyDynEnumTwoResponse + $ref: '#/components/schemas/DynEnumTwo' + operationId: ClassifyDynEnumTwo /call/ClassifyMessage: post: requestBody: @@ -1073,6 +1086,19 @@ components: required: - aud additionalProperties: false + ClassifyDynEnumTwo: + required: true + content: + application/json: + schema: + title: ClassifyDynEnumTwoRequest + type: object + properties: + input: + type: string + required: + - input + additionalProperties: false ClassifyMessage: required: true content: diff --git a/integ-tests/python/baml_client/async_client.py b/integ-tests/python/baml_client/async_client.py index 616af5968..615706ce7 100644 --- a/integ-tests/python/baml_client/async_client.py +++ b/integ-tests/python/baml_client/async_client.py @@ -107,6 +107,30 @@ async def AudioInput( mdl = create_model("AudioInputReturnType", inner=(str, ...)) return coerce(mdl, raw.parsed()) + async def ClassifyDynEnumTwo( + self, + input: str, + baml_options: BamlCallOptions = {}, + ) -> Union[types.DynEnumTwo, str]: + __tb__ = baml_options.get("tb", None) + if __tb__ is not None: + tb = __tb__._tb + else: + tb = None + __cr__ = baml_options.get("client_registry", None) + + raw = await self.__runtime.call_function( + "ClassifyDynEnumTwo", + { + "input": input, + }, + self.__ctx_manager.get(), + tb, + __cr__, + ) + mdl = create_model("ClassifyDynEnumTwoReturnType", inner=(Union[types.DynEnumTwo, str], ...)) + return coerce(mdl, raw.parsed()) + async def ClassifyMessage( self, input: str, @@ -1984,6 +2008,39 @@ def AudioInput( self.__ctx_manager.get(), ) + def ClassifyDynEnumTwo( + self, + input: str, + baml_options: BamlCallOptions = {}, + ) -> baml_py.BamlStream[Optional[Union[types.DynEnumTwo, str]], Union[types.DynEnumTwo, str]]: + __tb__ = baml_options.get("tb", None) + if __tb__ is not None: + tb = __tb__._tb + else: + tb = None + __cr__ = baml_options.get("client_registry", None) + + raw = self.__runtime.stream_function( + "ClassifyDynEnumTwo", + { + "input": input, + }, + None, + self.__ctx_manager.get(), + tb, + __cr__, + ) + + mdl = create_model("ClassifyDynEnumTwoReturnType", inner=(Union[types.DynEnumTwo, str], ...)) + partial_mdl = create_model("ClassifyDynEnumTwoPartialReturnType", inner=(Optional[Union[types.DynEnumTwo, str]], ...)) + + return baml_py.BamlStream[Optional[Union[types.DynEnumTwo, str]], Union[types.DynEnumTwo, str]]( + raw, + lambda x: coerce(partial_mdl, x), + lambda x: coerce(mdl, x), + self.__ctx_manager.get(), + ) + def ClassifyMessage( self, input: str, diff --git a/integ-tests/python/baml_client/inlinedbaml.py b/integ-tests/python/baml_client/inlinedbaml.py index 83d134fc5..ec1ca35eb 100644 --- a/integ-tests/python/baml_client/inlinedbaml.py +++ b/integ-tests/python/baml_client/inlinedbaml.py @@ -31,7 +31,7 @@ "test-files/comments/comments.baml": "// add some functions, classes, enums etc with comments all over.", "test-files/descriptions/descriptions.baml": "\nclass Nested {\n prop3 string | null @description(#\"\n write \"three\"\n \"#)\n prop4 string | null @description(#\"\n write \"four\"\n \"#) @alias(\"blah\")\n prop20 Nested2\n}\n\nclass Nested2 {\n prop11 string | null @description(#\"\n write \"three\"\n \"#)\n prop12 string | null @description(#\"\n write \"four\"\n \"#) @alias(\"blah\")\n}\n\nclass Schema {\n prop1 string | null @description(#\"\n write \"one\"\n \"#)\n prop2 Nested | string @description(#\"\n write \"two\"\n \"#)\n prop5 (string | null)[] @description(#\"\n write \"hi\"\n \"#)\n prop6 string | Nested[] @alias(\"blah\") @description(#\"\n write the string \"blah\" regardless of the other types here\n \"#)\n nested_attrs (string | null | Nested)[] @description(#\"\n write the string \"nested\" regardless of other types\n \"#)\n parens (string | null) @description(#\"\n write \"parens1\"\n \"#)\n other_group (string | (int | string)) @description(#\"\n write \"other\"\n \"#) @alias(other)\n}\n\n\nfunction SchemaDescriptions(input: string) -> Schema {\n client GPT4o\n prompt #\"\n Return a schema with this format:\n\n {{ctx.output_format}}\n \"#\n}", "test-files/dynamic/client-registry.baml": "// Intentionally use a bad key\nclient BadClient {\n provider openai\n options {\n model \"gpt-3.5-turbo\"\n api_key \"sk-invalid\"\n }\n}\n\nfunction ExpectFailure() -> string {\n client BadClient\n\n prompt #\"\n What is the capital of England?\n \"#\n}\n", - "test-files/dynamic/dynamic.baml": "class DynamicClassOne {\n @@dynamic\n}\n\nenum DynEnumOne {\n @@dynamic\n}\n\nenum DynEnumTwo {\n @@dynamic\n}\n\nclass SomeClassNestedDynamic {\n hi string\n @@dynamic\n\n}\n\nclass DynamicClassTwo {\n hi string\n some_class SomeClassNestedDynamic\n status DynEnumOne\n @@dynamic\n}\n\nfunction DynamicFunc(input: DynamicClassOne) -> DynamicClassTwo {\n client GPT35\n prompt #\"\n Please extract the schema from \n {{ input }}\n\n {{ ctx.output_format }}\n \"#\n}\n\nclass DynInputOutput {\n testKey string\n @@dynamic\n}\n\nfunction DynamicInputOutput(input: DynInputOutput) -> DynInputOutput {\n client GPT35\n prompt #\"\n Here is some input data:\n ----\n {{ input }}\n ----\n\n Extract the information.\n {{ ctx.output_format }}\n \"#\n}\n\nfunction DynamicListInputOutput(input: DynInputOutput[]) -> DynInputOutput[] {\n client GPT35\n prompt #\"\n Here is some input data:\n ----\n {{ input }}\n ----\n\n Extract the information.\n {{ ctx.output_format }}\n \"#\n}\n\n\n\nclass DynamicOutput {\n @@dynamic\n}\n \nfunction MyFunc(input: string) -> DynamicOutput {\n client GPT35\n prompt #\"\n Given a string, extract info using the schema:\n\n {{ input}}\n\n {{ ctx.output_format }}\n \"#\n}\n\n", + "test-files/dynamic/dynamic.baml": "class DynamicClassOne {\n @@dynamic\n}\n\nenum DynEnumOne {\n @@dynamic\n}\n\nenum DynEnumTwo {\n @@dynamic\n}\n\nclass SomeClassNestedDynamic {\n hi string\n @@dynamic\n\n}\n\nclass DynamicClassTwo {\n hi string\n some_class SomeClassNestedDynamic\n status DynEnumOne\n @@dynamic\n}\n\nfunction DynamicFunc(input: DynamicClassOne) -> DynamicClassTwo {\n client GPT35\n prompt #\"\n Please extract the schema from \n {{ input }}\n\n {{ ctx.output_format }}\n \"#\n}\n\nclass DynInputOutput {\n testKey string\n @@dynamic\n}\n\nfunction DynamicInputOutput(input: DynInputOutput) -> DynInputOutput {\n client GPT35\n prompt #\"\n Here is some input data:\n ----\n {{ input }}\n ----\n\n Extract the information.\n {{ ctx.output_format }}\n \"#\n}\n\nfunction DynamicListInputOutput(input: DynInputOutput[]) -> DynInputOutput[] {\n client GPT35\n prompt #\"\n Here is some input data:\n ----\n {{ input }}\n ----\n\n Extract the information.\n {{ ctx.output_format }}\n \"#\n}\n\n\n\nclass DynamicOutput {\n @@dynamic\n}\n \nfunction MyFunc(input: string) -> DynamicOutput {\n client GPT35\n prompt #\"\n Given a string, extract info using the schema:\n\n {{ input}}\n\n {{ ctx.output_format }}\n \"#\n}\n\nfunction ClassifyDynEnumTwo(input: string) -> DynEnumTwo {\n client GPT35\n prompt #\"\n Given a string, extract info using the schema:\n\n {{ input}}\n\n {{ ctx.output_format }}\n \"#\n}", "test-files/functions/input/named-args/single/named-audio.baml": "function AudioInput(aud: audio) -> string{\n client Gemini\n prompt #\"\n {{ _.role(\"user\") }}\n\n Does this sound like a roar? Yes or no? One word no other characters.\n \n {{ aud }}\n \"#\n}\n\n\ntest TestURLAudioInput{\n functions [AudioInput]\n args {\n aud{ \n url https://actions.google.com/sounds/v1/emergency/beeper_emergency_call.ogg\n }\n } \n}\n\n\n", "test-files/functions/input/named-args/single/named-boolean.baml": "\n\nfunction TestFnNamedArgsSingleBool(myBool: bool) -> string{\n client GPT35\n prompt #\"\n Return this value back to me: {{myBool}}\n \"#\n}\n\ntest TestFnNamedArgsSingleBool {\n functions [TestFnNamedArgsSingleBool]\n args {\n myBool true\n }\n}", "test-files/functions/input/named-args/single/named-class-list.baml": "\n\n\nfunction TestFnNamedArgsSingleStringList(myArg: string[]) -> string{\n client GPT35\n prompt #\"\n Return this value back to me: {{myArg}}\n \"#\n}\n\ntest TestFnNamedArgsSingleStringList {\n functions [TestFnNamedArgsSingleStringList]\n args {\n myArg [\"hello\", \"world\"]\n }\n}", diff --git a/integ-tests/python/baml_client/sync_client.py b/integ-tests/python/baml_client/sync_client.py index 23848a578..a5b2056f2 100644 --- a/integ-tests/python/baml_client/sync_client.py +++ b/integ-tests/python/baml_client/sync_client.py @@ -105,6 +105,30 @@ def AudioInput( mdl = create_model("AudioInputReturnType", inner=(str, ...)) return coerce(mdl, raw.parsed()) + def ClassifyDynEnumTwo( + self, + input: str, + baml_options: BamlCallOptions = {}, + ) -> Union[types.DynEnumTwo, str]: + __tb__ = baml_options.get("tb", None) + if __tb__ is not None: + tb = __tb__._tb + else: + tb = None + __cr__ = baml_options.get("client_registry", None) + + raw = self.__runtime.call_function_sync( + "ClassifyDynEnumTwo", + { + "input": input, + }, + self.__ctx_manager.get(), + tb, + __cr__, + ) + mdl = create_model("ClassifyDynEnumTwoReturnType", inner=(Union[types.DynEnumTwo, str], ...)) + return coerce(mdl, raw.parsed()) + def ClassifyMessage( self, input: str, @@ -1983,6 +2007,39 @@ def AudioInput( self.__ctx_manager.get(), ) + def ClassifyDynEnumTwo( + self, + input: str, + baml_options: BamlCallOptions = {}, + ) -> baml_py.BamlSyncStream[Optional[Union[types.DynEnumTwo, str]], Union[types.DynEnumTwo, str]]: + __tb__ = baml_options.get("tb", None) + if __tb__ is not None: + tb = __tb__._tb + else: + tb = None + __cr__ = baml_options.get("client_registry", None) + + raw = self.__runtime.stream_function_sync( + "ClassifyDynEnumTwo", + { + "input": input, + }, + None, + self.__ctx_manager.get(), + tb, + __cr__, + ) + + mdl = create_model("ClassifyDynEnumTwoReturnType", inner=(Union[types.DynEnumTwo, str], ...)) + partial_mdl = create_model("ClassifyDynEnumTwoPartialReturnType", inner=(Optional[Union[types.DynEnumTwo, str]], ...)) + + return baml_py.BamlSyncStream[Optional[Union[types.DynEnumTwo, str]], Union[types.DynEnumTwo, str]]( + raw, + lambda x: coerce(partial_mdl, x), + lambda x: coerce(mdl, x), + self.__ctx_manager.get(), + ) + def ClassifyMessage( self, input: str, diff --git a/integ-tests/ruby/Rakefile b/integ-tests/ruby/Rakefile index aedb3eb15..03f1c7405 100644 --- a/integ-tests/ruby/Rakefile +++ b/integ-tests/ruby/Rakefile @@ -10,7 +10,7 @@ end Rake::TestTask.new do |t| t.libs << "../../engine/language_client_ruby/lib" t.libs << "baml_client" - # t.test_files = FileList["test_filtered.rb"] - t.test_files = FileList["test_*.rb"] + t.test_files = FileList["test_filtered.rb"] + # t.test_files = FileList["test_*.rb"] t.options = '--verbose' end \ No newline at end of file diff --git a/integ-tests/ruby/baml_client/client.rb b/integ-tests/ruby/baml_client/client.rb index 3c4a6968a..315e023bf 100644 --- a/integ-tests/ruby/baml_client/client.rb +++ b/integ-tests/ruby/baml_client/client.rb @@ -119,7 +119,39 @@ def AudioInput( varargs: T.untyped, input: String, baml_options: T::Hash[Symbol, T.any(Baml::TypeBuilder, Baml::ClientRegistry)] - ).returns(Baml::Types::Category) + ).returns(T.any(Baml::Types::DynEnumTwo, String)) + } + def ClassifyDynEnumTwo( + *varargs, + input:, + baml_options: {} + ) + if varargs.any? + + raise ArgumentError.new("ClassifyDynEnumTwo 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( + "ClassifyDynEnumTwo", + { + input: input, + }, + @ctx_manager, + baml_options[:tb]&.instance_variable_get(:@registry), + baml_options[:client_registry], + ) + (raw.parsed_using_types(Baml::Types)) + end + + sig { + params( + varargs: T.untyped, + input: String, + baml_options: T::Hash[Symbol, T.any(Baml::TypeBuilder, Baml::ClientRegistry)] + ).returns(T.any(Baml::Types::Category, String)) } def ClassifyMessage( *varargs, @@ -151,7 +183,7 @@ def ClassifyMessage( varargs: T.untyped, input: String, baml_options: T::Hash[Symbol, T.any(Baml::TypeBuilder, Baml::ClientRegistry)] - ).returns(Baml::Types::Category) + ).returns(T.any(Baml::Types::Category, String)) } def ClassifyMessage2( *varargs, @@ -183,7 +215,7 @@ def ClassifyMessage2( varargs: T.untyped, input: String, baml_options: T::Hash[Symbol, T.any(Baml::TypeBuilder, Baml::ClientRegistry)] - ).returns(Baml::Types::Category) + ).returns(T.any(Baml::Types::Category, String)) } def ClassifyMessage3( *varargs, @@ -759,7 +791,7 @@ def FnClassOptionalOutput2( varargs: T.untyped, input: String, baml_options: T::Hash[Symbol, T.any(Baml::TypeBuilder, Baml::ClientRegistry)] - ).returns(T::Array[Baml::Types::EnumOutput]) + ).returns(T::Array[T.any(Baml::Types::EnumOutput, String)]) } def FnEnumListOutput( *varargs, @@ -791,7 +823,7 @@ def FnEnumListOutput( varargs: T.untyped, input: String, baml_options: T::Hash[Symbol, T.any(Baml::TypeBuilder, Baml::ClientRegistry)] - ).returns(Baml::Types::EnumOutput) + ).returns(T.any(Baml::Types::EnumOutput, String)) } def FnEnumOutput( *varargs, @@ -1047,7 +1079,7 @@ def FnOutputStringList( varargs: T.untyped, input: String, baml_options: T::Hash[Symbol, T.any(Baml::TypeBuilder, Baml::ClientRegistry)] - ).returns(Baml::Types::TestEnum) + ).returns(T.any(Baml::Types::TestEnum, String)) } def FnTestAliasedEnumOutput( *varargs, @@ -1109,7 +1141,7 @@ def FnTestClassAlias( sig { params( varargs: T.untyped, - myArg: Baml::Types::NamedArgsSingleEnum, + myArg: T.any(Baml::Types::NamedArgsSingleEnum, String), baml_options: T::Hash[Symbol, T.any(Baml::TypeBuilder, Baml::ClientRegistry)] ).returns(String) } @@ -1845,7 +1877,7 @@ def TestFnNamedArgsSingleClass( sig { params( varargs: T.untyped, - myArg: T::Array[Baml::Types::NamedArgsSingleEnumList], + myArg: T::Array[T.any(Baml::Types::NamedArgsSingleEnumList, String)], baml_options: T::Hash[Symbol, T.any(Baml::TypeBuilder, Baml::ClientRegistry)] ).returns(String) } @@ -2602,7 +2634,42 @@ def AudioInput( varargs: T.untyped, input: String, baml_options: T::Hash[Symbol, T.any(Baml::TypeBuilder, Baml::ClientRegistry)] - ).returns(Baml::BamlStream[Baml::Types::Category]) + ).returns(Baml::BamlStream[T.any(Baml::Types::DynEnumTwo, String)]) + } + def ClassifyDynEnumTwo( + *varargs, + input:, + baml_options: {} + ) + if varargs.any? + + raise ArgumentError.new("ClassifyDynEnumTwo 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( + "ClassifyDynEnumTwo", + { + input: input, + }, + @ctx_manager, + baml_options[:tb]&.instance_variable_get(:@registry), + baml_options[:client_registry], + ) + Baml::BamlStream[T.nilable(Baml::Types::DynEnumTwo), T.any(Baml::Types::DynEnumTwo, String)].new( + ffi_stream: raw, + ctx_manager: @ctx_manager + ) + end + + sig { + params( + varargs: T.untyped, + input: String, + baml_options: T::Hash[Symbol, T.any(Baml::TypeBuilder, Baml::ClientRegistry)] + ).returns(Baml::BamlStream[T.any(Baml::Types::Category, String)]) } def ClassifyMessage( *varargs, @@ -2626,7 +2693,7 @@ def ClassifyMessage( baml_options[:tb]&.instance_variable_get(:@registry), baml_options[:client_registry], ) - Baml::BamlStream[T.nilable(Baml::Types::Category), Baml::Types::Category].new( + Baml::BamlStream[T.nilable(Baml::Types::Category), T.any(Baml::Types::Category, String)].new( ffi_stream: raw, ctx_manager: @ctx_manager ) @@ -2637,7 +2704,7 @@ def ClassifyMessage( varargs: T.untyped, input: String, baml_options: T::Hash[Symbol, T.any(Baml::TypeBuilder, Baml::ClientRegistry)] - ).returns(Baml::BamlStream[Baml::Types::Category]) + ).returns(Baml::BamlStream[T.any(Baml::Types::Category, String)]) } def ClassifyMessage2( *varargs, @@ -2661,7 +2728,7 @@ def ClassifyMessage2( baml_options[:tb]&.instance_variable_get(:@registry), baml_options[:client_registry], ) - Baml::BamlStream[T.nilable(Baml::Types::Category), Baml::Types::Category].new( + Baml::BamlStream[T.nilable(Baml::Types::Category), T.any(Baml::Types::Category, String)].new( ffi_stream: raw, ctx_manager: @ctx_manager ) @@ -2672,7 +2739,7 @@ def ClassifyMessage2( varargs: T.untyped, input: String, baml_options: T::Hash[Symbol, T.any(Baml::TypeBuilder, Baml::ClientRegistry)] - ).returns(Baml::BamlStream[Baml::Types::Category]) + ).returns(Baml::BamlStream[T.any(Baml::Types::Category, String)]) } def ClassifyMessage3( *varargs, @@ -2696,7 +2763,7 @@ def ClassifyMessage3( baml_options[:tb]&.instance_variable_get(:@registry), baml_options[:client_registry], ) - Baml::BamlStream[T.nilable(Baml::Types::Category), Baml::Types::Category].new( + Baml::BamlStream[T.nilable(Baml::Types::Category), T.any(Baml::Types::Category, String)].new( ffi_stream: raw, ctx_manager: @ctx_manager ) @@ -3302,7 +3369,7 @@ def FnClassOptionalOutput2( varargs: T.untyped, input: String, baml_options: T::Hash[Symbol, T.any(Baml::TypeBuilder, Baml::ClientRegistry)] - ).returns(Baml::BamlStream[T::Array[Baml::Types::EnumOutput]]) + ).returns(Baml::BamlStream[T::Array[T.any(Baml::Types::EnumOutput, String)]]) } def FnEnumListOutput( *varargs, @@ -3326,7 +3393,7 @@ def FnEnumListOutput( baml_options[:tb]&.instance_variable_get(:@registry), baml_options[:client_registry], ) - Baml::BamlStream[T::Array[T.nilable(Baml::Types::EnumOutput)], T::Array[Baml::Types::EnumOutput]].new( + Baml::BamlStream[T::Array[T.nilable(Baml::Types::EnumOutput)], T::Array[T.any(Baml::Types::EnumOutput, String)]].new( ffi_stream: raw, ctx_manager: @ctx_manager ) @@ -3337,7 +3404,7 @@ def FnEnumListOutput( varargs: T.untyped, input: String, baml_options: T::Hash[Symbol, T.any(Baml::TypeBuilder, Baml::ClientRegistry)] - ).returns(Baml::BamlStream[Baml::Types::EnumOutput]) + ).returns(Baml::BamlStream[T.any(Baml::Types::EnumOutput, String)]) } def FnEnumOutput( *varargs, @@ -3361,7 +3428,7 @@ def FnEnumOutput( baml_options[:tb]&.instance_variable_get(:@registry), baml_options[:client_registry], ) - Baml::BamlStream[T.nilable(Baml::Types::EnumOutput), Baml::Types::EnumOutput].new( + Baml::BamlStream[T.nilable(Baml::Types::EnumOutput), T.any(Baml::Types::EnumOutput, String)].new( ffi_stream: raw, ctx_manager: @ctx_manager ) @@ -3617,7 +3684,7 @@ def FnOutputStringList( varargs: T.untyped, input: String, baml_options: T::Hash[Symbol, T.any(Baml::TypeBuilder, Baml::ClientRegistry)] - ).returns(Baml::BamlStream[Baml::Types::TestEnum]) + ).returns(Baml::BamlStream[T.any(Baml::Types::TestEnum, String)]) } def FnTestAliasedEnumOutput( *varargs, @@ -3641,7 +3708,7 @@ def FnTestAliasedEnumOutput( baml_options[:tb]&.instance_variable_get(:@registry), baml_options[:client_registry], ) - Baml::BamlStream[T.nilable(Baml::Types::TestEnum), Baml::Types::TestEnum].new( + Baml::BamlStream[T.nilable(Baml::Types::TestEnum), T.any(Baml::Types::TestEnum, String)].new( ffi_stream: raw, ctx_manager: @ctx_manager ) @@ -3685,7 +3752,7 @@ def FnTestClassAlias( sig { params( varargs: T.untyped, - myArg: Baml::Types::NamedArgsSingleEnum, + myArg: T.any(Baml::Types::NamedArgsSingleEnum, String), baml_options: T::Hash[Symbol, T.any(Baml::TypeBuilder, Baml::ClientRegistry)] ).returns(Baml::BamlStream[String]) } @@ -4490,7 +4557,7 @@ def TestFnNamedArgsSingleClass( sig { params( varargs: T.untyped, - myArg: T::Array[Baml::Types::NamedArgsSingleEnumList], + myArg: T::Array[T.any(Baml::Types::NamedArgsSingleEnumList, String)], baml_options: T::Hash[Symbol, T.any(Baml::TypeBuilder, Baml::ClientRegistry)] ).returns(Baml::BamlStream[String]) } diff --git a/integ-tests/ruby/baml_client/inlined.rb b/integ-tests/ruby/baml_client/inlined.rb index f3e13faec..f40c169f7 100644 --- a/integ-tests/ruby/baml_client/inlined.rb +++ b/integ-tests/ruby/baml_client/inlined.rb @@ -31,7 +31,7 @@ module Inlined "test-files/comments/comments.baml" => "// add some functions, classes, enums etc with comments all over.", "test-files/descriptions/descriptions.baml" => "\nclass Nested {\n prop3 string | null @description(#\"\n write \"three\"\n \"#)\n prop4 string | null @description(#\"\n write \"four\"\n \"#) @alias(\"blah\")\n prop20 Nested2\n}\n\nclass Nested2 {\n prop11 string | null @description(#\"\n write \"three\"\n \"#)\n prop12 string | null @description(#\"\n write \"four\"\n \"#) @alias(\"blah\")\n}\n\nclass Schema {\n prop1 string | null @description(#\"\n write \"one\"\n \"#)\n prop2 Nested | string @description(#\"\n write \"two\"\n \"#)\n prop5 (string | null)[] @description(#\"\n write \"hi\"\n \"#)\n prop6 string | Nested[] @alias(\"blah\") @description(#\"\n write the string \"blah\" regardless of the other types here\n \"#)\n nested_attrs (string | null | Nested)[] @description(#\"\n write the string \"nested\" regardless of other types\n \"#)\n parens (string | null) @description(#\"\n write \"parens1\"\n \"#)\n other_group (string | (int | string)) @description(#\"\n write \"other\"\n \"#) @alias(other)\n}\n\n\nfunction SchemaDescriptions(input: string) -> Schema {\n client GPT4o\n prompt #\"\n Return a schema with this format:\n\n {{ctx.output_format}}\n \"#\n}", "test-files/dynamic/client-registry.baml" => "// Intentionally use a bad key\nclient BadClient {\n provider openai\n options {\n model \"gpt-3.5-turbo\"\n api_key \"sk-invalid\"\n }\n}\n\nfunction ExpectFailure() -> string {\n client BadClient\n\n prompt #\"\n What is the capital of England?\n \"#\n}\n", - "test-files/dynamic/dynamic.baml" => "class DynamicClassOne {\n @@dynamic\n}\n\nenum DynEnumOne {\n @@dynamic\n}\n\nenum DynEnumTwo {\n @@dynamic\n}\n\nclass SomeClassNestedDynamic {\n hi string\n @@dynamic\n\n}\n\nclass DynamicClassTwo {\n hi string\n some_class SomeClassNestedDynamic\n status DynEnumOne\n @@dynamic\n}\n\nfunction DynamicFunc(input: DynamicClassOne) -> DynamicClassTwo {\n client GPT35\n prompt #\"\n Please extract the schema from \n {{ input }}\n\n {{ ctx.output_format }}\n \"#\n}\n\nclass DynInputOutput {\n testKey string\n @@dynamic\n}\n\nfunction DynamicInputOutput(input: DynInputOutput) -> DynInputOutput {\n client GPT35\n prompt #\"\n Here is some input data:\n ----\n {{ input }}\n ----\n\n Extract the information.\n {{ ctx.output_format }}\n \"#\n}\n\nfunction DynamicListInputOutput(input: DynInputOutput[]) -> DynInputOutput[] {\n client GPT35\n prompt #\"\n Here is some input data:\n ----\n {{ input }}\n ----\n\n Extract the information.\n {{ ctx.output_format }}\n \"#\n}\n\n\n\nclass DynamicOutput {\n @@dynamic\n}\n \nfunction MyFunc(input: string) -> DynamicOutput {\n client GPT35\n prompt #\"\n Given a string, extract info using the schema:\n\n {{ input}}\n\n {{ ctx.output_format }}\n \"#\n}\n\n", + "test-files/dynamic/dynamic.baml" => "class DynamicClassOne {\n @@dynamic\n}\n\nenum DynEnumOne {\n @@dynamic\n}\n\nenum DynEnumTwo {\n @@dynamic\n}\n\nclass SomeClassNestedDynamic {\n hi string\n @@dynamic\n\n}\n\nclass DynamicClassTwo {\n hi string\n some_class SomeClassNestedDynamic\n status DynEnumOne\n @@dynamic\n}\n\nfunction DynamicFunc(input: DynamicClassOne) -> DynamicClassTwo {\n client GPT35\n prompt #\"\n Please extract the schema from \n {{ input }}\n\n {{ ctx.output_format }}\n \"#\n}\n\nclass DynInputOutput {\n testKey string\n @@dynamic\n}\n\nfunction DynamicInputOutput(input: DynInputOutput) -> DynInputOutput {\n client GPT35\n prompt #\"\n Here is some input data:\n ----\n {{ input }}\n ----\n\n Extract the information.\n {{ ctx.output_format }}\n \"#\n}\n\nfunction DynamicListInputOutput(input: DynInputOutput[]) -> DynInputOutput[] {\n client GPT35\n prompt #\"\n Here is some input data:\n ----\n {{ input }}\n ----\n\n Extract the information.\n {{ ctx.output_format }}\n \"#\n}\n\n\n\nclass DynamicOutput {\n @@dynamic\n}\n \nfunction MyFunc(input: string) -> DynamicOutput {\n client GPT35\n prompt #\"\n Given a string, extract info using the schema:\n\n {{ input}}\n\n {{ ctx.output_format }}\n \"#\n}\n\nfunction ClassifyDynEnumTwo(input: string) -> DynEnumTwo {\n client GPT35\n prompt #\"\n Given a string, extract info using the schema:\n\n {{ input}}\n\n {{ ctx.output_format }}\n \"#\n}", "test-files/functions/input/named-args/single/named-audio.baml" => "function AudioInput(aud: audio) -> string{\n client Gemini\n prompt #\"\n {{ _.role(\"user\") }}\n\n Does this sound like a roar? Yes or no? One word no other characters.\n \n {{ aud }}\n \"#\n}\n\n\ntest TestURLAudioInput{\n functions [AudioInput]\n args {\n aud{ \n url https://actions.google.com/sounds/v1/emergency/beeper_emergency_call.ogg\n }\n } \n}\n\n\n", "test-files/functions/input/named-args/single/named-boolean.baml" => "\n\nfunction TestFnNamedArgsSingleBool(myBool: bool) -> string{\n client GPT35\n prompt #\"\n Return this value back to me: {{myBool}}\n \"#\n}\n\ntest TestFnNamedArgsSingleBool {\n functions [TestFnNamedArgsSingleBool]\n args {\n myBool true\n }\n}", "test-files/functions/input/named-args/single/named-class-list.baml" => "\n\n\nfunction TestFnNamedArgsSingleStringList(myArg: string[]) -> string{\n client GPT35\n prompt #\"\n Return this value back to me: {{myArg}}\n \"#\n}\n\ntest TestFnNamedArgsSingleStringList {\n functions [TestFnNamedArgsSingleStringList]\n args {\n myArg [\"hello\", \"world\"]\n }\n}", diff --git a/integ-tests/ruby/baml_client/types.rb b/integ-tests/ruby/baml_client/types.rb index 708a3aa4f..6752ac616 100644 --- a/integ-tests/ruby/baml_client/types.rb +++ b/integ-tests/ruby/baml_client/types.rb @@ -304,7 +304,7 @@ class DynamicClassTwo < T::Struct include Baml::Sorbet::Struct const :hi, String const :some_class, Baml::Types::SomeClassNestedDynamic - const :status, Baml::Types::DynEnumOne + const :status, T.any(Baml::Types::DynEnumOne, String) def initialize(props) super( @@ -524,7 +524,7 @@ class OptionalTest_ReturnType < T::Struct include Baml::Sorbet::Struct const :omega_1, T.nilable(Baml::Types::OptionalTest_Prop1) const :omega_2, T.nilable(String) - const :omega_3, T::Array[T.nilable(Baml::Types::OptionalTest_CategoryType)] + const :omega_3, T::Array[T.nilable(T.any(Baml::Types::OptionalTest_CategoryType, String))] def initialize(props) super( @@ -538,7 +538,7 @@ def initialize(props) end class OrderInfo < T::Struct include Baml::Sorbet::Struct - const :order_status, Baml::Types::OrderStatus + const :order_status, T.any(Baml::Types::OrderStatus, String) const :tracking_number, T.nilable(String) const :estimated_arrival_date, T.nilable(String) @@ -555,7 +555,7 @@ def initialize(props) class Person < T::Struct include Baml::Sorbet::Struct const :name, T.nilable(String) - const :hair_color, T.nilable(Baml::Types::Color) + const :hair_color, T.nilable(T.any(Baml::Types::Color, String)) def initialize(props) super( @@ -582,7 +582,7 @@ def initialize(props) end class RaysData < T::Struct include Baml::Sorbet::Struct - const :dataType, Baml::Types::DataType + const :dataType, T.any(Baml::Types::DataType, String) const :value, T.any(Baml::Types::Resume, Baml::Types::Event) def initialize(props) @@ -691,7 +691,7 @@ class SearchParams < T::Struct const :jobTitle, T.nilable(Baml::Types::WithReasoning) const :company, T.nilable(Baml::Types::WithReasoning) const :description, T::Array[Baml::Types::WithReasoning] - const :tags, T::Array[T.any(Baml::Types::Tag, String)] + const :tags, T::Array[T.any(T.any(Baml::Types::Tag, String), String)] def initialize(props) super( @@ -767,7 +767,7 @@ def initialize(props) class TestClassWithEnum < T::Struct include Baml::Sorbet::Struct const :prop1, String - const :prop2, Baml::Types::EnumInClass + const :prop2, T.any(Baml::Types::EnumInClass, String) def initialize(props) super( diff --git a/integ-tests/ruby/test_filtered.rb b/integ-tests/ruby/test_filtered.rb index 203ce9652..38fd2de63 100644 --- a/integ-tests/ruby/test_filtered.rb +++ b/integ-tests/ruby/test_filtered.rb @@ -8,5 +8,23 @@ b = Baml.Client describe "ruby<->baml integration tests (filtered)" do + it "tests dynamic enum output" do + t = Baml::TypeBuilder.new + t.DynEnumTwo.add_value("positive").description("The feedback expresses satisfaction or praise.") + t.DynEnumTwo.add_value("neutral").description("The feedback is neither clearly positive nor negative.") + t.DynEnumTwo.add_value("negative").description("The feedback expresses dissatisfaction or complaints.") + + # TODO: figure out a non-naive impl of #list_properties in Ruby + # puts t.DynamicOutput.list_properties + # t.DynamicOutput.list_properties.each do |prop| + # puts "Property: #{prop}" + # end + + output = b.ClassifyDynEnumTwo( + input: "My name is Harrison. My hair is black and I'm 6 feet tall.", + baml_options: {tb: t} + ) + puts output.inspect + end end \ No newline at end of file diff --git a/integ-tests/typescript/baml_client/async_client.ts b/integ-tests/typescript/baml_client/async_client.ts index f76248fed..f9b3a82e0 100644 --- a/integ-tests/typescript/baml_client/async_client.ts +++ b/integ-tests/typescript/baml_client/async_client.ts @@ -74,6 +74,22 @@ export class BamlAsyncClient { return raw.parsed() as string } + async ClassifyDynEnumTwo( + input: string, + __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } + ): Promise<(string | DynEnumTwo)> { + const raw = await this.runtime.callFunction( + "ClassifyDynEnumTwo", + { + "input": input + }, + this.ctx_manager.cloneContext(), + __baml_options__?.tb?.__tb(), + __baml_options__?.clientRegistry, + ) + return raw.parsed() as (string | DynEnumTwo) + } + async ClassifyMessage( input: string, __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } @@ -1326,6 +1342,29 @@ class BamlStreamClient { ) } + ClassifyDynEnumTwo( + input: string, + __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } + ): BamlStream, (string | DynEnumTwo)> { + const raw = this.runtime.streamFunction( + "ClassifyDynEnumTwo", + { + "input": input + }, + undefined, + this.ctx_manager.cloneContext(), + __baml_options__?.tb?.__tb(), + __baml_options__?.clientRegistry, + ) + return new BamlStream, (string | DynEnumTwo)>( + raw, + (a): a is RecursivePartialNull<(string | DynEnumTwo)> => a, + (a): a is (string | DynEnumTwo) => a, + this.ctx_manager.cloneContext(), + __baml_options__?.tb?.__tb(), + ) + } + ClassifyMessage( 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 f3461fba6..4706bde54 100644 --- a/integ-tests/typescript/baml_client/inlinedbaml.ts +++ b/integ-tests/typescript/baml_client/inlinedbaml.ts @@ -32,7 +32,7 @@ const fileMap = { "test-files/comments/comments.baml": "// add some functions, classes, enums etc with comments all over.", "test-files/descriptions/descriptions.baml": "\nclass Nested {\n prop3 string | null @description(#\"\n write \"three\"\n \"#)\n prop4 string | null @description(#\"\n write \"four\"\n \"#) @alias(\"blah\")\n prop20 Nested2\n}\n\nclass Nested2 {\n prop11 string | null @description(#\"\n write \"three\"\n \"#)\n prop12 string | null @description(#\"\n write \"four\"\n \"#) @alias(\"blah\")\n}\n\nclass Schema {\n prop1 string | null @description(#\"\n write \"one\"\n \"#)\n prop2 Nested | string @description(#\"\n write \"two\"\n \"#)\n prop5 (string | null)[] @description(#\"\n write \"hi\"\n \"#)\n prop6 string | Nested[] @alias(\"blah\") @description(#\"\n write the string \"blah\" regardless of the other types here\n \"#)\n nested_attrs (string | null | Nested)[] @description(#\"\n write the string \"nested\" regardless of other types\n \"#)\n parens (string | null) @description(#\"\n write \"parens1\"\n \"#)\n other_group (string | (int | string)) @description(#\"\n write \"other\"\n \"#) @alias(other)\n}\n\n\nfunction SchemaDescriptions(input: string) -> Schema {\n client GPT4o\n prompt #\"\n Return a schema with this format:\n\n {{ctx.output_format}}\n \"#\n}", "test-files/dynamic/client-registry.baml": "// Intentionally use a bad key\nclient BadClient {\n provider openai\n options {\n model \"gpt-3.5-turbo\"\n api_key \"sk-invalid\"\n }\n}\n\nfunction ExpectFailure() -> string {\n client BadClient\n\n prompt #\"\n What is the capital of England?\n \"#\n}\n", - "test-files/dynamic/dynamic.baml": "class DynamicClassOne {\n @@dynamic\n}\n\nenum DynEnumOne {\n @@dynamic\n}\n\nenum DynEnumTwo {\n @@dynamic\n}\n\nclass SomeClassNestedDynamic {\n hi string\n @@dynamic\n\n}\n\nclass DynamicClassTwo {\n hi string\n some_class SomeClassNestedDynamic\n status DynEnumOne\n @@dynamic\n}\n\nfunction DynamicFunc(input: DynamicClassOne) -> DynamicClassTwo {\n client GPT35\n prompt #\"\n Please extract the schema from \n {{ input }}\n\n {{ ctx.output_format }}\n \"#\n}\n\nclass DynInputOutput {\n testKey string\n @@dynamic\n}\n\nfunction DynamicInputOutput(input: DynInputOutput) -> DynInputOutput {\n client GPT35\n prompt #\"\n Here is some input data:\n ----\n {{ input }}\n ----\n\n Extract the information.\n {{ ctx.output_format }}\n \"#\n}\n\nfunction DynamicListInputOutput(input: DynInputOutput[]) -> DynInputOutput[] {\n client GPT35\n prompt #\"\n Here is some input data:\n ----\n {{ input }}\n ----\n\n Extract the information.\n {{ ctx.output_format }}\n \"#\n}\n\n\n\nclass DynamicOutput {\n @@dynamic\n}\n \nfunction MyFunc(input: string) -> DynamicOutput {\n client GPT35\n prompt #\"\n Given a string, extract info using the schema:\n\n {{ input}}\n\n {{ ctx.output_format }}\n \"#\n}\n\n", + "test-files/dynamic/dynamic.baml": "class DynamicClassOne {\n @@dynamic\n}\n\nenum DynEnumOne {\n @@dynamic\n}\n\nenum DynEnumTwo {\n @@dynamic\n}\n\nclass SomeClassNestedDynamic {\n hi string\n @@dynamic\n\n}\n\nclass DynamicClassTwo {\n hi string\n some_class SomeClassNestedDynamic\n status DynEnumOne\n @@dynamic\n}\n\nfunction DynamicFunc(input: DynamicClassOne) -> DynamicClassTwo {\n client GPT35\n prompt #\"\n Please extract the schema from \n {{ input }}\n\n {{ ctx.output_format }}\n \"#\n}\n\nclass DynInputOutput {\n testKey string\n @@dynamic\n}\n\nfunction DynamicInputOutput(input: DynInputOutput) -> DynInputOutput {\n client GPT35\n prompt #\"\n Here is some input data:\n ----\n {{ input }}\n ----\n\n Extract the information.\n {{ ctx.output_format }}\n \"#\n}\n\nfunction DynamicListInputOutput(input: DynInputOutput[]) -> DynInputOutput[] {\n client GPT35\n prompt #\"\n Here is some input data:\n ----\n {{ input }}\n ----\n\n Extract the information.\n {{ ctx.output_format }}\n \"#\n}\n\n\n\nclass DynamicOutput {\n @@dynamic\n}\n \nfunction MyFunc(input: string) -> DynamicOutput {\n client GPT35\n prompt #\"\n Given a string, extract info using the schema:\n\n {{ input}}\n\n {{ ctx.output_format }}\n \"#\n}\n\nfunction ClassifyDynEnumTwo(input: string) -> DynEnumTwo {\n client GPT35\n prompt #\"\n Given a string, extract info using the schema:\n\n {{ input}}\n\n {{ ctx.output_format }}\n \"#\n}", "test-files/functions/input/named-args/single/named-audio.baml": "function AudioInput(aud: audio) -> string{\n client Gemini\n prompt #\"\n {{ _.role(\"user\") }}\n\n Does this sound like a roar? Yes or no? One word no other characters.\n \n {{ aud }}\n \"#\n}\n\n\ntest TestURLAudioInput{\n functions [AudioInput]\n args {\n aud{ \n url https://actions.google.com/sounds/v1/emergency/beeper_emergency_call.ogg\n }\n } \n}\n\n\n", "test-files/functions/input/named-args/single/named-boolean.baml": "\n\nfunction TestFnNamedArgsSingleBool(myBool: bool) -> string{\n client GPT35\n prompt #\"\n Return this value back to me: {{myBool}}\n \"#\n}\n\ntest TestFnNamedArgsSingleBool {\n functions [TestFnNamedArgsSingleBool]\n args {\n myBool true\n }\n}", "test-files/functions/input/named-args/single/named-class-list.baml": "\n\n\nfunction TestFnNamedArgsSingleStringList(myArg: string[]) -> string{\n client GPT35\n prompt #\"\n Return this value back to me: {{myArg}}\n \"#\n}\n\ntest TestFnNamedArgsSingleStringList {\n functions [TestFnNamedArgsSingleStringList]\n args {\n myArg [\"hello\", \"world\"]\n }\n}", diff --git a/integ-tests/typescript/baml_client/sync_client.ts b/integ-tests/typescript/baml_client/sync_client.ts index 534fa022d..fea57cbc0 100644 --- a/integ-tests/typescript/baml_client/sync_client.ts +++ b/integ-tests/typescript/baml_client/sync_client.ts @@ -74,6 +74,22 @@ export class BamlSyncClient { return raw.parsed() as string } + ClassifyDynEnumTwo( + input: string, + __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry } + ): (string | DynEnumTwo) { + const raw = this.runtime.callFunctionSync( + "ClassifyDynEnumTwo", + { + "input": input + }, + this.ctx_manager.cloneContext(), + __baml_options__?.tb?.__tb(), + __baml_options__?.clientRegistry, + ) + return raw.parsed() as (string | DynEnumTwo) + } + ClassifyMessage( input: string, __baml_options__?: { tb?: TypeBuilder, clientRegistry?: ClientRegistry }