Bisect provider-rejected batches in triage and deep assessment
DeepSeek's content filter rejects a whole request (400 "Content Exists Risk") when one article trips it, which cost the other articles in the batch their assessment and retried them every run. New curate/batch.rs runs both stages through a bisecting runner: a rejected batch is split until the offending article is isolated, that article is retried once on the editor provider when it is a different one, and a still-rejected article is recorded as a provider_rejected assessment row so it is not retried for assessment_reuse_days. Cache reuse accepts rows from either configured model. Each stage logs reused/requested/rejected counts, the curation: line shows rejections when non-zero, explain prints them, and the llm_assess span reports the deep-set size. Implemented by a Claude agent from an orchestrator brief; verified fmt/clippy(-W dead_code)/test green (354 lib tests). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A1rCLQeKBgnBo3oTgHuTMe
This commit is contained in:
@@ -429,6 +429,12 @@ pub async fn render_explain(db: &Db, row: &ExplainRow) -> Result<String, sqlx::E
|
||||
let rationale = assessment
|
||||
.get::<Option<String>, _>("rationale")
|
||||
.unwrap_or_default();
|
||||
if assessment.get::<Option<String>, _>("kind").as_deref()
|
||||
== Some(super::triage::PROVIDER_REJECTED)
|
||||
{
|
||||
let _ = writeln!(out, " {stage}: rejected by provider — {rationale}");
|
||||
continue;
|
||||
}
|
||||
if stage == "deep" {
|
||||
let _ = writeln!(
|
||||
out,
|
||||
@@ -1007,6 +1013,56 @@ mod tests {
|
||||
assert_eq!(parsed["exploration"], true);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn explain_names_provider_rejections_instead_of_scores() {
|
||||
let (_dir, db) = db_with_articles(&[1]).await;
|
||||
let run_id = db.start_run(date(), Timestamp::now()).await.unwrap();
|
||||
write(
|
||||
&db,
|
||||
&CandidateRun {
|
||||
run_id,
|
||||
article_id: 1,
|
||||
stage: "admitted",
|
||||
excluded_reason: None,
|
||||
admitted_by: Some("[\"interest\"]"),
|
||||
signals_json: "{}",
|
||||
utility: None,
|
||||
rank_utility: None,
|
||||
cluster_id: None,
|
||||
cluster_rank: None,
|
||||
editor_why: None,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
sqlx::query(
|
||||
"INSERT INTO article_assessments
|
||||
(article_id, stage, model, prompt_version, score, fit, kind, rationale, assessed_at)
|
||||
VALUES (1, 'triage', 'deepseek-v4-flash', 1, NULL, NULL, 'provider_rejected',
|
||||
'deepseek: 400 Bad Request: Content Exists Risk', '2026-09-02T04:00:00Z'),
|
||||
(1, 'deep', 'deepseek-v4-flash', 1, NULL, NULL, 'provider_rejected',
|
||||
'deepseek: returned a refusal', '2026-09-02T04:00:00Z')",
|
||||
)
|
||||
.execute(db.pool())
|
||||
.await
|
||||
.unwrap();
|
||||
let text = explain(&db, date(), None, &ExplainTarget::Article(1))
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(
|
||||
text.contains(
|
||||
" triage: rejected by provider — deepseek: 400 Bad Request: Content Exists Risk\n"
|
||||
),
|
||||
"{text}"
|
||||
);
|
||||
assert!(
|
||||
text.contains(" deep: rejected by provider — deepseek: returned a refusal\n"),
|
||||
"{text}"
|
||||
);
|
||||
assert!(!text.contains("interest —"), "{text}");
|
||||
assert!(!text.contains("quality —"), "{text}");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn rows_are_upserted_with_every_column_replaced() {
|
||||
let (_dir, db) = db_with_articles(&[1]).await;
|
||||
|
||||
Reference in New Issue
Block a user