diff --git a/docs/docs.yml b/docs/docs.yml
index d7c8d75c8..9d13adf8f 100644
--- a/docs/docs.yml
+++ b/docs/docs.yml
@@ -131,12 +131,14 @@ navigation:
path: docs/snippets/template-string.mdx
- page: Test Cases
path: docs/snippets/test-cases.mdx
- - section: Advanced BAML Snippets
+ - section: Advanced BAML Features
contents:
- page: Dynamic Types
path: docs/calling-baml/dynamic-types.mdx
- page: Client Registry
path: docs/calling-baml/client-registry.mdx
+ - page: Custom Type Validations
+ path: docs/calling-baml/assertions.mdx
- section: BAML with Python/TS/Ruby
contents:
- page: Generate the BAML Client
@@ -149,7 +151,7 @@ navigation:
path: docs/calling-baml/streaming.mdx
- page: Concurrent function calls
path: docs/calling-baml/concurrent-calls.mdx
- - page: Multimodal
+ - page: Multimodal Input
path: docs/calling-baml/multi-modal.mdx
- section: Observability [Paid]
contents:
diff --git a/docs/docs/calling-baml/assertions.mdx b/docs/docs/calling-baml/assertions.mdx
new file mode 100644
index 000000000..72a5b383e
--- /dev/null
+++ b/docs/docs/calling-baml/assertions.mdx
@@ -0,0 +1,454 @@
+---
+slug: docs/calling-baml/assertions
+---
+BAML raises `BAMLValidationError` exceptions when it can't parse the response based on your type definitions. With custom type validations, you can set specific rules to ensure a field's value falls within an acceptable range.
+
+Validations **do not** modify the prompt or response data. They are only used to change the post hoc **validation logic** of the BAML parser.
+
+BAML provides two types of validations:
+- `@assert` for strict, exception-raising validations
+- `@check` for non-exception-raising validations, which allow you to access the data even when validation fails along with the validation results of which checks pass and fail.
+
+This guide covers how to write different types of `@assert` validations, but the same principles and syntax apply to `@check` validations.
+See [Non-exception Raising Checks](#non-exception-raising-checks) for more information.
+
+
+## Field-level Assertions
+Field-level assertions are used to validate individual fields in a response. These assertions can be written directly as inline attributes next to the field definition or on the line following the field definition.
+
+### Using `@assert`
+BAML will raise an exception if `Foo.bar` is not between 0 and 10.
+```baml BAML
+class Foo {
+ bar int @assert(this > 0 and this < 10) //this = Foo.bar value
+}
+```
+
+
+### Using `@assert` with `Union` Types
+Note that when using [`Unions`](../snippets/supported-types.mdx#union-), 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.
+```baml BAML
+class Foo {
+ bar (int @assert(this > 0 and this < 10)
+ | string @assert(this|length > 0 and this|contains("foobar")))
+}
+```
+
+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`).
+
+### Referencing other fields with `block`
+The `block` syntax can be used to reference other fields within the same class, whereas `this` is used to access the current field's value.
+
+This is useful when defining an assertion for a field that is dependent on the validated value of another field.
+```baml BAML
+class User {
+ user_age int @assert(
+ this|length > 0, "user_age_invalid"
+ )
+
+ parent_age string @assert(
+ this > 0 and this > block.user_age, "parent_age_invalid"
+ )
+}
+```
+
+
+## Block-level Assertions
+To validate an entire object by considering multiple fields together after their individual validations, use a block-level assertion with `@@assert`.
+
+```baml BAML
+class Foo {
+ password string @assert(this|length > 10)
+ confirm_password string
+
+ @@assert(this.confirm_password == this.password)
+}
+```
+In this example, the `password` field must be longer than 10 characters, and the `Foo` class includes a block-level assertion to ensure `password` and `confirm_password` match.
+
+{/* For block-level assertions, you don't need to use the `block` keyword because `this` refers to the entire block. */}
+
+
+## Chaining Assertions
+You can have multiple assertions on a single field by using the `and` operator or by chaining multiple `@assert` attributes.
+
+In this example, the asserts on `bar` and `baz` are equivalent.
+```baml BAML
+class Foo {
+ bar int @assert(this > 0 and this < 10)
+ baz int @assert(this > 0) @assert(this < 10)
+}
+```
+
+Chained asserts are evaluated in order from left to right. If the first assert fails, the second assert will not be evaluated.
+
+## Reference Assertion Parameters
+Sometimes, you may need to validate a field differently for different instances of a class or based on the input to the function returning the class.
+
+### Function Input
+Use constructor inputs for your class to define dynamic assertions. In this example, the `quote` field must be present in the `text` input string.
+```baml BAML
+class Citation(text: string) {
+ quote string @assert(this in block.text, "exact_citation_not_found")
+ idx int
+}
+```
+
+Pass the `full_text` string to the `Citation` constructor to validate the `quote` field. The `block` keyword allows access to `full_text` for validation from the scope of the function.
+```baml BAML
+function GetCitations(full_text: string) -> Citation(text=block.full_text) {
+ client GPT4
+ prompt #"
+ Generate a citation of the text below in MLA format:
+ {{full_text}}
+
+ {{ctx.output_format}}
+
+ "#
+}
+```
+### Parent Class Input
+You may also use input assertions when composing a class from a parent class. In this example, the `quote` field must be present in the `contents` field of the `Book` class, which is passed as an input to the `Citation` class.
+```baml BAML
+class Citation(text: string) {
+ quote string @assert(this in block.text, "exact_citation_not_found")
+ idx int
+}
+
+class Book(contents: string) {
+ citation Citation(text=block.contents)
+ author string
+ publisher string
+}
+
+function GetBookDetails(book_contents: string) -> Book(contents=block.book_contents) {
+ client GPT4
+ prompt #"
+ Gather the details of the book with the following contents:
+ {{book_contents}}
+
+ {{ctx.output_format}}
+ "#
+}
+```
+
+
+## Writing Assertions
+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](../snippets/prompt-syntax/what-is-jinja.mdx) or the [Minijinja filters docs](https://docs.rs/minijinja/latest/minijinja/filters/index.html#functions) for more information on writing expressions.
+
+
+{/* ### Operators
+| Assertion | Types |
+|------------------|------------------------|
+| length | array, map, string |
+| regex match | string |
+| eq, ne | all |
+| gt, ge, lt, le | int, float, string |
+| xor | int, float, bool |
+| and, or | int, float, bool |
+| contains | string, array, map |
+| index [] | array, map, string |
+| min, max | int, float | */}
+{/*
+| custom function | all |
+| unique | array | every item is unique (consider using set type?) |
+| default | all | default value to fill if not found |
+| reference | all | when a value references another value | */}
+
+{/* Operators are called using the `|` symbol, followed by the operator name.
+```baml BAML
+class Foo {
+ bar int @assert(this|gt 0)
+}
+``` */}
+
+
+### Expression keywords
+- `this` refers to the value of the current field being validated.
+- `block` refers to the entire object being validated. It can be used to reference other fields within the same class.
+
+
+`.field` is used to refer to a specific field within the context of `this` or `block`.
+Access nested fields of a data type by chaining the field names together with a `.` as shown below.
+```baml BAML
+class Resume {
+ name string
+ experience string[]
+
+}
+
+class Person {
+ resume Resume @assert(this.experience|length > 0)
+ person_name name @assert(this == block.resume.name) //nested field access
+}
+```
+
+
+
+
+
+
+## Assertion Errors
+### Custom Error Messages
+When validations 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 error messages for each assertion, which will be included in the exception for that failure case. If you don't define a custom message, BAML will use a default message.
+
+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**.
+```baml BAML
+class Citation {
+ //@assert(, )
+ quote string @assert(
+ this|length > 0, "exact_citation_not_found"
+ )
+
+ website_link string @assert(
+ this|contains("https://"), "invalid_link"
+ )
+}
+```
+
+`BAMLValidationError` will propagate up to the top-level function, where you can catch it and handle it as needed.
+
+
+```python Python
+from baml_client import b
+from baml_client.types import Citation
+
+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: {e.error_message}")
+ except Exception as e:
+ print(f"An unexpected error occurred: {e}")
+
+```
+
+```typescript Typescript
+import { b, BAMLValidationError } from './baml_client';
+import { Citation } from './baml_client/types';
+
+const main = () => {
+ try {
+ const citation: 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;
+ for (const [check_name, result] of Object.entries(checks)) {
+ console.log(`Check ${check_name}: ${result ? 'passed' : 'failed'}`);
+ }
+
+ const author = citation.author;
+ console.log(`Author: ${author}`);
+ } catch (e) {
+ if (e instanceof BAMLValidationError) {
+ console.log(`Validation error: ${e.error_message}`);
+ } else {
+ console.error(e);
+ }
+ }
+};
+```
+
+
+
+### Validation Order
+
+When validating a class with multiple assertions, BAML raises a `BAMLValidationError` for the first failed assertion it finds, validating sequentially from top to bottom.
+
+ BAML validates assertions with dependencies after validating their dependencies, so `parent_age` would be validated after `user_age`.
+```baml BAML
+class User {
+ parent_age string @assert(
+ this > 0 and this > block.user_age, "parent_age_invalid"
+ )
+
+ user_age int @assert(
+ this|length > 0, "user_age_invalid"
+ )
+}
+```
+
+
+## Non-exception Raising Checks
+
+Use `@check` to access data even if an assertion fails. It provides both the raw data and the assertion error without raising an exception, unlike `@assert`. This is useful when you need the data despite validation failures. Besides this, `@check` works the same as `@assert`.
+
+
+To return both the data and the possible warning, BAML will return a `BamlCheckedValue` object, which contains the parsed data and the validation results for each check.
+
+To access the value, use the `value` attribute of the `BamlCheckedValue` object, and use the `checks` attribute to access a map of the checks used and their results during validation.
+
+```rust BamlCheckedValue
+interface BamlCheckedValue {
+ value T
+ checks {} // map of error message to true (passed) or false (failed)
+}
+```
+
+```baml BAML
+class Citation {
+ //@check(, )
+ quote string @check(
+ this|length > 0, "exact_citation_not_found"
+ )
+ line_number string @assert(
+ this|length >= 0, "no_line_number"
+ )
+}
+
+function GetCitation(full_text: string) -> Citation {
+ client GPT4
+ prompt #"
+ Generate a citation of the text below in MLA format:
+ {{full_text}}
+
+ {{ctx.output_format}}
+ "#
+}
+
+```
+
+Note that the `line_number` field uses `@assert` instead of `@check`. This means that while `quote` will return wrapped in a `BamlCheckedValue` object, `line_number` will raise an exception if the assertion fails and return as a regular field if it passes.
+
+```python Python
+from baml_client import b
+from baml_client.types import Citation
+
+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 the error messages for each check and its status
+ checks = citation.quote.checks
+ for check_name, result in checks.items():
+ print(f"Check {check_name}: {'passed' if result else 'failed'}")
+
+ # Access the author field directly, as it uses @assert
+ author = citation.author
+ print(f"Author: {author}")
+
+```
+
+```typescript Typescript
+import { b } 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 the error messages for each check and its status
+ const checks = citation.quote.checks
+ for (const [check_name, result] of Object.entries(checks)) {
+ console.log(`Check ${check_name}: ${result ? 'passed' : 'failed'}`)
+ }
+
+ // Access the author field directly, as it uses @assert
+ const author = citation.author
+ console.log(`Author: ${author}`)
+}
+```
+
+
+
+
+You can also chain multiple `@check` and `@assert` attributes on a single field.
+```baml BAML
+class Foo {
+ bar string @check(this|length > 0, "bar_empty")
+ @assert(this|contains("foo"), "bar_no_foo")
+ @check(this|contains("fizzle"), "bar_no_fizzle")
+ @assert(this|contains("baz"), "bar_no_baz")
+}
+```
+In this example, the `@assert` statements are checked during parsing of types, and if they fail, a `BAMLValidationError` is raised. The `@check` statements are checked after parsing, and the data is returned as a `BamlCheckedValue` object with the validation results.
+
+ 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.
+
+
+## Advanced Examples
+While the following examples show more complex minijinja expressions, see the [Minijinja filters docs](https://docs.rs/minijinja/latest/minijinja/filters/index.html#functions) for more information on available operators to use in your assertions.
+
+
+--------
+The `Address` class below demonstrates how to validate a postal code based on the country field. The postal code must be 5 digits for the USA, 6 digits for Canada, and match a specific pattern for the UK.
+```baml BAML
+class Address {
+ street string @assert(this|length > 0)
+ city string @assert(this|length > 0)
+ country string @assert(this in ["USA", "Canada", "UK"])
+ postal_code string @assert(
+ (this|length == 5 and block.country == "USA") or
+ (this|length == 6 and block.country == "Canada") or
+ (this|matches("^[A-Z]{1,2}[0-9][A-Z0-9]? [0-9][ABD-HJLNP-UW-Z]{2}$") and block.country == "UK"),
+ "Invalid postal code for the specified country"
+ )
+}
+```
+--------
+
+The `Person` class below demonstrates how to individually validate a person's name, age, and email address. Additionally, it includes a block-level assertion to ensure that a person must be 18 or older to have a USA address.
+```baml BAML
+class Person {
+ name string @assert(this|length >= 2)
+ age int @assert(this >= 0)
+ email string @assert(this|matches("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$"))
+ address Address
+
+ @@assert(
+ this.age >= 18 or this.address.country != "USA",
+ "Person must be 18 or older to have a USA address"
+ )
+}
+```
+--------
+
+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.
+```baml BAML
+class Book {
+ title string @assert(this|length > 0)
+ author string @assert(this|length > 0)
+ isbn string @assert(
+ this|matches("^(97(8|9))?\\d{9}(\\d|X)$"),
+ "Invalid ISBN format"
+ )
+ publication_year int @assert(1000 <= this <= 2100)
+ genres string[] @assert(1 <= this|length <= 10)
+}
+
+class Library {
+ name string
+ books Book[] @assert(this|length > 0)
+ total_books int @assert(this == block.books|length)
+
+ @@assert(
+ this.books|map(attribute='isbn')|unique()|length == this.books|length,
+ "All books must have unique ISBNs"
+ )
+}
+```
+
+
+
diff --git a/docs/docs/calling-baml/dynamic-types.mdx b/docs/docs/calling-baml/dynamic-types.mdx
index 0a48b4f4f..ff68b4b51 100644
--- a/docs/docs/calling-baml/dynamic-types.mdx
+++ b/docs/docs/calling-baml/dynamic-types.mdx
@@ -12,7 +12,7 @@ Sometimes you have a **output schemas that change at runtime** -- for example if
Here are the steps to make this work:
1. Add `@@dynamic` to the class or enum definition to mark it as dynamic
-```rust baml
+```rust BAML
enum Category {
VALUE1 // normal static enum values that don't change
VALUE2
diff --git a/docs/docs/calling-baml/multi-modal.mdx b/docs/docs/calling-baml/multi-modal.mdx
index 90af56e51..67883a4bf 100644
--- a/docs/docs/calling-baml/multi-modal.mdx
+++ b/docs/docs/calling-baml/multi-modal.mdx
@@ -3,8 +3,6 @@ slug: docs/calling-baml/multi-modal
---
-## Multi-modal input
-
### Images
Calling a BAML function with an `image` input argument type (see [image types](../snippets/supported-types.mdx)).
diff --git a/docs/docs/snippets/class.mdx b/docs/docs/snippets/class.mdx
index ac97b00e0..20ce5ee17 100644
--- a/docs/docs/snippets/class.mdx
+++ b/docs/docs/snippets/class.mdx
@@ -12,12 +12,13 @@ In the context of LLMs, classes describe the type of the variables you can injec
-```baml Baml
+```baml BAML
class Foo {
property1 string
property2 int?
property3 Bar[]
property4 MyEnum
+ property5 int | string
}
```
@@ -31,6 +32,8 @@ class Foo(BaseModel):
property2: Optional[int]= None
property3: List[Bar]
property4: MyEnum
+ property5: Union[int, str]
+
```
```typescript Typescript Equivalent
@@ -43,6 +46,7 @@ const FooZod = z.object({
property2: z.number().int().nullable().optional(),
property3: z.array(BarZod),
property4: MyEnumZod,
+ property5: z.union([z.number().int(), z.string()])
});
type Foo = z.infer;
@@ -51,27 +55,71 @@ type Foo = z.infer;
## Class Attributes
+### Contextual Attributes
+Contextual attributes modify the prompt and serve to provide additional context to the LLM.
+
If set, will allow you to add fields to the class dynamically at runtime (in your python/ts/etc code). See [dynamic classes](../calling-baml/dynamic-types.mdx) for more information.
+
+```baml BAML
+class MyClass {
+ property1 string
+ property2 int?
+
+ @@dynamic // allows me to later property3 float[] at runtime
+}
+```
+### Validation Attributes
+Validation attributes are used to enforce constraints on the fields of a class. They do not modify the prompt, but rather the post hoc validation logic of the BAML parser.
+
+Add class-level assertions using Jinja templates to ensure certain properties. If an assertion fails, a `BAMLValidationError` is raised. See [custom type assertions](../calling-baml/assertions.mdx) for details.
+
```baml BAML
class MyClass {
property1 string
property2 int?
- @@dynamic // allows me to later propert3 float[] at runtime
+ @@assert(block.property2 > property1|length) // property2 must be greater than the length of property1
}
```
+
+
+
+Use `@@check` to access data even if an assertion fails. This is useful in scenarios where you want to be informed of a validation failure but still need the data. It returns a `BamlCheckedValue` object with the parsed data and validation results. Access the value via the `value` attribute and the validation results via the `checks_results` attribute. See [non-exception raising type checks](../calling-baml/assertions.mdx#non-exception-checks) for details.
+
+
+```baml BAML
+class MyClass {
+ property1 string
+ property2 int?
+
+ @@check(block.property2 > property1|length, "compare_prop1_length_with_prop2") // property2 must be greater than the length of property1
+}
+```
+
+
+
+
+
+
+
## Field Attributes
-When prompt engineering, you can also alias values and add descriptions.
+When prompt engineering, you can also inline attributes to the fields of a class to provide additional context or constraints. Multiple attributes can be appended to a field.
+### Contextual Attributes
+Contextual attributes modify the prompt and serve to provide additional context to the LLM.
This adds some additional context to the field in the prompt.
-
-
```baml BAML
class MyClass {
@@ -96,6 +148,49 @@ class MyClass {
}
```
+
+
+
+### Validation Attributes
+Validation attributes are used to enforce constraints on the fields of a class. They do not modify the prompt, but rather the post hoc validation logic of the BAML parser.
+
+
+
+Add field assertions using Jinja templates to ensure specific properties. If the assertion fails, the prompt will fail. Because assertions are evaluated at runtime, `Union` cases must have type-instance-specific assertions. See [custom type assertions](../calling-baml/assertions.mdx) for more information.
+
+```baml BAML
+class MyClass {
+ property1 string @alias("name") @assert(this|length > 0, "name_not_empty")
+ age int? @assert(this > 0, "age_positive")
+}
+```
+
+
+
+The `@check` attribute allows you to access data even if an assertion fails. This is useful in scenarios where you want to be informed of a validation failure but still need the data.
+
+When using `@check`, the field returns a `BamlCheckedValue` object, which includes both the parsed data and the validation results. You can access the actual value through the `value` attribute and the validation results through the `checks_results` attribute. For more details, see [non-exception raising type checks](../calling-baml/assertions.mdx#non-exception-checks).
+
+```baml BAML
+class MyClass {
+ property1 string @alias("name") @check(this|length > 0, "name_not_empty")
+ age int? @check(this > 0, "age_positive")
+ hometown string @check(this == "New York", "hometown_new_york")
+}
+```
+
+
+
+
+
+
+
## Constraints
Classes may have any number of properties.
diff --git a/docs/docs/snippets/prompt-syntax/what-is-jinja.mdx b/docs/docs/snippets/prompt-syntax/what-is-jinja.mdx
index 0607d1cbe..cc997c8de 100644
--- a/docs/docs/snippets/prompt-syntax/what-is-jinja.mdx
+++ b/docs/docs/snippets/prompt-syntax/what-is-jinja.mdx
@@ -4,7 +4,7 @@ slug: docs/snippets/prompt-syntax/what-is-jinja
---
-BAML Prompt strings are essentially [Jinja](https://jinja.palletsprojects.com/en/3.1.x/templates/) templates, which offer the ability to express logic and data manipulation within strings. Jinja is a very popular and mature templating language amongst Python developers, so Github Copilot or another LLM can already help you write most of the logic you want.
+BAML Prompt strings are essentially [Minijinja](https://docs.rs/minijinja/latest/minijinja/filters/index.html#functions) templates, which offer the ability to express logic and data manipulation within strings. Jinja is a very popular and mature templating language amongst Python developers, so Github Copilot or another LLM can already help you write most of the logic you want.
## Jinja Cookbook
@@ -20,7 +20,7 @@ When in doubt -- use the BAML VSCode Playground preview. It will show you the fu
Here's how you can iterate over a list of items, accessing each item's attributes:
-```jinja
+```jinja Jinja
function MyFunc(messages: Message[]) -> string {
prompt #"
{% for message in messages %}
@@ -34,7 +34,7 @@ function MyFunc(messages: Message[]) -> string {
Use conditional statements to control the flow and output of your templates based on conditions:
-```jinja
+```jinja Jinja
function MyFunc(user: User) -> string {
prompt #"
{% if user.is_active %}
@@ -81,4 +81,4 @@ function MyFunc(arg1: string, user: User) -> string {
```
### Built-in filters
-See [jinja docs](https://jinja.palletsprojects.com/en/3.1.x/templates/#list-of-builtin-filters)
\ No newline at end of file
+See [Jinja docs](https://jinja.palletsprojects.com/en/3.1.x/templates/#list-of-builtin-filters)
\ No newline at end of file
diff --git a/engine/.turbo/daemon/d1f419c7a2c5692a-turbo.log.2024-07-23 b/engine/.turbo/daemon/d1f419c7a2c5692a-turbo.log.2024-07-23
new file mode 100644
index 000000000..e69de29bb
diff --git a/engine/Cargo.lock b/engine/Cargo.lock
index 656399753..19504fffd 100644
--- a/engine/Cargo.lock
+++ b/engine/Cargo.lock
@@ -2409,6 +2409,7 @@ dependencies = [
"pest_derive",
"serde",
"serde_json",
+ "test-log",
]
[[package]]
diff --git a/engine/baml-lib/baml-core/src/ir/ir_helpers/mod.rs b/engine/baml-lib/baml-core/src/ir/ir_helpers/mod.rs
index 22909521d..993936972 100644
--- a/engine/baml-lib/baml-core/src/ir/ir_helpers/mod.rs
+++ b/engine/baml-lib/baml-core/src/ir/ir_helpers/mod.rs
@@ -7,7 +7,7 @@ use crate::{
error_not_found, error_unsupported,
ir::{
repr::{IntermediateRepr, Walker},
- Class, Client, Enum, EnumValue, Field, Function, RetryPolicy, TemplateString, TestCase,
+ Class, Client, Enum, EnumValue, Field, FunctionNode, RetryPolicy, TemplateString, TestCase,
},
};
use anyhow::Result;
@@ -15,14 +15,14 @@ use baml_types::{BamlMap, BamlValue};
use super::repr;
-pub type FunctionWalker<'a> = Walker<'a, &'a Function>;
+pub type FunctionWalker<'a> = Walker<'a, &'a FunctionNode>;
pub type EnumWalker<'a> = Walker<'a, &'a Enum>;
pub type EnumValueWalker<'a> = Walker<'a, &'a EnumValue>;
pub type ClassWalker<'a> = Walker<'a, &'a Class>;
pub type TemplateStringWalker<'a> = Walker<'a, &'a TemplateString>;
pub type ClientWalker<'a> = Walker<'a, &'a Client>;
pub type RetryPolicyWalker<'a> = Walker<'a, &'a RetryPolicy>;
-pub type TestCaseWalker<'a> = Walker<'a, (&'a Function, &'a TestCase)>;
+pub type TestCaseWalker<'a> = Walker<'a, (&'a FunctionNode, &'a TestCase)>;
pub type ClassFieldWalker<'a> = Walker<'a, &'a Field>;
pub trait IRHelper {
@@ -89,22 +89,11 @@ impl IRHelper for IntermediateRepr {
fn find_function<'a>(&'a self, function_name: &str) -> Result> {
match self.walk_functions().find(|f| f.name() == function_name) {
Some(f) => match f.item.elem {
- repr::Function::V1(_) => {
- error_unsupported!(
- "function",
- function_name,
- "legacy functions cannot use the runtime"
- )
- }
- repr::Function::V2(_) => Ok(f),
+ repr::Function { .. } => Ok(f),
},
None => {
// Get best match.
- let functions = self
- .walk_functions()
- .filter(|f| f.is_v2())
- .map(|f| f.name())
- .collect::>();
+ let functions = self.walk_functions().map(|f| f.name()).collect::>();
error_not_found!("function", function_name, &functions)
}
}
@@ -166,17 +155,7 @@ impl IRHelper for IntermediateRepr {
params: &BamlMap,
allow_implicit_cast_to_string: bool,
) -> Result {
- let function_params = match function.inputs() {
- either::Either::Left(_) => {
- // legacy functions are not supported
- error_unsupported!(
- "function",
- function.name(),
- "legacy functions cannot use the runtime"
- )
- }
- either::Either::Right(defs) => defs,
- };
+ let function_params = function.inputs();
// Now check that all required parameters are present.
let mut scope = ScopeStack::new();
diff --git a/engine/baml-lib/baml-core/src/ir/json_schema.rs b/engine/baml-lib/baml-core/src/ir/json_schema.rs
index 152df8516..b63311946 100644
--- a/engine/baml-lib/baml-core/src/ir/json_schema.rs
+++ b/engine/baml-lib/baml-core/src/ir/json_schema.rs
@@ -5,7 +5,7 @@ use serde_json::json;
use super::{
repr::{self},
- Class, Enum, FieldType, Function, FunctionArgs, IntermediateRepr, Walker,
+ Class, Enum, FieldType, FunctionArgs, FunctionNode, IntermediateRepr, Walker,
};
pub trait WithJsonSchema {
@@ -43,20 +43,14 @@ impl WithJsonSchema for IntermediateRepr {
}
}
-impl WithJsonSchema for (&Function, bool) {
+impl WithJsonSchema for (&FunctionNode, bool) {
fn json_schema(&self) -> serde_json::Value {
let (f, is_input) = self;
let mut res = if *is_input {
- match &f.elem {
- repr::Function::V1(f) => f.inputs.json_schema(),
- repr::Function::V2(f) => f.inputs.json_schema(),
- }
+ f.elem.inputs.json_schema()
} else {
- match &f.elem {
- repr::Function::V1(f) => f.output.elem.json_schema(),
- repr::Function::V2(f) => f.output.elem.json_schema(),
- }
+ f.elem.output.json_schema()
};
// Add a title field to the schema
diff --git a/engine/baml-lib/baml-core/src/ir/mod.rs b/engine/baml-lib/baml-core/src/ir/mod.rs
index 120559de3..c3c61639b 100644
--- a/engine/baml-lib/baml-core/src/ir/mod.rs
+++ b/engine/baml-lib/baml-core/src/ir/mod.rs
@@ -19,11 +19,9 @@ pub type FieldType = baml_types::FieldType;
pub type Expression = repr::Expression;
pub type Identifier = repr::Identifier;
pub type TypeValue = baml_types::TypeValue;
-pub type Function = repr::Node;
+pub type FunctionNode = repr::Node;
#[allow(dead_code)]
-pub(super) type FunctionV1 = repr::FunctionV1;
-#[allow(dead_code)]
-pub(super) type FunctionV2 = repr::FunctionV2;
+pub(super) type Function = repr::Function;
pub(super) type FunctionArgs = repr::FunctionArgs;
pub(super) type Impl = repr::Node;
pub type Client = repr::Node;
diff --git a/engine/baml-lib/baml-core/src/ir/repr.rs b/engine/baml-lib/baml-core/src/ir/repr.rs
index 262a69457..7e7672db1 100644
--- a/engine/baml-lib/baml-core/src/ir/repr.rs
+++ b/engine/baml-lib/baml-core/src/ir/repr.rs
@@ -1,17 +1,20 @@
-use std::collections::HashSet;
+use std::{collections::HashSet, default};
use anyhow::{anyhow, bail, Context, Result};
use baml_types::FieldType;
use either::Either;
-
use indexmap::IndexMap;
+
+use internal_baml_diagnostics::DatamodelError;
+
use internal_baml_parser_database::{
walkers::{
ClassWalker, ClientWalker, ConfigurationWalker, EnumValueWalker, EnumWalker, FieldWalker,
- FunctionWalker, TemplateStringWalker, VariantWalker,
+ FunctionWalker, TemplateStringWalker,
},
- ParserDatabase, PromptAst, RetryPolicyStrategy, ToStringAttributes, WithStaticRenames,
+ ParserDatabase, PromptAst, RetryPolicyStrategy, ToStringAttributes,
};
+use internal_baml_schema_ast::ast::SubType;
use internal_baml_schema_ast::ast::{self, FieldArity, WithName, WithSpan};
use serde::Serialize;
@@ -139,8 +142,7 @@ impl IntermediateRepr {
.map(|e| e.node(db))
.collect::>>()?,
functions: db
- .walk_old_functions()
- .chain(db.walk_new_functions())
+ .walk_functions()
.map(|e| e.node(db))
.collect::>>()?,
clients: db
@@ -199,13 +201,6 @@ pub struct NodeAttributes {
#[serde(with = "indexmap::map::serde_seq")]
meta: IndexMap,
- /// Overrides for the specified AST node in a given implementation (which is keyed by FunctionId
- /// and ImplementationId). In .baml files these are represented in the implementation, but in the
- /// IR AST we attach them to the AST node so that all metadata associated with an IRnode can be
- /// accessed from that node, rather than through a different IR node.
- #[serde(with = "indexmap::map::serde_seq")]
- overrides: IndexMap<(FunctionId, ImplementationId), IndexMap>,
-
// Spans
#[serde(skip)]
pub span: Option,
@@ -267,7 +262,6 @@ pub trait WithRepr {
fn attributes(&self, _: &ParserDatabase) -> NodeAttributes {
NodeAttributes {
meta: IndexMap::new(),
- overrides: IndexMap::new(),
span: None,
}
}
@@ -292,19 +286,24 @@ fn type_with_arity(t: FieldType, arity: &FieldArity) -> FieldType {
impl WithRepr for ast::FieldType {
fn repr(&self, db: &ParserDatabase) -> Result {
Ok(match self {
- ast::FieldType::Identifier(arity, idn) => type_with_arity(
- match idn {
- ast::Identifier::Primitive(t, ..) => FieldType::Primitive(*t),
- ast::Identifier::Local(name, _) => match db.find_type(idn) {
- Some(Either::Left(_class_walker)) => Ok(FieldType::Class(name.clone())),
- Some(Either::Right(_enum_walker)) => Ok(FieldType::Enum(name.clone())),
- None => Err(anyhow!("Field type uses unresolvable local identifier")),
- }?,
- _ => bail!("Field type uses unsupported identifier type"),
+ ast::FieldType::Primitive(_, typeval, ..) => {
+ let repr = FieldType::Primitive(typeval.clone());
+
+ repr
+ }
+ ast::FieldType::Symbol(arity, idn, ..) => type_with_arity(
+ match db.find_type_by_str(idn) {
+ Some(Either::Left(class_walker)) => {
+ FieldType::Class(class_walker.name().to_string())
+ }
+ Some(Either::Right(enum_walker)) => {
+ FieldType::Enum(enum_walker.name().to_string())
+ }
+ None => return Err(anyhow!("Field type uses unresolvable local identifier")),
},
arity,
),
- ast::FieldType::List(ft, dims, _) => {
+ ast::FieldType::List(ft, dims, ..) => {
// NB: potential bug: this hands back a 1D list when dims == 0
let mut repr = FieldType::List(Box::new(ft.repr(db)?));
@@ -314,11 +313,11 @@ impl WithRepr for ast::FieldType {
repr
}
- ast::FieldType::Map(kv, _) => {
+ ast::FieldType::Map(kv, ..) => {
// NB: we can't just unpack (*kv) into k, v because that would require a move/copy
FieldType::Map(Box::new((*kv).0.repr(db)?), Box::new((*kv).1.repr(db)?))
}
- ast::FieldType::Union(arity, t, _) => {
+ ast::FieldType::Union(arity, t, ..) => {
// NB: preempt union flattening by mixing arity into union types
let mut types = t.iter().map(|ft| ft.repr(db)).collect::>>()?;
@@ -328,7 +327,7 @@ impl WithRepr for ast::FieldType {
FieldType::Union(types)
}
- ast::FieldType::Tuple(arity, t, _) => type_with_arity(
+ ast::FieldType::Tuple(arity, t, ..) => type_with_arity(
FieldType::Tuple(t.iter().map(|ft| ft.repr(db)).collect::>>()?),
arity,
),
@@ -414,9 +413,7 @@ impl WithRepr for ast::Expression {
// because that's modelled as Identifier::ENV, not Identifier::Ref
Ok(Expression::String(r.full_name.clone()))
}
- ast::Identifier::Primitive(p, _) => {
- Ok(Expression::Identifier(Identifier::Primitive(*p)))
- }
+
ast::Identifier::Invalid(_, _) => {
Err(anyhow!("Cannot represent an invalid parser-AST identifier"))
}
@@ -447,7 +444,7 @@ impl WithRepr for TemplateStringWalker<'_> {
fn attributes(&self, _: &ParserDatabase) -> NodeAttributes {
NodeAttributes {
meta: Default::default(),
- overrides: Default::default(),
+
span: Some(self.span().clone()),
}
}
@@ -456,8 +453,7 @@ impl WithRepr for TemplateStringWalker<'_> {
Ok(TemplateString {
name: self.name().to_string(),
params: self.ast_node().input().map_or(vec![], |e| match e {
- ast::FunctionArgs::Named(arg_list) => arg_list
- .args
+ ast::BlockArgs { args, .. } => args
.iter()
.filter_map(|(id, arg)| {
arg.field_type
@@ -469,15 +465,12 @@ impl WithRepr for TemplateStringWalker<'_> {
.ok()
})
.collect::>(),
- ast::FunctionArgs::Unnamed(_) => {
- vec![]
- }
+ _ => vec![],
}),
content: self.template_string().to_string(),
})
}
}
-
type EnumId = String;
#[derive(serde::Serialize, Debug)]
@@ -493,23 +486,9 @@ impl WithRepr for EnumValueWalker<'_> {
fn attributes(&self, db: &ParserDatabase) -> NodeAttributes {
let mut attributes = NodeAttributes {
meta: to_ir_attributes(db, self.get_default_attributes()),
- overrides: IndexMap::new(),
span: Some(self.span().clone()),
};
- for r#fn in db.walk_old_functions() {
- for r#impl in r#fn.walk_variants() {
- let node_attributes = to_ir_attributes(db, self.get_override(&r#impl));
-
- if !node_attributes.is_empty() {
- attributes.overrides.insert(
- (r#fn.name().to_string(), r#impl.name().to_string()),
- node_attributes,
- );
- }
- }
- }
-
attributes
}
@@ -520,26 +499,11 @@ impl WithRepr for EnumValueWalker<'_> {
impl WithRepr for EnumWalker<'_> {
fn attributes(&self, db: &ParserDatabase) -> NodeAttributes {
- let mut attributes = NodeAttributes {
- meta: to_ir_attributes(db, self.get_default_attributes()),
- overrides: IndexMap::new(),
+ let attributes = NodeAttributes {
+ meta: to_ir_attributes(db, self.get_default_attributes(SubType::Enum)),
span: Some(self.span().clone()),
};
-
- attributes.meta = to_ir_attributes(db, self.get_default_attributes());
-
- for r#fn in db.walk_old_functions() {
- for r#impl in r#fn.walk_variants() {
- let node_attributes =
- to_ir_attributes(db, r#impl.find_serializer_attributes(self.name()));
- if !node_attributes.is_empty() {
- attributes.overrides.insert(
- (r#fn.name().to_string(), r#impl.name().to_string()),
- node_attributes,
- );
- }
- }
- }
+ println!("Enum Attributes: {:?}", attributes);
attributes
}
@@ -563,31 +527,26 @@ pub struct Field {
impl WithRepr for FieldWalker<'_> {
fn attributes(&self, db: &ParserDatabase) -> NodeAttributes {
- let mut attributes = NodeAttributes {
+ let attributes = NodeAttributes {
meta: to_ir_attributes(db, self.get_default_attributes()),
- overrides: IndexMap::new(),
span: Some(self.span().clone()),
};
- for r#fn in db.walk_old_functions() {
- for r#impl in r#fn.walk_variants() {
- let node_attributes = to_ir_attributes(db, self.get_override(&r#impl));
- if !node_attributes.is_empty() {
- attributes.overrides.insert(
- (r#fn.name().to_string(), r#impl.name().to_string()),
- node_attributes,
- );
- }
- }
- }
-
attributes
}
fn repr(&self, db: &ParserDatabase) -> Result {
Ok(Field {
name: self.name().to_string(),
- r#type: self.ast_field().field_type.node(db)?,
+ r#type: Node {
+ elem: self
+ .ast_field()
+ .expr
+ .clone()
+ .ok_or(anyhow!("Field type is None"))?
+ .repr(db)?,
+ attributes: self.attributes(db),
+ },
})
}
}
@@ -603,27 +562,14 @@ pub struct Class {
impl WithRepr for ClassWalker<'_> {
fn attributes(&self, db: &ParserDatabase) -> NodeAttributes {
- let mut attributes = NodeAttributes {
- meta: to_ir_attributes(db, self.get_default_attributes()),
- overrides: IndexMap::new(),
+ let default_attributes = self.get_default_attributes(SubType::Class);
+ println!("Default Attributes: {:?}", default_attributes);
+ let attributes = NodeAttributes {
+ meta: to_ir_attributes(db, default_attributes),
span: Some(self.span().clone()),
};
- attributes.meta = to_ir_attributes(db, self.get_default_attributes());
-
- for r#fn in db.walk_old_functions() {
- for r#impl in r#fn.walk_variants() {
- let node_attributes =
- to_ir_attributes(db, r#impl.find_serializer_attributes(self.name()));
- if !node_attributes.is_empty() {
- attributes.overrides.insert(
- (r#fn.name().to_string(), r#impl.name().to_string()),
- node_attributes,
- );
- }
- }
- }
-
+ println!("Class Attributes: {:?}", attributes);
attributes
}
@@ -697,65 +643,33 @@ pub enum FunctionArgs {
type FunctionId = String;
-#[derive(serde::Serialize, Debug)]
-#[serde(tag = "version")]
-pub enum Function {
- V1(FunctionV1),
- V2(FunctionV2),
-}
-
impl Function {
pub fn name(&self) -> &str {
- match self {
- Function::V1(f) => &f.name,
- Function::V2(f) => &f.name,
- }
+ &self.name
}
pub fn output(&self) -> &FieldType {
- match &self {
- Function::V1(f) => &f.output.elem,
- Function::V2(f) => &f.output.elem,
- }
+ &self.output
}
- pub fn inputs(&self) -> either::Either<&FunctionArgs, &Vec<(String, FieldType)>> {
- match &self {
- Function::V1(f) => either::Either::Left(&f.inputs),
- Function::V2(f) => either::Either::Right(&f.inputs),
- }
+ pub fn inputs(&self) -> &Vec<(String, FieldType)> {
+ &self.inputs
}
pub fn tests(&self) -> &Vec> {
- match &self {
- Function::V1(f) => &f.tests,
- Function::V2(f) => &f.tests,
- }
+ &self.tests
}
pub fn configs(&self) -> Option<&Vec> {
- match &self {
- Function::V1(_) => None,
- Function::V2(f) => Some(&f.configs),
- }
+ Some(&self.configs)
}
}
#[derive(serde::Serialize, Debug)]
-pub struct FunctionV1 {
- pub name: FunctionId,
- pub inputs: FunctionArgs,
- pub output: Node,
- pub impls: Vec>,
- pub tests: Vec>,
- pub default_impl: Option,
-}
-
-#[derive(serde::Serialize, Debug)]
-pub struct FunctionV2 {
+pub struct Function {
pub name: FunctionId,
pub inputs: Vec<(String, FieldType)>,
- pub output: Node,
+ pub output: FieldType,
pub tests: Vec>,
pub configs: Vec,
pub default_config: String,
@@ -770,114 +684,6 @@ pub struct FunctionConfig {
pub client: ClientId,
}
-impl WithRepr for VariantWalker<'_> {
- fn attributes(&self, _db: &ParserDatabase) -> NodeAttributes {
- NodeAttributes {
- meta: IndexMap::new(),
- overrides: IndexMap::new(),
- span: Some(self.span().clone()),
- }
- }
-
- fn repr(&self, db: &ParserDatabase) -> Result {
- let function_name = self.ast_variant().function_name().name();
- let impl_name = self.name();
- // Convert the IndexMap to a Vec of tuples
- let mut replacers_vec: Vec<(_, _)> = self
- .properties()
- .replacers
- // NB: .0 should really be .input
- .0
- .iter()
- .map(|r| (r.0.key(), r.1.clone()))
- .collect();
- // Sort the Vec by the keys
- replacers_vec.sort_by(|a, b| a.0.cmp(&b.0));
- // Convert the sorted Vec back to an IndexMap
- let sorted_replacers: IndexMap = replacers_vec
- .into_iter()
- .map(|(k, v)| (k.clone(), v.clone()))
- .collect();
-
- Ok(Implementation {
- r#type: OracleType::LLM,
- name: self.name().to_string(),
- function_name: function_name.to_string(),
- prompt: self.properties().to_prompt().repr(db)?,
- input_replacers: sorted_replacers,
- output_replacers: self
- .properties()
- .replacers
- // NB: .1 should really be .output
- .1
- .iter()
- .map(|r| (r.0.key(), r.1.clone()))
- .collect(),
- client: self.properties().client.value.clone(),
- overrides: self
- .ast_variant()
- .iter_serializers()
- .filter_map(|(_k, v)| {
- let matches = match self.db.find_type_by_str(v.name()) {
- Some(either) => match either {
- Either::Left(left_value) => {
- let cls_res = left_value.repr(db);
- match cls_res {
- Ok(cls) => cls
- .static_fields
- .iter()
- .flat_map(|f| {
- process_field(
- &f.attributes.overrides,
- &f.elem.name,
- function_name,
- impl_name,
- )
- })
- .collect::>(),
-
- _ => vec![],
- }
- }
- Either::Right(right_value) => {
- let enm_res = right_value.repr(db);
- match enm_res {
- Ok(enm) => enm
- .values
- .iter()
- .flat_map(|f| {
- process_field(
- &f.attributes.overrides,
- &f.elem.0,
- function_name,
- impl_name,
- )
- })
- .collect::>(),
-
- _ => vec![],
- }
- }
- },
- None => {
- vec![]
- }
- };
-
- if matches.is_empty() {
- None
- } else {
- Some(AliasOverride {
- name: v.name().to_string(),
- aliased_keys: matches,
- })
- }
- })
- .collect::>(),
- })
- }
-}
-
fn process_field(
overrides: &IndexMap<(String, String), IndexMap>, // Adjust the type according to your actual field type
original_name: &str,
@@ -926,79 +732,30 @@ impl WithRepr for FunctionWalker<'_> {
fn attributes(&self, _: &ParserDatabase) -> NodeAttributes {
NodeAttributes {
meta: Default::default(),
- overrides: Default::default(),
span: Some(self.span().clone()),
}
}
fn repr(&self, db: &ParserDatabase) -> Result {
- if self.is_old_function() {
- Ok(Function::V1(self.repr(db)?))
- } else {
- Ok(Function::V2(self.repr(db)?))
- }
- }
-}
-
-impl WithRepr for FunctionWalker<'_> {
- fn repr(&self, db: &ParserDatabase) -> Result {
- if !self.is_old_function() {
- bail!("Cannot represent a new function as a FunctionV1")
- }
- Ok(FunctionV1 {
+ Ok(Function {
name: self.name().to_string(),
- inputs: match self.ast_function().input() {
- ast::FunctionArgs::Named(arg_list) => FunctionArgs::NamedArgList(
- arg_list
- .args
- .iter()
- .map(|(id, arg)| Ok((id.name().to_string(), arg.field_type.repr(db)?)))
- .collect::>>()?,
- ),
- ast::FunctionArgs::Unnamed(arg) => {
- FunctionArgs::UnnamedArg(arg.field_type.node(db)?.elem)
- }
- },
- output: match self.ast_function().output() {
- ast::FunctionArgs::Named(_) => bail!("Functions may not return named args"),
- ast::FunctionArgs::Unnamed(arg) => arg.field_type.node(db),
- }?,
- default_impl: self.metadata().default_impl.as_ref().map(|f| f.0.clone()),
- impls: {
- let mut impls = self
- .walk_variants()
- .map(|e| e.node(db))
- .collect::>>()?;
- impls.sort_by(|a, b| a.elem.name.cmp(&&b.elem.name));
- impls
- },
- tests: self
- .walk_tests()
- .map(|e| e.node(db))
+ inputs: self
+ .ast_function()
+ .input()
+ .expect("msg")
+ .args
+ .iter()
+ .map(|arg| {
+ let field_type = arg.1.field_type.repr(db)?;
+ Ok((arg.0.to_string(), field_type))
+ })
.collect::>>()?,
- })
- }
-}
-
-impl WithRepr for FunctionWalker<'_> {
- fn repr(&self, db: &ParserDatabase) -> Result {
- if self.is_old_function() {
- bail!("Cannot represent a new function as a FunctionV1")
- }
- Ok(FunctionV2 {
- name: self.name().to_string(),
- inputs: match self.ast_function().input() {
- ast::FunctionArgs::Named(arg_list) => arg_list
- .args
- .iter()
- .map(|(id, arg)| Ok((id.name().to_string(), arg.field_type.repr(db)?)))
- .collect::>>()?,
- ast::FunctionArgs::Unnamed(_) => bail!("Unnamed args not supported"),
- },
- output: match self.ast_function().output() {
- ast::FunctionArgs::Named(_) => bail!("Functions may not return named args"),
- ast::FunctionArgs::Unnamed(arg) => arg.field_type.node(db),
- }?,
+ output: self
+ .ast_function()
+ .output()
+ .expect("need block arg")
+ .field_type
+ .repr(db)?,
configs: vec![FunctionConfig {
name: "default_config".to_string(),
prompt_template: self.jinja_prompt().to_string(),
@@ -1032,7 +789,6 @@ impl WithRepr for ClientWalker<'_> {
fn attributes(&self, _: &ParserDatabase) -> NodeAttributes {
NodeAttributes {
meta: IndexMap::new(),
- overrides: IndexMap::new(),
span: Some(self.span().clone()),
}
}
@@ -1073,7 +829,6 @@ impl WithRepr for ConfigurationWalker<'_> {
fn attributes(&self, _db: &ParserDatabase) -> NodeAttributes {
NodeAttributes {
meta: IndexMap::new(),
- overrides: IndexMap::new(),
span: Some(self.span().clone()),
}
}
@@ -1104,7 +859,6 @@ impl WithRepr for ConfigurationWalker<'_> {
fn attributes(&self, _db: &ParserDatabase) -> NodeAttributes {
NodeAttributes {
meta: IndexMap::new(),
- overrides: IndexMap::new(),
span: Some(self.span().clone()),
}
}
diff --git a/engine/baml-lib/baml-core/src/ir/walker.rs b/engine/baml-lib/baml-core/src/ir/walker.rs
index 3b0bd80a9..4751eb703 100644
--- a/engine/baml-lib/baml-core/src/ir/walker.rs
+++ b/engine/baml-lib/baml-core/src/ir/walker.rs
@@ -8,59 +8,41 @@ use std::collections::HashMap;
use super::{
repr::{self, FunctionConfig},
- Class, Client, Enum, EnumValue, Expression, Field, Function, FunctionV2, Identifier, Impl,
+ Class, Client, Enum, EnumValue, Expression, Field, Function, FunctionNode, Identifier, Impl,
RetryPolicy, TemplateString, TestCase, Walker,
};
-impl<'a> Walker<'a, &'a Function> {
+impl<'a> Walker<'a, &'a FunctionNode> {
pub fn name(&self) -> &'a str {
self.elem().name()
}
pub fn is_v1(&self) -> bool {
- matches!(self.item.elem, repr::Function::V1(_))
+ false
}
pub fn is_v2(&self) -> bool {
- matches!(self.item.elem, repr::Function::V2(_))
- }
-
- pub fn as_v2(&self) -> Option<&'a FunctionV2> {
- match &self.item.elem {
- repr::Function::V1(_) => None,
- repr::Function::V2(f) => Some(f),
- }
+ true
}
pub fn client_name(&self) -> Option<&'a str> {
- if let Some(v2) = self.as_v2() {
- if let Some(c) = v2.configs.first() {
- return Some(c.client.as_str());
- }
+ if let Some(c) = self.elem().configs.first() {
+ return Some(c.client.as_str());
}
None
}
-
pub fn walk_impls(
&'a self,
- ) -> either::Either<
- impl Iterator- >,
- impl Iterator
- >,
- > {
- match &self.item.elem {
- repr::Function::V1(f) => either::Either::Left(f.impls.iter().map(|i| Walker {
- db: self.db,
- item: (self.item, i),
- })),
- repr::Function::V2(f) => either::Either::Right(f.configs.iter().map(|c| Walker {
- db: self.db,
- item: (self.item, c),
- })),
- }
+ ) -> impl Iterator
- > {
+ self.elem().configs.iter().map(|c| Walker {
+ db: self.db,
+ item: (self.elem(), c),
+ })
}
-
- pub fn walk_tests(&'a self) -> impl Iterator
- > {
+ pub fn walk_tests(
+ &'a self,
+ ) -> impl Iterator
- > {
self.elem().tests().iter().map(|i| Walker {
db: self.db,
item: (self.item, i),
@@ -70,7 +52,7 @@ impl<'a> Walker<'a, &'a Function> {
pub fn find_test(
&'a self,
test_name: &str,
- ) -> Option> {
+ ) -> Option> {
self.walk_tests().find(|t| t.item.1.elem.name == test_name)
}
@@ -79,16 +61,11 @@ impl<'a> Walker<'a, &'a Function> {
}
pub fn output(&self) -> &'a baml_types::FieldType {
- match &self.item.elem {
- repr::Function::V1(f) => &f.output.elem,
- repr::Function::V2(f) => &f.output.elem,
- }
+ self.elem().output()
}
- pub fn inputs(
- &self,
- ) -> either::Either<&'a repr::FunctionArgs, &'a Vec<(String, baml_types::FieldType)>> {
- self.item.elem.inputs()
+ pub fn inputs(&self) -> &'a Vec<(String, baml_types::FieldType)> {
+ self.elem().inputs()
}
pub fn span(&self) -> Option<&crate::Span> {
@@ -234,9 +211,9 @@ impl Expression {
}
}
-impl<'a> Walker<'a, (&'a Function, &'a Impl)> {
+impl<'a> Walker<'a, (&'a FunctionNode, &'a Impl)> {
#[allow(dead_code)]
- pub fn function(&'a self) -> Walker<'a, &'a Function> {
+ pub fn function(&'a self) -> Walker<'a, &'a FunctionNode> {
Walker {
db: self.db,
item: self.item.0,
@@ -248,7 +225,7 @@ impl<'a> Walker<'a, (&'a Function, &'a Impl)> {
}
}
-impl<'a> Walker<'a, (&'a Function, &'a TestCase)> {
+impl<'a> Walker<'a, (&'a FunctionNode, &'a TestCase)> {
pub fn matches(&self, function_name: &str, test_name: &str) -> bool {
self.item.0.elem.name() == function_name && self.item.1.elem.name == test_name
}
@@ -279,7 +256,7 @@ impl<'a> Walker<'a, (&'a Function, &'a TestCase)> {
.collect()
}
- pub fn function(&'a self) -> Walker<'a, &'a Function> {
+ pub fn function(&'a self) -> Walker<'a, &'a FunctionNode> {
Walker {
db: self.db,
item: self.item.0,
diff --git a/engine/baml-lib/baml-core/src/validate/generator_loader/mod.rs b/engine/baml-lib/baml-core/src/validate/generator_loader/mod.rs
index a32c80b6b..9234ac921 100644
--- a/engine/baml-lib/baml-core/src/validate/generator_loader/mod.rs
+++ b/engine/baml-lib/baml-core/src/validate/generator_loader/mod.rs
@@ -23,7 +23,7 @@ pub(crate) fn load_generators_from_ast<'i>(
}
fn parse_generator(
- ast_generator: &ast::GeneratorConfig,
+ ast_generator: &ast::ValueExprBlock,
diagnostics: &mut Diagnostics,
) -> Option {
let errors = match v2::parse_generator(ast_generator, &diagnostics.root_path) {
diff --git a/engine/baml-lib/baml-core/src/validate/generator_loader/v0.rs b/engine/baml-lib/baml-core/src/validate/generator_loader/v0.rs
index 6663a155b..e825bed19 100644
--- a/engine/baml-lib/baml-core/src/validate/generator_loader/v0.rs
+++ b/engine/baml-lib/baml-core/src/validate/generator_loader/v0.rs
@@ -64,7 +64,7 @@ fn parse_optional_key<'a>(
}
pub(crate) fn parse_generator(
- ast_generator: &ast::GeneratorConfig,
+ ast_generator: &ast::ValueExprBlock,
baml_src: &PathBuf,
) -> Result> {
let generator_name = ast_generator.name();
@@ -79,14 +79,14 @@ pub(crate) fn parse_generator(
let args = ast_generator
.fields()
.iter()
- .map(|arg| match &arg.value {
+ .map(|arg| match &arg.expr {
Some(expr) => {
if FIRST_CLASS_PROPERTIES.iter().any(|k| *k == arg.name()) {
Ok((arg.name(), expr))
} else {
Err(DatamodelError::new_property_not_known_error(
arg.name(),
- arg.span.clone(),
+ arg.span().clone(),
FIRST_CLASS_PROPERTIES.to_vec(),
))
}
diff --git a/engine/baml-lib/baml-core/src/validate/generator_loader/v1.rs b/engine/baml-lib/baml-core/src/validate/generator_loader/v1.rs
index 922d43ad7..37e8b2f65 100644
--- a/engine/baml-lib/baml-core/src/validate/generator_loader/v1.rs
+++ b/engine/baml-lib/baml-core/src/validate/generator_loader/v1.rs
@@ -58,7 +58,7 @@ fn parse_optional_key<'a>(
}
pub(crate) fn parse_generator(
- ast_generator: &ast::GeneratorConfig,
+ ast_generator: &ast::ValueExprBlock,
baml_src: &PathBuf,
) -> Result> {
let generator_name = ast_generator.name();
@@ -73,14 +73,14 @@ pub(crate) fn parse_generator(
let args = ast_generator
.fields()
.iter()
- .map(|arg| match &arg.value {
+ .map(|arg| match &arg.expr {
Some(expr) => {
if FIRST_CLASS_PROPERTIES.iter().any(|k| *k == arg.name()) {
Ok((arg.name(), expr))
} else {
Err(DatamodelError::new_property_not_known_error(
arg.name(),
- arg.span.clone(),
+ arg.span().clone(),
FIRST_CLASS_PROPERTIES.to_vec(),
))
}
diff --git a/engine/baml-lib/baml-core/src/validate/generator_loader/v2.rs b/engine/baml-lib/baml-core/src/validate/generator_loader/v2.rs
index 13ac2b434..94a5a0824 100644
--- a/engine/baml-lib/baml-core/src/validate/generator_loader/v2.rs
+++ b/engine/baml-lib/baml-core/src/validate/generator_loader/v2.rs
@@ -61,7 +61,7 @@ fn parse_optional_key<'a>(
}
pub(crate) fn parse_generator(
- ast_generator: &ast::GeneratorConfig,
+ ast_generator: &ast::ValueExprBlock,
baml_src: &PathBuf,
) -> Result> {
let generator_name = ast_generator.name();
@@ -77,14 +77,14 @@ pub(crate) fn parse_generator(
let args = ast_generator
.fields()
.iter()
- .map(|arg| match &arg.value {
+ .map(|arg| match &arg.expr {
Some(expr) => {
if FIRST_CLASS_PROPERTIES.iter().any(|k| *k == arg.name()) {
Ok((arg.name(), expr))
} else {
Err(DatamodelError::new_property_not_known_error(
arg.name(),
- arg.span.clone(),
+ arg.span().clone(),
FIRST_CLASS_PROPERTIES.to_vec(),
))
}
diff --git a/engine/baml-lib/baml-core/src/validate/validation_pipeline/validations.rs b/engine/baml-lib/baml-core/src/validate/validation_pipeline/validations.rs
index df3a3da89..72c9f11e3 100644
--- a/engine/baml-lib/baml-core/src/validate/validation_pipeline/validations.rs
+++ b/engine/baml-lib/baml-core/src/validate/validation_pipeline/validations.rs
@@ -5,14 +5,12 @@ mod cycle;
mod enums;
mod functions;
mod types;
-mod variants;
use super::context::Context;
pub(super) fn validate(ctx: &mut Context<'_>) {
enums::validate(ctx);
classes::validate(ctx);
- variants::validate(ctx);
functions::validate(ctx);
clients::validate(ctx);
configurations::validate(ctx);
diff --git a/engine/baml-lib/baml-core/src/validate/validation_pipeline/validations/classes.rs b/engine/baml-lib/baml-core/src/validate/validation_pipeline/validations/classes.rs
index 946963d63..a0b2a6036 100644
--- a/engine/baml-lib/baml-core/src/validate/validation_pipeline/validations/classes.rs
+++ b/engine/baml-lib/baml-core/src/validate/validation_pipeline/validations/classes.rs
@@ -8,11 +8,15 @@ pub(super) fn validate(ctx: &mut Context<'_>) {
for c in cls.static_fields() {
let field = c.ast_field();
- validate_type(ctx, &field.field_type);
+ if let Some(ft) = &field.expr {
+ validate_type(ctx, &ft);
+ }
}
for c in cls.dynamic_fields() {
let field = c.ast_field();
- validate_type(ctx, &field.field_type);
+ if let Some(ft) = &field.expr {
+ validate_type(ctx, &ft);
+ }
}
}
}
diff --git a/engine/baml-lib/baml-core/src/validate/validation_pipeline/validations/configurations.rs b/engine/baml-lib/baml-core/src/validate/validation_pipeline/validations/configurations.rs
index 45899fd20..9876e138f 100644
--- a/engine/baml-lib/baml-core/src/validate/validation_pipeline/validations/configurations.rs
+++ b/engine/baml-lib/baml-core/src/validate/validation_pipeline/validations/configurations.rs
@@ -6,9 +6,7 @@ pub(super) fn validate(ctx: &mut Context<'_>) {
for _config in ctx.db.walk_retry_policies() {
// Nothing to validate.
}
- for _config in ctx.db.walk_printers() {
- // Nothing to validate.
- }
+
for config in ctx.db.walk_test_cases() {
// Ensure that the test case name is valid.
let case = config.test_case();
diff --git a/engine/baml-lib/baml-core/src/validate/validation_pipeline/validations/cycle.rs b/engine/baml-lib/baml-core/src/validate/validation_pipeline/validations/cycle.rs
index b07fb1b2e..62dbe0785 100644
--- a/engine/baml-lib/baml-core/src/validate/validation_pipeline/validations/cycle.rs
+++ b/engine/baml-lib/baml-core/src/validate/validation_pipeline/validations/cycle.rs
@@ -1,7 +1,7 @@
use std::collections::HashSet;
use internal_baml_diagnostics::DatamodelError;
-use internal_baml_schema_ast::ast::{ClassId, WithIdentifier, WithName, WithSpan};
+use internal_baml_schema_ast::ast::{TypeExpId, WithIdentifier, WithName, WithSpan};
use crate::validate::validation_pipeline::context::Context;
@@ -27,7 +27,7 @@ pub(super) fn validate(ctx: &mut Context<'_>) {
.collect::>();
// Now we can check for cycles using topological sort.
- let mut stack: Vec<(ClassId, Vec)> = Vec::new(); // This stack now also keeps track of the path
+ let mut stack: Vec<(TypeExpId, Vec)> = Vec::new(); // This stack now also keeps track of the path
let mut visited = HashSet::new();
let mut in_stack = HashSet::new();
diff --git a/engine/baml-lib/baml-core/src/validate/validation_pipeline/validations/functions.rs b/engine/baml-lib/baml-core/src/validate/validation_pipeline/validations/functions.rs
index 649b6d60f..ea8cd79f9 100644
--- a/engine/baml-lib/baml-core/src/validate/validation_pipeline/validations/functions.rs
+++ b/engine/baml-lib/baml-core/src/validate/validation_pipeline/validations/functions.rs
@@ -1,51 +1,12 @@
+use crate::validate::validation_pipeline::context::Context;
+
use internal_baml_diagnostics::{DatamodelError, DatamodelWarning, Span};
-use internal_baml_schema_ast::ast::{WithIdentifier, WithName, WithSpan};
-use crate::validate::validation_pipeline::context::Context;
+use internal_baml_schema_ast::ast::{WithIdentifier, WithName, WithSpan};
use super::types::validate_type;
pub(super) fn validate(ctx: &mut Context<'_>) {
- for func in ctx.db.walk_old_functions() {
- for args in func.walk_input_args().chain(func.walk_output_args()) {
- let arg = args.ast_arg();
- validate_type(ctx, &arg.1.field_type)
- }
-
- // Check if the function has multiple impls, if it does,
- // we require an impl.
- match &func.metadata().default_impl {
- Some((default_impl, span)) => {
- if !func
- .walk_variants()
- .find(|v| v.name() == default_impl)
- .is_some()
- {
- ctx.push_error(DatamodelError::new_impl_not_found_error(
- &default_impl,
- func.walk_variants()
- .map(|v| v.name().to_string())
- .collect::>(),
- span.clone(),
- ))
- }
- }
- None => {
- let num_impls = func.walk_variants().len();
- if num_impls >= 2 {
- ctx.push_error(DatamodelError::new_validation_error(
- &format!(
- "{} has multiple impls({}). Add a `default_impl your_impl` field to the function",
- func.name(),
- num_impls
- ),
- func.identifier().span().clone(),
- ))
- }
- }
- }
- }
-
let clients = ctx
.db
.walk_clients()
@@ -74,9 +35,7 @@ pub(super) fn validate(ctx: &mut Context<'_>) {
};
defined_types.start_scope();
- if let Some(internal_baml_schema_ast::ast::FunctionArgs::Named(p)) =
- template.ast_node().input()
- {
+ if let Some(p) = template.ast_node().input() {
p.args.iter().for_each(|(name, t)| {
defined_types.add_variable(name.name(), ctx.db.to_jinja_type(&t.field_type))
});
@@ -111,7 +70,7 @@ pub(super) fn validate(ctx: &mut Context<'_>) {
defined_types.errors_mut().clear();
}
- for func in ctx.db.walk_new_functions() {
+ for func in ctx.db.walk_functions() {
for args in func.walk_input_args().chain(func.walk_output_args()) {
let arg = args.ast_arg();
validate_type(ctx, &arg.1.field_type)
@@ -130,13 +89,37 @@ pub(super) fn validate(ctx: &mut Context<'_>) {
))
}
}
+ // for arg in func.walk_output_args() {
+ // let arg_ast = arg.ast_arg();
+ // let type_name = arg_ast.1.name().to_string(); // Store the name in a variable
+ // match arg_ast.1.field_type {
+ // FieldType::Symbol(..) if ctx.db.find_type_by_str(type_name.as_str()).is_none() => {
+ // ctx.push_error(DatamodelError::not_found_error(
+ // &format!(
+ // "Output argument cannot be resolved. Did you mean something else?"
+ // ),
+ // &arg_ast.1.name(),
+ // arg_ast.1.span().clone(),
+ // ctx.db
+ // .walk_classes()
+ // .chain(ctx.db.walk_enums())
+ // .map(|c| c.name().to_string())
+ // .collect(),
+ // ));
+ // }
+ // _ => {}
+ // }
+ // }
let prompt = func.metadata().prompt.as_ref().unwrap();
defined_types.start_scope();
+
func.walk_input_args().for_each(|arg| {
- let name = arg.name();
- let field_type = ctx.db.to_jinja_type(arg.field_type());
- defined_types.add_variable(name, field_type);
+ let name = arg.ast_arg().0.unwrap().name().to_string();
+
+ let field_type = ctx.db.to_jinja_type(&arg.ast_arg().1.field_type);
+
+ defined_types.add_variable(&name, field_type);
});
match internal_baml_jinja::validate_template(
func.name(),
@@ -146,10 +129,10 @@ pub(super) fn validate(ctx: &mut Context<'_>) {
Ok(_) => {}
Err(e) => {
let pspan = prompt.span();
- if let Some(_e) = e.parsing_errors {
+ if let Some(e) = e.parsing_errors {
// ctx.push_error(DatamodelError::new_validation_error(
// &format!("Error parsing jinja template: {}", e),
- // e.line(),
+ // // e.,
// ))
} else {
e.errors.iter().for_each(|t| {
diff --git a/engine/baml-lib/baml-core/src/validate/validation_pipeline/validations/types.rs b/engine/baml-lib/baml-core/src/validate/validation_pipeline/validations/types.rs
index 42754a94b..dd69ab134 100644
--- a/engine/baml-lib/baml-core/src/validate/validation_pipeline/validations/types.rs
+++ b/engine/baml-lib/baml-core/src/validate/validation_pipeline/validations/types.rs
@@ -30,21 +30,15 @@ fn validate_type_exists(ctx: &mut Context<'_>, field_type: &FieldType) {
.iter()
.for_each(|f| match ctx.db.find_type(f) {
Some(_) => {}
- None => match f {
- Identifier::Primitive(..) => {}
- _ => errors_with_names(ctx, f),
- },
+ None => errors_with_names(ctx, f),
});
}
fn validate_type_allowed(ctx: &mut Context<'_>, field_type: &FieldType) {
match field_type {
- FieldType::Map(kv_types, _) => {
+ FieldType::Map(kv_types, ..) => {
match &kv_types.0 {
- FieldType::Identifier(
- FieldArity::Required,
- Identifier::Primitive(TypeValue::String, _),
- ) => {}
+ FieldType::Primitive(FieldArity::Required, _, ..) => {}
key_type => {
ctx.push_error(DatamodelError::new_validation_error(
"Maps may only have strings as keys",
@@ -55,9 +49,34 @@ fn validate_type_allowed(ctx: &mut Context<'_>, field_type: &FieldType) {
validate_type_allowed(ctx, &kv_types.1);
// TODO:assert key_type is string or int or null
}
- FieldType::Identifier(_, _) => {}
- FieldType::List(field_type, _, _) => validate_type_allowed(ctx, field_type),
- FieldType::Tuple(_, field_types, _) | FieldType::Union(_, field_types, _) => {
+
+ FieldType::Primitive(_, type_value, span, _) => {
+ let primitives = vec!["int", "float", "bool", "string", "image", "audio"];
+ if !primitives.contains(&type_value.to_string().as_str()) {
+ ctx.push_error(DatamodelError::not_found_error(
+ "Primitive type",
+ &type_value.to_string(),
+ span.clone(),
+ primitives.iter().map(|&s| s.to_string()).collect(),
+ ));
+ }
+ }
+ FieldType::Symbol(_, name, span, _) => {
+ if ctx.db.find_type_by_str(name).is_none() {
+ ctx.push_error(DatamodelError::not_found_error(
+ "Type",
+ name,
+ span.clone(),
+ ctx.db
+ .walk_classes()
+ .chain(ctx.db.walk_enums())
+ .map(|c| c.name().to_string())
+ .collect(),
+ ));
+ }
+ }
+ FieldType::List(field_type, ..) => validate_type_allowed(ctx, field_type),
+ FieldType::Tuple(_, field_types, ..) | FieldType::Union(_, field_types, ..) => {
for field_type in field_types {
validate_type_allowed(ctx, field_type);
}
diff --git a/engine/baml-lib/baml-core/src/validate/validation_pipeline/validations/variants.rs b/engine/baml-lib/baml-core/src/validate/validation_pipeline/validations/variants.rs
deleted file mode 100644
index 2b942a7d3..000000000
--- a/engine/baml-lib/baml-core/src/validate/validation_pipeline/validations/variants.rs
+++ /dev/null
@@ -1,116 +0,0 @@
-use internal_baml_diagnostics::DatamodelError;
-
-use internal_baml_parser_database::{PrinterType, PromptVariable};
-use internal_baml_schema_ast::ast::{Identifier, WithIdentifier, WithName, WithSpan};
-
-use crate::validate::validation_pipeline::context::Context;
-
-pub(super) fn validate(ctx: &mut Context<'_>) {
- for variant in ctx.db.walk_variants() {
- let client = &variant.properties().client;
-
- if variant.client().is_none() {
- ctx.push_error(DatamodelError::new_validation_error(
- &format!("Unknown client `{}`", client.value),
- client.span.clone(),
- ));
- }
-
- if let Some(_function) = variant.walk_function() {
- // Ensure that every serializer is valid.
- variant.ast_variant().iter_serializers().for_each(|(_, f)| {
- match ctx.db.find_type(f.identifier()) {
- Some(_) => {}
- None => {
- ctx.push_error(DatamodelError::new_validation_error(
- &format!("Unknown override `{}`", f.identifier().name()),
- f.identifier().span().clone(),
- ));
- }
- }
- });
-
- if let Some((idx, _)) = variant.properties().output_adapter {
- // Ensure that all input types exist.
- let adapter = &variant.ast_variant()[idx];
-
- adapter
- .from
- .flat_idns()
- .iter()
- .filter(|f| match f {
- // Primitive types don't need to found.
- Identifier::Primitive(..) => false,
- _ => true,
- })
- .for_each(|f| match ctx.db.find_type(f) {
- Some(_) => {}
- None => {
- ctx.push_error(DatamodelError::new_type_not_found_error(
- f.name(),
- ctx.db.valid_type_names(),
- f.span().clone(),
- ));
- }
- });
- }
-
- // Ensure that all blocks are valid.
- variant
- .properties()
- .prompt_replacements
- .iter()
- .filter_map(|p| match p {
- PromptVariable::Enum(e) => e.printer.as_ref().map(|f| (f, "enum")),
- PromptVariable::Type(t) => t.printer.as_ref().map(|f| (f, "type")),
- PromptVariable::Input(_) => None,
- PromptVariable::Chat(_) => None,
- })
- .for_each(|(printer, t)| {
- match ctx.db.find_printer(&printer.0) {
- Some(w) => {
- match w.printer() {
- PrinterType::Enum(_) => {
- if t == "enum" {
- // All good.
- } else {
- ctx.push_error(DatamodelError::new_validation_error(
- &format!(
- "Expected a printer, found printer {}",
- printer.0
- ),
- printer.1.clone(),
- ));
- }
- }
- PrinterType::Type(_) => {
- if t == "type" {
- // All good.
- } else {
- ctx.push_error(DatamodelError::new_validation_error(
- &format!(
- "Expected a printer, found printer {}",
- printer.0
- ),
- printer.1.clone(),
- ));
- }
- }
- }
- }
- None => ctx.push_error(DatamodelError::new_type_not_found_error(
- &printer.0,
- ctx.db.valid_printer_names(),
- printer.1.clone(),
- )),
- }
- });
- } else {
- ctx.push_error(DatamodelError::new_type_not_found_error(
- variant.function_identifier().name(),
- ctx.db.valid_function_names(),
- variant.function_identifier().span().clone(),
- ));
- }
- }
-}
diff --git a/engine/baml-lib/baml-types/src/field_type/mod.rs b/engine/baml-lib/baml-types/src/field_type/mod.rs
index c495e34fa..a4dabd61b 100644
--- a/engine/baml-lib/baml-types/src/field_type/mod.rs
+++ b/engine/baml-lib/baml-types/src/field_type/mod.rs
@@ -11,7 +11,20 @@ pub enum TypeValue {
Image,
Audio,
}
-
+impl TypeValue {
+ pub fn from_str(s: &str) -> Option {
+ match s {
+ "string" => Some(TypeValue::String),
+ "int" => Some(TypeValue::Int),
+ "float" => Some(TypeValue::Float),
+ "bool" => Some(TypeValue::Bool),
+ "null" => Some(TypeValue::Null),
+ "image" => Some(TypeValue::Image),
+ "audio" => Some(TypeValue::Audio),
+ _ => None,
+ }
+ }
+}
impl std::fmt::Display for TypeValue {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
diff --git a/engine/baml-lib/baml/tests/validation_files/class/attributes.baml b/engine/baml-lib/baml/tests/validation_files/class/attributes.baml
new file mode 100644
index 000000000..de778e110
--- /dev/null
+++ b/engine/baml-lib/baml/tests/validation_files/class/attributes.baml
@@ -0,0 +1,10 @@
+class TestClassAlias {
+ key string @alias("key-dash")
+ @description(#"
+ This is a description for key
+ af asdf
+ "#)
+ key2 string @alias("key21")
+ key3 string @alias("key with space")
+ key4 string @alias("key.with.punctuation/123")
+}
\ No newline at end of file
diff --git a/engine/baml-lib/baml/tests/validation_files/class/dependency_cycle.baml b/engine/baml-lib/baml/tests/validation_files/class/dependency_cycle.baml
index 74e17dc4b..571a8e966 100644
--- a/engine/baml-lib/baml/tests/validation_files/class/dependency_cycle.baml
+++ b/engine/baml-lib/baml/tests/validation_files/class/dependency_cycle.baml
@@ -8,24 +8,4 @@ class InterfaceOne {
class InterfaceThree {
interface InterfaceThree
-}
-
-
-// error: Error validating: These classes form a dependency cycle: InterfaceTwo
-// --> class/dependency_cycle.baml:1
-// |
-// |
-// 1 | class InterfaceTwo {
-// |
-// error: Error validating: These classes form a dependency cycle: InterfaceOne
-// --> class/dependency_cycle.baml:5
-// |
-// 4 |
-// 5 | class InterfaceOne {
-// |
-// error: Error validating: These classes form a dependency cycle: InterfaceThree
-// --> class/dependency_cycle.baml:9
-// |
-// 8 |
-// 9 | class InterfaceThree {
-// |
+}
\ No newline at end of file
diff --git a/engine/baml-lib/baml/tests/validation_files/class/map_types.baml b/engine/baml-lib/baml/tests/validation_files/class/map_types.baml
index 7843d8409..b87d3de6c 100644
--- a/engine/baml-lib/baml/tests/validation_files/class/map_types.baml
+++ b/engine/baml-lib/baml/tests/validation_files/class/map_types.baml
@@ -31,18 +31,6 @@ function InputAndOutput(i1: map, i2: map) -> m
"#
}
-// error: Error validating: Maps may only have strings as keys
-// --> class/map_types.baml:16
-// |
-// 15 |
-// 16 | b1 map
-// |
-// error: Error validating: Maps may only have strings as keys
-// --> class/map_types.baml:17
-// |
-// 16 | b1 map
-// 17 | b2 map
-// |
// error: Error validating: Maps may only have strings as keys
// --> class/map_types.baml:18
// |
@@ -62,12 +50,6 @@ function InputAndOutput(i1: map, i2: map) -> m
// 20 | b5 map
// |
// error: Error validating: Maps may only have strings as keys
-// --> class/map_types.baml:23
-// |
-// 22 | c1 string | map
-// 23 | c2 string | map
-// |
-// error: Error validating: Maps may only have strings as keys
// --> class/map_types.baml:24
// |
// 23 | c2 string | map
diff --git a/engine/baml-lib/baml/tests/validation_files/class/secure_types.baml b/engine/baml-lib/baml/tests/validation_files/class/secure_types.baml
index 548c3d36c..bb008b4d5 100644
--- a/engine/baml-lib/baml/tests/validation_files/class/secure_types.baml
+++ b/engine/baml-lib/baml/tests/validation_files/class/secure_types.baml
@@ -17,163 +17,25 @@ class ComplexTypes {
o (((int | string) | bool[]), (float, double) | long_long_identifier_123)
}
-// error: Type `apple_pie` does not exist. Did you mean one of these: `ComplexTypes`, `float`, `bool`, `string`, `int`?
-// --> class/secure_types.baml:3
-// |
-// 2 | class ComplexTypes {
-// 3 | a map
-// |
// error: Error validating: Maps may only have strings as keys
// --> class/secure_types.baml:3
// |
// 2 | class ComplexTypes {
// 3 | a map
// |
-// error: Type `char` does not exist. Did you mean one of these: `int`, `float`, `bool`, `string`, `ComplexTypes`?
-// --> class/secure_types.baml:4
-// |
-// 3 | a map
-// 4 | b (int, map, (char | float)[][] | long_word_123.foobar[])
-// |
-// error: Type `long_word_123.foobar` does not exist. Did you mean one of these: `float`, `bool`, `ComplexTypes`, `string`, `int`?
-// --> class/secure_types.baml:4
-// |
-// 3 | a map
-// 4 | b (int, map, (char | float)[][] | long_word_123.foobar[])
-// |
// error: Error validating: Maps may only have strings as keys
-// --> class/secure_types.baml:4
-// |
-// 3 | a map
-// 4 | b (int, map, (char | float)[][] | long_word_123.foobar[])
-// |
-// error: Type `apple123_456_pie` does not exist. Did you mean one of these: `ComplexTypes`, `float`, `bool`, `string`, `int`?
-// --> class/secure_types.baml:5
-// |
-// 4 | b (int, map, (char | float)[][] | long_word_123.foobar[])
-// 5 | c apple123_456_pie | (stringer, bool[], (int | char))[]
-// |
-// error: Type `stringer` does not exist. Did you mean one of these: `string`, `int`, `float`, `bool`, `ComplexTypes`?
-// --> class/secure_types.baml:5
-// |
-// 4 | b (int, map, (char | float)[][] | long_word_123.foobar[])
-// 5 | c apple123_456_pie | (stringer, bool[], (int | char))[]
-// |
-// error: Type `char` does not exist. Did you mean one of these: `int`, `float`, `bool`, `string`, `ComplexTypes`?
-// --> class/secure_types.baml:5
-// |
-// 4 | b (int, map, (char | float)[][] | long_word_123.foobar[])
-// 5 | c apple123_456_pie | (stringer, bool[], (int | char))[]
-// |
-// error: Type `char` does not exist. Did you mean one of these: `int`, `float`, `bool`, `string`, `ComplexTypes`?
// --> class/secure_types.baml:6
// |
// 5 | c apple123_456_pie | (stringer, bool[], (int | char))[]
// 6 | d map
// |
// error: Error validating: Maps may only have strings as keys
-// --> class/secure_types.baml:6
-// |
-// 5 | c apple123_456_pie | (stringer, bool[], (int | char))[]
-// 6 | d map
-// |
-// error: Type `char` does not exist. Did you mean one of these: `int`, `float`, `bool`, `string`, `ComplexTypes`?
-// --> class/secure_types.baml:7
-// |
-// 6 | d map
-// 7 | e ((int, string | char) | ((float, double) | long[], bool)[][][])
-// |
-// error: Type `double` does not exist. Did you mean one of these: `bool`, `string`, `int`, `float`, `ComplexTypes`?
-// --> class/secure_types.baml:7
-// |
-// 6 | d map
-// 7 | e ((int, string | char) | ((float, double) | long[], bool)[][][])
-// |
-// error: Type `long` does not exist. Did you mean one of these: `int`, `float`, `bool`, `string`, `ComplexTypes`?
-// --> class/secure_types.baml:7
-// |
-// 6 | d map
-// 7 | e ((int, string | char) | ((float, double) | long[], bool)[][][])
-// |
-// error: Type `VeryLongWord_With_123_Numbers` does not exist.
-// --> class/secure_types.baml:8
-// |
-// 7 | e ((int, string | char) | ((float, double) | long[], bool)[][][])
-// 8 | f VeryLongWord_With_123_Numbers[][][][]
-// |
-// error: Type `char` does not exist. Did you mean one of these: `int`, `float`, `bool`, `string`, `ComplexTypes`?
-// --> class/secure_types.baml:9
-// |
-// 8 | f VeryLongWord_With_123_Numbers[][][][]
-// 9 | g (int, (float, char, bool), string[]) | tuple_inside_tuple[]
-// |
-// error: Type `tuple_inside_tuple` does not exist. Did you mean one of these: `ComplexTypes`, `int`, `string`, `float`, `bool`?
-// --> class/secure_types.baml:9
-// |
-// 8 | f VeryLongWord_With_123_Numbers[][][][]
-// 9 | g (int, (float, char, bool), string[]) | tuple_inside_tuple[]
-// |
-// error: Type `char` does not exist. Did you mean one of these: `int`, `float`, `bool`, `string`, `ComplexTypes`?
// --> class/secure_types.baml:10
// |
// 9 | g (int, (float, char, bool), string[]) | tuple_inside_tuple[]
// 10 | h (((int | string)[]) | map)
// |
// error: Error validating: Maps may only have strings as keys
-// --> class/secure_types.baml:10
-// |
-// 9 | g (int, (float, char, bool), string[]) | tuple_inside_tuple[]
-// 10 | h (((int | string)[]) | map)
-// |
-// error: Type `apple` does not exist. Did you mean one of these: `bool`, `int`, `float`, `string`, `ComplexTypes`?
-// --> class/secure_types.baml:11
-// |
-// 10 | h (((int | string)[]) | map)
-// 11 | i (apple, banana | cherry | date_fruit | eggplant_vegetable)[]
-// |
-// error: Type `banana` does not exist. Did you mean one of these: `string`, `int`, `float`, `bool`, `ComplexTypes`?
-// --> class/secure_types.baml:11
-// |
-// 10 | h (((int | string)[]) | map)
-// 11 | i (apple, banana | cherry | date_fruit | eggplant_vegetable)[]
-// |
-// error: Type `cherry` does not exist. Did you mean one of these: `string`, `int`, `float`, `bool`, `ComplexTypes`?
-// --> class/secure_types.baml:11
-// |
-// 10 | h (((int | string)[]) | map)
-// 11 | i (apple, banana | cherry | date_fruit | eggplant_vegetable)[]
-// |
-// error: Type `date_fruit` does not exist. Did you mean one of these: `string`, `float`, `int`, `bool`, `ComplexTypes`?
-// --> class/secure_types.baml:11
-// |
-// 10 | h (((int | string)[]) | map)
-// 11 | i (apple, banana | cherry | date_fruit | eggplant_vegetable)[]
-// |
-// error: Type `eggplant_vegetable` does not exist. Did you mean one of these: `ComplexTypes`, `string`, `int`, `float`, `bool`?
-// --> class/secure_types.baml:11
-// |
-// 10 | h (((int | string)[]) | map)
-// 11 | i (apple, banana | cherry | date_fruit | eggplant_vegetable)[]
-// |
-// error: Type `char` does not exist. Did you mean one of these: `int`, `float`, `bool`, `string`, `ComplexTypes`?
-// --> class/secure_types.baml:12
-// |
-// 11 | i (apple, banana | cherry | date_fruit | eggplant_vegetable)[]
-// 12 | j ((char, int[][], (bool | string[][])) | double[][][][], (float, int)[])
-// |
-// error: Type `double` does not exist. Did you mean one of these: `bool`, `string`, `int`, `float`, `ComplexTypes`?
-// --> class/secure_types.baml:12
-// |
-// 11 | i (apple, banana | cherry | date_fruit | eggplant_vegetable)[]
-// 12 | j ((char, int[][], (bool | string[][])) | double[][][][], (float, int)[])
-// |
-// error: Type `long` does not exist. Did you mean one of these: `int`, `float`, `bool`, `string`, `ComplexTypes`?
-// --> class/secure_types.baml:13
-// |
-// 12 | j ((char, int[][], (bool | string[][])) | double[][][][], (float, int)[])
-// 13 | k map | map
-// |
-// error: Type `double` does not exist. Did you mean one of these: `bool`, `string`, `int`, `float`, `ComplexTypes`?
// --> class/secure_types.baml:13
// |
// 12 | j ((char, int[][], (bool | string[][])) | double[][][][], (float, int)[])
@@ -186,66 +48,6 @@ class ComplexTypes {
// 13 | k map | map
// |
// error: Error validating: Maps may only have strings as keys
-// --> class/secure_types.baml:13
-// |
-// 12 | j ((char, int[][], (bool | string[][])) | double[][][][], (float, int)[])
-// 13 | k map | map
-// |
-// error: Type `AlphaNumeric_123_456_789` does not exist.
-// --> class/secure_types.baml:14
-// |
-// 13 | k map | map
-// 14 | l AlphaNumeric_123_456_789 | (int, bool?) | char[]
-// |
-// error: Type `char` does not exist. Did you mean one of these: `int`, `float`, `bool`, `string`, `ComplexTypes`?
-// --> class/secure_types.baml:14
-// |
-// 13 | k map | map
-// 14 | l AlphaNumeric_123_456_789 | (int, bool?) | char[]
-// |
-// error: Type `tuple_1` does not exist. Did you mean one of these: `float`, `bool`, `string`, `int`, `ComplexTypes`?
-// --> class/secure_types.baml:15
-// |
-// 14 | l AlphaNumeric_123_456_789 | (int, bool?) | char[]
-// 15 | m (tuple_1, tuple_2 | tuple_3, (tuple_4, tuple_5))[]
-// |
-// error: Type `tuple_2` does not exist. Did you mean one of these: `float`, `bool`, `string`, `int`, `ComplexTypes`?
-// --> class/secure_types.baml:15
-// |
-// 14 | l AlphaNumeric_123_456_789 | (int, bool?) | char[]
-// 15 | m (tuple_1, tuple_2 | tuple_3, (tuple_4, tuple_5))[]
-// |
-// error: Type `tuple_3` does not exist. Did you mean one of these: `float`, `bool`, `string`, `int`, `ComplexTypes`?
-// --> class/secure_types.baml:15
-// |
-// 14 | l AlphaNumeric_123_456_789 | (int, bool?) | char[]
-// 15 | m (tuple_1, tuple_2 | tuple_3, (tuple_4, tuple_5))[]
-// |
-// error: Type `tuple_4` does not exist. Did you mean one of these: `float`, `bool`, `string`, `int`, `ComplexTypes`?
-// --> class/secure_types.baml:15
-// |
-// 14 | l AlphaNumeric_123_456_789 | (int, bool?) | char[]
-// 15 | m (tuple_1, tuple_2 | tuple_3, (tuple_4, tuple_5))[]
-// |
-// error: Type `tuple_5` does not exist. Did you mean one of these: `float`, `bool`, `string`, `int`, `ComplexTypes`?
-// --> class/secure_types.baml:15
-// |
-// 14 | l AlphaNumeric_123_456_789 | (int, bool?) | char[]
-// 15 | m (tuple_1, tuple_2 | tuple_3, (tuple_4, tuple_5))[]
-// |
-// error: Type `another_key` does not exist. Did you mean one of these: `string`, `int`, `ComplexTypes`, `float`, `bool`?
-// --> class/secure_types.baml:16
-// |
-// 15 | m (tuple_1, tuple_2 | tuple_3, (tuple_4, tuple_5))[]
-// 16 | n map>
-// |
-// error: Type `complex_key_type` does not exist. Did you mean one of these: `ComplexTypes`, `float`, `bool`, `int`, `string`?
-// --> class/secure_types.baml:16
-// |
-// 15 | m (tuple_1, tuple_2 | tuple_3, (tuple_4, tuple_5))[]
-// 16 | n map>
-// |
-// error: Error validating: Maps may only have strings as keys
// --> class/secure_types.baml:16
// |
// 15 | m (tuple_1, tuple_2 | tuple_3, (tuple_4, tuple_5))[]
@@ -257,15 +59,3 @@ class ComplexTypes {
// 15 | m (tuple_1, tuple_2 | tuple_3, (tuple_4, tuple_5))[]
// 16 | n map>
// |
-// error: Type `double` does not exist. Did you mean one of these: `bool`, `string`, `int`, `float`, `ComplexTypes`?
-// --> class/secure_types.baml:17
-// |
-// 16 | n map>
-// 17 | o (((int | string) | bool[]), (float, double) | long_long_identifier_123)
-// |
-// error: Type `long_long_identifier_123` does not exist. Did you mean `ComplexTypes`?
-// --> class/secure_types.baml:17
-// |
-// 16 | n map>
-// 17 | o (((int | string) | bool[]), (float, double) | long_long_identifier_123)
-// |
diff --git a/engine/baml-lib/baml/tests/validation_files/class/spelling_error.baml b/engine/baml-lib/baml/tests/validation_files/class/spelling_error.baml
index edb2ccd78..940c62e38 100644
--- a/engine/baml-lib/baml/tests/validation_files/class/spelling_error.baml
+++ b/engine/baml-lib/baml/tests/validation_files/class/spelling_error.baml
@@ -4,22 +4,4 @@ class InterfaceTwo {
class InterfaceOne {
uses InterfaceT
-}
-
-
-
-
-
-
-// error: Type `strin` does not exist. Did you mean one of these: `string`, `int`, `float`, `bool`, `InterfaceOne`, `InterfaceTwo`?
-// --> class/spelling_error.baml:2
-// |
-// 1 | class InterfaceTwo {
-// 2 | interface strin
-// |
-// error: Type `InterfaceT` does not exist. Did you mean one of these: `InterfaceTwo`, `InterfaceOne`, `int`, `string`, `float`, `bool`?
-// --> class/spelling_error.baml:6
-// |
-// 5 | class InterfaceOne {
-// 6 | uses InterfaceT
-// |
+}
\ No newline at end of file
diff --git a/engine/baml-lib/baml/tests/validation_files/class/unknown_type.baml b/engine/baml-lib/baml/tests/validation_files/class/unknown_type.baml
index c5554052e..2bc485811 100644
--- a/engine/baml-lib/baml/tests/validation_files/class/unknown_type.baml
+++ b/engine/baml-lib/baml/tests/validation_files/class/unknown_type.baml
@@ -1,16 +1,4 @@
class InterfaceTwo {
interface string
prop2 Bar
-}
-
-
-
-
-
-
-// error: Type `Bar` does not exist. Did you mean one of these: `int`, `bool`, `float`, `string`, `InterfaceTwo`?
-// --> class/unknown_type.baml:3
-// |
-// 2 | interface string
-// 3 | prop2 Bar
-// |
+}
\ No newline at end of file
diff --git a/engine/baml-lib/baml/tests/validation_files/client/bad_template_args.baml b/engine/baml-lib/baml/tests/validation_files/client/bad_template_args.baml
index a753fba49..e0c5f02fc 100644
--- a/engine/baml-lib/baml/tests/validation_files/client/bad_template_args.baml
+++ b/engine/baml-lib/baml/tests/validation_files/client/bad_template_args.baml
@@ -2,15 +2,24 @@ client MyClient2 {
provider baml-openai-chat
}
-
-
-
-
-
-
-// error: Error validating: Only LLM clients are supported. Use: client
+// error: Error validating: This line is invalid. It does not start with any known Baml schema keyword.
// --> client/bad_template_args.baml:1
// |
// |
// 1 | client MyClient2 {
+// 2 | provider baml-openai-chat
+// |
+// error: Error validating: This line is invalid. It does not start with any known Baml schema keyword.
+// --> client/bad_template_args.baml:2
+// |
+// 1 | client MyClient2 {
+// 2 | provider baml-openai-chat
+// 3 | }
+// |
+// error: Error validating: This line is invalid. It does not start with any known Baml schema keyword.
+// --> client/bad_template_args.baml:3
+// |
+// 2 | provider baml-openai-chat
+// 3 | }
+// 4 |
// |
diff --git a/engine/baml-lib/baml/tests/validation_files/client/missing_template_args.baml b/engine/baml-lib/baml/tests/validation_files/client/missing_template_args.baml
index b20366e4d..fe0a640f8 100644
--- a/engine/baml-lib/baml/tests/validation_files/client/missing_template_args.baml
+++ b/engine/baml-lib/baml/tests/validation_files/client/missing_template_args.baml
@@ -1,12 +1,3 @@
client MyClient {
provider baml-openai-chat
-}
-
-// error: Error validating: Missing template for client. (did you forget )
-// --> client/missing_template_args.baml:1
-// |
-// |
-// 1 | client MyClient {
-// 2 | provider baml-openai-chat
-// 3 | }
-// |
+}
\ No newline at end of file
diff --git a/engine/baml-lib/baml/tests/validation_files/client/period_in_model_type.baml b/engine/baml-lib/baml/tests/validation_files/client/period_in_model_type.baml
index 5f93d8811..59adb740c 100644
--- a/engine/baml-lib/baml/tests/validation_files/client/period_in_model_type.baml
+++ b/engine/baml-lib/baml/tests/validation_files/client/period_in_model_type.baml
@@ -4,6 +4,4 @@ client MyClient {
model gpt-3.5-turbo
max_tokens 100
}
-}
-
-
+}
\ No newline at end of file
diff --git a/engine/baml-lib/baml/tests/validation_files/client/unknown_prop.baml b/engine/baml-lib/baml/tests/validation_files/client/unknown_prop.baml
index c8325d1fd..0db5d1238 100644
--- a/engine/baml-lib/baml/tests/validation_files/client/unknown_prop.baml
+++ b/engine/baml-lib/baml/tests/validation_files/client/unknown_prop.baml
@@ -6,7 +6,6 @@ client MyClient {
}
}
-
// error: Error validating: Unknown field `myExtraProp` in client
// --> client/unknown_prop.baml:3
// |
diff --git a/engine/baml-lib/baml/tests/validation_files/dictionary/valid_dictionary.baml b/engine/baml-lib/baml/tests/validation_files/dictionary/valid_dictionary.baml
index eae426153..45547bac4 100644
--- a/engine/baml-lib/baml/tests/validation_files/dictionary/valid_dictionary.baml
+++ b/engine/baml-lib/baml/tests/validation_files/dictionary/valid_dictionary.baml
@@ -37,6 +37,4 @@ client MyClient {
#{//a comment in a prompt}
"#
}
-}
-
-
+}
\ No newline at end of file
diff --git a/engine/baml-lib/baml/tests/validation_files/enum/invalid_commas.baml b/engine/baml-lib/baml/tests/validation_files/enum/invalid_commas.baml
index e55dd3ec6..de9fef648 100644
--- a/engine/baml-lib/baml/tests/validation_files/enum/invalid_commas.baml
+++ b/engine/baml-lib/baml/tests/validation_files/enum/invalid_commas.baml
@@ -4,21 +4,21 @@ enum Test {
C,
}
-// error: Error validating: This line is not an enum value definition. BAML enums don't have commas, and all values must be all caps
+// error: Error validating: This line is not a valid field or attribute definition. A valid class property looks like: 'myProperty string[] @description("This is a description")'
// --> enum/invalid_commas.baml:2
// |
// 1 | enum Test {
// 2 | A,
// 3 | B,
// |
-// error: Error validating: This line is not an enum value definition. BAML enums don't have commas, and all values must be all caps
+// error: Error validating: This line is not a valid field or attribute definition. A valid class property looks like: 'myProperty string[] @description("This is a description")'
// --> enum/invalid_commas.baml:3
// |
// 2 | A,
// 3 | B,
// 4 | C,
// |
-// error: Error validating: This line is not an enum value definition. BAML enums don't have commas, and all values must be all caps
+// error: Error validating: This line is not a valid field or attribute definition. A valid class property looks like: 'myProperty string[] @description("This is a description")'
// --> enum/invalid_commas.baml:4
// |
// 3 | B,
diff --git a/engine/baml-lib/baml/tests/validation_files/functions/missing_input_output_value.baml b/engine/baml-lib/baml/tests/validation_files/functions/missing_input_output_value.baml
deleted file mode 100644
index 345bffda0..000000000
--- a/engine/baml-lib/baml/tests/validation_files/functions/missing_input_output_value.baml
+++ /dev/null
@@ -1,40 +0,0 @@
-function A {
- input
-}
-
-function B {
- input string
- output
-}
-
-// error: Error validating: Missing function field type
-// --> functions/missing_input_output_value.baml:2
-// |
-// 1 | function A {
-// 2 | input
-// 3 | }
-// |
-// error: Error validating function "A": This function declaration is invalid. It is missing an input or output field.
-// --> functions/missing_input_output_value.baml:1
-// |
-// |
-// 1 | function A {
-// 2 | input
-// 3 | }
-// |
-// error: Error validating: Missing function field type
-// --> functions/missing_input_output_value.baml:7
-// |
-// 6 | input string
-// 7 | output
-// 8 | }
-// |
-// error: Error validating function "B": This function declaration is invalid. It is missing an input or output field.
-// --> functions/missing_input_output_value.baml:5
-// |
-// 4 |
-// 5 | function B {
-// 6 | input string
-// 7 | output
-// 8 | }
-// |
diff --git a/engine/baml-lib/baml/tests/validation_files/functions/nonexistent_input_output_types.baml b/engine/baml-lib/baml/tests/validation_files/functions/nonexistent_input_output_types.baml
deleted file mode 100644
index 8d3a4f21e..000000000
--- a/engine/baml-lib/baml/tests/validation_files/functions/nonexistent_input_output_types.baml
+++ /dev/null
@@ -1,22 +0,0 @@
-// test comment
-function Foo {
- input Bar
- output Baz
-}
-
-
-
-
-
-// error: Type `Bar` does not exist. Did you mean one of these: `int`, `bool`, `float`, `string`?
-// --> functions/nonexistent_input_output_types.baml:3
-// |
-// 2 | function Foo {
-// 3 | input Bar
-// |
-// error: Type `Baz` does not exist. Did you mean one of these: `int`, `bool`, `float`, `string`?
-// --> functions/nonexistent_input_output_types.baml:4
-// |
-// 3 | input Bar
-// 4 | output Baz
-// |
diff --git a/engine/baml-lib/baml/tests/validation_files/functions/valid_functions.baml b/engine/baml-lib/baml/tests/validation_files/functions/valid_functions.baml
deleted file mode 100644
index 39a8415e8..000000000
--- a/engine/baml-lib/baml/tests/validation_files/functions/valid_functions.baml
+++ /dev/null
@@ -1,23 +0,0 @@
-function One {
- input string
- output string
-}
-
-class Hello {
- world string
-}
-
-class Hello2 {
- /// Comment about this
- world string // Comment about this
- /// This works
- // This works
- relevant_phrase_from_user_document string // This breaks
- // test
- @alias(foo)
-}
-
-function Two {
- input Hello
- output Hello2
-}
diff --git a/engine/baml-lib/baml/tests/validation_files/functions/valid_multiple_arg.baml b/engine/baml-lib/baml/tests/validation_files/functions/valid_multiple_arg.baml
deleted file mode 100644
index 664e3b60f..000000000
--- a/engine/baml-lib/baml/tests/validation_files/functions/valid_multiple_arg.baml
+++ /dev/null
@@ -1,20 +0,0 @@
-class Bar {
- hello string
-}
-
-function Foo {
- input (arg_name: Bar, arg_2: Bar)
- output string
-}
-
-function ExtractAllergies {
- input (hello: string, world: string)
- output Allergies[]
-}
-
-// error: Type `Allergies` does not exist. Did you mean one of these: `string`, `Bar`, `int`, `float`, `bool`?
-// --> functions/valid_multiple_arg.baml:12
-// |
-// 11 | input (hello: string, world: string)
-// 12 | output Allergies[]
-// |
diff --git a/engine/baml-lib/baml/tests/validation_files/functions_v2/duplicate_names.baml b/engine/baml-lib/baml/tests/validation_files/functions_v2/duplicate_names.baml
index ef6e2d3ac..06ad56c8e 100644
--- a/engine/baml-lib/baml/tests/validation_files/functions_v2/duplicate_names.baml
+++ b/engine/baml-lib/baml/tests/validation_files/functions_v2/duplicate_names.baml
@@ -8,7 +8,7 @@ function Bar(a: string, b: int | bool) -> int {
prompt #"fa"#
}
-// error: The function "Bar" cannot be defined because a function[deprecated signature] with that name already exists.
+// error: The function "Bar" cannot be defined because a function with that name already exists.
// --> functions_v2/duplicate_names.baml:6
// |
// 5 |
diff --git a/engine/baml-lib/baml/tests/validation_files/functions_v2/invalid.baml b/engine/baml-lib/baml/tests/validation_files/functions_v2/invalid.baml
index a7fcfb9d1..a14705a95 100644
--- a/engine/baml-lib/baml/tests/validation_files/functions_v2/invalid.baml
+++ b/engine/baml-lib/baml/tests/validation_files/functions_v2/invalid.baml
@@ -7,22 +7,6 @@ function FooBar(arg) -> bar {
}
-
-// error: Error validating function "Foo": No return type specified. Valid function syntax is
-// ```
-// function Foo(param1: String, param2: String) -> ReturnType {
-// client SomeClient
-// prompt #"..."#
-// }
-// ```
-// --> functions_v2/invalid.baml:1
-// |
-// |
-// 1 | function Foo() -> {
-// 2 | client Bar
-// 3 | prompt #"fa"#
-// 4 | }
-// |
// error: Error validating: No type specified for argument: arg. Expected: `arg: type`
// --> functions_v2/invalid.baml:6
// |
diff --git a/engine/baml-lib/baml/tests/validation_files/functions_v2/invalid2.baml b/engine/baml-lib/baml/tests/validation_files/functions_v2/invalid2.baml
index 95ad32ca8..8ebfc292f 100644
--- a/engine/baml-lib/baml/tests/validation_files/functions_v2/invalid2.baml
+++ b/engine/baml-lib/baml/tests/validation_files/functions_v2/invalid2.baml
@@ -29,64 +29,21 @@ function Foo6(arg: int) -> float {
prompt
}
-// error: Error validating: Missing `prompt` field in function. Add to the block:
-// ```
-// prompt #"..."#
-// ```
-// --> functions_v2/invalid2.baml:1
+// error: Error validating Function "Foo4": expr as val!! This field declaration is invalid. It is either missing a name or a type.
+// --> functions_v2/invalid2.baml:17
// |
+// 16 | client bar
+// 17 | prompt
// |
-// 1 | function Foo1(arg: int) -> float {
+// error: Error validating Function "Foo5": expr as val!! This field declaration is invalid. It is either missing a name or a type.
+// --> functions_v2/invalid2.baml:22
// |
-// error: Error validating: Missing `client` field in function. Add to the block:
-// ```
-// client GPT4
-// ```
-// --> functions_v2/invalid2.baml:5
-// |
-// 4 |
-// 5 | function Foo2(arg: int) -> float {
-// |
-// error: Expected a template_string value, but received string value `"..."`.
-// --> functions_v2/invalid2.baml:11
-// |
-// 10 | client bar
-// 11 | prompt "..."
-// |
-// error: Error validating: Missing `prompt` field in function. Add to the block:
-// ```
-// prompt #"..."#
-// ```
-// --> functions_v2/invalid2.baml:9
-// |
-// 8 |
-// 9 | function Foo3(arg: int) -> float {
-// |
-// error: Error validating: Missing `prompt` field in function. Add to the block:
-// ```
-// prompt #"..."#
-// ```
-// --> functions_v2/invalid2.baml:15
-// |
-// 14 |
-// 15 | function Foo4(arg: int) -> float {
-// |
-// error: Error validating: Missing `client` field in function. Add to the block:
-// ```
-// client GPT4
-// ```
-// --> functions_v2/invalid2.baml:21
-// |
-// 20 |
// 21 | function Foo5(arg: int) -> float {
+// 22 | client
// |
-// error: Error validating: Missing `prompt` and `client` fields in function. Add to the block:
-// ```
-// client GPT4
-// prompt #"..."#
-// ```
-// --> functions_v2/invalid2.baml:27
+// error: Error validating Function "Foo6": expr as val!! This field declaration is invalid. It is either missing a name or a type.
+// --> functions_v2/invalid2.baml:28
// |
-// 26 |
// 27 | function Foo6(arg: int) -> float {
+// 28 | client
// |
diff --git a/engine/baml-lib/baml/tests/validation_files/functions_v2/tests/parameters b/engine/baml-lib/baml/tests/validation_files/functions_v2/tests/parameters
new file mode 100644
index 000000000..27d343d5a
--- /dev/null
+++ b/engine/baml-lib/baml/tests/validation_files/functions_v2/tests/parameters
@@ -0,0 +1,14 @@
+function TestFnNamedArgsSingleBool(myBool: bool) -> string{
+ client Vertex
+ prompt #"
+ Return this value back to me: {{myBool}}
+ "#
+}
+
+// error: Error validating: A BAML file must have the file extension `.baml`
+// --> functions_v2/tests/parameters:1
+// |
+// |
+// 1 | function TestFnNamedArgsSingleBool(myBool: bool) -> string{
+// | ^ Unexpected token.
+// |
diff --git a/engine/baml-lib/baml/tests/validation_files/functions_v2/tests/valid_tests.baml b/engine/baml-lib/baml/tests/validation_files/functions_v2/tests/valid_tests.baml
index 746a452e5..100886cd1 100644
--- a/engine/baml-lib/baml/tests/validation_files/functions_v2/tests/valid_tests.baml
+++ b/engine/baml-lib/baml/tests/validation_files/functions_v2/tests/valid_tests.baml
@@ -72,4 +72,11 @@ test Foo {
functions [InputEnum]
args {
}
-}
\ No newline at end of file
+}
+
+// warning: 'email' is a string, expected class
+// --> functions_v2/tests/valid_tests.baml:22
+// |
+// 21 | client Bar
+// 22 | prompt #" subject line {{ email.subject }} "#
+// |
diff --git a/engine/baml-lib/baml/tests/validation_files/functions_v2/valid.baml b/engine/baml-lib/baml/tests/validation_files/functions_v2/valid.baml
index 5167c9e4f..0e792b6e8 100644
--- a/engine/baml-lib/baml/tests/validation_files/functions_v2/valid.baml
+++ b/engine/baml-lib/baml/tests/validation_files/functions_v2/valid.baml
@@ -60,4 +60,11 @@ string
function InputImage(image: image) -> string {
client Bar
prompt #" image {{ image }} "#
-}
\ No newline at end of file
+}
+
+// warning: 'email' is a string, expected class
+// --> functions_v2/valid.baml:22
+// |
+// 21 | client Bar
+// 22 | prompt #" subject line {{ email.subject }} "#
+// |
diff --git a/engine/baml-lib/baml/tests/validation_files/generators/v1.baml b/engine/baml-lib/baml/tests/validation_files/generators/v1.baml
index a4b47bdac..58d2142ba 100644
--- a/engine/baml-lib/baml/tests/validation_files/generators/v1.baml
+++ b/engine/baml-lib/baml/tests/validation_files/generators/v1.baml
@@ -38,4 +38,4 @@ generator lang_ruby_2 {
output_type typescript
default_client_mode "sync"
output_dir "../"
-}
+}
\ No newline at end of file
diff --git a/engine/baml-lib/baml/tests/validation_files/strings/unquoted_strings.baml b/engine/baml-lib/baml/tests/validation_files/strings/unquoted_strings.baml
index 41997accc..c1614dda4 100644
--- a/engine/baml-lib/baml/tests/validation_files/strings/unquoted_strings.baml
+++ b/engine/baml-lib/baml/tests/validation_files/strings/unquoted_strings.baml
@@ -1,5 +1,5 @@
client Hello {
- provider baml-openai-chat
+ provider openai
options {
thing hello'world
banned @helloworld
@@ -8,33 +8,11 @@ client Hello {
}
}
-// error: Error validating: This line is not a valid field or attribute definition. A valid property may look like: 'myProperty "some value"' for example, with no colons.
+// error: Error validating Client "Hello": expr as val!! This field declaration is invalid. It is either missing a name or a type.
// --> strings/unquoted_strings.baml:3
// |
-// 2 | provider baml-openai-chat
+// 2 | provider openai
// 3 | options {
-// 4 | thing hello'world
-// |
-// error: Error validating: This line is not a valid field or attribute definition. A valid property may look like: 'myProperty "some value"' for example, with no colons.
-// --> strings/unquoted_strings.baml:4
-// |
-// 3 | options {
-// 4 | thing hello'world
-// 5 | banned @helloworld
-// |
-// error: Error validating: This line is not a valid field or attribute definition. A valid property may look like: 'myProperty "some value"' for example, with no colons.
-// --> strings/unquoted_strings.baml:6
-// |
-// 5 | banned @helloworld
-// 6 | banned2 #helloworld
-// 7 | banned3 hello(world)
-// |
-// error: Error validating: This line is not a valid field or attribute definition. A valid property may look like: 'myProperty "some value"' for example, with no colons.
-// --> strings/unquoted_strings.baml:7
-// |
-// 6 | banned2 #helloworld
-// 7 | banned3 hello(world)
-// 8 | }
// |
// error: Error validating: This line is invalid. It does not start with any known Baml schema keyword.
// --> strings/unquoted_strings.baml:9
diff --git a/engine/baml-lib/baml/tests/validation_files/template_string/duplicate_names.baml b/engine/baml-lib/baml/tests/validation_files/template_string/duplicate_names.baml
index 5a4540ef9..27ac7eece 100644
--- a/engine/baml-lib/baml/tests/validation_files/template_string/duplicate_names.baml
+++ b/engine/baml-lib/baml/tests/validation_files/template_string/duplicate_names.baml
@@ -11,17 +11,13 @@ function SomeString {
output string
}
-
-
-
-
// error: The template_string "SomeString" cannot be defined because a template_string with that name already exists.
// --> template_string/duplicate_names.baml:5
// |
// 4 |
// 5 | template_string SomeString(a: int) #"
// |
-// error: The function[deprecated signature] "SomeString" cannot be defined because a template_string with that name already exists.
+// error: The function "SomeString" cannot be defined because a template_string with that name already exists.
// --> template_string/duplicate_names.baml:9
// |
// 8 |
diff --git a/engine/baml-lib/baml/tests/validation_files/variants/bad_client_def.baml b/engine/baml-lib/baml/tests/validation_files/variants/bad_client_def.baml
deleted file mode 100644
index b0470542d..000000000
--- a/engine/baml-lib/baml/tests/validation_files/variants/bad_client_def.baml
+++ /dev/null
@@ -1,29 +0,0 @@
-// The error here is bad.
-function Foo {
- input string
- output string
-}
-
-client MyClient {
- provider baml-openai-chat
-}
-
-impl V1 {
- client MyClient
- prompt "Hello there"
-}
-
-
-
-// warning: To use comments and {#vars} use a block string. #"..."# instead.
-// --> variants/bad_client_def.baml:13
-// |
-// 12 | client MyClient
-// 13 | prompt "Hello there"
-// |
-// error: Error validating: Did you mean `client` instead of `client<...>`?
-// --> variants/bad_client_def.baml:12
-// |
-// 11 | impl V1 {
-// 12 | client MyClient
-// |
diff --git a/engine/baml-lib/baml/tests/validation_files/variants/client_unknown.baml b/engine/baml-lib/baml/tests/validation_files/variants/client_unknown.baml
deleted file mode 100644
index aed0d869e..000000000
--- a/engine/baml-lib/baml/tests/validation_files/variants/client_unknown.baml
+++ /dev/null
@@ -1,22 +0,0 @@
-function Foo {
- input string
- output string
-}
-
-impl FooImpl {
- client MadeUpClient
- prompt #"
- hello world {#input}
- {#print_type(output)}
- "#
-}
-
-
-
-
-// error: Error validating: Unknown client `MadeUpClient`
-// --> variants/client_unknown.baml:7
-// |
-// 6 | impl FooImpl {
-// 7 | client MadeUpClient
-// |
diff --git a/engine/baml-lib/baml/tests/validation_files/variants/missing_template_args.baml b/engine/baml-lib/baml/tests/validation_files/variants/missing_template_args.baml
deleted file mode 100644
index f08bf4e63..000000000
--- a/engine/baml-lib/baml/tests/validation_files/variants/missing_template_args.baml
+++ /dev/null
@@ -1,33 +0,0 @@
-function Foo {
- input string
- output string
-}
-
-client MyClient {
- provider baml-openai-chat
-}
-
-impl Impl1 {
- prompt "hello"
- client MyClient
-}
-
-impl Impl2 {
- prompt "hello"
- client MyClient
-}
-
-
-
-// error: Error validating: impl requires 2 template args. (did you forget )
-// --> variants/missing_template_args.baml:10
-// |
-// 9 |
-// 10 | impl Impl1 {
-// |
-// error: Error validating: Missing template for impl. (did you forget )
-// --> variants/missing_template_args.baml:15
-// |
-// 14 |
-// 15 | impl Impl2 {
-// |
diff --git a/engine/baml-lib/baml/tests/validation_files/variants/prompt/adapters.baml b/engine/baml-lib/baml/tests/validation_files/variants/prompt/adapters.baml
deleted file mode 100644
index 1c016d621..000000000
--- a/engine/baml-lib/baml/tests/validation_files/variants/prompt/adapters.baml
+++ /dev/null
@@ -1,110 +0,0 @@
-function Foo {
- input InputType
- output OutputType
-
- default_impl FooImpl
-}
-
-class InputType {
- a InputType2
- b bool
-}
-
-class OtherInput {
- d string
-}
-
-class InputType2 {
- c string
-}
-
-class OutputType {
- x Sentiment
- y bool
-}
-
-client MyClient {
- provider baml-openai-chat
-}
-
-enum Sentiment {
- HAPPY
-}
-
-impl FooImpl {
- client MyClient
- prompt #"
- A {#input.a}!! {// A
- multiline
- comment //}
-
-
- {#input.a.c}
-
- the rest of the prompt
- no-tab
- tab1
- tab2
- morespaces here
- {#input.a} {#input.a} hi there
- JSON:
- {#print_enum(Sentiment)}
- {#print_type(output)}
- "#
-
- adapter python#"
- return OutputType(x=Sentiment.HAPPY, y=False)
- "#
-}
-
-impl BarImpl {
- client MyClient
- prompt #"
- A {#input.a}!! {// A
- multiline
- comment //}
-
-
- {#input.a.c}
-
- the rest of the prompt
- no-tab
- tab1
- tab2
- morespaces here
- {#input.a} {#input.a} hi there
- JSON:
- {#print_enum(Sentiment)}
- {#print_type(output)}
- "#
-
- adapter python#"
- return OutputType(x=Sentiment.HAPPY, y=False)
- "#
-}
-
-impl Bar2Impl {
- client MyClient
- prompt #"
- A {#input.a}!! {// A
- multiline
- comment //}
-
-
- {#input.a.c}
-
- the rest of the prompt
- no-tab
- tab1
- tab2
- morespaces here
- {#input.a} {#input.a} hi there
- JSON:
- {#print_enum(Sentiment)}
- {#print_type(output)}
- "#
-
- adapter python#"
- return OutputType(x=Sentiment.HAPPY, y=False)
- "#
-}
diff --git a/engine/baml-lib/baml/tests/validation_files/variants/prompt/invalid_chat.baml b/engine/baml-lib/baml/tests/validation_files/variants/prompt/invalid_chat.baml
deleted file mode 100644
index d26830245..000000000
--- a/engine/baml-lib/baml/tests/validation_files/variants/prompt/invalid_chat.baml
+++ /dev/null
@@ -1,68 +0,0 @@
-function Foo {
- input InputType
- output OutputType
-}
-
-class InputType {
- a InputType2
- b bool
-}
-
-class OtherInput {
- d string
-}
-
-class InputType2 {
- c string
-}
-
-class OutputType {
- x Sentiment
- y bool
-}
-
-client MyClient {
- provider baml-openai-chat
-}
-
-enum Sentiment {
- HAPPY
-}
-
-impl FooImpl{
- client MyClient
- prompt #"
- A {#input.a}!! {// A
- multiline
- comment //}
-
-
- {#input.a.c}
- {#chat()}
- the rest of the prompt
- no-tab
- tab1
- tab2
- morespaces here
- {#chat}
- {#input.a} {#input.a} hi there
- JSON:
- {#print_enum(Sentiment)}
- {#print_type(output)}
- "#
-}
-
-
-
-// error: Unexpected token. Expected one of: Expected exactly one argument for role. e.g. {#chat(user)} or {#chat(system)}
-// --> variants/prompt/invalid_chat.baml:41
-// |
-// 40 | {#input.a.c}
-// 41 | {#chat()}
-// |
-// error: Unexpected token. Expected one of: Expected exactly one argument for role. e.g. {#chat(user)} or {#chat(system)}
-// --> variants/prompt/invalid_chat.baml:47
-// |
-// 46 | morespaces here
-// 47 | {#chat}
-// |
diff --git a/engine/baml-lib/baml/tests/validation_files/variants/prompt/overrides.baml b/engine/baml-lib/baml/tests/validation_files/variants/prompt/overrides.baml
deleted file mode 100644
index 6591cebc2..000000000
--- a/engine/baml-lib/baml/tests/validation_files/variants/prompt/overrides.baml
+++ /dev/null
@@ -1,63 +0,0 @@
-function Foo {
- input InputType
- output OutputType
-}
-
-class InputType {
- a InputType2
- b bool
-}
-
-class OtherInput {
- d string
-}
-
-class InputType2 {
- c string
-}
-
-class OutputType {
- x Sentiment
- y bool
-}
-
-client MyClient {
- provider baml-openai-chat
-}
-
-enum Sentiment {
- HAPPY
-}
-
-impl FooImpl{
- client MyClient
-
- override Sentiment {
- HAPPY @alias("happy")
- // Then
- @description(#"
- foo
- "#)
- }
-
- prompt #"
- A {#input.a}!! {// A
- multiline
- comment //}
-
-
- {#input.a.c}
-
- the rest of the prompt
- no-tab
- tab1
- tab2
- morespaces here
- {#input.a} {#input.a} hi there
- JSON:
- {#print_enum(Sentiment)}
- {#print_type(output)}
- "#
-}
-
-
diff --git a/engine/baml-lib/baml/tests/validation_files/variants/prompt/spaces_in_prompts.baml b/engine/baml-lib/baml/tests/validation_files/variants/prompt/spaces_in_prompts.baml
deleted file mode 100644
index 27958e1e0..000000000
--- a/engine/baml-lib/baml/tests/validation_files/variants/prompt/spaces_in_prompts.baml
+++ /dev/null
@@ -1,52 +0,0 @@
-function Foo {
- input InputType
- output OutputType
-}
-
-class InputType {
- a InputType2
- b bool
-}
-
-class OtherInput {
- d string
-}
-
-class InputType2 {
- c string
-}
-
-class OutputType {
- x Sentiment
- y bool
-}
-
-client MyClient {
- provider baml-openai-chat
-}
-
-enum Sentiment {
- HAPPY
-}
-
-impl FooImpl{
- client MyClient
- prompt #"
- A {# input.a }!! {// A
- multiline
- comment //}
-
-
- {# input.a.c }
-
- the rest of the prompt
- no-tab
- tab1
- tab2
- morespaces here
- {# input.a } {# input.a } hi there
- JSON:
- {#print_enum( Sentiment) }
- {#print_type(output )}
- "#
-}
diff --git a/engine/baml-lib/baml/tests/validation_files/variants/prompt/unknown_prompt_var_multi_arg.baml b/engine/baml-lib/baml/tests/validation_files/variants/prompt/unknown_prompt_var_multi_arg.baml
deleted file mode 100644
index 1482b9fd5..000000000
--- a/engine/baml-lib/baml/tests/validation_files/variants/prompt/unknown_prompt_var_multi_arg.baml
+++ /dev/null
@@ -1,37 +0,0 @@
-class Bar {
- hello string
-}
-
-// TODO: finish this validation test..
-function Foo {
- input (arg_name: Bar, arg_2: Bar)
- output string
-}
-
-
-client MyClient {
- provider baml-openai-chat
-}
-
-impl FooImpl{
- client MyClient
- prompt #"
- A {#input.e}
- {#InputType.e}
-
- the rest of the prompt
- tabbed-in
- JSON:
- {#print_enum(enumarg)}
- {#print_type(typearg)}
- "#
-}
-
-
-
-// error: Error validating: Must start with `input`
-// --> variants/prompt/unknown_prompt_var_multi_arg.baml:20
-// |
-// 19 | A {#input.e}
-// 20 | {#InputType.e}
-// |
diff --git a/engine/baml-lib/baml/tests/validation_files/variants/prompt/unknown_prompt_variables.baml b/engine/baml-lib/baml/tests/validation_files/variants/prompt/unknown_prompt_variables.baml
deleted file mode 100644
index 06ba258d3..000000000
--- a/engine/baml-lib/baml/tests/validation_files/variants/prompt/unknown_prompt_variables.baml
+++ /dev/null
@@ -1,79 +0,0 @@
-function Foo {
- input InputType
- output OutputType
-}
-
-class InputType {
- a string
- b bool
-}
-
-class OutputType {
- x string
- y bool
-}
-
-client MyClient {
- provider baml-openai-chat
-}
-
-impl FooImpl{
- client MyClient
- prompt #"
- A {#input.e}
- {#input.e}
- {#input.e.y}
-
- the rest of the prompt
- tabbed-in
- JSON:
- {#print_enum(enumarg)}
- {#print_type(typearg)}
- "#
-}
-
-enum Enumarg {
- A
- B
- C
-}
-
-
-
-
-
-
-
-
-
-
-// error: Error validating: Unknown field `e` in class `InputType`
-// --> variants/prompt/unknown_prompt_variables.baml:23
-// |
-// 22 | prompt #"
-// 23 | A {#input.e}
-// |
-// error: Error validating: Unknown field `e` in class `InputType`
-// --> variants/prompt/unknown_prompt_variables.baml:24
-// |
-// 23 | A {#input.e}
-// 24 | {#input.e}
-// |
-// error: Error validating: Unknown field `e` in class `InputType`
-// --> variants/prompt/unknown_prompt_variables.baml:25
-// |
-// 24 | {#input.e}
-// 25 | {#input.e.y}
-// |
-// error: Enum `enumarg` does not exist. No Enums are used in the output of this function.
-// --> variants/prompt/unknown_prompt_variables.baml:30
-// |
-// 29 | JSON:
-// 30 | {#print_enum(enumarg)}
-// |
-// error: Type `typearg` does not exist. Did you mean one of these: `output`, `OutputType`?
-// --> variants/prompt/unknown_prompt_variables.baml:31
-// |
-// 30 | {#print_enum(enumarg)}
-// 31 | {#print_type(typearg)}
-// |
diff --git a/engine/baml-lib/baml/tests/validation_files/variants/prompt/valid_chat_prompts.baml b/engine/baml-lib/baml/tests/validation_files/variants/prompt/valid_chat_prompts.baml
deleted file mode 100644
index d7b5f2a82..000000000
--- a/engine/baml-lib/baml/tests/validation_files/variants/prompt/valid_chat_prompts.baml
+++ /dev/null
@@ -1,55 +0,0 @@
-function Foo {
- input InputType
- output OutputType
-}
-
-class InputType {
- a InputType2
- b bool
-}
-
-class OtherInput {
- d string
-}
-
-class InputType2 {
- c string
-}
-
-class OutputType {
- x Sentiment
- y bool
-}
-
-client MyClient {
- provider baml-openai-chat
-}
-
-enum Sentiment {
- HAPPY
-}
-
-impl FooImpl{
- client MyClient
- prompt #"
- A {#input.a}!! {// A
- multiline
- comment //}
-
-
- {#input.a.c}
- {#chat(user)}
- the rest of the prompt
- no-tab
- tab1
- tab2
- morespaces here
- {#chat(system)}
- {#input.a} {#input.a} hi there
- JSON:
- {#print_enum(Sentiment)}
- {#print_type(output)}
- "#
-}
-
-
diff --git a/engine/baml-lib/baml/tests/validation_files/variants/prompt/valid_prompt.baml b/engine/baml-lib/baml/tests/validation_files/variants/prompt/valid_prompt.baml
deleted file mode 100644
index 28592882d..000000000
--- a/engine/baml-lib/baml/tests/validation_files/variants/prompt/valid_prompt.baml
+++ /dev/null
@@ -1,81 +0,0 @@
-function Foo {
- input InputType
- output OutputType
- default_impl FooImpl
-}
-
-class InputType {
- a InputType2
- b bool
-}
-
-class OtherInput {
- d string
-}
-
-class InputType2 {
- c string
-}
-
-class OutputType {
- x Sentiment
- y bool
-}
-
-client MyClient {
- provider baml-openai-chat
-}
-
-enum Sentiment {
- HAPPY
-}
-
-impl FooImpl{
- client MyClient
- prompt #"
- A {#input.a}!! {// A
- multiline
- comment //}
-
-
- {#input.a.c}
-
- the rest of the prompt
- no-tab
- tab1
- tab2
- morespaces here
- {#input.a} {#input.a} hi there
- JSON:
- {#print_enum(Sentiment)}
- {#print_type(output)}
- "#
-}
-
-
-
-impl FooImpl3 {
- // Include a trailing space
- client MyClient
- prompt #"
- A {#input.a}!! {// A
- multiline
- comment //}
-
-
- {#input.a.c}
-
- the rest of the prompt
- no-tab
- tab1
- tab2
- morespaces here
- {#input.a} {#input.a} hi there
- JSON:
- {#print_enum(Sentiment)}
- {#print_type(output)}
- "#
-}
-
-
-
diff --git a/engine/baml-lib/baml/tests/validation_files/variants/require_client_field.baml b/engine/baml-lib/baml/tests/validation_files/variants/require_client_field.baml
deleted file mode 100644
index 8bb2031df..000000000
--- a/engine/baml-lib/baml/tests/validation_files/variants/require_client_field.baml
+++ /dev/null
@@ -1,24 +0,0 @@
-function Foo {
- input string
- output string
-}
-
-impl FooImpl{
- prompt "hello world"
-}
-
-
-
-
-// warning: To use comments and {#vars} use a block string. #"..."# instead.
-// --> variants/require_client_field.baml:7
-// |
-// 6 | impl FooImpl{
-// 7 | prompt "hello world"
-// |
-// error: Error validating: Missing `client` field in impl
-// --> variants/require_client_field.baml:6
-// |
-// 5 |
-// 6 | impl FooImpl{
-// |
diff --git a/engine/baml-lib/diagnostics/src/error.rs b/engine/baml-lib/diagnostics/src/error.rs
index 05bdc20a8..fc63f1972 100644
--- a/engine/baml-lib/diagnostics/src/error.rs
+++ b/engine/baml-lib/diagnostics/src/error.rs
@@ -400,8 +400,19 @@ impl DatamodelError {
type_name: &str,
name: &str,
span: Span,
- names: Vec,
+ mut names: Vec,
) -> DatamodelError {
+ // Include a list of primitives in the names
+ let primitives = vec![
+ "int".to_string(),
+ "float".to_string(),
+ "bool".to_string(),
+ "string".to_string(),
+ "image".to_string(),
+ "audio".to_string(),
+ ];
+ names.extend(primitives);
+
let close_names = sort_by_match(name, &names, Some(3));
let suggestions = if names.is_empty() {
"".to_string()
@@ -419,7 +430,10 @@ impl DatamodelError {
)
};
- Self::new(format!("{type_name} {name} not found.{suggestions}"), span)
+ Self::new(
+ format!("{type_name} {name} not found.{suggestions} (from error.rs l.422)"),
+ span,
+ )
}
pub fn type_not_used_in_prompt_error(
@@ -470,7 +484,7 @@ impl DatamodelError {
)
};
- Self::new(format!("{}{}", prefix, suggestions), span)
+ Self::new(format!("{}{} (l.476 error.rs)", prefix, suggestions), span)
}
pub fn new_type_not_found_error(
@@ -493,7 +507,7 @@ impl DatamodelError {
// If there are multiple close names, suggest them all
let suggestions = close_names.join("`, `");
format!(
- "Type `{}` does not exist. Did you mean one of these: `{}`?",
+ "Type `{}` does not exist. Did you mean one of these: `{}`? (error.rs l.499)",
type_name, suggestions
)
};
@@ -501,34 +515,6 @@ impl DatamodelError {
Self::new(msg, span)
}
- pub fn new_impl_not_found_error(
- impl_name: &str,
- names: Vec,
- span: Span,
- ) -> DatamodelError {
- let close_names = sort_by_match(impl_name, &names, Some(10));
-
- let msg = if close_names.is_empty() {
- // If no names are close enough, suggest nothing or provide a generic message
- format!("impl `{}` does not exist.", impl_name)
- } else if close_names.len() == 1 {
- // If there's only one close name, suggest it
- format!(
- "impl `{}` does not exist. Did you mean `{}`?",
- impl_name, close_names[0]
- )
- } else {
- // If there are multiple close names, suggest them all
- let suggestions = close_names.join("`, `");
- format!(
- "impl `{}` does not exist. Did you mean one of these: `{}`?",
- impl_name, suggestions
- )
- };
-
- Self::new(msg, span)
- }
-
pub fn new_attribute_not_known_error(attribute_name: &str, span: Span) -> DatamodelError {
Self::new(format!("Attribute not known: \"@{attribute_name}\"."), span)
}
diff --git a/engine/baml-lib/diagnostics/src/warning.rs b/engine/baml-lib/diagnostics/src/warning.rs
index 03fcc4bb5..0990de839 100644
--- a/engine/baml-lib/diagnostics/src/warning.rs
+++ b/engine/baml-lib/diagnostics/src/warning.rs
@@ -55,7 +55,7 @@ impl DatamodelWarning {
// If there are multiple close names, suggest them all
let suggestions = close_names.join("`, `");
format!(
- "Type `{}` does not exist. Did you mean one of these: `{}`?",
+ "Type `{}` does not exist. Did you mean one of these: `{}`? ",
type_name, suggestions
)
};
diff --git a/engine/baml-lib/parser-database/src/attributes/mod.rs b/engine/baml-lib/parser-database/src/attributes/mod.rs
index b36dd9a47..c710bdd59 100644
--- a/engine/baml-lib/parser-database/src/attributes/mod.rs
+++ b/engine/baml-lib/parser-database/src/attributes/mod.rs
@@ -1,122 +1,71 @@
-use internal_baml_diagnostics::DatamodelError;
-use internal_baml_schema_ast::ast::{
- Class, ClassId, Enum, EnumId, Top, TopId, Variant, VariantConfigId, WithSpan,
-};
+use internal_baml_schema_ast::ast::{Top, TopId, TypeExpId, TypeExpressionBlock};
mod alias;
mod description;
mod get;
mod meta;
mod to_string_attribute;
-
-use crate::{
- context::Context,
- types::{ClassAttributes, EnumAttributes, SerializerAttributes, VariantAttributes},
-};
+use crate::{context::Context, types::ClassAttributes, types::EnumAttributes};
+use internal_baml_schema_ast::ast::SubType;
pub(super) fn resolve_attributes(ctx: &mut Context<'_>) {
for top in ctx.ast.iter_tops() {
match top {
(TopId::Class(class_id), Top::Class(ast_class)) => {
- resolve_class_attributes(class_id, ast_class, ctx)
+ resolve_type_exp_block_attributes(class_id, ast_class, ctx, SubType::Class)
}
(TopId::Enum(enum_id), Top::Enum(ast_enum)) => {
- resolve_enum_attributes(enum_id, ast_enum, ctx)
- }
- (TopId::Variant(ctid), Top::Variant(ast_variant)) if ast_variant.is_llm() => {
- resolve_llm_variant_attributes(ctid, ast_variant, ctx)
+ resolve_type_exp_block_attributes(enum_id, ast_enum, ctx, SubType::Enum)
}
_ => (),
}
}
}
-fn resolve_enum_attributes<'db>(enum_id: EnumId, ast_enum: &'db Enum, ctx: &mut Context<'db>) {
- let mut enum_attributes = EnumAttributes::default();
-
- for (value_idx, _value) in ast_enum.iter_values() {
- ctx.visit_attributes((enum_id, value_idx).into());
- if let Some(attrs) = to_string_attribute::visit(ctx, false) {
- enum_attributes.value_serilizers.insert(value_idx, attrs);
- }
- ctx.validate_visited_attributes();
- }
-
- // Now validate the enum attributes.
- ctx.visit_attributes(enum_id.into());
- enum_attributes.serilizer = to_string_attribute::visit(ctx, true);
- ctx.validate_visited_attributes();
-
- ctx.types.enum_attributes.insert(enum_id, enum_attributes);
-}
-
-fn resolve_class_attributes<'db>(class_id: ClassId, ast_class: &'db Class, ctx: &mut Context<'db>) {
- let mut class_attributes = ClassAttributes::default();
-
- for (field_id, _) in ast_class.iter_fields() {
- ctx.visit_attributes((class_id, field_id).into());
- if let Some(attrs) = to_string_attribute::visit(ctx, false) {
- class_attributes.field_serilizers.insert(field_id, attrs);
- }
- ctx.validate_visited_attributes();
- }
-
- // Now validate the class attributes.
- ctx.visit_attributes(class_id.into());
- class_attributes.serilizer = to_string_attribute::visit(ctx, true);
- ctx.validate_visited_attributes();
-
- ctx.types
- .class_attributes
- .insert(class_id, class_attributes);
-}
-
-fn resolve_llm_variant_attributes<'db>(
- variant_id: VariantConfigId,
- ast_variant: &'db Variant,
+fn resolve_type_exp_block_attributes<'db>(
+ type_id: TypeExpId,
+ ast_typexpr: &'db TypeExpressionBlock,
ctx: &mut Context<'db>,
+ sub_type: SubType,
) {
- let mut variant_attributes = VariantAttributes::default();
-
- for (field_id, _) in ast_variant.iter_fields() {
- ctx.visit_attributes((variant_id, field_id).into());
- // Variant fields can have no attributes (for now).
- // TODO: Support expressions to have attributes.
- ctx.validate_visited_attributes();
- }
-
- for (serializer_idx, serializer) in ast_variant.iter_serializers() {
- let mut serializer_attr = SerializerAttributes::default();
- for (field_id, _value_idx) in serializer.iter_fields() {
- ctx.visit_attributes((variant_id, serializer_idx, field_id).into());
- if let Some(attrs) = to_string_attribute::visit(ctx, false) {
- serializer_attr.field_serilizers.insert(field_id, attrs);
+ match sub_type {
+ SubType::Enum => {
+ let mut enum_attributes = EnumAttributes::default();
+
+ for (value_idx, _value) in ast_typexpr.iter_fields() {
+ ctx.visit_attributes((type_id, value_idx).into());
+ if let Some(attrs) = to_string_attribute::visit(ctx, false) {
+ enum_attributes.value_serilizers.insert(value_idx, attrs);
+ }
+ ctx.validate_visited_attributes();
}
+
+ // Now validate the enum attributes.
+ ctx.visit_attributes(type_id.into());
+ enum_attributes.serilizer = to_string_attribute::visit(ctx, true);
ctx.validate_visited_attributes();
+
+ ctx.types.enum_attributes.insert(type_id, enum_attributes);
}
+ SubType::Class => {
+ let mut class_attributes = ClassAttributes::default();
+
+ for (field_idx, _field) in ast_typexpr.iter_fields() {
+ ctx.visit_attributes((type_id, field_idx).into());
+ if let Some(attrs) = to_string_attribute::visit(ctx, false) {
+ class_attributes.field_serilizers.insert(field_idx, attrs);
+ }
+ ctx.validate_visited_attributes();
+ }
- // Now validate the class attributes.
- ctx.visit_attributes((variant_id, serializer_idx).into());
- serializer_attr.serilizer = to_string_attribute::visit(ctx, true);
- ctx.validate_visited_attributes();
+ // Now validate the class attributes.
+ ctx.visit_attributes(type_id.into());
+ class_attributes.serilizer = to_string_attribute::visit(ctx, true);
+ ctx.validate_visited_attributes();
- if variant_attributes
- .serializers
- .insert(serializer_idx, serializer_attr)
- .is_some()
- {
- ctx.push_error(DatamodelError::new_validation_error(
- "Duplicate serializer name.",
- serializer.name.span().clone(),
- ));
+ ctx.types.class_attributes.insert(type_id, class_attributes);
}
- }
-
- // Now validate the class attributes.
- ctx.visit_attributes(variant_id.into());
- ctx.validate_visited_attributes();
- ctx.types
- .variant_attributes
- .insert(variant_id, variant_attributes);
+ _ => (),
+ }
}
diff --git a/engine/baml-lib/parser-database/src/attributes/to_string_attribute.rs b/engine/baml-lib/parser-database/src/attributes/to_string_attribute.rs
index 63173bb3f..baae5d767 100644
--- a/engine/baml-lib/parser-database/src/attributes/to_string_attribute.rs
+++ b/engine/baml-lib/parser-database/src/attributes/to_string_attribute.rs
@@ -23,6 +23,12 @@ pub(super) fn visit(ctx: &mut Context<'_>, as_block: bool) -> Option, as_block: bool) -> Option>();
-
- default_impl.iter().for_each(|((_, fid), impl_name, span)| {
- self.types.function.get_mut(fid).unwrap().default_impl =
- Some((impl_name.clone(), span.clone()))
- });
}
fn finalize_dependencies(&mut self, diag: &mut Diagnostics) {
@@ -276,108 +254,6 @@ impl ParserDatabase {
}
}
- fn finalize_prompt_validation(&mut self, diag: &mut Diagnostics) {
- let mut vars: HashMap<_, _> = Default::default();
-
- self.walk_variants().for_each(|variant| {
- let mut input_replacers = HashMap::new();
- let mut output_replacers = HashMap::new();
- let mut chat_replacers = vec![];
- if let Some(fn_walker) = variant.walk_function() {
- // Now lets validate the prompt is what we expect.
- let prompt_variables = &variant.properties().prompt_replacements;
-
- let num_errors = prompt_variables.iter().fold(0, |count, f| match f {
- PromptVariable::Input(variable) => {
- // Ensure the prompt has an input path that works.
- match types::post_prompt::process_input(self, fn_walker, variable) {
- Ok(replacer) => {
- input_replacers.insert(variable.to_owned(), replacer);
- count
- }
- Err(e) => {
- diag.push_error(e);
- count + 1
- }
- }
- }
- PromptVariable::Enum(blk) => {
- // Ensure the prompt has an enum path that works.
- match types::post_prompt::process_print_enum(
- self, variant, fn_walker, blk, diag,
- ) {
- Ok(result) => {
- output_replacers.insert(blk.to_owned(), result);
- count
- }
- Err(e) => {
- diag.push_error(e);
- count + 1
- }
- }
- }
- PromptVariable::Type(blk) => {
- // Ensure the prompt has an enum path that works.
- match types::post_prompt::process_print_type(self, variant, fn_walker, blk)
- {
- Ok(result) => {
- output_replacers.insert(blk.to_owned(), result);
- count
- }
- Err(e) => {
- diag.push_error(e);
- count + 1
- }
- }
- }
- PromptVariable::Chat(c) => {
- chat_replacers.push(c.clone());
- count
- }
- });
-
- if num_errors == 0 {
- // Some simple error checking.
- let span = &variant.properties().prompt.key_span;
- // Validation already done that the prompt is valid.
- // We should just ensure that atleast one of the input or output replacers is used.
- if input_replacers.is_empty() {
- diag.push_warning(DatamodelWarning::prompt_variable_unused(
- "Expected prompt to use {#input}",
- span.clone(),
- ));
- }
-
- // TODO: We should ensure every enum the class uses is used here.
- if output_replacers.is_empty() {
- diag.push_warning(DatamodelWarning::prompt_variable_unused(
- "Expected prompt to use {#print_type(..)} or {#print_enum(..)} but none was found",
- span.clone(),
- ));
- }
-
- // Only in this case update the prompt.
- vars.insert(
- variant.id,
- (input_replacers, output_replacers, chat_replacers),
- );
- }
- } else {
- diag.push_error(DatamodelError::new_type_not_found_error(
- variant.function_identifier().name(),
- self.valid_function_names(),
- variant.function_identifier().span().clone(),
- ));
- }
- });
-
- if !diag.has_errors() {
- vars.into_iter().for_each(|(k, v)| {
- self.types.variant_properties.get_mut(&k).unwrap().replacers = v;
- });
- }
- }
-
/// The parsed AST.
pub fn ast(&self) -> &ast::SchemaAst {
&self.ast
diff --git a/engine/baml-lib/parser-database/src/names/mod.rs b/engine/baml-lib/parser-database/src/names/mod.rs
index 29ad773ce..0d211fa21 100644
--- a/engine/baml-lib/parser-database/src/names/mod.rs
+++ b/engine/baml-lib/parser-database/src/names/mod.rs
@@ -5,7 +5,8 @@ use crate::{
coerce, coerce_array, Context, DatamodelError, StaticType, StringId,
};
-use internal_baml_schema_ast::ast::{ConfigBlockProperty, WithIdentifier};
+use baml_types::FieldType;
+use internal_baml_schema_ast::ast::{ConfigBlockProperty, Expression, Field, WithIdentifier};
use rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet};
use validate_reserved_names::*;
@@ -21,7 +22,7 @@ pub(super) struct Names {
pub(super) generators: HashMap,
/// Tests have their own namespace.
pub(super) tests: HashMap>,
- pub(super) model_fields: HashMap<(ast::ClassId, StringId), ast::FieldId>,
+ pub(super) model_fields: HashMap<(ast::TypeExpId, StringId), ast::FieldId>,
// pub(super) composite_type_fields: HashMap<(ast::CompositeTypeId, StringId), ast::FieldId>,
}
@@ -31,7 +32,6 @@ pub(super) struct Names {
/// - Model, enum and type alias names
/// - Generators
/// - Model fields for each model
-/// - Enum variants for each enum
pub(super) fn resolve_names(ctx: &mut Context<'_>) {
let mut tmp_names: HashSet<&str> = HashSet::default(); // throwaway container for duplicate checking
let mut names = Names::default();
@@ -45,27 +45,29 @@ pub(super) fn resolve_names(ctx: &mut Context<'_>) {
validate_enum_name(ast_enum, ctx.diagnostics);
validate_attribute_identifiers(ast_enum, ctx);
- for value in &ast_enum.values {
+ for value in &ast_enum.fields {
validate_enum_value_name(value, ctx.diagnostics);
+
validate_attribute_identifiers(value, ctx);
- if !tmp_names.insert(value.name.name()) {
+ if !tmp_names.insert(value.name()) {
ctx.push_error(DatamodelError::new_duplicate_enum_value_error(
ast_enum.name.name(),
- value.name.name(),
- value.span.clone(),
+ value.name(),
+ value.span().clone(),
))
}
}
Some(either::Left(&mut names.tops))
}
+
(ast::TopId::Class(model_id), ast::Top::Class(ast_class)) => {
validate_class_name(ast_class, ctx.diagnostics);
validate_attribute_identifiers(ast_class, ctx);
for (field_id, field) in ast_class.iter_fields() {
- validate_class_fiel_name(field, ctx.diagnostics);
+ validate_class_field_name(field, ctx.diagnostics);
validate_attribute_identifiers(field, ctx);
let field_name_id = ctx.interner.intern(field.name());
@@ -97,68 +99,73 @@ pub(super) fn resolve_names(ctx: &mut Context<'_>) {
(_, ast::Top::TemplateString(_)) => {
unreachable!("Encountered impossible template_string declaration during parsing")
}
- (ast::TopId::Function(_function_id), ast::Top::FunctionOld(ast_function)) => {
+
+ (ast::TopId::Function(_function_id), ast::Top::Function(ast_function)) => {
validate_function_name(ast_function, ctx.diagnostics);
validate_attribute_identifiers(ast_function, ctx);
Some(either::Left(&mut names.tops))
}
- (_, ast::Top::FunctionOld(_)) => {
+ (_, ast::Top::Function(_)) => {
unreachable!("Encountered impossible function declaration during parsing")
}
- (ast::TopId::Function(_function_id), ast::Top::Function(ast_function)) => {
- validate_function_name(ast_function, ctx.diagnostics);
- validate_attribute_identifiers(ast_function, ctx);
+
+ (ast::TopId::Client(_), ast::Top::Client(ast_client)) => {
+ validate_client_name(ast_client, ctx.diagnostics);
+ validate_attribute_identifiers(ast_client, ctx);
+
+ ctx.interner.intern(ast_client.identifier().name());
Some(either::Left(&mut names.tops))
}
- (_, ast::Top::Function(_)) => {
- unreachable!("Encountered impossible function declaration during parsing")
+ (_, ast::Top::Client(_)) => {
+ unreachable!("Encountered impossible client declaration during parsing")
+ }
+
+ (ast::TopId::RetryPolicy(_), ast::Top::RetryPolicy(ast_retry_policy)) => {
+ validate_retry(ast_retry_policy, ctx.diagnostics);
+ validate_attribute_identifiers(ast_retry_policy, ctx);
+
+ ctx.interner.intern(ast_retry_policy.identifier().name());
+
+ Some(either::Left(&mut names.tops))
+ }
+ (_, ast::Top::RetryPolicy(_)) => {
+ unreachable!("Encountered impossible retry_policy declaration during parsing")
}
+
(_, ast::Top::Generator(generator)) => {
validate_generator_name(generator, ctx.diagnostics);
check_for_duplicate_properties(top, generator.fields(), &mut tmp_names, ctx);
Some(either::Left(&mut names.generators))
}
- (_, ast::Top::Variant(variant)) => {
- validate_variant_name(variant, ctx.diagnostics);
- check_for_duplicate_properties(top, &variant.fields, &mut tmp_names, ctx);
- Some(either::Left(&mut names.tops))
- }
- (_, ast::Top::Client(client)) => {
- validate_client_name(client, ctx.diagnostics);
- check_for_duplicate_properties(top, client.fields(), &mut tmp_names, ctx);
- Some(either::Left(&mut names.tops))
- }
- (_, ast::Top::Config(config)) => {
- validate_config_name(config, ctx.diagnostics);
- check_for_duplicate_properties(top, config.fields(), &mut tmp_names, ctx);
- match config {
- ast::Configuration::TestCase(t) => {
- // TODO: I think we should do this later after all parsing, as duplication
- // would work best as a validation error with walkers.
- let function_ids = t
- .iter_fields()
- .find(|f| f.1.name() == "functions")
- .and_then(|f| match f.1.value {
- Some(ref v) => coerce_array(v, &coerce::string, ctx.diagnostics),
- None => None,
- });
-
- match function_ids {
- Some(f) => Some(either::Right(f)),
- None => {
- ctx.push_error(DatamodelError::new_validation_error(
- "Test case must have a functions field",
- t.identifier().span().clone(),
- ));
- None
- }
- }
+
+ (ast::TopId::TestCase(testcase_id), ast::Top::TestCase(testcase)) => {
+ validate_test(testcase, ctx.diagnostics);
+ check_for_duplicate_properties(top, testcase.fields(), &mut tmp_names, ctx);
+
+ // TODO: I think we should do this later after all parsing, as duplication
+ // would work best as a validation error with walkers.
+ let function_ids = testcase
+ .iter_fields()
+ .find(|f| f.1.name() == "functions")
+ .and_then(|f| match f.1.expr {
+ Some(ref v) => coerce_array(v, &coerce::string, ctx.diagnostics),
+ None => None,
+ });
+
+ match function_ids {
+ Some(f) => Some(either::Right(f)),
+ None => {
+ ctx.push_error(DatamodelError::new_validation_error(
+ "Test case must have a functions field",
+ testcase.identifier().span().clone(),
+ ));
+ None
}
- _ => Some(either::Left(&mut names.tops)),
}
}
+ _ => None,
};
match namespace {
@@ -195,22 +202,6 @@ fn insert_name(
let name = ctx.interner.intern(top.name());
if let Some(existing) = namespace.insert(name, top_id) {
- // For variants, we do extra checks.
- if let (Some(existing_variant), Some(current_variant)) =
- (ctx.ast[existing].as_variant(), top.as_variant())
- {
- let existing_function_name = existing_variant.function_name().name();
- let current_function_name = current_variant.function_name().name();
-
- let existing_type = ctx.ast[existing].get_type();
- let current_type = top.get_type();
-
- if existing_type == current_type && existing_function_name == current_function_name {
- ctx.push_error(duplicate_top_error(&ctx.ast[existing], top));
- return;
- }
- }
-
let current_type = top.get_type();
if current_type != "impl" && current_type != "impl>" {
ctx.push_error(duplicate_top_error(&ctx.ast[existing], top));
@@ -238,17 +229,17 @@ fn assert_is_not_a_reserved_scalar_type(ident: &ast::Identifier, ctx: &mut Conte
fn check_for_duplicate_properties<'a>(
top: &ast::Top,
- props: &'a [ConfigBlockProperty],
+ props: &'a [Field],
tmp_names: &mut HashSet<&'a str>,
ctx: &mut Context<'_>,
) {
tmp_names.clear();
for arg in props {
- if !tmp_names.insert(arg.name.name()) {
+ if !tmp_names.insert(arg.name()) {
ctx.push_error(DatamodelError::new_duplicate_config_key_error(
&format!("{} \"{}\"", top.get_type(), top.name()),
- arg.name.name(),
- arg.name.span().clone(),
+ arg.name(),
+ arg.span().clone(),
));
}
}
diff --git a/engine/baml-lib/parser-database/src/names/validate_reserved_names.rs b/engine/baml-lib/parser-database/src/names/validate_reserved_names.rs
index 6a5cfdd1b..781a838fc 100644
--- a/engine/baml-lib/parser-database/src/names/validate_reserved_names.rs
+++ b/engine/baml-lib/parser-database/src/names/validate_reserved_names.rs
@@ -1,3 +1,5 @@
+use internal_baml_schema_ast::ast::{FieldType, ValueExprBlockType};
+
use crate::{
ast::{self, WithIdentifier, WithName},
DatamodelError, Diagnostics,
@@ -8,46 +10,42 @@ pub(crate) fn validate_attribute_name(ast_attr: &ast::Attribute, diagnostics: &m
}
pub(crate) fn validate_generator_name(
- ast_gen: &ast::GeneratorConfig,
+ ast_gen: &ast::ValueExprBlock,
diagnostics: &mut Diagnostics,
) {
validate_name("generator", ast_gen.identifier(), diagnostics, false);
}
-pub(crate) fn validate_client_name(ast_client: &ast::Client, diagnostics: &mut Diagnostics) {
+pub(crate) fn validate_client_name(
+ ast_client: &ast::ValueExprBlock,
+ diagnostics: &mut Diagnostics,
+) {
validate_name("client", ast_client.identifier(), diagnostics, true);
}
-pub(crate) fn validate_config_name(ast_config: &ast::Configuration, diagnostics: &mut Diagnostics) {
- match ast_config {
- ast::Configuration::TestCase(_) => {
- validate_name(
- ast_config.get_type(),
- ast_config.identifier(),
- diagnostics,
- false,
- );
- }
- _ => validate_name(
- ast_config.get_type(),
- ast_config.identifier(),
- diagnostics,
- // Test cases don't need to be upper case.
- true,
- ),
- }
+pub(crate) fn validate_test(ast_config: &ast::ValueExprBlock, diagnostics: &mut Diagnostics) {
+ validate_name("test", ast_config.identifier(), diagnostics, false);
}
-pub(crate) fn validate_variant_name(ast_variant: &ast::Variant, diagnostics: &mut Diagnostics) {
- validate_name("impl", ast_variant.identifier(), diagnostics, false);
+pub(crate) fn validate_retry(ast_config: &ast::ValueExprBlock, diagnostics: &mut Diagnostics) {
+ validate_name(
+ "retry",
+ ast_config.identifier(),
+ diagnostics,
+ // Test cases don't need to be upper case.
+ true,
+ )
}
-pub(crate) fn validate_class_name(ast_class: &ast::Class, diagnostics: &mut Diagnostics) {
+pub(crate) fn validate_class_name(
+ ast_class: &ast::TypeExpressionBlock,
+ diagnostics: &mut Diagnostics,
+) {
validate_name("class", ast_class.identifier(), diagnostics, true);
}
-pub(crate) fn validate_class_fiel_name(
- ast_class_field: &ast::Field,
+pub(crate) fn validate_class_field_name(
+ ast_class_field: &ast::Field,
diagnostics: &mut Diagnostics,
) {
validate_name(
@@ -70,19 +68,25 @@ pub(crate) fn validate_template_string_name(
);
}
-pub(crate) fn validate_function_name(ast_func: &ast::Function, diagnostics: &mut Diagnostics) {
+pub(crate) fn validate_function_name(
+ ast_func: &ast::ValueExprBlock,
+ diagnostics: &mut Diagnostics,
+) {
validate_name("function", ast_func.identifier(), diagnostics, true);
}
-pub(crate) fn validate_enum_name(ast_enum: &ast::Enum, diagnostics: &mut Diagnostics) {
+pub(crate) fn validate_enum_name(
+ ast_enum: &ast::TypeExpressionBlock,
+ diagnostics: &mut Diagnostics,
+) {
validate_name("enum", ast_enum.identifier(), diagnostics, true);
- ast_enum.iter_values().for_each(|(_, val)| {
+ ast_enum.iter_fields().for_each(|(_, val)| {
validate_name("enum value", val.identifier(), diagnostics, true);
})
}
pub(crate) fn validate_enum_value_name(
- ast_enum_value: &ast::EnumValue,
+ ast_enum_value: &ast::Field,
diagnostics: &mut Diagnostics,
) {
validate_name("enum value", ast_enum_value.identifier(), diagnostics, true);
@@ -105,11 +109,7 @@ fn validate_name(
"Namespace imports (using '.') are not yet supported.",
span.clone(),
)),
- ast::Identifier::Primitive(_t, span) => Err(DatamodelError::new_name_error(
- _type,
- &format!("{} is a primitive type.", idn.name()),
- span.clone(),
- )),
+
ast::Identifier::Invalid(_, span) | ast::Identifier::String(_, span) => {
Err(DatamodelError::new_name_error(
_type,
diff --git a/engine/baml-lib/parser-database/src/printer/mod.rs b/engine/baml-lib/parser-database/src/printer/mod.rs
index 1492dd5b3..a92caa8f4 100644
--- a/engine/baml-lib/parser-database/src/printer/mod.rs
+++ b/engine/baml-lib/parser-database/src/printer/mod.rs
@@ -1,123 +1,17 @@
-use std::collections::HashMap;
-
use internal_baml_diagnostics::DatamodelError;
-use internal_baml_prompt_parser::ast::PrinterBlock;
-use internal_baml_schema_ast::ast::WithName;
mod print_enum_default;
mod print_type_default;
-use crate::{
- interner::StringId,
- types::{StaticStringAttributes, ToStringAttributes},
- walkers::VariantWalker,
- ParserDatabase,
-};
+use crate::ParserDatabase;
/// Trait
pub trait WithSerializeableContent {
/// Trait to render an object.
- fn serialize_data(
- &self,
- variant: Option<&VariantWalker<'_>>,
- db: &'_ ParserDatabase,
- ) -> serde_json::Value;
+ fn serialize_data(&self, db: &'_ ParserDatabase) -> serde_json::Value;
}
/// Trait
-pub trait WithStaticRenames<'db>: WithName {
- /// Overrides for local names.
- fn get_override(&self, variant: &VariantWalker<'db>) -> Option<&'_ ToStringAttributes>;
- /// Overrides for local names.
- fn get_default_attributes(&self) -> Option<&'db ToStringAttributes>;
-
- /// Overrides for local names.
- fn alias(&'db self, variant: Option<&VariantWalker<'db>>, db: &'db ParserDatabase) -> String {
- let (overrides, defaults) = self.get_attributes(variant);
-
- let override_alias = overrides.and_then(|o| *o.alias());
- let base_alias = defaults.and_then(|a| *a.alias());
-
- match (override_alias, base_alias) {
- (Some(id), _) => db[id].to_string(),
- (None, Some(id)) => db[id].to_string(),
- (None, None) => self.name().to_string(),
- }
- }
-
- /// Overrides for local names.
- fn maybe_alias(&'db self, db: &'db ParserDatabase) -> Option {
- let defaults = match self.get_default_attributes() {
- Some(ToStringAttributes::Static(refs)) => Some(refs),
- _ => None,
- };
- let base_alias = defaults.and_then(|a| *a.alias());
- base_alias.map(|id| db[id].to_string())
- }
-
- /// Overrides for local names.
- fn meta(
- &'db self,
- variant: Option<&VariantWalker<'db>>,
- db: &'db ParserDatabase,
- ) -> HashMap {
- let (overrides, defaults) = self.get_attributes(variant);
-
- let mut meta: HashMap = Default::default();
- match defaults {
- Some(a) => {
- meta.extend(a.meta());
- }
- None => {}
- };
-
- if let Some(o) = overrides {
- meta.extend(o.meta());
- }
-
- meta.iter()
- .map(|(&k, &v)| (db[k].to_string(), db[v].to_string()))
- .collect::>()
- }
-
- /// Overrides for skip.
- fn skip(&'db self, variant: Option<&VariantWalker<'db>>) -> bool {
- let (overrides, defaults) = self.get_attributes(variant);
-
- let override_alias = overrides.and_then(|o| *o.skip());
- let base_alias = defaults.and_then(|a| *a.skip());
- match (override_alias, base_alias) {
- (Some(id), _) => id,
- (None, Some(id)) => id,
- (None, None) => false,
- }
- }
-
- /// Overrides for local names.
- fn get_attributes(
- &'db self,
- variant: Option<&VariantWalker<'db>>,
- ) -> (
- Option<&'db StaticStringAttributes>,
- Option<&'db StaticStringAttributes>,
- ) {
- let defaults = match self.get_default_attributes() {
- Some(ToStringAttributes::Static(refs)) => Some(refs),
- _ => None,
- };
- match variant {
- Some(variant) => {
- let overrides = match self.get_override(variant) {
- Some(ToStringAttributes::Static(refs)) => Some(refs),
- _ => None,
- };
-
- (overrides, defaults)
- }
- None => (None, defaults),
- }
- }
-}
/// Trait
pub trait WithSerialize: WithSerializeableContent {
@@ -125,8 +19,6 @@ pub trait WithSerialize: WithSerializeableContent {
fn serialize(
&self,
db: &'_ ParserDatabase,
- variant: Option<&VariantWalker<'_>>,
- block: Option<&PrinterBlock>,
span: &internal_baml_diagnostics::Span,
) -> Result;
@@ -139,14 +31,7 @@ pub trait WithSerialize: WithSerializeableContent {
}
/// print_type, print_enum implementation
-pub fn serialize_with_printer(
- is_enum: bool,
- template: Option,
- json: serde_json::Value,
-) -> Result {
- if template.is_some() {
- return Err("printer keyword is not yet supported".to_string());
- }
+pub fn serialize_with_printer(is_enum: bool, json: serde_json::Value) -> Result {
if is_enum {
Ok(print_enum_default::print_enum(json))
} else {
diff --git a/engine/baml-lib/parser-database/src/types/configurations.rs b/engine/baml-lib/parser-database/src/types/configurations.rs
index 0d8ababde..f145220a2 100644
--- a/engine/baml-lib/parser-database/src/types/configurations.rs
+++ b/engine/baml-lib/parser-database/src/types/configurations.rs
@@ -1,7 +1,5 @@
use internal_baml_diagnostics::{DatamodelError, DatamodelWarning, Span};
-use internal_baml_schema_ast::ast::{
- ConfigurationId, PrinterConfig, RetryPolicyConfig, WithIdentifier, WithName, WithSpan,
-};
+use internal_baml_schema_ast::ast::{ValExpId, ValueExprBlock, WithIdentifier, WithName, WithSpan};
use regex::Regex;
use crate::{coerce, coerce_array, coerce_expression::coerce_map, context::Context};
@@ -33,71 +31,9 @@ fn dedent(s: &str) -> String {
.to_string()
}
-pub(crate) fn visit_printer<'db>(
- idx: ConfigurationId,
- config: &'db PrinterConfig,
- ctx: &mut Context<'db>,
-) {
- let mut template = None;
-
- config
- .iter_fields()
- .for_each(|(_idx, f)| match (f.name(), &f.value) {
- (name, None) => {
- ctx.push_error(DatamodelError::new_config_property_missing_value_error(
- name,
- config.name(),
- "printer",
- f.identifier().span().clone(),
- ))
- }
- ("template", Some(val)) => match coerce::string_with_span(val, ctx.diagnostics) {
- Some((t, span)) => template = Some((dedent(t), span.clone())),
- None => {}
- },
- (name, Some(_)) => ctx.push_error(DatamodelError::new_property_not_known_error(
- name,
- f.identifier().span().clone(),
- ["template"].to_vec(),
- )),
- });
-
- match (
- template,
- coerce::string_with_span(&config.printer_type, ctx.diagnostics),
- ) {
- (None, _) => ctx.push_error(DatamodelError::new_validation_error(
- "Missing `template` property",
- config.identifier().span().clone(),
- )),
- (Some(template), Some(("enum", _))) => {
- ctx.types
- .printers
- .insert(idx, PrinterType::Enum(Printer { template }));
- }
- (Some(template), Some(("type", _))) => {
- ctx.types
- .printers
- .insert(idx, PrinterType::Type(Printer { template }));
- }
- (Some(_), Some((name, span))) => {
- ctx.push_error(DatamodelError::new_validation_error(
- &format!(
- "Unknown printer type: {}. Options are `type` or `enum`",
- name
- ),
- span.clone(),
- ));
- }
- (Some(_), None) => {
- // errors are handled by coerce::string_with_span
- }
- }
-}
-
pub(crate) fn visit_retry_policy<'db>(
- idx: ConfigurationId,
- config: &'db RetryPolicyConfig,
+ idx: ValExpId,
+ config: &'db ValueExprBlock,
ctx: &mut Context<'db>,
) {
let mut max_reties = None;
@@ -109,7 +45,7 @@ pub(crate) fn visit_retry_policy<'db>(
config
.iter_fields()
- .for_each(|(_idx, f)| match (f.name(), &f.value) {
+ .for_each(|(_idx, f)| match (f.name(), &f.expr) {
(name, None) => {
ctx.push_error(DatamodelError::new_config_property_missing_value_error(
name,
@@ -261,8 +197,8 @@ fn visit_strategy(
}
pub(crate) fn visit_test_case<'db>(
- idx: ConfigurationId,
- config: &'db RetryPolicyConfig,
+ idx: ValExpId,
+ config: &'db ValueExprBlock,
ctx: &mut Context<'db>,
) {
let mut functions = None;
@@ -270,7 +206,7 @@ pub(crate) fn visit_test_case<'db>(
config
.iter_fields()
- .for_each(|(_idx, f)| match (f.name(), &f.value) {
+ .for_each(|(_idx, f)| match (f.name(), &f.expr) {
(name, None) => {
ctx.push_error(DatamodelError::new_config_property_missing_value_error(
name,
diff --git a/engine/baml-lib/parser-database/src/types/mod.rs b/engine/baml-lib/parser-database/src/types/mod.rs
index e4936cd12..efb2cb39d 100644
--- a/engine/baml-lib/parser-database/src/types/mod.rs
+++ b/engine/baml-lib/parser-database/src/types/mod.rs
@@ -2,14 +2,14 @@ use std::collections::{HashMap, HashSet};
use std::hash::Hash;
use crate::coerce;
+use crate::types::configurations::visit_test_case;
use crate::{context::Context, DatamodelError};
use indexmap::IndexMap;
use internal_baml_diagnostics::{DatamodelWarning, Span};
use internal_baml_prompt_parser::ast::{ChatBlock, PrinterBlock, Variable};
use internal_baml_schema_ast::ast::{
- self, AdapterId, ClassId, ClientId, ConfigurationId, EnumId, EnumValueId, Expression, FieldId,
- FieldType, FunctionId, RawString, SerializerFieldId, VariantConfigId, VariantSerializerId,
+ self, Expression, FieldId, FieldType, RawString, TypeExpId, ValExpId, ValueExprBlockType,
WithIdentifier, WithName, WithSpan,
};
@@ -28,41 +28,43 @@ pub use to_string_attributes::{
pub(crate) use types::EnumAttributes;
pub(crate) use types::*;
+use self::configurations::visit_retry_policy;
+
pub(super) fn resolve_types(ctx: &mut Context<'_>) {
for (top_id, top) in ctx.ast.iter_tops() {
match (top_id, top) {
- (_, ast::Top::Enum(enm)) => visit_enum(enm, ctx),
+ (ast::TopId::Enum(idx), ast::Top::Enum(model)) => visit_enum(model, ctx),
+ (_, ast::Top::Enum(enm)) => unreachable!("Enum misconfigured"),
+
(ast::TopId::Class(idx), ast::Top::Class(model)) => visit_class(idx, model, ctx),
(_, ast::Top::Class(_)) => unreachable!("Class misconfigured"),
(ast::TopId::TemplateString(idx), ast::Top::TemplateString(template_string)) => {
visit_template_string(idx, template_string, ctx)
}
(_, ast::Top::TemplateString(_)) => unreachable!("TemplateString misconfigured"),
- (ast::TopId::Function(idx), ast::Top::FunctionOld(function)) => {
- visit_old_function(idx, function, ctx)
- }
- (_, ast::Top::FunctionOld(_)) => unreachable!("Function misconfigured"),
+
(ast::TopId::Function(idx), ast::Top::Function(function)) => {
visit_function(idx, function, ctx)
}
(_, ast::Top::Function(_)) => unreachable!("Function misconfigured"),
- (ast::TopId::Variant(idx), ast::Top::Variant(variant)) => {
- visit_variant(idx, variant, ctx)
- }
- (_, ast::Top::Variant(_)) => unreachable!("Variant misconfigured"),
(ast::TopId::Client(idx), ast::Top::Client(client)) => {
visit_client(idx, client, ctx);
}
+
(_, ast::Top::Client(_)) => unreachable!("Client misconfigured"),
- (_, ast::Top::Generator(_generator)) => {}
- (ast::TopId::Config((idx, _)), ast::Top::Config(cfg)) => {
- visit_config(idx, cfg, ctx);
+ (ast::TopId::RetryPolicy(idx), ast::Top::RetryPolicy(config)) => {
+ visit_retry_policy(idx, config, ctx);
+ }
+ (_, ast::Top::RetryPolicy(_)) => unreachable!("RetryPolicy misconfigured"),
+ (ast::TopId::TestCase(idx), ast::Top::TestCase(config)) => {
+ visit_test_case(idx, config, ctx);
}
- (_, ast::Top::Config(_)) => unreachable!("Config misconfigured"),
+ (_, ast::Top::TestCase(_)) => unreachable!("TestCase misconfigured"),
+
+ _ => {}
}
}
}
-
#[derive(Debug, Clone)]
/// Variables used inside of raw strings.
pub enum PromptVariable {
@@ -118,19 +120,6 @@ pub struct StringValue {
pub key_span: Span,
}
-#[derive(Debug)]
-pub struct VariantProperties {
- pub client: StringValue,
- pub prompt: StringValue,
- pub prompt_replacements: Vec,
- pub replacers: (
- HashMap,
- HashMap,
- Vec,
- ),
- pub output_adapter: Option<(AdapterId, Vec)>,
-}
-
/// The representation of a prompt.
pub enum PromptAst<'a> {
/// For single string prompts
@@ -142,93 +131,6 @@ pub enum PromptAst<'a> {
Chat(Vec<(Option<&'a ChatBlock>, String)>, Vec<(String, String)>),
}
-impl VariantProperties {
- pub fn output_adapter_for_language(&self, language: &str) -> Option<&str> {
- self.output_adapter.as_ref().and_then(|f| {
- f.1.iter()
- .find(|r| r.language.as_ref().map(|(l, _)| l.as_str()) == Some(language))
- .map(|r| r.value())
- })
- }
-
- pub fn to_prompt(&self) -> PromptAst<'_> {
- let (input, output, chats) = &self.replacers;
-
- // Replace all the inputs with the input replacers
- let mut used_inputs = vec![];
- let prompt = input
- .iter()
- .fold(self.prompt.value.clone(), |prompt, (k, val)| {
- // Only add the input if it's used in the prompt
- let key = k.key();
- if prompt.contains(&key) {
- used_inputs.push((key.clone(), val.clone()));
- prompt
- } else {
- prompt
- }
- });
- // Replace all the outputs with the output replacers
- let prompt = output.iter().fold(prompt, |prompt, (k, val)| {
- prompt.replace(&k.key(), &val.to_string())
- });
-
- used_inputs.sort();
-
- if chats.is_empty() {
- PromptAst::String(prompt, used_inputs)
- } else {
- // Split the prompt into parts based on the chat blocks.
- let mut last_idx = 0;
- let mut parts = vec![];
- for chat in chats {
- let splitter = chat.key();
- let idx = prompt[last_idx..].find(&splitter);
- if let Some(idx) = idx {
- parts.push((
- Some(chat),
- (idx + last_idx, idx + last_idx + splitter.len()),
- ));
- last_idx += idx + splitter.len();
- }
- }
-
- match parts.first() {
- // If the first chat block is not at the start of the prompt, add the first part.
- Some(&(Some(_), (start, _))) if start > 0 => {
- parts.insert(0, (None, (0, 0)));
- }
- Some(_) => {}
- _ => unreachable!("At least one chat block should exist"),
- }
-
- // Each chat block owns a part of the prompt. until the next chat block.
- PromptAst::Chat(
- parts
- .iter()
- .enumerate()
- .filter_map(|(idx, &(chat, (_, start)))| {
- let end = if idx + 1 < parts.len() {
- parts[idx + 1].1 .0
- } else {
- prompt.len()
- };
-
- let p = prompt[start..end].trim();
- if p.is_empty() {
- info!("Skipping empty prompt part: {} {} {}", idx, start, end);
- None
- } else {
- Some((chat, p.to_string()))
- }
- })
- .collect(),
- used_inputs,
- )
- }
- }
-}
-
#[derive(Debug, Clone)]
pub struct ClientProperties {
pub provider: (String, Span),
@@ -325,24 +227,22 @@ pub struct TemplateStringProperties {
#[derive(Debug, Default)]
pub(super) struct Types {
- pub(super) enum_attributes: HashMap,
- pub(super) class_attributes: HashMap,
- pub(super) class_dependencies: HashMap>,
- pub(super) function: HashMap,
- pub(super) variant_attributes: HashMap,
- pub(super) variant_properties: HashMap,
- pub(super) client_properties: HashMap,
- pub(super) retry_policies: HashMap,
- pub(super) printers: HashMap,
- pub(super) test_cases: HashMap,
+ pub(super) enum_attributes: HashMap,
+ pub(super) class_attributes: HashMap,
+ pub(super) class_dependencies: HashMap>,
+ pub(super) function: HashMap,
+
+ pub(super) client_properties: HashMap,
+ pub(super) retry_policies: HashMap,
+ pub(super) test_cases: HashMap,
pub(super) template_strings:
- HashMap, TemplateStringProperties>,
+ HashMap, TemplateStringProperties>,
}
impl Types {
pub(super) fn refine_class_field(
&self,
- (class_id, field_id): (ClassId, FieldId),
+ (class_id, field_id): (TypeExpId, FieldId),
) -> either::Either {
match self.class_attributes.get(&class_id) {
Some(attrs) => match attrs.field_serilizers.get(&field_id) {
@@ -355,7 +255,7 @@ impl Types {
pub(super) fn refine_enum_value(
&self,
- (enum_id, value_id): (EnumId, EnumValueId),
+ (enum_id, value_id): (TypeExpId, FieldId),
) -> either::Either {
match self.enum_attributes.get(&enum_id) {
Some(attrs) => match attrs.value_serilizers.get(&value_id) {
@@ -365,28 +265,6 @@ impl Types {
None => either::Either::Left(value_id.into()),
}
}
-
- #[allow(dead_code)]
- pub(super) fn refine_serializer_field(
- &self,
- (variant_id, serializer_id, value_id): (
- VariantConfigId,
- VariantSerializerId,
- SerializerFieldId,
- ),
- ) -> either::Either {
- match self
- .variant_attributes
- .get(&variant_id)
- .and_then(|r| r.serializers.get(&serializer_id))
- {
- Some(attrs) => match attrs.field_serilizers.get(&value_id) {
- Some(ToStringAttributes::Dynamic(_)) => either::Either::Right(value_id.into()),
- _ => either::Either::Left(value_id.into()),
- },
- None => either::Either::Left(value_id.into()),
- }
- }
}
fn visit_template_string<'db>(
@@ -414,35 +292,40 @@ fn visit_template_string<'db>(
);
}
-fn visit_enum<'db>(_enm: &'db ast::Enum, _ctx: &mut Context<'db>) {}
+fn visit_enum<'db>(_enm: &'db ast::TypeExpressionBlock, _ctx: &mut Context<'db>) {}
-fn visit_class<'db>(class_id: ast::ClassId, class: &'db ast::Class, ctx: &mut Context<'db>) {
+fn visit_class<'db>(
+ class_id: ast::TypeExpId,
+ class: &'db ast::TypeExpressionBlock,
+ ctx: &mut Context<'db>,
+) {
let used_types = class
.iter_fields()
- .flat_map(|(_, f)| f.field_type.flat_idns())
- .filter(|id| {
- id.is_valid_type()
- && match id {
- ast::Identifier::Primitive(..) => false,
- _ => true,
- }
- })
- .map(|f| f.name().to_string())
+ .flat_map(|(_, f)| f.expr.iter().flat_map(|e| e.flat_idns()))
+ // .filter(|id| {
+ // id.is_valid_type()
+ // && match id {
+ // ast::Identifier::Primitive(..) => false,
+ // _ => true,
+ // }
+ // })
+ .map(|id| id.name().to_string())
.collect::>();
ctx.types.class_dependencies.insert(class_id, used_types);
}
-fn visit_function<'db>(idx: FunctionId, function: &'db ast::Function, ctx: &mut Context<'db>) {
+fn visit_function<'db>(idx: ValExpId, function: &'db ast::ValueExprBlock, ctx: &mut Context<'db>) {
let input_deps = function
.input()
- .flat_idns()
+ .map(|input| input.flat_idns())
+ .unwrap_or_else(Vec::new)
.iter()
.map(|f| f.name().to_string())
.collect::>();
-
let output_deps = function
.output()
- .flat_idns()
+ .map(|output| output.field_type.flat_idns())
+ .unwrap_or_else(Vec::new)
.iter()
.map(|f| f.name().to_string())
.collect::>();
@@ -453,25 +336,13 @@ fn visit_function<'db>(idx: FunctionId, function: &'db ast::Function, ctx: &mut
.iter_fields()
.for_each(|(_idx, field)| match field.name() {
"prompt" => {
- if field.template_args.is_some() {
- ctx.push_error(DatamodelError::new_validation_error(
- "Template args are not allowed in `prompt`.",
- field.span().clone(),
- ));
- }
- prompt = match &field.value {
+ prompt = match &field.expr {
Some(val) => coerce::template_string(val, ctx.diagnostics),
None => None,
}
}
"client" => {
- if field.template_args.is_some() {
- ctx.push_error(DatamodelError::new_validation_error(
- "Template args are not allowed in `client`.",
- field.span().clone(),
- ));
- }
- client = match &field.value {
+ client = match &field.expr {
Some(val) => coerce::string_with_span(val, ctx.diagnostics)
.map(|(v, span)| (v.to_string(), span.clone())),
None => None,
@@ -525,97 +396,17 @@ fn visit_function<'db>(idx: FunctionId, function: &'db ast::Function, ctx: &mut
}
}
-fn visit_old_function<'db>(idx: FunctionId, function: &'db ast::Function, ctx: &mut Context<'db>) {
- let input_deps = function
- .input()
- .flat_idns()
- .iter()
- .map(|f| f.name().to_string())
- .collect::>();
-
- let output_deps = function
- .output()
- .flat_idns()
- .iter()
- .map(|f| f.name().to_string())
- .collect::>();
-
- let mut default_impl = None;
- function
- .iter_fields()
- .for_each(|(_idx, field)| match field.name() {
- "default_impl" => {
- if field.template_args.is_some() {
- ctx.push_error(DatamodelError::new_validation_error(
- "Did you mean `impl` instead of `impl<...>`?",
- field.span().clone(),
- ));
- }
- default_impl = match &field.value {
- Some(val) => coerce::string_with_span(val, ctx.diagnostics)
- .map(|(v, span)| (v.to_string(), span.clone())),
- None => None,
- }
- }
- config => ctx.push_error(DatamodelError::new_validation_error(
- &format!("Unknown field `{}` in function", config),
- field.span().clone(),
- )),
- });
-
- ctx.types.function.insert(
- idx,
- FunctionType {
- default_impl,
- dependencies: (input_deps, output_deps),
- prompt: None,
- client: None,
- },
- );
-}
-
-fn visit_client<'db>(idx: ClientId, client: &'db ast::Client, ctx: &mut Context<'db>) {
- if !client.is_llm() {
- ctx.push_error(DatamodelError::new_validation_error(
- "Only LLM clients are supported. Use: client",
- client.identifier().span().clone(),
- ));
- return;
- }
-
+fn visit_client<'db>(idx: ValExpId, client: &'db ast::ValueExprBlock, ctx: &mut Context<'db>) {
let mut provider = None;
let mut retry_policy = None;
let mut options: Vec<(String, Expression)> = Vec::new();
client
.iter_fields()
.for_each(|(_idx, field)| match field.name() {
- "provider" => {
- if field.template_args.is_some() {
- ctx.push_error(DatamodelError::new_validation_error(
- "Did you mean `provider` instead of `provider<...>`?",
- field.span().clone(),
- ));
- }
- provider = field.value.as_ref()
- }
- "retry_policy" => {
- if field.template_args.is_some() {
- ctx.push_error(DatamodelError::new_validation_error(
- "Did you mean `retry_policy` instead of `retry_policy<...>`?",
- field.span().clone(),
- ));
- }
- retry_policy = field.value.as_ref()
- }
+ "provider" => provider = field.expr.as_ref(),
+ "retry_policy" => retry_policy = field.expr.as_ref(),
"options" => {
- if field.template_args.is_some() {
- ctx.push_error(DatamodelError::new_validation_error(
- "Did you mean `options` instead of `options<...>`?",
- field.span().clone(),
- ));
- }
-
- match field.value.as_ref() {
+ match field.expr.as_ref() {
Some(ast::Expression::Map(map, span)) => {
map.iter().for_each(|(key, value)| {
if let Some(key) = coerce::string(key, ctx.diagnostics) {
@@ -679,233 +470,6 @@ fn visit_client<'db>(idx: ClientId, client: &'db ast::Client, ctx: &mut Context<
}
}
-fn visit_variant<'db>(idx: VariantConfigId, variant: &'db ast::Variant, ctx: &mut Context<'db>) {
- if !variant.is_llm() {
- ctx.push_error(DatamodelError::new_validation_error(
- "Only LLM variants are supported. Use: impl",
- variant.span().clone(),
- ));
- return;
- }
-
- let mut client = None;
- let mut prompt = None;
-
- variant
- .iter_fields()
- .for_each(|(_idx, field)| match field.name() {
- "client" => {
- if field.template_args.is_some() {
- ctx.push_error(DatamodelError::new_validation_error(
- "Did you mean `client` instead of `client<...>`?",
- field.span().clone(),
- ));
- }
- match field.value.as_ref() {
- Some(item) => client = Some((item, field.identifier().span().clone())),
- _ => {}
- }
- }
- "prompt" => {
- if field.template_args.is_some() {
- ctx.push_error(DatamodelError::new_validation_error(
- "Did you mean `prompt` instead of `prompt<...>`?",
- field.span().clone(),
- ));
- }
- match field.value.as_ref() {
- Some(item) => prompt = Some((item, field.identifier().span().clone())),
- _ => {}
- }
- }
- config => ctx.push_error(DatamodelError::new_validation_error(
- &format!("Unknown field `{}` in impl", config),
- field.span().clone(),
- )),
- });
-
- let client = if let Some((client, client_key_span)) = client {
- coerce::string_with_span(client, ctx.diagnostics).map(|client| (client, client_key_span))
- } else {
- ctx.push_error(DatamodelError::new_validation_error(
- "Missing `client` field in impl",
- variant.identifier().span().clone(),
- ));
- None
- };
-
- let prompt = if let Some((prompt, prompt_key_span)) = prompt {
- if let Some(prompt) = prompt.as_raw_string_value() {
- validate_prompt(ctx, prompt).map(|(cleaned_prompt, replacer)| {
- ((cleaned_prompt, prompt.span(), replacer), prompt_key_span)
- })
- } else if let Some((prompt, span)) = coerce::string_with_span(prompt, ctx.diagnostics) {
- // warn the user that we are using this without validation.
- ctx.push_warning(DatamodelWarning::new(
- "To use comments and {#vars} use a block string. #\"...\"# instead.".into(),
- span.clone(),
- ));
- Some((
- (prompt.to_string(), span, Default::default()),
- prompt_key_span,
- ))
- } else {
- // Errors are handled by coerce.
- None
- }
- } else {
- ctx.push_error(DatamodelError::new_validation_error(
- "Missing `prompt` field in impl",
- variant.identifier().span().clone(),
- ));
- None
- };
-
- // Ensure that the adapters are valid.
- let (_input_adapter, output_adapter) =
- variant
- .iter_adapters()
- .fold((None, None), |prev, (idx, adapter)| {
- let is_input = match &adapter.from {
- FieldType::Identifier(arity, idn) if idn.name() == "input" => {
- if arity.is_optional() {
- ctx.push_error(DatamodelError::new_validation_error(
- "The `input` adapter cannot be optional.",
- idn.span().clone(),
- ));
- false
- } else {
- true
- }
- }
- _ => false,
- };
-
- let is_output = match &adapter.to {
- FieldType::Identifier(arity, idn) if idn.name() == "output" => {
- if arity.is_optional() {
- ctx.push_error(DatamodelError::new_validation_error(
- "The `output` adapter cannot be optional.",
- idn.span().clone(),
- ));
- false
- } else {
- true
- }
- }
- _ => false,
- };
-
- if is_input && is_output {
- ctx.push_error(DatamodelError::new_validation_error(
- "The `input` and `output` adapters cannot be used together.",
- adapter.span().clone(),
- ));
- } else if is_input {
- if prev.0.is_some() {
- ctx.push_error(DatamodelError::new_validation_error(
- "The `input` adapter can only be used once.",
- adapter.span().clone(),
- ));
- } else {
- // Ensure the expr is either a string of array of strings.
- let impls = if let Some((arr, _)) = adapter.converter.as_array() {
- Some(
- arr.iter()
- .filter_map(|item| coerce::raw_string(item, ctx.diagnostics))
- .collect::>(),
- )
- } else {
- coerce::raw_string(&adapter.converter, ctx.diagnostics)
- .map(|raw| vec![raw])
- };
-
- if let Some(impls) = impls {
- ctx.push_warning(DatamodelWarning::new(
- "The `input` adapter is note yet supported.".into(),
- adapter.span().clone(),
- ));
- return (Some((idx, impls)), prev.1);
- }
- }
- } else if is_output {
- if prev.1.is_some() {
- ctx.push_error(DatamodelError::new_validation_error(
- "The `output` adapter can only be used once.",
- adapter.span().clone(),
- ));
- } else {
- let impls = if let Some((arr, _)) = adapter.converter.as_array() {
- Some(
- arr.iter()
- .filter_map(|item| coerce::raw_string(item, ctx.diagnostics))
- .cloned()
- .collect::>(),
- )
- } else {
- coerce::raw_string(&adapter.converter, ctx.diagnostics)
- .map(|raw| vec![raw.clone()])
- };
-
- if let Some(impls) = impls {
- return (prev.0, Some((idx, impls)));
- }
- }
- } else {
- ctx.push_error(DatamodelError::new_validation_error(
- "The `input` or `output` adapter must be used.",
- adapter.span().clone(),
- ));
- }
- prev
- });
-
- match (client, prompt) {
- (
- Some(((client, client_span), client_key_span)),
- Some(((prompt, prompt_span, replacers), prompt_key_span)),
- ) => {
- ctx.types.variant_properties.insert(
- idx,
- VariantProperties {
- client: StringValue {
- value: client.to_string(),
- span: client_span.clone(),
- key_span: client_key_span,
- },
- prompt: StringValue {
- value: prompt.to_string(),
- span: prompt_span.clone(),
- key_span: prompt_key_span,
- },
- prompt_replacements: replacers,
- replacers: Default::default(),
- output_adapter,
- },
- );
- }
- _ => {}
- }
-}
-
-fn visit_config<'db>(
- idx: ConfigurationId,
- config: &'db ast::Configuration,
- ctx: &mut Context<'db>,
-) {
- match config {
- ast::Configuration::RetryPolicy(retry) => {
- configurations::visit_retry_policy(idx, retry, ctx);
- }
- ast::Configuration::Printer(printer) => {
- configurations::visit_printer(idx, printer, ctx);
- }
- ast::Configuration::TestCase(test_case) => {
- configurations::visit_test_case(idx, test_case, ctx);
- }
- }
-}
-
/// Prisma's builtin scalar types.
#[derive(Debug, Copy, Clone, PartialEq, Hash, Eq)]
#[allow(missing_docs)]
@@ -956,42 +520,18 @@ impl StaticType {
#[derive(Copy, Clone, PartialEq, Debug, Hash, Eq, PartialOrd, Ord)]
pub struct DynamicFieldId(u32);
-impl From for DynamicFieldId {
- fn from(id: SerializerFieldId) -> Self {
- DynamicFieldId(id.0)
- }
-}
-
impl From for DynamicFieldId {
fn from(id: FieldId) -> Self {
DynamicFieldId(id.0)
}
}
-impl From for DynamicFieldId {
- fn from(id: EnumValueId) -> Self {
- DynamicFieldId(id.0)
- }
-}
-
/// An opaque identifier for a class field.
#[derive(Copy, Clone, PartialEq, Debug, Eq, Hash)]
pub struct StaticFieldId(u32);
-impl From for StaticFieldId {
- fn from(id: SerializerFieldId) -> Self {
- StaticFieldId(id.0)
- }
-}
-
impl From for StaticFieldId {
fn from(id: FieldId) -> Self {
StaticFieldId(id.0)
}
}
-
-impl From for StaticFieldId {
- fn from(id: EnumValueId) -> Self {
- StaticFieldId(id.0)
- }
-}
diff --git a/engine/baml-lib/parser-database/src/types/post_prompt.rs b/engine/baml-lib/parser-database/src/types/post_prompt.rs
index 07a05b436..1a555f5ff 100644
--- a/engine/baml-lib/parser-database/src/types/post_prompt.rs
+++ b/engine/baml-lib/parser-database/src/types/post_prompt.rs
@@ -3,10 +3,7 @@ use internal_baml_diagnostics::{DatamodelError, DatamodelWarning, Diagnostics};
use internal_baml_prompt_parser::ast::{PrinterBlock, Variable, WithSpan};
use internal_baml_schema_ast::ast::{self, WithName};
-use crate::{
- walkers::{FunctionWalker, VariantWalker},
- ParserDatabase, WithSerialize,
-};
+use crate::{walkers::FunctionWalker, ParserDatabase, WithSerialize};
pub(crate) fn process_input(
db: &ParserDatabase,
@@ -20,48 +17,38 @@ pub(crate) fn process_input(
));
}
- match walker.ast_function().input() {
- ast::FunctionArgs::Unnamed(arg) => {
- validate_variable_path(db, variable, 1, &arg.field_type)?;
- let mut new_path = variable.path.clone();
- new_path[0] = "arg".to_string();
- Ok(new_path.join("."))
- }
- ast::FunctionArgs::Named(args) => {
- if variable.path.len() < 2 {
- return Err(DatamodelError::new_validation_error(
- "Named arguments must have at least one argument (input.my_var_name)",
- variable.span.clone(),
- ));
- }
- let path_name = &variable.path[1];
- match args
- .iter_args()
- .find(|(_, (name, _))| name.name() == path_name)
- {
- Some((_, (_, arg))) => {
- validate_variable_path(db, variable, 2, &arg.field_type)?;
- Ok(variable.path[1..].join("."))
- }
- None => Err(DatamodelError::new_validation_error(
- &format!(
- "Unknown arg `{}`. Could be one of: {}",
- path_name,
- args.iter_args()
- .map(|(_, (name, _))| name.name())
- .collect::>()
- .join(", ")
- ),
- variable.span.clone(),
- )),
- }
+ let args = walker.ast_function().input().expect("Expected input args");
+ if variable.path.len() < 2 {
+ return Err(DatamodelError::new_validation_error(
+ "Named arguments must have at least one argument (input.my_var_name)",
+ variable.span.clone(),
+ ));
+ }
+ let path_name = &variable.path[1];
+ match args
+ .iter_args()
+ .find(|(_, (name, _))| name.name() == path_name)
+ {
+ Some((_, (_, arg))) => {
+ validate_variable_path(db, variable, 2, &arg.field_type)?;
+ Ok(variable.path[1..].join("."))
}
+ None => Err(DatamodelError::new_validation_error(
+ &format!(
+ "Unknown arg `{}`. Could be one of: {}",
+ path_name,
+ args.iter_args()
+ .map(|(_, (name, _))| name.name())
+ .collect::>()
+ .join(", ")
+ ),
+ variable.span.clone(),
+ )),
}
}
pub(crate) fn process_print_enum(
db: &ParserDatabase,
- walker: VariantWalker<'_>,
fn_walker: FunctionWalker<'_>,
blk: &PrinterBlock,
diag: &mut Diagnostics,
@@ -99,7 +86,7 @@ pub(crate) fn process_print_enum(
variable.span.clone(),
));
}
- enum_walker.serialize(fn_walker.db, Some(&walker), Some(blk), blk.span())
+ enum_walker.serialize(fn_walker.db, blk.span())
}
Some(Either::Left(_)) => Err(DatamodelError::new_validation_error(
"Expected enum, found class",
@@ -118,13 +105,12 @@ pub(crate) fn process_print_enum(
pub(crate) fn process_print_type(
db: &ParserDatabase,
- walker: VariantWalker<'_>,
fn_walker: FunctionWalker<'_>,
blk: &PrinterBlock,
) -> Result {
let variable = &blk.target;
if variable.text == "output" {
- return fn_walker.serialize(fn_walker.db, Some(&walker), Some(blk), blk.span());
+ return fn_walker.serialize(fn_walker.db, blk.span());
}
let candidates = fn_walker
@@ -140,7 +126,7 @@ pub(crate) fn process_print_type(
f.required_classes()
.any(|idn| idn.name() == cls_walker.name())
}) {
- true => cls_walker.serialize(fn_walker.db, Some(&walker), Some(blk), blk.span()),
+ true => cls_walker.serialize(fn_walker.db, blk.span()),
false => Err(DatamodelError::type_not_used_in_prompt_error(
false,
true,
@@ -179,7 +165,7 @@ fn validate_variable_path(
let next_path_name = variable.path[next_index].clone();
match current {
- ast::FieldType::Union(_, ft, _) => match ft
+ ast::FieldType::Union(_, ft, ..) => match ft
.iter()
.any(|ft| validate_variable_path(db, variable, next_index, ft).is_ok())
{
@@ -189,27 +175,56 @@ fn validate_variable_path(
variable.span.clone(),
)),
},
- ast::FieldType::Map(_, _) => Err(DatamodelError::new_validation_error(
+ ast::FieldType::Primitive(_, ft, _, ..) => Err(DatamodelError::new_validation_error(
+ "Primitive types are not indexable in the prompt",
+ variable.span.clone(),
+ )),
+ ast::FieldType::Map(_, _, ..) => Err(DatamodelError::new_validation_error(
"Dictionary types are not supported",
variable.span.clone(),
)),
- ast::FieldType::Tuple(_, _, _) => Err(DatamodelError::new_validation_error(
+ ast::FieldType::Tuple(_, _, _, _) => Err(DatamodelError::new_validation_error(
"Tuple types are not supported",
variable.span.clone(),
)),
- ast::FieldType::List(_, _, _) => Err(DatamodelError::new_validation_error(
+ ast::FieldType::List(_, _, _, _) => Err(DatamodelError::new_validation_error(
"List types are not yet indexable in the prompt",
variable.span.clone(),
)),
- ast::FieldType::Identifier(_, idn) => match db.find_type(idn) {
+ ast::FieldType::Symbol(_, idn, ..) => match db.find_type_by_str(idn) {
Some(Either::Left(cls)) => {
match cls
.static_fields()
.find(|f| f.name() == next_path_name.as_str())
{
Some(field) => {
- let t = field.r#type();
- validate_variable_path(db, variable, next_index + 1, t)
+ if let Some(t) = field.r#type() {
+ validate_variable_path(db, variable, next_index + 1, t)
+ } else {
+ match cls
+ .dynamic_fields()
+ .find(|f| f.name() == next_path_name.as_str())
+ {
+ Some(_) => {
+ // Throw an error if the next path is not the last path.
+ if next_index + 1 < variable.path.len() {
+ Err(DatamodelError::new_validation_error(
+ "get properties must be the last path in the prompt",
+ variable.span.clone(),
+ ))
+ } else {
+ Ok(())
+ }
+ }
+ None => Err(DatamodelError::new_validation_error(
+ &format!(
+ "Unknown field `{}` in class `{}`",
+ next_path_name, idn
+ ),
+ variable.span.clone(),
+ )),
+ }
+ }
}
None => match cls
.dynamic_fields()
@@ -227,40 +242,20 @@ fn validate_variable_path(
}
}
None => Err(DatamodelError::new_validation_error(
- &format!(
- "Unknown field `{}` in class `{}`",
- next_path_name,
- idn.name()
- ),
+ &format!("Unknown field `{}` in class `{}`", next_path_name, idn),
variable.span.clone(),
)),
},
}
}
+
Some(Either::Right(_)) => Err(DatamodelError::new_validation_error(
"Enum values are not indexable in the prompt",
variable.span.clone(),
)),
None => match idn {
- ast::Identifier::Primitive(_p, _) => Err(DatamodelError::new_validation_error(
- &format!(
- "{0} has no field {1}. {0} is of type: {2}",
- variable.path[..next_index].join("."),
- next_path_name,
- idn.name()
- ),
- variable.span.clone(),
- )),
- ast::Identifier::Ref(_, _) => Err(DatamodelError::new_validation_error(
- "Namespace imports (using '.') are not yet supported.",
- variable.span.clone(),
- )),
- ast::Identifier::ENV(_, _) => Err(DatamodelError::new_validation_error(
- "Environment variables are not indexable in the prompt",
- variable.span.clone(),
- )),
_ => Err(DatamodelError::new_validation_error(
- &format!("Unknown type `{}`.", idn.name()),
+ &format!("Unknown type `{}`.", idn),
variable.span.clone(),
)),
},
diff --git a/engine/baml-lib/parser-database/src/types/types.rs b/engine/baml-lib/parser-database/src/types/types.rs
index c6bc23375..260e3791a 100644
--- a/engine/baml-lib/parser-database/src/types/types.rs
+++ b/engine/baml-lib/parser-database/src/types/types.rs
@@ -1,12 +1,12 @@
use std::collections::HashMap;
-use internal_baml_schema_ast::ast::{EnumValueId, FieldId, SerializerFieldId, VariantSerializerId};
+use internal_baml_schema_ast::ast::FieldId;
use super::to_string_attributes::ToStringAttributes;
#[derive(Debug, Default)]
pub struct EnumAttributes {
- pub value_serilizers: HashMap