fern/01-guide/05-baml-advanced/checks-and-asserts.mdx
With checks and asserts, you can set specific rules to ensure your data's value falls within an acceptable range.
BAML provides two types of validations:
@assert for strict validations. If a type fails an @assert validation, it
will not be returned in the response. If the failing assertion was part of the
top-level type, it will raise an exception. If it's part of a container, it
will be removed from the container.@check for non-exception-raising validations. Whether a @check passes or
fails, the data will be returned. You can access the results of invidividual
checks in the response data.Assertions are used to guarantee properties about a type or its components in a response. They can be written directly as inline attributes next to the field definition or on the line following the field definition, or on a top-level type used in a function declaration.
@assertBAML will raise an exception if a function returns a Foo where Foo.bar
is not between 0 and 10.
If the function NextInt8 returns 128, BAML will raise an exception.
class Foo {
bar int @assert(between_0_and_10, {{ this > 0 and this < 10 }}) //this = Foo.bar value
}
function NextInt8(a: int) -> int @assert(ok_int8, {{ this >= -128 and this < 127 }}) {
client GPT4
prompt #"Return the number after {{ a }}"#
}
See Jinja in Attributes for a longer description of the Jinja syntax available in asserts.
Asserts may be applied to a whole class via @@assert.
class Bar {
baz int
quux string
@@assert(length_limit, {{ this.quux|length < this.baz }})
}
@assert with Union TypesNote that when using Unions, it is
crucial to specify where the @assert attribute is applied within the union
type, as it is not known until runtime which type the value will be.
class Foo {
bar (int @assert(positive, {{ this > 0 }}) | bool @assert(is_true, {{ this }}))
}
In the above example, the @assert attribute is applied specifically to the
int and string instances of the Union, rather than to the Foo.bar field
as a whole.
Likewise, the keyword this refers to the value of the type instance it is
directly associated with (e.g., int or string).
You can have multiple assertions on a single field by chaining multiple @assert attributes.
In this example, the asserts on bar and baz are equivalent.
class Foo {
bar int @assert(between_0_and_10, {{ this > 0 and this < 10 }})
baz int @assert(positive, {{ this > 0 }}) @assert(less_than_10, {{ this < 10 }})
}
Chained asserts are evaluated in order from left to right. If the first assert fails, the second assert will not be evaluated.
Assertions are represented as Jinja expressions and can be used to validate various types of data. Possible constraints include checking the length of a string, comparing two values, or verifying the presence of a substring with regular expressions.
In the future, we plan to support shorthand syntax for common assertions to make writing them easier.
For now, see our Jinja cookbook / guide or the Minijinja filters docs for more information on writing expressions.
this refers to the value of the current field being validated.this.field is used to refer to a specific field within the context of this.
Access nested fields of a data type by chaining the field names together with a . as shown below.
class Resume {
name string
experience string[]
}
class Person {
resume Resume @assert({{ this.experience|length > 0 }}, "Nonzero experience")
person_name name
}
When asserts fail, your BAML function will raise a BamlValidationError
exception, same as when parsing fails. You can catch this exception and handle
it as you see fit.
You can define custom names for each assertion, which will be included in the exception for that failure case. If you don't define a custom name, BAML will display the body of the assert expression.
In this example, if the quote field is empty, BAML raises a
BamlValidationError with the message "exact_citation_not_found". If the
website_link field does not contain "https://", it raises a
BamlValidationError with the message invalid_link.
class Citation {
//@assert(<name>, <expr>)
quote string @assert(exact_citation_found,
{{ this|length > 0 }}
)
website_link string @assert(valid_link,
{{ this|regex_match("https://") }}
)
}
def main(): try: citation: Citation = b.GetCitation("SpaceX, is an American spacecraft manufacturer, launch service provider...")
# Access the value of the quote field
quote = citation.quote
website_link = citation.website_link
print(f"Quote: {quote} from {website_link}")
except BamlValidationError as e:
print(f"Validation error: {str(e)}")
except Exception as e:
print(f"An unexpected error occurred: {e}")
</Tab>
<Tab title="TypeScript" language="typescript">
```typescript
import { b, BamlValidationError } from './baml_client';
import { Citation } from './baml_client/types';
const main = () => {
try {
const citation = b.GetCitation("SpaceX, is an American spacecraft manufacturer, launch service provider...");
const quote = citation.quote.value;
console.log(`Quote: ${quote}`);
const checks = citation.quote.checks;
console.log(`Check exact_citation_found: ${checks.exact_citation_found.status}`);
for (const check of get_checks(checks)) {
console.log(`Check ${check.name}: ${check.status}`);
}
const author = citation.author;
console.log(`Author: ${author}`);
} catch (e) {
if (e instanceof BamlValidationError) {
console.log(`Validation error: ${e}`);
} else {
console.error(e);
}
}
};
import ( "context" "fmt"
"github.com/boundaryml/baml"
)
func main() { ctx := context.Background()
citation, err := baml.GetCitation(ctx, "SpaceX, is an American spacecraft manufacturer, launch service provider...", nil)
if err != nil {
// Handle validation errors
if validationErr, ok := err.(*baml.ValidationError); ok {
fmt.Printf("Validation error: %v\n", validationErr)
return
}
fmt.Printf("An unexpected error occurred: %v\n", err)
return
}
// Access the citation fields
fmt.Printf("Quote: %s from %s\n", citation.Quote, citation.WebsiteLink)
}
</Tab>
<Tab title="Rust" language="rust">
```rust
use myproject::baml_client::sync_client::B;
fn main() {
match B.GetCitation.call("SpaceX, is an American spacecraft manufacturer, launch service provider...") {
Ok(citation) => {
// Access the citation fields
println!("Quote: {} from {}", citation.quote, citation.website_link);
}
Err(e) => {
let err_str = format!("{:?}", e).to_lowercase();
if err_str.contains("validation") {
println!("Validation error: {}", e);
} else {
eprintln!("An unexpected error occurred: {}", e);
}
}
}
}
@check attributes add validation without raising exceptions if they fail.
Types with @check attributes allow the checks to be inspected at
runtime.
type Bar = ( bar int @check(less_than_zero, {{ this < 0 }}) )[]
The following example uses both @check and @assert. If line_number fails its
@assert, no Citation will be returned by GetCitation(). However,
exact_citation_not_found can fail without interrupting the result. Because it
was a @check, client code can inspect the result of the check.
class Citation {
quote string @check(
exact_citation_match,
{{ this|length > 0 }}
)
line_number string @assert(
has_line_number,
{{ this|length >= 0 }}
)
}
function GetCitation(full_text: string) -> Citation {
client GPT4
prompt #"
Generate a citation of the text below in MLA format:
{{full_text}}
{{ctx.output_format}}
"#
}
def main(): citation = b.GetCitation("SpaceX, is an American spacecraft manufacturer, launch service provider...")
# Access the value of the quote field
quote = citation.quote.value
print(f"Quote: {quote}")
# Access a particular check.
quote_match_check = citation.quote.checks['exact_citation_match'].status
print(f"Citation match status: {quote_match_check})")
# Access each check and its status.
for check in get_checks(citation.quote.checks):
print(f"Check {check.name}: {check.status}")
</Tab>
<Tab title="TypeScript" language="typescript">
```typescript
import { b, get_checks } from './baml_client'
import { Citation } from './baml_client/types'
const main = () => {
const citation = b.GetCitation("SpaceX, is an American spacecraft manufacturer, launch service provider...");
// Access the value of the quote field
const quote = citation.quote.value
console.log(`Quote: ${quote}`)
// Access a particular check.
const quote_match_check = citation.quote.checks.exact_citation_match.status;
console.log(`Exact citation status: ${quote_match_check}`);
// Access each check and its status.
for (const check of get_checks(citation.quote.checks)) {
console.log(`Check: ${check.name}, Status: ${check.status}`)
}
}
import ( "context" "fmt"
"github.com/boundaryml/baml"
)
func main() { ctx := context.Background()
citation, err := baml.GetCitation(ctx, "SpaceX, is an American spacecraft manufacturer, launch service provider...", nil)
if err != nil {
panic(fmt.Sprintf("Failed to get citation: %v", err))
}
// Access the value of the quote field
quote := citation.Quote.Value
fmt.Printf("Quote: %s\n", quote)
// Access a particular check
exactCitationMatch := citation.Quote.Checks["exact_citation_match"].Status
fmt.Printf("Citation match status: %s\n", exactCitationMatch)
// Access each check and its status
for name, check := range citation.Quote.Checks {
fmt.Printf("Check %s: %s\n", name, check.Status)
}
}
</Tab>
<Tab title="Rust" language="rust">
```rust
use myproject::baml_client::sync_client::B;
fn main() {
let citation = B.GetCitation
.call("SpaceX, is an American spacecraft manufacturer, launch service provider...")
.unwrap();
// Access the value of the quote field
let quote = &citation.quote.value;
println!("Quote: {}", quote);
// Access a particular check
let exact_citation_match = &citation.quote.checks["exact_citation_match"].status;
println!("Citation match status: {}", exact_citation_match);
// Access each check and its status
for (name, check) in &citation.quote.checks {
println!("Check {}: {}", name, check.status);
}
}
You can also chain multiple @check and @assert attributes on a single field.
class Foo {
bar string @check(bar_nonempty, {{ this|length > 0 }})
@assert(bar_no_foo, {{ this|regex_match("foo") }})
@check(bar_no_fizzle, {{ this|regex_match("fizzle") }})
@assert(bar_no_baz, {{ this|regex_match("baz") }})
}
<Tip> When using @check, all checks on the response data are evaluated even if
one fails. In contrast, with @assert, a failure will stop the parsing process
and immediately raise an exception. </Tip>
The following example shows more complex minijinja expressions, see the Minijinja filters docs for more information on available operators to use in your assertions.
The Book and Library classes below demonstrate how to validate a book's
title, author, ISBN, publication year, genres, and a library's name and books.
The block-level assertion in the Library class ensures that all books have
unique ISBNs.
class Book {
title string @assert(this|length > 0)
author string @assert(this|length > 0)
isbn string @assert(
{{ this|regex_match("^(97(8|9))?\d{9}(\d|X)$") }},
"Invalid ISBN format"
)
publication_year int @assert(valid_pub_year, {{ 1000 <= this <= 2100 }})
genres string[] @assert(valid_length, {{ 1 <= this|length <= 10 }})
}
class Library {
name string
books Book[] @assert(nonempty_books, {{ this|length > 0 }})
@assert(unique_isbn, {{ this|map(attribute='isbn')|unique()|length == this|length }} )
}
In this example, we use a block-level @@assert to check a dependency across
a pair of fields.
class Person {
name string @assert(valid_name, {{ this|length >= 2 }})
age int @assert(valid_age, {{ this >= 0 }})
address Address
@@assert(not_usa_minor, {{
this.age >= 18 or this.address.country != "USA",
}})
}