feat: LLM node failures propgate up
This commit is contained in:
+32
-16
@@ -28,36 +28,46 @@ impl LlmNodeExecutor {
|
|||||||
parent_ctx: &mut RequestContext,
|
parent_ctx: &mut RequestContext,
|
||||||
) -> Result<LlmExecutionOutcome> {
|
) -> Result<LlmExecutionOutcome> {
|
||||||
let result = run(node, state_manager, parent_ctx).await;
|
let result = run(node, state_manager, parent_ctx).await;
|
||||||
let (output, failed) = match result {
|
let (output, failure_reason) = match result {
|
||||||
Ok(raw) => match &node.output_schema {
|
Ok(raw) => match &node.output_schema {
|
||||||
Some(schema) => match structured::extract(&raw, schema, parent_ctx).await {
|
Some(schema) => match structured::extract(&raw, schema, parent_ctx).await {
|
||||||
Ok(value) => (value, false),
|
Ok(value) => (value, None),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
warn!("llm node structured extraction failed: {e}");
|
warn!("llm node structured extraction failed: {e}");
|
||||||
(
|
(
|
||||||
Value::String(format!("LLM node structured-extraction failed: {e}")),
|
Value::String(format!("LLM node structured-extraction failed: {e}")),
|
||||||
true,
|
Some(format!("structured-extraction failed: {e}")),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
None => (Value::String(raw), false),
|
None => (Value::String(raw), None),
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
warn!("llm node failed: {e}");
|
warn!("llm node failed: {e}");
|
||||||
(Value::String(format!("LLM node failed: {e}")), true)
|
(
|
||||||
|
Value::String(format!("LLM node failed: {e}")),
|
||||||
|
Some(format!("LLM call failed: {e:#}")),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
apply_state_updates_with_output(node, state_manager, &output);
|
apply_state_updates_with_output(node, state_manager, &output);
|
||||||
Ok(outcome_from(failed, node.fallback.as_deref()))
|
outcome_from(failure_reason.as_deref(), node.fallback.as_deref())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn outcome_from(failed: bool, fallback: Option<&str>) -> LlmExecutionOutcome {
|
fn outcome_from(
|
||||||
if failed && let Some(fb) = fallback {
|
failure_reason: Option<&str>,
|
||||||
LlmExecutionOutcome::FellBack(fb.to_string())
|
fallback: Option<&str>,
|
||||||
} else {
|
) -> Result<LlmExecutionOutcome> {
|
||||||
LlmExecutionOutcome::Continue
|
match (failure_reason, fallback) {
|
||||||
|
(None, _) => Ok(LlmExecutionOutcome::Continue),
|
||||||
|
(Some(_), Some(fb)) => Ok(LlmExecutionOutcome::FellBack(fb.to_string())),
|
||||||
|
(Some(reason), None) => bail!(
|
||||||
|
"LLM node failed and no fallback declared: {reason}. \
|
||||||
|
Add a `fallback:` route on the node to route on failure, \
|
||||||
|
or fix the underlying error."
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -445,23 +455,29 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn outcome_from_success_is_continue() {
|
fn outcome_from_success_is_continue() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
outcome_from(false, Some("fb")),
|
outcome_from(None, Some("fb")).unwrap(),
|
||||||
|
LlmExecutionOutcome::Continue
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
outcome_from(None, None).unwrap(),
|
||||||
LlmExecutionOutcome::Continue
|
LlmExecutionOutcome::Continue
|
||||||
);
|
);
|
||||||
assert_eq!(outcome_from(false, None), LlmExecutionOutcome::Continue);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn outcome_from_failure_with_fallback_is_fell_back() {
|
fn outcome_from_failure_with_fallback_is_fell_back() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
outcome_from(true, Some("fb")),
|
outcome_from(Some("HTTP 404"), Some("fb")).unwrap(),
|
||||||
LlmExecutionOutcome::FellBack("fb".to_string())
|
LlmExecutionOutcome::FellBack("fb".to_string())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn outcome_from_failure_without_fallback_is_continue() {
|
fn outcome_from_failure_without_fallback_propagates_error() {
|
||||||
assert_eq!(outcome_from(true, None), LlmExecutionOutcome::Continue);
|
let err = outcome_from(Some("HTTP 404"), None).unwrap_err();
|
||||||
|
let msg = format!("{err:#}");
|
||||||
|
assert!(msg.contains("no fallback declared"), "got: {msg}");
|
||||||
|
assert!(msg.contains("HTTP 404"), "got: {msg}");
|
||||||
}
|
}
|
||||||
|
|
||||||
fn node_with_schema(updates: Option<HashMap<String, String>>, schema: Value) -> LlmNode {
|
fn node_with_schema(updates: Option<HashMap<String, String>>, schema: Value) -> LlmNode {
|
||||||
|
|||||||
Reference in New Issue
Block a user