Skip to content

Commit d6c4ff3

Browse files
authored
Fix codegen when streaming done types (#1861)
<!-- ELLIPSIS_HIDDEN --> > [!IMPORTANT] > Fixes code generation for streaming done types by adjusting handling of `stream.done` attributes and updating integration tests. > > - **Behavior**: > - Adjusts handling of `stream.done` and `stream.not_null` attributes in `generate_types.rs`. > - Updates logic for generating type references in `ToTypeReferenceInTypeDefinition` and `ToTypeReferenceInClientDefinition` traits. > - Adds handling for optional fields during streaming in `generate_types.rs`. > - **Tests**: > - Updates integration tests in `integ-tests` directory to cover new streaming behavior. > - Adds new test cases for `stream.done` types in `semantic_streaming.baml` and `constraints.baml`. > - **Misc**: > - Adds `TwoStoriesOneTitleCheck` interface to `types.ts` to support new test cases. > - Updates `flake.nix` to include new build configurations for testing. > > <sup>This description was created by </sup>[<img alt="Ellipsis" src="https://img.shields.io/badge/Ellipsis-blue?color=175173">](https://www.ellipsis.dev?ref=BoundaryML%2Fbaml&utm_source=github&utm_medium=referral)<sup> for 31b01be. You can [customize](https://app.ellipsis.dev/BoundaryML/settings/summaries) this summary. It will automatically update as commits are pushed.</sup> <!-- ELLIPSIS_HIDDEN -->
1 parent 429936d commit d6c4ff3

61 files changed

Lines changed: 5016 additions & 665 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎engine/baml-lib/baml/build.rs‎

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,20 @@ const PROMPT_FIDDLE_EXAMPLE_DIR: &str =
77
concat!("/../../../typescript/fiddle-frontend/public/_examples/all-projects/baml_src");
88

99
fn main() {
10-
build_folder_tests(
11-
BAML_CLI_INIT_DIR,
12-
"tests/validation_files/baml_cli_init.baml",
13-
);
14-
build_folder_tests(
15-
PROMPT_FIDDLE_EXAMPLE_DIR,
16-
"tests/validation_files/prompt_fiddle_example.baml",
17-
);
18-
build_validation_tests();
19-
// build_reformat_tests();
10+
if std::env::var("SKIP_BAML_VALIDATION").unwrap_or_default() == "1" {
11+
println!("cargo:warning=Skipping BAML validation during build");
12+
} else {
13+
build_folder_tests(
14+
BAML_CLI_INIT_DIR,
15+
"tests/validation_files/baml_cli_init.baml",
16+
);
17+
build_folder_tests(
18+
PROMPT_FIDDLE_EXAMPLE_DIR,
19+
"tests/validation_files/prompt_fiddle_example.baml",
20+
);
21+
build_validation_tests();
22+
// build_reformat_tests();
23+
}
2024
}
2125

2226
fn build_validation_tests() {

‎engine/language_client_codegen/src/python/generate_types.rs‎

Lines changed: 91 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -193,33 +193,21 @@ impl<'ir> From<ClassWalker<'ir>> for PartialPythonClass<'ir> {
193193
let needed: bool = f.attributes.get("stream.not_null").is_some();
194194
let (_, metadata) = c.ir.distribute_metadata(&f.elem.r#type.elem);
195195
let done: bool = metadata.1.done;
196-
let field = match (done, needed) {
197-
// A normal partial field.
198-
(false, false) => add_default_value(
199-
c.ir,
200-
&f.elem.r#type.elem,
201-
&f.elem.r#type.elem.to_partial_type_ref(c.ir, false, false),
202-
),
203-
// A field with @stream.done and no @stream.not_null
204-
(true, false) => add_default_value(
205-
c.ir,
206-
&f.elem.r#type.elem,
207-
&optional(&f.elem.r#type.elem.to_type_ref(c.ir, true)),
208-
),
209-
(false, true) => add_default_value(
210-
c.ir,
211-
&f.elem.r#type.elem,
212-
&f.elem.r#type.elem.to_partial_type_ref(c.ir, false, true),
213-
),
214-
(true, true) => add_default_value(
215-
c.ir,
216-
&f.elem.r#type.elem,
217-
&f.elem.r#type.elem.to_type_ref(c.ir, true), // TODO: Fix.
218-
),
196+
let (field, optional) = match (done, needed) {
197+
(false, false) => {
198+
f.elem.r#type.elem.to_partial_type_ref(c.ir, false, false)
199+
}
200+
(true, false) => (format!("Optional[{}]",f.elem.r#type.elem.to_type_ref(c.ir, true)), true),
201+
(false, true) => f.elem.r#type.elem.to_partial_type_ref(c.ir, true, true),
202+
(true, true) => (f.elem.r#type.elem.to_type_ref(c.ir, true), false),
219203
};
220204
(
221205
f.elem.name.as_str(),
222-
field,
206+
add_default_value(
207+
c.ir,
208+
&f.elem.r#type.elem,
209+
&field,
210+
),
223211
f.elem.docstring.as_ref().map(render_docstring),
224212
)
225213
})
@@ -304,7 +292,12 @@ pub fn to_python_literal(literal: &LiteralValue) -> String {
304292

305293
trait ToTypeReferenceInTypeDefinition {
306294
fn to_type_ref(&self, ir: &IntermediateRepr, module_prefix: bool) -> String;
307-
fn to_partial_type_ref(&self, ir: &IntermediateRepr, wrapped: bool, needed: bool) -> String;
295+
fn to_partial_type_ref(
296+
&self,
297+
ir: &IntermediateRepr,
298+
wrapped: bool,
299+
needed: bool,
300+
) -> (String, bool);
308301
}
309302

310303
impl ToTypeReferenceInTypeDefinition for FieldType {
@@ -362,11 +355,20 @@ impl ToTypeReferenceInTypeDefinition for FieldType {
362355
}
363356
None => base.to_type_ref(ir, use_module_prefix),
364357
},
365-
FieldType::Arrow(_) => todo!("Arrow types should not be used in generated type definitions"),
358+
FieldType::Arrow(_) => {
359+
todo!("Arrow types should not be used in generated type definitions")
360+
}
366361
}
367362
}
368363

369-
fn to_partial_type_ref(&self, ir: &IntermediateRepr, wrapped: bool, needed: bool) -> String {
364+
/// How to serialize the partial version of a field type.
365+
/// Also returns whether the field is optional during streaming.
366+
fn to_partial_type_ref(
367+
&self,
368+
ir: &IntermediateRepr,
369+
wrapped: bool,
370+
needed: bool,
371+
) -> (String, bool) {
370372
let (base_type, metadata) = ir.distribute_metadata(self);
371373
let is_partial_type = !metadata.1.done;
372374
let use_module_prefix = !is_partial_type;
@@ -376,9 +378,9 @@ impl ToTypeReferenceInTypeDefinition for FieldType {
376378
let base_rep = match base_type {
377379
FieldType::Class(name) => {
378380
if wrapped || needed {
379-
format!("\"{module_prefix}{name}\"")
381+
(format!("\"{module_prefix}{name}\""), false)
380382
} else {
381-
format!("Optional[\"{module_prefix}{name}\"]")
383+
(format!("Optional[\"{module_prefix}{name}\"]"), true)
382384
}
383385
}
384386
FieldType::Enum(name) => {
@@ -388,98 +390,108 @@ impl ToTypeReferenceInTypeDefinition for FieldType {
388390
.unwrap_or(false)
389391
{
390392
if needed || wrapped {
391-
format!("Union[types.{name}, str]")
393+
(format!("Union[types.{name}, str]"), false)
392394
} else {
393-
format!("Optional[Union[types.{name}, str]]")
395+
(format!("Optional[Union[types.{name}, str]]"), true)
394396
}
395397
} else {
396398
if needed || wrapped {
397-
format!("types.{name}")
399+
(format!("types.{name}"), false)
398400
} else {
399-
format!("Optional[types.{name}]")
401+
(format!("Optional[types.{name}]"), true)
400402
}
401403
}
402404
}
403405
FieldType::RecursiveTypeAlias(name) => {
404406
if wrapped {
405-
format!("\"{name}\"")
407+
(format!("\"{name}\""), false)
406408
} else {
407-
format!("Optional[\"{name}\"]")
409+
(format!("Optional[\"{name}\"]"), true)
408410
}
409411
}
410412
FieldType::Literal(value) => {
411413
if needed || wrapped {
412-
to_python_literal(value)
414+
(to_python_literal(value), false)
413415
} else {
414-
format!("Optional[{}]", to_python_literal(value))
416+
(format!("Optional[{}]", to_python_literal(value)), true)
415417
}
416418
} // TODO: Handle `needed` here.
417419

418-
FieldType::List(inner) => {
419-
format!("List[{}]", inner.to_partial_type_ref(ir, true, false))
420-
}
421-
FieldType::Map(key, value) => format!(
422-
"Dict[{}, {}]",
423-
key.to_type_ref(ir, use_module_prefix),
424-
value.to_partial_type_ref(ir, false, false)
420+
FieldType::List(inner) => (
421+
format!("List[{}]", inner.to_partial_type_ref(ir, true, false).0),
422+
false,
423+
),
424+
FieldType::Map(key, value) => (
425+
format!(
426+
"Dict[{}, {}]",
427+
key.to_type_ref(ir, use_module_prefix),
428+
value.to_partial_type_ref(ir, false, false).0
429+
),
430+
false,
425431
),
426432
FieldType::Primitive(r#type) => {
427433
if needed || wrapped {
428-
r#type.to_python()
434+
(r#type.to_python(), false)
429435
} else {
430-
format!("Optional[{}]", r#type.to_python())
436+
(format!("Optional[{}]", r#type.to_python()), true)
431437
}
432438
}
433439
FieldType::Union(inner) => {
434440
let union_contents = inner
435441
.iter()
436-
.map(|t| t.to_partial_type_ref(ir, true, false))
442+
.map(|t| t.to_partial_type_ref(ir, true, false).0)
437443
.collect::<Vec<_>>()
438444
.join(", ");
439445
if needed || wrapped {
440-
format!("Union[{union_contents}]")
446+
(format!("Union[{union_contents}]"), false)
441447
} else {
442-
format!("Optional[Union[{union_contents}]]")
448+
(format!("Optional[Union[{union_contents}]]"), true)
443449
}
444450
}
445451
FieldType::Tuple(inner) => {
446452
let tuple_contents = inner
447453
.iter()
448-
.map(|t| t.to_partial_type_ref(ir, false, false))
454+
.map(|t| t.to_partial_type_ref(ir, false, false).0)
449455
.collect::<Vec<_>>()
450456
.join(", ");
451457
if needed || wrapped {
452-
format!("Tuple[{tuple_contents}]")
458+
(format!("Tuple[{tuple_contents}]"), false)
453459
} else {
454-
format!("Optional[Tuple[{tuple_contents}]]")
460+
(format!("Optional[Tuple[{tuple_contents}]]"), true)
455461
}
456462
}
457-
FieldType::Optional(inner) => {
458-
format!("Optional[{}]", inner.to_partial_type_ref(ir, true, false))
459-
}
463+
FieldType::Optional(inner) => (
464+
format!("Optional[{}]", inner.to_partial_type_ref(ir, true, false).0),
465+
true,
466+
),
460467
FieldType::WithMetadata { .. } => {
461468
unreachable!("distribute_metadata makes this branch unreachable.")
462469
}
463-
FieldType::Arrow(_) => todo!("Arrow types should not be used in generated type definitions"),
470+
FieldType::Arrow(_) => {
471+
todo!("Arrow types should not be used in generated type definitions")
472+
}
464473
};
465474
let base_type_ref = if is_partial_type {
466475
base_rep
467476
} else {
468477
if needed {
469-
base_type.to_type_ref(ir, use_module_prefix)
478+
(base_type.to_type_ref(ir, use_module_prefix), false)
470479
} else {
471480
base_rep
472481
}
473482
};
474483
let rep_with_checks = match field_type_attributes(self) {
475484
Some(checks) => {
476485
let checks_type_ref = type_name_for_checks(&checks);
477-
format!("Checked[{base_type_ref},{checks_type_ref}]")
486+
(
487+
format!("Checked[{},{checks_type_ref}]", base_type_ref.0),
488+
base_type_ref.1,
489+
)
478490
}
479491
None => base_type_ref,
480492
};
481493
let rep_with_stream_state = if with_state {
482-
stream_state(&rep_with_checks)
494+
(stream_state(&rep_with_checks.0), rep_with_checks.1)
483495
} else {
484496
rep_with_checks
485497
};
@@ -505,6 +517,7 @@ fn stream_state(base: &str) -> String {
505517
#[cfg(test)]
506518
mod tests {
507519
use super::*;
520+
use baml_types::StreamingBehavior;
508521
use internal_baml_core::ir::repr::{make_test_ir, IntermediateRepr};
509522

510523
#[test]
@@ -516,7 +529,7 @@ mod tests {
516529
let full = optional_list.to_type_ref(&ir, false);
517530
let partial = optional_list.to_partial_type_ref(&ir, false, false);
518531
assert_eq!(full, "Optional[List[str]]");
519-
assert_eq!(partial, "Optional[List[str]]");
532+
assert_eq!(partial.0, "Optional[List[str]]");
520533
}
521534

522535
#[test]
@@ -528,6 +541,23 @@ mod tests {
528541
let full = optional_list.to_type_ref(&ir, false);
529542
let partial = optional_list.to_partial_type_ref(&ir, false, false);
530543
assert_eq!(full, "Optional[List[str]]");
531-
assert_eq!(partial, "Optional[List[str]]");
544+
assert_eq!(partial.0, "Optional[List[str]]");
545+
}
546+
547+
#[test]
548+
fn test_stream_done_type() {
549+
let ir = make_test_ir("").unwrap();
550+
let done_type = FieldType::WithMetadata {
551+
base: Box::new(FieldType::class("Foo")),
552+
streaming_behavior: StreamingBehavior {
553+
done: true,
554+
state: false,
555+
},
556+
constraints: vec![],
557+
};
558+
let full = done_type.to_type_ref(&ir, false);
559+
let partial = done_type.to_partial_type_ref(&ir, false, false);
560+
assert_eq!(full, "\"Foo\"");
561+
assert_eq!(partial.0, "Optional[\"types.Foo\"]");
532562
}
533563
}

0 commit comments

Comments
 (0)