fix(graph): reject as == output_key on map nodes
A map node whose item binding shares its name with output_key would have the binding collected as the chain's result whenever the chain never wrote it, while the missing-writer warning stayed silent. The collision is now a validator error attributed to the map node and, with validation off, a runtime error in run_map before any item forks.
This commit is contained in:
2 files changed
+116
No files matched your search
@@ -643,6 +643,16 @@ impl GraphValidator {
|
||||
let NodeType::Map(m) = &node.node_type else {
|
||||
continue;
|
||||
};
|
||||
if m.as_name == m.output_key {
|
||||
result.error(ValidationError::with_node(
|
||||
map_id,
|
||||
format!(
|
||||
"map node '{map_id}': `as` and `output_key` are both '{}'; the item \
|
||||
binding would be collected as the chain's result",
|
||||
m.as_name
|
||||
),
|
||||
));
|
||||
}
|
||||
// A missing entry is reported by `validate_node_references`.
|
||||
let mut members: Vec<String> = branch_subgraph(graph, &m.branch).into_iter().collect();
|
||||
if members.is_empty() {
|
||||
@@ -3505,6 +3515,56 @@ mod tests {
|
||||
map
|
||||
}
|
||||
|
||||
fn map_with_as(id: &str, branch: &str, next: Option<&str>, as_name: &str) -> Node {
|
||||
let mut map = map_node_basic(id, branch, next);
|
||||
if let NodeType::Map(ref mut mm) = map.node_type {
|
||||
mm.as_name = as_name.into();
|
||||
}
|
||||
map
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn map_as_equal_output_key_errors() {
|
||||
let map = map_with_as("m", "br", Some("end"), "output");
|
||||
let branch = llm_with_state_updates("br", &[("output", "{{output}}")], None);
|
||||
let graph = graph_with(
|
||||
vec![("m", map), ("br", branch), ("end", end_node("end"))],
|
||||
"m",
|
||||
);
|
||||
|
||||
let result = validator().validate(&graph);
|
||||
|
||||
assert!(
|
||||
result.errors.iter().any(|e| e.message
|
||||
== "map node 'm': `as` and `output_key` are both 'output'; the item binding \
|
||||
would be collected as the chain's result"
|
||||
&& e.node_id.as_deref() == Some("m")),
|
||||
"expected as/output_key collision error: {:?}",
|
||||
result.errors
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn map_as_distinct_from_output_key_passes() {
|
||||
let map = map_with_as("m", "br", Some("end"), "item");
|
||||
let branch = llm_with_state_updates("br", &[("output", "{{output}}")], None);
|
||||
let graph = graph_with(
|
||||
vec![("m", map), ("br", branch), ("end", end_node("end"))],
|
||||
"m",
|
||||
);
|
||||
|
||||
let result = validator().validate(&graph);
|
||||
|
||||
assert!(
|
||||
!result
|
||||
.errors
|
||||
.iter()
|
||||
.any(|e| e.message.contains("`as` and `output_key`")),
|
||||
"distinct as/output_key must not error: {:?}",
|
||||
result.errors
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn map_max_concurrency_non_template_string_errors() {
|
||||
let map = map_with_cap("m", "br", Some("end"), "4");
|
||||
|
||||
Reference in new issue
Block a user