Skip to main content

fabula_discovery/
traits.rs

1//! # Design Note: Synchronous Traits
2//!
3//! All traits are synchronous. For LLM-based evaluators or generators
4//! that call async APIs, implementors should use `tokio::runtime::Runtime::block_on`
5//! or equivalent. Making these traits async would require `async-trait` and
6//! complicate the common case. This may change in a future version.
7
8use crate::corpus::TraceCorpus;
9use crate::score::ScoredPattern;
10use fabula::pattern::Pattern;
11
12/// Proposes candidate patterns. Receives scored feedback to guide the next round.
13///
14/// Generators maintain internal state across rounds — a population of
15/// high-scoring patterns, frequency tables, or conversation history with an LLM.
16pub trait CandidateGenerator {
17    /// Propose up to `budget` candidate patterns from the corpus.
18    fn generate(&mut self, corpus: &TraceCorpus, budget: usize) -> Vec<Pattern<String, String>>;
19
20    /// Receive scored results from the previous round.
21    /// The generator can use these to guide future proposals.
22    fn feedback(&mut self, scored: &[ScoredPattern<String, String>]);
23
24    /// Human-readable name for this generator (used in score metadata).
25    fn name(&self) -> &str;
26}
27
28/// Scores a candidate pattern against a corpus.
29///
30/// Multiple evaluators can run on the same candidate. Each returns a named
31/// score that is aggregated into a [`PatternScore`](crate::PatternScore).
32pub trait PatternEvaluator {
33    /// Score a candidate pattern.
34    fn evaluate(&self, pattern: &Pattern<String, String>, corpus: &TraceCorpus) -> f64;
35
36    /// Human-readable name for this evaluator (used as score key).
37    fn name(&self) -> &str;
38}
39
40/// Decides whether a scored pattern is worth keeping.
41pub trait PatternFilter {
42    /// Returns true if the pattern should be kept.
43    fn accept(&self, scored: &ScoredPattern<String, String>) -> bool;
44}
45
46/// Accepts patterns whose composite score meets or exceeds a threshold.
47pub struct ThresholdFilter {
48    /// Minimum composite score to accept.
49    pub threshold: f64,
50    /// Per-evaluator weights for composite scoring. Missing evaluators default to weight 1.0.
51    pub weights: std::collections::HashMap<String, f64>,
52}
53
54impl PatternFilter for ThresholdFilter {
55    fn accept(&self, scored: &ScoredPattern<String, String>) -> bool {
56        scored.score.composite(&self.weights) >= self.threshold
57    }
58}