Union types allow a value to be one of several specified types. This is particularly useful for function calling, where different return types may be needed depending on context.
Syntax
A type that can be any of the specified types
int | string // Can be an integer or string
(int | string) | MyClass // Parentheses for clarity
string | MyClass | int[] // Multiple types including arrays
Order is important! int | string is NOT the same as string | int.For example, "1" will be parsed as:
int when the type is int | string
string when the type is string | int
The parser attempts to match types from left to right.
Basic Union Example
function ProcessData(input: int | string) -> string {
client "openai/gpt-4o-mini"
prompt #"
Process this input: {{ input }}
{% if input is number %}
(Treating as a number)
{% else %}
(Treating as text)
{% endif %}
"#
}
test NumericInput {
functions [ProcessData]
args { input 42 }
}
test TextInput {
functions [ProcessData]
args { input "hello" }
}
Generated Types
from typing import Union
input: Union[int, str] = 42
# or
input: Union[int, str] = "hello"
Literal Union Types
Primitive types can be constrained to specific literal values:
function ClassifyIssue(issue_description: string) -> "bug" | "enhancement" {
client "openai/gpt-4o-mini"
prompt #"
Classify the issue based on the following description:
{{ ctx.output_format }}
{{ _.role("user")}}
{{ issue_description }}
"#
}
test BugReport {
functions [ClassifyIssue]
args { issue_description "App crashes on startup" }
}
Generated Types
from typing import Literal
result: Literal["bug", "enhancement"] = "bug"
Union with Classes
Unions are powerful for function calling scenarios:
class EmailAction {
recipient string
subject string
body string
}
class CalendarAction {
event_name string
date string
duration_minutes int
}
class SearchAction {
query string
max_results int
}
function DetermineAction(user_request: string) -> EmailAction | CalendarAction | SearchAction {
client "openai/gpt-4o-mini"
prompt #"
Based on the user's request, determine which action to take:
{{ ctx.output_format }}
User request: {{ user_request }}
"#
}
test EmailRequest {
functions [DetermineAction]
args { user_request "Send an email to [email protected] about the meeting" }
}
test CalendarRequest {
functions [DetermineAction]
args { user_request "Schedule a 30-minute meeting tomorrow at 2pm" }
}
Generated Types
from typing import Union
from baml_client.types import EmailAction, CalendarAction, SearchAction
result: Union[EmailAction, CalendarAction, SearchAction] = EmailAction(
recipient="[email protected]",
subject="Meeting",
body="..."
)
Union with Optional Types
function GetValue(key: string) -> int? | string {
client "openai/gpt-4o-mini"
prompt #"
Return the value for key: {{ key }}
If it's a number, return the number.
If it's text, return the text.
If not found, return null.
"#
}
Generated Types
from typing import Union, Optional
result: Union[Optional[int], str] = None # null case
# or
result: Union[Optional[int], str] = 42 # int case
# or
result: Union[Optional[int], str] = "text" # string case
Union Arrays
Combining unions with arrays:
function ProcessMixedList(data: (int | string)[]) -> string {
client "openai/gpt-4o-mini"
prompt #"
Process this list of mixed data: {{ data }}
"#
}
test MixedData {
functions [ProcessMixedList]
args { data [1, "hello", 42, "world"] }
}
Generated Types
from typing import List, Union
data: List[Union[int, str]] = [1, "hello", 42, "world"]
Complex Union Examples
Example 1: Optional Int, String Array, or Class
int? | string[] | MyClass
Example 2: Array of Union Optional Types
(int? | string[] | MyClass)[]
Example 3: Literal Values Union
Using Unions in Prompts
BAML automatically formats union types in prompts using ctx.output_format:
function ExtractData(text: string) -> int | string | bool {
client "openai/gpt-4o-mini"
prompt #"
Extract structured data from the text.
{{ ctx.output_format }}
Text: {{ text }}
"#
}
The ctx.output_format will generate appropriate JSON schema describing the union type for the LLM.
Type Checking at Runtime
When working with union types in your code:
from baml_client import b
from baml_client.types import EmailAction, CalendarAction
result = await b.DetermineAction("Send email to [email protected]")
if isinstance(result, EmailAction):
print(f"Email to: {result.recipient}")
elif isinstance(result, CalendarAction):
print(f"Calendar event: {result.event_name}")
else:
print(f"Search query: {result.query}")
Best Practices
- Order matters - Place more specific types before general ones
- Use literal unions for fixed sets of string/int/bool values instead of enums when you don’t need descriptions
- Document unions - Add comments explaining what each type variant represents
- Type narrowing - Use appropriate type checking in your client code
- Consider enums - For classification with descriptions, use enums instead of literal string unions
Validation Rules
Parsing Priority:BAML attempts to parse union types from left to right. The first successful parse wins.// "123" will parse as int
function Example1() -> int | string { ... }
// "123" will parse as string
function Example2() -> string | int { ... }