Sometimes you have a output schemas that change at runtime — for example if you have a list of Categories that you need to classify that come from a database, or your schema is user-provided.

Dynamic types are types that can be modified at runtime, which means you can change the output schema of a function at runtime.

Here are the steps to make this work:

  1. Add @@dynamic to the class or enum definition to mark it as dynamic
baml
1enum Category {
2 VALUE1 // normal static enum values that don't change
3 VALUE2
4 @@dynamic // this enum can have more values added at runtime
5}
6
7function DynamicCategorizer(input: string) -> Category {
8 client GPT4
9 prompt #"
10 Given a string, classify it into a category
11 {{ input }}
12
13 {{ ctx.output_format }}
14 "#
15}
  1. Create a TypeBuilder and modify the existing type. All dynamic types you define in BAML will be available as properties of TypeBuilder. Think of the typebuilder as a registry of modified runtime types that the baml function will read from when building the output schema in the prompt.
1from baml_client.type_builder import TypeBuilder
2from baml_client import b
3
4async def run():
5 tb = TypeBuilder()
6 tb.Category.add_value('VALUE3')
7 tb.Category.add_value('VALUE4')
8 # Pass the typebuilder in the baml_options argument -- the last argument of the function.
9 res = await b.DynamicCategorizer("some input", { "tb": tb })
10 # Now res can be VALUE1, VALUE2, VALUE3, or VALUE4
11 print(res)

Dynamic BAML Classes

Existing BAML classes marked with @@dynamic will be available as properties of TypeBuilder.

BAML
1class User {
2 name string
3 age int
4 @@dynamic
5}
6
7function DynamicUserCreator(user_info: string) -> User {
8 client GPT4
9 prompt #"
10 Extract the information from this chunk of text:
11 "{{ user_info }}"
12
13 {{ ctx.output_format }}
14 "#
15}

Modify the User schema at runtime:

1from baml_client.type_builder import TypeBuilder
2from baml_client import b
3
4async def run():
5 tb = TypeBuilder()
6 tb.User.add_property('email', tb.string())
7 tb.User.add_property('address', tb.string())
8 res = await b.DynamicUserCreator("some user info", { "tb": tb })
9 # Now res can have email and address fields
10 print(res)

Creating new dynamic classes or enums not in BAML

Here we create a new Hobbies enum, and a new class called Address.

1from baml_client.type_builder import TypeBuilder
2from baml_client import b
3
4async def run():
5 tb = TypeBuilder()
6 const hobbiesEnum = tb.add_enum('Hobbies')
7 hobbiesEnum.add_value('Soccer')
8 hobbiesEnum.add_value('Reading')
9
10 address_class = tb.add_class('Address')
11 address_class.add_property('street', tb.string())
12
13 tb.User.add_property('hobby', hobbiesEnum.type().optional())
14 tb.User.add_property('address', addressClass.type().optional())
15 res = await b.DynamicUserCreator("some user info", { "tb": tb })
16 # Now res might have the hobby property, which can be Soccer or Reading
17 print(res)

Adding descriptions to dynamic types

1tb = TypeBuilder()
2tb.User.add_property("email", tb.string()).description("The user's email")

Building dynamic types from JSON schema

We have a working implementation of this, but are waiting for a concrete use case to merge it. Please chime in on the GitHub issue if this is something you’d like to use.

1import pydantic
2from baml_client import b
3
4class Person(pydantic.BaseModel):
5 last_name: list[str]
6 height: Optional[float] = pydantic.Field(description="Height in meters")
7
8tb = TypeBuilder()
9tb.unstable_features.add_json_schema(Person.model_json_schema())
10
11res = await b.ExtractPeople(
12 "My name is Harrison. My hair is black and I'm 6 feet tall. I'm pretty good around the hoop. I like giraffes.",
13 {"tb": tb},
14)