Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
108 changes: 17 additions & 91 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

42 changes: 39 additions & 3 deletions crates/openplotva-app/src/dialog_jobs/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1319,6 +1319,11 @@ async fn dialog_worker_fails_when_answer_sanitizes_to_empty_after_attempts_exhau
});
let effects = EffectsStub::default();

let reporter = crate::runtime_routing::RoutingEventReporter::new(
crate::runtime_routing::RoutingEventBuffer::new(8),
None,
None,
);
let outcomes = crate::dialog_turn::DialogTurnObserver::new(
crate::dialog_turn::RuntimeTurnOutcomeBuffer::new(8),
None,
Expand All @@ -1338,7 +1343,7 @@ async fn dialog_worker_fails_when_answer_sanitizes_to_empty_after_attempts_exhau
terminal_signal: crate::dialog_turn::TurnSignalPolicy::default(),
obligations: None,
now,
routing_events: None,
routing_events: Some(&reporter),
turn_outcomes: Some(&outcomes),
session: leaked_session_wiring(),
llm_runs: None,
Expand All @@ -1362,6 +1367,7 @@ async fn dialog_worker_fails_when_answer_sanitizes_to_empty_after_attempts_exhau
assert_eq!(rows[0].outcome, "terminal_failed");
assert_eq!(rows[0].reason.as_deref(), Some("sanitized_empty_exhausted"));
assert_eq!(rows[0].user_signal.as_deref(), Some("none"));
assert_validator_exhaustion_routing_event(&reporter, job_id);
Ok(())
}

Expand Down Expand Up @@ -1691,6 +1697,11 @@ async fn dialog_worker_retries_provider_empty_output_and_fails_on_exhaustion()
);
let provider = ProviderStub::returning(DialogOutput::default());
let effects = EffectsStub::default();
let reporter = crate::runtime_routing::RoutingEventReporter::new(
crate::runtime_routing::RoutingEventBuffer::new(8),
None,
None,
);
let outcomes = crate::dialog_turn::DialogTurnObserver::new(
crate::dialog_turn::RuntimeTurnOutcomeBuffer::new(8),
None,
Expand All @@ -1704,7 +1715,7 @@ async fn dialog_worker_retries_provider_empty_output_and_fails_on_exhaustion()
terminal_signal: crate::dialog_turn::TurnSignalPolicy::default(),
obligations: None,
now,
routing_events: None,
routing_events: Some(&reporter),
turn_outcomes: Some(&outcomes),
session: leaked_session_wiring(),
llm_runs: None,
Expand Down Expand Up @@ -1750,6 +1761,7 @@ async fn dialog_worker_retries_provider_empty_output_and_fails_on_exhaustion()
assert_eq!(rows[0].outcome, "terminal_failed");
assert_eq!(rows[0].reason.as_deref(), Some("provider_empty_exhausted"));
assert_eq!(rows[0].attempt, 2);
assert_validator_exhaustion_routing_event(&reporter, job_id);
Ok(())
}

Expand All @@ -1768,6 +1780,11 @@ async fn dialog_worker_requeues_undeliverable_answer_instead_of_failing_at_send(
..DialogOutput::default()
});
let effects = EffectsStub::default();
let reporter = crate::runtime_routing::RoutingEventReporter::new(
crate::runtime_routing::RoutingEventBuffer::new(8),
None,
None,
);
let outcomes = crate::dialog_turn::DialogTurnObserver::new(
crate::dialog_turn::RuntimeTurnOutcomeBuffer::new(8),
None,
Expand All @@ -1781,7 +1798,7 @@ async fn dialog_worker_requeues_undeliverable_answer_instead_of_failing_at_send(
terminal_signal: crate::dialog_turn::TurnSignalPolicy::default(),
obligations: None,
now,
routing_events: None,
routing_events: Some(&reporter),
turn_outcomes: Some(&outcomes),
session: leaked_session_wiring(),
llm_runs: None,
Expand Down Expand Up @@ -1829,6 +1846,7 @@ async fn dialog_worker_requeues_undeliverable_answer_instead_of_failing_at_send(
rows[0].reason.as_deref(),
Some("undeliverable_answer_exhausted")
);
assert_validator_exhaustion_routing_event(&reporter, job_id);
Ok(())
}

Expand Down Expand Up @@ -7651,3 +7669,21 @@ async fn random_finish_keeps_tool_history_and_report() -> Result<(), Box<dyn Err
assert!(effects.intermediates().is_empty());
Ok(())
}

/// A provider that answered but whose output our validators rejected must
/// surface as a model-output failure, never as a provider protocol fault.
fn assert_validator_exhaustion_routing_event(
reporter: &crate::runtime_routing::RoutingEventReporter,
job_id: i64,
) {
let routing_events = reporter.buffer().routing_events(10);
let exhausted = routing_events
.iter()
.find(|event| event.event_type == "all_attempts_exhausted")
.expect("job retry exhaustion routing event");
assert_eq!(exhausted.job_id, Some(job_id));
assert_eq!(
exhausted.detail["last_retryable_reason"],
"model_output_rejected"
);
}
4 changes: 2 additions & 2 deletions crates/openplotva-app/src/dialog_turn/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -888,7 +888,7 @@ where
RetryableDialogProviderFailure {
queue_name: ctx.queue_name,
provider_name: step.provider.as_str(),
reason: openplotva_llm::retry::FailureReason::ProviderProtocolError,
reason: openplotva_llm::retry::FailureReason::ModelOutputRejected,
codes,
error,
max_attempts: if image_action_missing {
Expand Down Expand Up @@ -1064,7 +1064,7 @@ where
RetryableDialogProviderFailure {
queue_name: ctx.queue_name,
provider_name: step.provider.as_str(),
reason: openplotva_llm::retry::FailureReason::ProviderProtocolError,
reason: openplotva_llm::retry::FailureReason::ModelOutputRejected,
codes: UNDELIVERABLE_RETRY_CODES,
error: &error,
max_attempts: ctx.max_llm_job_attempts.max(1),
Expand Down
Loading
Loading