Skip to content

Commit 46921b7

Browse files
Maintain field order when rendering class in Python (#1931)
Fix #1477 Converting Python objects to Rust objects in the FFI code used a `HashMap` which messes up ordering when rendering the Rust objects in the prompt. <!-- ELLIPSIS_HIDDEN --> ---- > [!IMPORTANT] > Replaces `HashMap` with `IndexMap` to maintain field order in FFI conversions and adds a test to verify this behavior. > > - **Behavior**: > - Replaces `HashMap` with `IndexMap` in `parse_py_type.rs` to maintain field order when converting Python objects to Rust objects. > - Adds a new test `test_maintain_field_order` in `prompt_renderer.test.ts` to verify field order is preserved. > - **Code Removal**: > - Removes `IntoIterator` implementation for `MinijinjaBamlClass` in `baml_value_to_jinja_value.rs`. > - **Misc**: > - Adds `MaintainFieldOrder` class to `type_builder.ts` to support new functionality. > > <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 33923ee. 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 6a38672 commit 46921b7

58 files changed

Lines changed: 1566 additions & 46 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/jinja-runtime/src/baml_value_to_jinja_value.rs

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
use std::collections::HashMap;
21
use std::sync::Arc;
32

43
use baml_types::EvaluationContext;
@@ -199,7 +198,7 @@ impl minijinja::value::StructObject for MinijinjaBamlClass {
199198
}
200199

201200
fn fields(&self) -> Vec<Arc<str>> {
202-
self.class.keys().into_iter().map(|k| intern(k)).collect()
201+
self.class.keys().into_iter().map(|k| intern(k)).collect()
203202
}
204203
}
205204

@@ -208,18 +207,6 @@ struct MinijinjaBamlClass {
208207
key_to_alias: IndexMap<String, String>,
209208
}
210209

211-
impl IntoIterator for MinijinjaBamlClass {
212-
type Item = (String, minijinja::Value);
213-
type IntoIter = std::collections::hash_map::IntoIter<String, minijinja::Value>;
214-
215-
fn into_iter(self) -> Self::IntoIter {
216-
self.class
217-
.into_iter()
218-
.collect::<HashMap<String, minijinja::Value>>()
219-
.into_iter()
220-
}
221-
}
222-
223210
impl std::fmt::Display for MinijinjaBamlClass {
224211
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
225212
let mut map = IndexMap::new();

engine/language_client_python/src/parse_py_type.rs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
use std::collections::HashMap;
1+
use std::collections::{BTreeMap, HashMap};
22

33
use anyhow::Result;
44
use baml_types::{BamlMap, BamlValue};
5+
use indexmap::IndexMap;
56
use pyo3::{
67
exceptions::{PyRuntimeError, PyTypeError},
78
prelude::{PyAnyMethods, PyTypeMethods},
@@ -51,8 +52,8 @@ impl From<Errors> for PyErr {
5152

5253
enum MappedPyType {
5354
Enum(String, String),
54-
Class(String, HashMap<String, PyObject>),
55-
Map(HashMap<String, PyObject>),
55+
Class(String, IndexMap<String, PyObject>),
56+
Map(HashMap<String, PyObject>), // TODO: Does this need to maintain order?
5657
List(Vec<PyObject>),
5758
String(String),
5859
Int(i64),
@@ -246,11 +247,11 @@ pub fn parse_py_type(
246247
}
247248
})
248249
.unwrap_or("<UnnamedBaseModel>".to_string());
249-
let mut fields = HashMap::new();
250-
// Get regular fields
250+
let mut fields = IndexMap::new();
251+
// Get regular fields. Maintain order, no HashMap.
251252
if let Ok(model_fields) = t
252253
.getattr("model_fields")?
253-
.extract::<HashMap<String, PyObject>>()
254+
.extract::<BTreeMap<String, PyObject>>()
254255
{
255256
for (key, _) in model_fields {
256257
if let Ok(value) = any.getattr(py, key.as_str()) {
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
class MaintainFieldOrder {
2+
a string
3+
b string
4+
c string
5+
}
6+
7+
function UseMaintainFieldOrder(input: MaintainFieldOrder) -> MaintainFieldOrder {
8+
client GPT35
9+
prompt #"
10+
Return this value back to me: {{input}}
11+
"#
12+
}
13+
14+

integ-tests/go/baml_client/client.go

Lines changed: 62 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

integ-tests/go/baml_client/encode.go

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

integ-tests/go/baml_client/inlinedbaml.go

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

integ-tests/go/baml_client/stream_types/stream_types.go

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

integ-tests/go/baml_client/types/types.go

Lines changed: 51 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

integ-tests/openapi/baml_client/openapi.yaml

Lines changed: 43 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)