feat(evaluator): add optional RAGAS evaluator - #126
Merged
Conversation
Collaborator
Author
|
Related |
Collaborator
Author
jakub-walaszczyk
left a comment
Collaborator
There was a problem hiding this comment.
Overall very strong PR. There are some suggestions, that seem to address LLM-generated content. Once we address them we may ship it with coming v0.12.0.
2 additional comments:
- you should also introduce documentation update
- make sure docstrings are in place even for private methods (this is for developers in the first place)
Adds RagasEvaluator (+ ragas adapters) as a regular dependency, an llm_judge_mode selector (base/ragas/all/none) on run_rag_optimization, a shared build_aggregate_metric helper, evaluator-aware metric resolution, and docs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: “Piotr <phelm@redhat.com>
Cover the two surfaces the RAGAS unit tests deliberately skip: - Drive the real ragas.evaluate machinery through the ai4rag adapters with local fake models (no network), asserting the pipeline runs end-to-end, delegates to both the LLM and embedding adapters, and returns well-formed results in the unit range. This catches RAGAS version drift in the dataset schema, evaluate() signature, result columns, and BaseRagasLLM/BaseRagasEmbeddings interfaces. - Wire a RagasEvaluator into a full AI4RAGExperiment run (real Chroma, mocked search-space models) with only the scoring step stubbed, verifying RAGAS metrics are routed by evaluator type into results. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: “Piotr <phelm@redhat.com>
Reference-based unitxt metrics (faithfulness, answer_correctness) delegate to TokenOverlap, which crashes with "max() iterable argument is empty" when a record has no references (empty contexts/ground_truths). One such record aborted the whole evaluation via the broad except -> EvaluationError. Evaluate each reference-based metric only over the records that carry its references: unevaluable records contribute no per-question score and are left out of the mean/CI. Metrics that share an evaluable-row set still run in a single evaluate() call, so the healthy case is unchanged in behaviour and cost. Add real-unitxt regression tests reproducing the production crash. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: “Piotr <phelm@redhat.com>
When the optimization metric is produced but has a None mean (now reachable for reference-based unitxt metrics whose records all lack references), the score lookup used next((mean ...), None), which conflated "metric absent" with "metric present but unscored" and raised a misleading 'not found' error that aborted the whole optimization run. Split the two cases in a new _resolve_optimization_score helper: a genuinely absent metric (wrong name/evaluator) still raises RAGExperimentError with an evaluator-qualified message, while a produced-but-None metric returns None so the optimizer records a failed — not fatal — iteration (matching the float|None contract and gam_opt's None handling). Also disambiguate the optimization_metric flag in the streamed payload by name AND evaluator, so a colliding name (unitxt vs ragas 'faithfulness') only flags the actual target. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: “Piotr <phelm@redhat.com>
jakub-walaszczyk
left a comment
Collaborator
There was a problem hiding this comment.
Minor comments to resolve and we are getting closer and closer to the merge. The solution for milvus returning empty contexts will be merged soon 👀
Call the AI4RAGRagasLLM / AI4RAGRagasEmbeddings constructors directly instead of wrapping them in make_ragas_llm / make_ragas_embeddings factories that added nothing over the constructors themselves. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: “Piotr <phelm@redhat.com>
…ashing" This reverts commit b5f0369. The empty retrieved contexts were caused by a Milvus read-after-write consistency issue (documents fetched too soon after indexing), not by the metrics themselves, and that root cause is being fixed separately. Restore the simpler single evaluate() call. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: “Piotr <phelm@redhat.com>
Both the metrics and optimization_metric setters now expect RAGMetric instances selected from Metrics and reject bare name strings, which are ambiguous because a name (e.g. "faithfulness") is shared across the unitxt and RAGAS evaluators. The optimization pipeline resolves its configured metric name to a concrete RAGMetric before constructing the experiment, binding ambiguous names to the unitxt variant under the assumption that only unitxt metrics (plus the custom overall_score) drive optimization. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: “Piotr <phelm@redhat.com>
The leaderboard keyed aggregate scores by metric name only, so when both the unitxt and RAGAS evaluators emit "faithfulness" the RAGAS score silently overwrote the unitxt one (last-wins), conflating two distinct metrics into a single column that could show the wrong evaluator's value. Key aggregate scores by a collision-free key: unitxt/custom metrics keep their bare name, other evaluators are prefixed (e.g. "ragas_faithfulness") so each variant gets its own leaderboard column. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: “Piotr <phelm@redhat.com>
The mkdocstrings directives still pointed at make_ragas_llm / make_ragas_embeddings, which were removed; point them at the AI4RAGRagasLLM / AI4RAGRagasEmbeddings classes so the strict docs build passes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: “Piotr <phelm@redhat.com>
Update the evaluation guide and data-flow doc after tightening the metrics / optimization_metric API to reject bare name strings: drop the string-based examples and note that a RAGMetric instance from Metrics is required. Also fix the optimization-score pseudocode to match on both name and evaluator, as the code does for colliding names. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: “Piotr <phelm@redhat.com>
jakub-walaszczyk
approved these changes
Aug 19, 2026
Medokins
pushed a commit
that referenced
this pull request
Aug 19, 2026
* feat(evaluator): add RAGAS evaluator with llm_judge_mode selector Adds RagasEvaluator (+ ragas adapters) as a regular dependency, an llm_judge_mode selector (base/ragas/all/none) on run_rag_optimization, a shared build_aggregate_metric helper, evaluator-aware metric resolution, and docs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: “Piotr <phelm@redhat.com> * test(evaluator): add functional tests for RAGAS evaluator Cover the two surfaces the RAGAS unit tests deliberately skip: - Drive the real ragas.evaluate machinery through the ai4rag adapters with local fake models (no network), asserting the pipeline runs end-to-end, delegates to both the LLM and embedding adapters, and returns well-formed results in the unit range. This catches RAGAS version drift in the dataset schema, evaluate() signature, result columns, and BaseRagasLLM/BaseRagasEmbeddings interfaces. - Wire a RagasEvaluator into a full AI4RAGExperiment run (real Chroma, mocked search-space models) with only the scoring step stubbed, verifying RAGAS metrics are routed by evaluator type into results. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: “Piotr <phelm@redhat.com> * fix(unitxt): exclude records without references instead of crashing Reference-based unitxt metrics (faithfulness, answer_correctness) delegate to TokenOverlap, which crashes with "max() iterable argument is empty" when a record has no references (empty contexts/ground_truths). One such record aborted the whole evaluation via the broad except -> EvaluationError. Evaluate each reference-based metric only over the records that carry its references: unevaluable records contribute no per-question score and are left out of the mean/CI. Metrics that share an evaluable-row set still run in a single evaluate() call, so the healthy case is unchanged in behaviour and cost. Add real-unitxt regression tests reproducing the production crash. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: “Piotr <phelm@redhat.com> * fix(experiment): treat unscored optimization metric as failed iteration When the optimization metric is produced but has a None mean (now reachable for reference-based unitxt metrics whose records all lack references), the score lookup used next((mean ...), None), which conflated "metric absent" with "metric present but unscored" and raised a misleading 'not found' error that aborted the whole optimization run. Split the two cases in a new _resolve_optimization_score helper: a genuinely absent metric (wrong name/evaluator) still raises RAGExperimentError with an evaluator-qualified message, while a produced-but-None metric returns None so the optimizer records a failed — not fatal — iteration (matching the float|None contract and gam_opt's None handling). Also disambiguate the optimization_metric flag in the streamed payload by name AND evaluator, so a colliding name (unitxt vs ragas 'faithfulness') only flags the actual target. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: “Piotr <phelm@redhat.com> * refactor(evaluator): drop ragas adapter factory functions Call the AI4RAGRagasLLM / AI4RAGRagasEmbeddings constructors directly instead of wrapping them in make_ragas_llm / make_ragas_embeddings factories that added nothing over the constructors themselves. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: “Piotr <phelm@redhat.com> * Revert "fix(unitxt): exclude records without references instead of crashing" This reverts commit b5f0369. The empty retrieved contexts were caused by a Milvus read-after-write consistency issue (documents fetched too soon after indexing), not by the metrics themselves, and that root cause is being fixed separately. Restore the simpler single evaluate() call. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: “Piotr <phelm@redhat.com> * refactor(experiment): require RAGMetric instances for metrics Both the metrics and optimization_metric setters now expect RAGMetric instances selected from Metrics and reject bare name strings, which are ambiguous because a name (e.g. "faithfulness") is shared across the unitxt and RAGAS evaluators. The optimization pipeline resolves its configured metric name to a concrete RAGMetric before constructing the experiment, binding ambiguous names to the unitxt variant under the assumption that only unitxt metrics (plus the custom overall_score) drive optimization. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: “Piotr <phelm@redhat.com> * fix(leaderboard): disambiguate colliding metric names by evaluator The leaderboard keyed aggregate scores by metric name only, so when both the unitxt and RAGAS evaluators emit "faithfulness" the RAGAS score silently overwrote the unitxt one (last-wins), conflating two distinct metrics into a single column that could show the wrong evaluator's value. Key aggregate scores by a collision-free key: unitxt/custom metrics keep their bare name, other evaluators are prefixed (e.g. "ragas_faithfulness") so each variant gets its own leaderboard column. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: “Piotr <phelm@redhat.com> * docs(evaluator): reference ragas adapter classes not removed factories The mkdocstrings directives still pointed at make_ragas_llm / make_ragas_embeddings, which were removed; point them at the AI4RAGRagasLLM / AI4RAGRagasEmbeddings classes so the strict docs build passes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: “Piotr <phelm@redhat.com> * docs: reflect RAGMetric-only metric API and evaluator-aware matching Update the evaluation guide and data-flow doc after tightening the metrics / optimization_metric API to reject bare name strings: drop the string-based examples and note that a RAGMetric instance from Metrics is required. Also fix the optimization-score pseudocode to match on both name and evaluator, as the code does for colliding names. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: “Piotr <phelm@redhat.com> --------- Signed-off-by: “Piotr <phelm@redhat.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Nikodem Szwast <nikodemszwast@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Description
Adds RAGAS as an optional, independent evaluator alongside the in-house LLM judge, exposing four RAG metrics (faithfulness, answer relevancy, context precision, context recall) that run through the pipeline's already-configured foundation and embedding models.
Motivation
The project only offered its in-house LLM-as-a-Judge metrics. RAGAS is a widely-used, standardized RAG evaluation library, and supporting it gives users an independent, comparable set of metrics without having to stand up a separate evaluation stack. Wrapping our own model abstractions means RAGAS reuses the endpoint the rest of the pipeline is already configured with instead of opening its own OpenAI/LangChain client.
Changes
RagasEvaluatorai4rag/evaluator/ragas_evaluator.py- scores RAG metrics via the RAGAS library and returns them in the sharedEvaluationMetricsResultshape, with bootstrap confidence intervals computed the same way as the other evaluators. Failed/slow samples yield NaN → None instead of aborting the whole pattern evaluation.ai4rag/evaluator/ragas_adapters.pythinBaseRagasLLM/BaseRagasEmbeddingswrappers delegating toBaseFoundationModel.chatandBaseEmbeddingModel. All ragas/langchain imports are local so importingai4rag.evaluatornever requires the optional extra.ai4rag/evaluator/metric.py-RAGAS_FAITHFULNESS,RAGAS_ANSWER_RELEVANCY,RAGAS_CONTEXT_PRECISION,RAGAS_CONTEXT_RECALL, plus aragasevaluator literal.run_rag_optimizationgains aragas_enabled: bool = Falseflag that appends aRagasEvaluator;AI4RAGExperimentadds the four RAGAS metrics to its default set when a ragas evaluator is present;RagasEvaluatorexported fromai4rag.evaluator.RunConfig(timeout=1200, max_workers=4)to avoid overwhelming small evaluating models and to prevent spurious thread-poolTimeoutErrors.Testing
Added
tests/unit/ai4rag/evaluator/test_ragas_evaluator.pycovering the evaluator.