TypeBuilder

TypeBuilder is used to create or modify output schemas at runtime. It’s particularly useful when you have dynamic output structures that can’t be determined at compile time - like categories from a database or user-provided schemas.

Here’s a simple example of using TypeBuilder to add new enum values before calling a BAML function:

BAML Code

1enum Category {
2 RED
3 BLUE
4 @@dynamic // Makes this enum modifiable at runtime
5}
6
7function Categorize(text: string) -> Category {
8 prompt #"
9 Categorize this text:
10 {{ text }}
11
12 {{ ctx.output_format }}
13 "#
14}

Runtime Usage

1from baml_client.type_builder import TypeBuilder
2from baml_client import b
3
4# Create a TypeBuilder instance
5tb = TypeBuilder()
6
7# Add new values to the Category enum
8tb.Category.add_value('GREEN')
9tb.Category.add_value('YELLOW')
10
11# Pass the typebuilder when calling the function
12result = await b.Categorize("The sun is bright", {"tb": tb})
13# result can now be RED, BLUE, GREEN, or YELLOW

Dynamic Types

There are two ways to use TypeBuilder:

  1. Modifying existing BAML types marked with @@dynamic
  2. Creating entirely new types at runtime

Modifying Existing Types

To modify an existing BAML type, mark it with @@dynamic:

Classes
example
1class User {
2 name string
3 age int
4 @@dynamic // Allow adding more properties
5}

Runtime Usage

1tb = TypeBuilder()
2tb.User.add_property('email', tb.string())
3tb.User.add_property('address', tb.string())
Enums
example
1enum Category {
2 VALUE1
3 VALUE2
4 @@dynamic // Allow adding more values
5}

Runtime Usage

1tb = TypeBuilder()
2tb.Category.add_value('VALUE3')
3tb.Category.add_value('VALUE4')

Creating New Types

You can also create entirely new types at runtime:

1tb = TypeBuilder()
2
3# Create a new enum
4hobbies = tb.add_enum("Hobbies")
5hobbies.add_value("Soccer")
6hobbies.add_value("Reading")
7
8# Create a new class
9address = tb.add_class("Address")
10address.add_property("street", tb.string())
11address.add_property("city", tb.string())
12
13# Attach new types to existing BAML type
14tb.User.add_property("hobbies", hobbies.type().list())
15tb.User.add_property("address", address.type())

Type Builders

TypeBuilder provides methods for building different kinds of types:

MethodDescriptionExample
string()Creates a string typetb.string()
int()Creates an integer typetb.int()
float()Creates a float typetb.float()
bool()Creates a boolean typetb.bool()
list()Makes a type into a listtb.string().list()
optional()Makes a type optionaltb.string().optional()

Adding Descriptions

You can add descriptions to properties and enum values to help guide the LLM:

1tb = TypeBuilder()
2
3# Add description to a property
4tb.User.add_property("email", tb.string()) \
5 .description("User's primary email address")
6
7# Add description to an enum value
8tb.Category.add_value("URGENT") \
9 .description("Needs immediate attention")

Common Patterns

Here are some common patterns when using TypeBuilder:

  1. Dynamic Categories: When categories come from a database or external source
1categories = fetch_categories_from_db()
2tb = TypeBuilder()
3for category in categories:
4 tb.Category.add_value(category)
  1. Form Fields: When extracting dynamic form fields
1fields = get_form_fields()
2tb = TypeBuilder()
3form = tb.add_class("Form")
4for field in fields:
5 form.add_property(field.name, tb.string())
  1. Optional Properties: When some fields might not be present
1tb = TypeBuilder()
2tb.User.add_property("middle_name", tb.string().optional())

All types added through TypeBuilder must be connected to the return type of your BAML function. Standalone types that aren’t referenced won’t affect the output schema.

Future Features

We’re working on additional features for TypeBuilder:

  • JSON Schema support (awaiting use cases)
  • OpenAPI schema integration
  • Pydantic model support

If you’re interested in these features, please join the discussion in our GitHub issues.