Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement tests for dynamic types #1343

Merged
merged 30 commits into from
Feb 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
ba7fe42
Grammar
antoniosarosi Jan 16, 2025
765f06f
Parser
antoniosarosi Jan 17, 2025
80d164c
Remove `SPACER_TEXT` from `type_builder_block` grammar
antoniosarosi Jan 17, 2025
726ea22
Parse `type_builder_block` in `value_expression_block`
antoniosarosi Jan 17, 2025
dde691a
Add type alias to `parse_block` test
antoniosarosi Jan 19, 2025
abd4284
Refactor branching in `parse_value_expression_block`
antoniosarosi Jan 20, 2025
fe1d5ac
Merge branch 'canary' into antonio/dynamic-types-tests
antoniosarosi Jan 20, 2025
3f00ac5
Report error when `type_builder` is not allowed
antoniosarosi Jan 20, 2025
deef90b
Report error on multiple type builder blocks
antoniosarosi Jan 20, 2025
9cd53e4
AST hack
antoniosarosi Jan 28, 2025
33e60c0
Add validation test for missing `@@dynamic`
antoniosarosi Jan 28, 2025
f9074d1
Rename validation tests & add test for type aliases
antoniosarosi Jan 29, 2025
c9ebc55
Add validations for `@@dyanmic` attr
antoniosarosi Jan 29, 2025
9af1206
Fix dynamic attr check
antoniosarosi Jan 29, 2025
334d99f
Remove debug print statements
antoniosarosi Jan 29, 2025
e5eb0b3
Avoid name collisions in cloned ASTs
antoniosarosi Jan 30, 2025
44af158
Merge branch 'canary' into antonio/dynamic-types-tests
antoniosarosi Jan 30, 2025
53c8992
Support recursive type aliases in type builder blocks
antoniosarosi Jan 31, 2025
f685948
Explain hack
antoniosarosi Jan 31, 2025
771fb61
Merge branch 'canary' into antonio/dynamic-types-tests
antoniosarosi Feb 1, 2025
7209a65
Render prompt in real time
antoniosarosi Feb 2, 2025
5da0bfa
Fix `BLOCK_LEVEL_CATCH_ALL` in parser
antoniosarosi Feb 2, 2025
06105d0
Validate multiple `dynamic` defs for same type
antoniosarosi Feb 2, 2025
a3d33de
Validate cycles introduced by type builder blocks
antoniosarosi Feb 2, 2025
6c72696
VSCode syntax
antoniosarosi Feb 3, 2025
5ee049c
Lezer syntax
antoniosarosi Feb 3, 2025
e89e0f6
Fix typo
antoniosarosi Feb 3, 2025
bfe4f04
Allow any variant for dynamic enums
antoniosarosi Feb 3, 2025
7b0afc1
Merge branch 'canary' into antonio/dynamic-types-tests
antoniosarosi Feb 3, 2025
483363d
Don't panic :)
antoniosarosi Feb 3, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ RUN curl https://mise.run | sh \
# Install Rust
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y

# Install WASM tools
RUN cargo install [email protected] wasm-pack

