khive-retrieval 0.14.0

Hybrid retrieval composer (HNSW + BM25 + fusion + graph + cross-encoder) with deterministic scoring
Documentation
#[tokio::test]
async fn candidate_scores_do_not_need_to_be_clone() {
    #[derive(Debug, PartialEq, Eq)]
    struct NonCloneScore(u8);

    let result = materialize_ranked_prefix(
        vec![RankedCandidate {
            key: 1_u8,
            score: NonCloneScore(7),
        }],
        1,
        NonZeroUsize::new(1).unwrap(),
        limits(1),
        |candidate| candidate.key,
        |_| Ok::<_, &'static str>(()),
        |_| ready(Ok::<_, &'static str>(vec![(1_u8, ())])),
        |_, _| MaterializationDecision::<_, Reason, &'static str>::Keep("kept"),
    )
    .await
    .unwrap();
    assert_eq!(result.accepted[0].candidate.score, NonCloneScore(7));
}

#[tokio::test]
async fn kth_keep_ignores_later_loaded_classifier_results_but_validates_the_batch() {
    let validated = RefCell::new(Vec::new());
    let classified = RefCell::new(Vec::new());
    let result = materialize_ranked_prefix(
        candidates(&[1, 2, 3]),
        1,
        NonZeroUsize::new(3).unwrap(),
        limits(3),
        |candidate| candidate.key,
        |candidate| {
            validated.borrow_mut().push(candidate.key);
            Ok::<_, &'static str>(())
        },
        |_| ready(Ok(vec![(1, ()), (2, ()), (3, ())])),
        |candidate, _| {
            classified.borrow_mut().push(candidate.key);
            if candidate.key == 1 {
                MaterializationDecision::<&str, Reason, &'static str>::Keep("kept")
            } else {
                MaterializationDecision::<&str, Reason, &'static str>::Fatal("must be ignored")
            }
        },
    )
    .await;
    assert!(
        result.is_ok(),
        "later loaded classifier Fatal must be ignored: {result:?}"
    );
    let result = result.unwrap();
    assert_eq!(*validated.borrow(), vec![1, 2, 3]);
    assert_eq!(*classified.borrow(), vec![1]);
    assert_eq!(result.accepted[0].output, "kept");
    assert_eq!(result.drop_counts.total(), 0);
}

#[tokio::test]
async fn invalid_tail_after_k_fails_without_more_loader_io() {
    let loader_calls = Cell::new(0);
    let result = materialize_ranked_prefix(
        candidates(&[1, 2, 3]),
        1,
        NonZeroUsize::new(1).unwrap(),
        limits(3),
        |candidate| candidate.key,
        |candidate| {
            if candidate.key == 3 {
                Err("invalid tail")
            } else {
                Ok(())
            }
        },
        |keys| {
            loader_calls.set(loader_calls.get() + 1);
            ready(Ok(keys.into_iter().map(|key| (key, ())).collect()))
        },
        |_, _| MaterializationDecision::<(), Reason, &'static str>::Keep(()),
    )
    .await;
    assert_eq!(result, Err(MaterializationError::Caller("invalid tail")));
    assert_eq!(loader_calls.get(), 1);
}

#[tokio::test]
async fn earlier_loader_failure_wins_over_a_later_invalid_candidate() {
    let validated = RefCell::new(Vec::new());
    let result = materialize_ranked_prefix(
        candidates(&[1, 2]),
        2,
        NonZeroUsize::new(1).unwrap(),
        limits(2),
        |candidate| candidate.key,
        |candidate| {
            validated.borrow_mut().push(candidate.key);
            if candidate.key == 2 {
                Err("later invalid")
            } else {
                Ok(())
            }
        },
        |_| ready(Err::<Vec<(u8, ())>, _>("earlier loader")),
        |_, _| MaterializationDecision::<(), Reason, &'static str>::Keep(()),
    )
    .await;
    assert_eq!(result, Err(MaterializationError::Caller("earlier loader")));
    assert_eq!(*validated.borrow(), vec![1]);
}