# Install Infisical
RUN curl -1sLf 'https://dl.cloudsmith.io/public/infisical/infisical-cli/setup.deb.sh' | sudo -E bash \
&& sudo apt update && sudo apt install -y infisical
4 changes: 3 additions & 1 deletion engine/baml-lib/baml-core/src/ir/ir_helpers/to_baml_arg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,9 @@ impl ArgCoercer {
(FieldType::Enum(name), _) => match value {
BamlValue::String(s) => {
if let Ok(e) = ir.find_enum(name) {
if e.walk_values().any(|v| v.item.elem.0 == *s) {
if e.walk_values().any(|v| v.item.elem.0 == *s)
|| e.item.attributes.get("dynamic_type").is_some()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this morally the same behavior that already exists for Class argument coersion in the case of dynamic types? https://github.com/BoundaryML/baml/pull/1343/files#diff-5fa0605b8093a86ff1b1b8e89032c02d46a052907bfa6dc38032c3ffc37aca02R238 I'm not sure, but it is. Just double-checking.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes it's the same behavior. I think it makes sense, if you have a dynamic enum and add variants to it at runtime, you would not be able to pass those variants to functions if the coercer doesn't allow it. We might want to restrict both classes and enums to only values added at runtime or in type builder blocks, but for now this does the job.

{
Ok(BamlValue::Enum(name.to_string(), s.to_string()))
} else {
scope.push_error(format!(
Expand Down
151 changes: 115 additions & 36 deletions engine/baml-lib/baml-core/src/ir/repr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ use std::collections::HashSet;

use anyhow::{anyhow, Result};
use baml_types::{
Constraint, ConstraintLevel, FieldType, JinjaExpression, Resolvable, StreamingBehavior, StringOr,
UnresolvedValue,
Constraint, ConstraintLevel, FieldType, JinjaExpression, Resolvable, StreamingBehavior,
StringOr, UnresolvedValue,
};
use either::Either;
use indexmap::{IndexMap, IndexSet};
Expand All @@ -15,7 +15,9 @@ use internal_baml_parser_database::{
Attributes, ParserDatabase, PromptAst, RetryPolicyStrategy, TypeWalker,
};

use internal_baml_schema_ast::ast::{self, Attribute, FieldArity, SubType, ValExpId, WithName, WithSpan};
use internal_baml_schema_ast::ast::{
self, Attribute, FieldArity, SubType, ValExpId, WithName, WithSpan,
};
use internal_llm_client::{ClientProvider, ClientSpec, UnresolvedClientProperty};
use serde::Serialize;

Expand Down Expand Up @@ -179,6 +181,9 @@ impl IntermediateRepr {
db: &ParserDatabase,
configuration: Configuration,
) -> Result<IntermediateRepr> {
// TODO: We're iterating over the AST tops once for every property in
// the IR. Easy performance optimization here by iterating only one time
// and distributing the tops to the appropriate IR properties.
let mut repr = IntermediateRepr {
enums: db
.walk_enums()
Expand Down Expand Up @@ -347,10 +352,7 @@ fn to_ir_attributes(
});
let streaming_done = streaming_done.as_ref().and_then(|v| {
if *v {
Some((
"stream.done".to_string(),
UnresolvedValue::Bool(true, ()),
))
Some(("stream.done".to_string(), UnresolvedValue::Bool(true, ())))
} else {
None
}
Expand Down Expand Up @@ -594,7 +596,6 @@ impl WithRepr<FieldType> for ast::FieldType {
),
};


let use_metadata = has_constraints || has_special_streaming_behavior;
let with_constraints = if use_metadata {
FieldType::WithMetadata {
Expand All @@ -609,30 +610,6 @@ impl WithRepr<FieldType> for ast::FieldType {
}
}

// #[derive(serde::Serialize, Debug)]
// pub enum Identifier {
// /// Starts with env.*
// ENV(String),
// /// The path to a Local Identifer + the local identifer. Separated by '.'
// #[allow(dead_code)]
// Ref(Vec<String>),
// /// A string without spaces or '.' Always starts with a letter. May contain numbers
// Local(String),
// /// Special types (always lowercase).
// Primitive(baml_types::TypeValue),
// }

// impl Identifier {
// pub fn name(&self) -> String {
// match self {
// Identifier::ENV(k) => k.clone(),
// Identifier::Ref(r) => r.join("."),
// Identifier::Local(l) => l.clone(),
// Identifier::Primitive(p) => p.to_string(),
// }
// }
// }

type TemplateStringId = String;

#[derive(Debug)]
Expand Down Expand Up @@ -717,7 +694,15 @@ impl WithRepr<Enum> for EnumWalker<'_> {

fn repr(&self, db: &ParserDatabase) -> Result<Enum> {
Ok(Enum {
name: self.name().to_string(),
// TODO: #1343 Temporary solution until we implement scoping in the AST.
name: if self.ast_type_block().is_dynamic_type_def {
self.name()
.strip_prefix(ast::DYNAMIC_TYPE_NAME_PREFIX)
.unwrap()
.to_string()
} else {
self.name().to_string()
},
values: self
.values()
.map(|w| {
Expand Down Expand Up @@ -803,7 +788,15 @@ impl WithRepr<Class> for ClassWalker<'_> {

fn repr(&self, db: &ParserDatabase) -> Result<Class> {
Ok(Class {
name: self.name().to_string(),
// TODO: #1343 Temporary solution until we implement scoping in the AST.
name: if self.ast_type_block().is_dynamic_type_def {
self.name()
.strip_prefix(ast::DYNAMIC_TYPE_NAME_PREFIX)
.unwrap()
.to_string()
} else {
self.name().to_string()
},
static_fields: self
.static_fields()
.map(|e| e.node(db))
Expand Down Expand Up @@ -1118,6 +1111,21 @@ impl WithRepr<RetryPolicy> for ConfigurationWalker<'_> {
}
}

// TODO: #1343 Temporary solution until we implement scoping in the AST.
#[derive(Debug)]
pub enum TypeBuilderEntry {
Enum(Node<Enum>),
Class(Node<Class>),
TypeAlias(Node<TypeAlias>),
}

// TODO: #1343 Temporary solution until we implement scoping in the AST.
#[derive(Debug)]
pub struct TestTypeBuilder {
pub entries: Vec<TypeBuilderEntry>,
pub structural_recursive_alias_cycles: Vec<IndexMap<String, FieldType>>,
}

#[derive(serde::Serialize, Debug)]
pub struct TestCaseFunction(String);

Expand All @@ -1133,6 +1141,7 @@ pub struct TestCase {
pub functions: Vec<Node<TestCaseFunction>>,
pub args: IndexMap<String, UnresolvedValue<()>>,
pub constraints: Vec<Constraint>,
pub type_builder: TestTypeBuilder,
}

impl WithRepr<TestCaseFunction> for (&ConfigurationWalker<'_>, usize) {
Expand Down Expand Up @@ -1180,6 +1189,69 @@ impl WithRepr<TestCase> for ConfigurationWalker<'_> {
let functions = (0..self.test_case().functions.len())
.map(|i| (self, i).node(db))
.collect::<Result<Vec<_>>>()?;

// TODO: #1343 Temporary solution until we implement scoping in the AST.
let enums = self
.test_case()
.type_builder_scoped_db
.walk_enums()
.filter(|e| {
self.test_case().type_builder_scoped_db.ast()[e.id].is_dynamic_type_def
|| db.find_type_by_str(e.name()).is_none()
})
.map(|e| e.node(&self.test_case().type_builder_scoped_db))
.collect::<Result<Vec<Node<Enum>>>>()?;
let classes = self
.test_case()
.type_builder_scoped_db
.walk_classes()
.filter(|c| {
self.test_case().type_builder_scoped_db.ast()[c.id].is_dynamic_type_def
|| db.find_type_by_str(c.name()).is_none()
})
.map(|c| c.node(&self.test_case().type_builder_scoped_db))
.collect::<Result<Vec<Node<Class>>>>()?;
let type_aliases = self
.test_case()
.type_builder_scoped_db
.walk_type_aliases()
.filter(|a| db.find_type_by_str(a.name()).is_none())
.map(|a| a.node(&self.test_case().type_builder_scoped_db))
.collect::<Result<Vec<Node<TypeAlias>>>>()?;
let mut type_builder_entries = Vec::new();

for e in enums {
type_builder_entries.push(TypeBuilderEntry::Enum(e));
}
for c in classes {
type_builder_entries.push(TypeBuilderEntry::Class(c));
}
for a in type_aliases {
type_builder_entries.push(TypeBuilderEntry::TypeAlias(a));
}

let mut recursive_aliases = vec![];
for cycle in self
.test_case()
.type_builder_scoped_db
.recursive_alias_cycles()
{
let mut component = IndexMap::new();
for id in cycle {
let alias = &self.test_case().type_builder_scoped_db.ast()[*id];
// Those are global cycles, skip.
if db.find_type_by_str(alias.name()).is_some() {
continue;
}
// Cycles defined in the scoped test type builder block.
component.insert(
alias.name().to_string(),
alias.value.repr(&self.test_case().type_builder_scoped_db)?,
);
}
recursive_aliases.push(component);
}

Ok(TestCase {
name: self.name().to_string(),
args: self
Expand All @@ -1195,9 +1267,14 @@ impl WithRepr<TestCase> for ConfigurationWalker<'_> {
.constraints
.into_iter()
.collect::<Vec<_>>(),
type_builder: TestTypeBuilder {
entries: type_builder_entries,
structural_recursive_alias_cycles: recursive_aliases,
},
})
}
}

#[derive(Debug, Clone, Serialize)]
pub enum Prompt {
// The prompt stirng, and a list of input replacer keys (raw key w/ magic string, and key to replace with)
Expand Down Expand Up @@ -1440,7 +1517,6 @@ mod tests {
let alias = class.find_field("field").unwrap();

assert_eq!(*alias.r#type(), FieldType::Primitive(TypeValue::Int));

}

#[test]
Expand All @@ -1461,7 +1537,10 @@ mod tests {
let class = ir.find_class("Test").unwrap();
let alias = class.find_field("field").unwrap();

let FieldType::WithMetadata { base, constraints, .. } = alias.r#type() else {
let FieldType::WithMetadata {
base, constraints, ..
} = alias.r#type()
else {
panic!(
"expected resolved constrained type, found {:?}",
alias.r#type()
Expand Down
19 changes: 17 additions & 2 deletions engine/baml-lib/baml-core/src/ir/walker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ use internal_llm_client::ClientSpec;
use std::collections::{HashMap, HashSet};

use super::{
repr::{self, FunctionConfig, WithRepr},
Class, Client, Enum, EnumValue, Field, FunctionNode, IRHelper, Impl, RetryPolicy,
repr::{self, FunctionConfig, TypeBuilderEntry, WithRepr},
Class, Client, Enum, EnumValue, Field, FieldType, FunctionNode, IRHelper, Impl, RetryPolicy,
TemplateString, TestCase, TypeAlias, Walker,
};
use crate::ir::jinja_helpers::render_expression;
Expand Down Expand Up @@ -224,6 +224,21 @@ impl<'a> Walker<'a, (&'a FunctionNode, &'a TestCase)> {
.collect()
}

// TODO: #1343 Temporary solution until we implement scoping in the AST.
pub fn type_builder_contents(&self) -> &[TypeBuilderEntry] {
&self.item.1.elem.type_builder.entries
}

// TODO: #1343 Temporary solution until we implement scoping in the AST.
pub fn type_builder_recursive_aliases(&self) -> &[IndexMap<String, FieldType>] {
&self
.item
.1
.elem
.type_builder
.structural_recursive_alias_cycles
}

pub fn function(&'a self) -> Walker<'a, &'a FunctionNode> {
Walker {
db: self.db,
Expand Down
Loading
Loading