rubric_llm 0.1.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +9 -1
- data/README.md +30 -20
- data/lib/rubric_llm/evaluator.rb +1 -1
- data/lib/rubric_llm/judge.rb +64 -10
- data/lib/rubric_llm/metrics/base.rb +1 -4
- data/lib/rubric_llm/metrics/context_precision.rb +1 -3
- data/lib/rubric_llm/metrics/context_recall.rb +1 -3
- data/lib/rubric_llm/metrics/correctness.rb +1 -3
- data/lib/rubric_llm/metrics/factual_accuracy.rb +1 -3
- data/lib/rubric_llm/metrics/faithfulness.rb +1 -3
- data/lib/rubric_llm/metrics/relevance.rb +1 -3
- data/lib/rubric_llm/retrieval_result.rb +1 -1
- data/lib/rubric_llm/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6ed5a9f9ece0c78f86b4134211e39d8100e046046686ec296697d794cefcce15
|
|
4
|
+
data.tar.gz: 40e120b95e7d8b5711e495f909e357395fd5ffbae76cb49dd8aa6c5212488196
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e07a90dabde33a66007ce21bd2173c577ce61fb1a964ce6946f2df26d03ff29dac18bc5757d209fcf5f8d76b5e8bb72c33562f250ee2335adc9aa1c83b0aada1
|
|
7
|
+
data.tar.gz: 8fcf6d081679021ac44aa729f63e243aabe344aa93c730a5b660d4fcc981963a08f7017276416b68b7b8b2d390756c537469263cdad92772e5f23a66199f1b0e
|
data/CHANGELOG.md
CHANGED
|
@@ -5,7 +5,15 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
-
## [
|
|
8
|
+
## [0.2.0] - 2026-07-11
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- Enforce schema-backed judge responses through RubyLLM structured output when supported
|
|
13
|
+
- Raise `RubricLLM::JudgeError` for empty, malformed, missing-score, non-numeric, or out-of-range judge responses instead of returning `nil` or clamping invalid scores
|
|
14
|
+
- Require `ruby_llm ~> 1.13` for named schema payload support in structured output
|
|
15
|
+
- Record judge failures per metric in `RubricLLM.evaluate` and `RubricLLM.evaluate_batch` as a `nil` score with the error message in details and continue the run, while non-judge errors propagate
|
|
16
|
+
- Remove dead score clamping and nil-response guards from LLM metrics now that the judge validates the response contract
|
|
9
17
|
|
|
10
18
|
## [0.1.2] - 2026-04-30
|
|
11
19
|
|
data/README.md
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
# RubricLLM
|
|
2
2
|
|
|
3
|
-
Lightweight LLM evaluation framework for Ruby, inspired by [
|
|
3
|
+
Lightweight LLM evaluation framework for Ruby, inspired by [DeepEval](https://github.com/confident-ai/deepeval), powered by [RubyLLM](https://github.com/crmne/ruby_llm).
|
|
4
4
|
|
|
5
5
|
[](https://badge.fury.io/rb/rubric_llm)
|
|
6
6
|
[](https://github.com/dpaluy/rubric_llm/actions/workflows/ci.yml)
|
|
7
|
+
[](https://github.com/dpaluy/rubric_llm/wiki)
|
|
7
8
|
|
|
8
9
|
Provider-agnostic evaluation with pluggable metrics, statistical A/B comparison, and test framework integration — no Rails, no ActiveRecord, no UI. Works anywhere Ruby runs.
|
|
9
10
|
|
|
@@ -32,16 +33,16 @@ RubricLLM.configure do |c|
|
|
|
32
33
|
end
|
|
33
34
|
|
|
34
35
|
result = RubricLLM.evaluate(
|
|
35
|
-
question: "What is
|
|
36
|
-
answer: "
|
|
37
|
-
context: [
|
|
38
|
-
|
|
36
|
+
question: "What is Ruby's core design philosophy?",
|
|
37
|
+
answer: "Ruby was designed by Yukihiro Matsumoto to optimize for developer happiness and productivity, prioritizing the programmer's joy over machine efficiency.",
|
|
38
|
+
context: [
|
|
39
|
+
"Yukihiro Matsumoto, Ruby's creator, has stated that Ruby is designed to make programmers happy. " \
|
|
40
|
+
"He optimized the language for human readability and developer productivity rather than raw machine performance."
|
|
41
|
+
],
|
|
42
|
+
ground_truth: "Ruby is designed to maximize developer happiness and productivity."
|
|
39
43
|
)
|
|
40
44
|
|
|
41
|
-
result.
|
|
42
|
-
result.relevance # => 0.92
|
|
43
|
-
result.correctness # => 0.98
|
|
44
|
-
result.overall # => 0.94
|
|
45
|
+
result.correctness # => 0.99
|
|
45
46
|
result.pass? # => true
|
|
46
47
|
```
|
|
47
48
|
|
|
@@ -54,7 +55,7 @@ Minitest assertions, see [examples/README.md](examples/README.md).
|
|
|
54
55
|
|
|
55
56
|
```ruby
|
|
56
57
|
RubricLLM.configure do |c|
|
|
57
|
-
c.judge_model = "gpt-
|
|
58
|
+
c.judge_model = "gpt-5.5" # any model RubyLLM supports
|
|
58
59
|
c.judge_provider = :openai # :openai, :anthropic, :gemini, etc.
|
|
59
60
|
c.temperature = 0.0 # deterministic scoring (default)
|
|
60
61
|
c.max_tokens = 4096 # max tokens for judge response
|
|
@@ -83,7 +84,7 @@ config = RubricLLM::Config.from_env
|
|
|
83
84
|
### Per-Evaluation Override
|
|
84
85
|
|
|
85
86
|
```ruby
|
|
86
|
-
custom = RubricLLM::Config.new(judge_model: "claude-
|
|
87
|
+
custom = RubricLLM::Config.new(judge_model: "claude-opus-4-5", judge_provider: :anthropic)
|
|
87
88
|
|
|
88
89
|
result = RubricLLM.evaluate(question: "...", answer: "...", config: custom)
|
|
89
90
|
report = RubricLLM.evaluate_batch(dataset, config: custom)
|
|
@@ -94,7 +95,7 @@ report = RubricLLM.evaluate_batch(dataset, config: custom)
|
|
|
94
95
|
```ruby
|
|
95
96
|
# config/initializers/rubric_llm.rb
|
|
96
97
|
RubricLLM.configure do |c|
|
|
97
|
-
c.judge_model = "gpt-
|
|
98
|
+
c.judge_model = "gpt-5.5"
|
|
98
99
|
c.judge_provider = :openai
|
|
99
100
|
end
|
|
100
101
|
```
|
|
@@ -107,12 +108,12 @@ These metrics use a judge LLM to evaluate quality. Each sends a structured promp
|
|
|
107
108
|
|
|
108
109
|
| Metric | Question it answers | Requires |
|
|
109
110
|
|--------|-------------------|----------|
|
|
110
|
-
| **Faithfulness** | Is every claim in the answer supported by the context? | `context` |
|
|
111
|
-
| **Relevance** | Does the answer address what was asked? | `question` |
|
|
112
111
|
| **Correctness** | Does the answer match the known correct answer? | `ground_truth` |
|
|
112
|
+
| **Relevance** | Does the answer address what was asked? | `question` |
|
|
113
113
|
| **Context Precision** | Are the retrieved context chunks actually relevant? | `question`, `context` |
|
|
114
|
-
| **Context Recall** | Do the contexts cover the information in the ground truth? | `context`, `ground_truth` |
|
|
115
114
|
| **Factual Accuracy** | Are there factual discrepancies between candidate and reference? | `ground_truth` |
|
|
115
|
+
| **Context Recall** | Do the contexts cover the information in the ground truth? | `context`, `ground_truth` |
|
|
116
|
+
| **Faithfulness** | Is every claim in the answer supported by the context? | `context` |
|
|
116
117
|
|
|
117
118
|
```ruby
|
|
118
119
|
# Only context — gets faithfulness, relevance, context_precision
|
|
@@ -163,8 +164,8 @@ result = RubricLLM.evaluate_retrieval(
|
|
|
163
164
|
)
|
|
164
165
|
|
|
165
166
|
result.precision_at_k(3) # => 0.67
|
|
166
|
-
result.recall_at_k(3) # =>
|
|
167
|
-
result.mrr # =>
|
|
167
|
+
result.recall_at_k(3) # => 0.90
|
|
168
|
+
result.mrr # => 0.90
|
|
168
169
|
result.ndcg # => 0.86
|
|
169
170
|
result.hit_rate # => 1.0
|
|
170
171
|
```
|
|
@@ -206,7 +207,7 @@ report.to_json # returns JSON string
|
|
|
206
207
|
Compare two models with statistical significance testing:
|
|
207
208
|
|
|
208
209
|
```ruby
|
|
209
|
-
config_a = RubricLLM::Config.new(judge_model: "gpt-
|
|
210
|
+
config_a = RubricLLM::Config.new(judge_model: "gpt-5.5")
|
|
210
211
|
config_b = RubricLLM::Config.new(judge_model: "claude-sonnet-4-6")
|
|
211
212
|
|
|
212
213
|
report_a = RubricLLM.evaluate_batch(dataset, config: config_a)
|
|
@@ -229,6 +230,8 @@ comparison.significant_regressions # => []
|
|
|
229
230
|
|
|
230
231
|
Significance markers: `*` (p < 0.05), `**` (p < 0.01), `***` (p < 0.001)
|
|
231
232
|
|
|
233
|
+
For the statistical reasoning behind paired t-tests and how to read these p-values, see [Understanding A/B Comparison](https://github.com/dpaluy/rubric_llm/wiki/Understanding-A-B-Comparison) on the wiki.
|
|
234
|
+
|
|
232
235
|
## Test Integration
|
|
233
236
|
|
|
234
237
|
### Minitest
|
|
@@ -318,8 +321,8 @@ RubricLLM uses LLM-as-Judge — an LLM scores another LLM's output. This is the
|
|
|
318
321
|
|
|
319
322
|
Mitigations built into the framework:
|
|
320
323
|
|
|
321
|
-
- **Cross-model judging.** Configure a different model as judge than the one being evaluated. Don't let
|
|
322
|
-
- **Retrieval metrics are pure math.** `precision_at_k`, `recall_at_k`, `mrr`, `ndcg` — no LLM involved, no judge bias.
|
|
324
|
+
- **Cross-model judging.** Configure a different model as judge than the one being evaluated. Don't let gpt-5.5 grade gpt-5.5.
|
|
325
|
+
- **Retrieval metrics are pure math.** `precision_at_k`, `recall_at_k`, `mrr`, `ndcg` — no LLM involved, no judge bias. See [Why Retrieval Metrics Are Pure Math](https://github.com/dpaluy/rubric_llm/wiki/Why-Retrieval-Metrics-Are-Pure-Math).
|
|
323
326
|
- **Custom non-LLM metrics.** Subclass `Metrics::Base` with regex checks, embedding similarity, or any deterministic logic.
|
|
324
327
|
- **Statistical comparison.** A/B testing with paired t-tests surfaces systematic judge bias across runs.
|
|
325
328
|
|
|
@@ -340,6 +343,13 @@ Ruby has two LLM evaluation options today. Neither fits most use cases:
|
|
|
340
343
|
| **Pluggable metrics** | No (fixed set) | Yes | Yes |
|
|
341
344
|
| **Retrieval metrics** | Yes | No | Yes |
|
|
342
345
|
|
|
346
|
+
## Further Reading
|
|
347
|
+
|
|
348
|
+
Deep dives live in the [project wiki](https://github.com/dpaluy/rubric_llm/wiki):
|
|
349
|
+
|
|
350
|
+
- [Understanding A/B Comparison](https://github.com/dpaluy/rubric_llm/wiki/Understanding-A-B-Comparison) — what paired t-tests and p-values mean for model comparison
|
|
351
|
+
- [Why Retrieval Metrics Are Pure Math](https://github.com/dpaluy/rubric_llm/wiki/Why-Retrieval-Metrics-Are-Pure-Math) — why `precision_at_k`, `recall_at_k`, `mrr`, `ndcg`, and `hit_rate` are deterministic and bias-free
|
|
352
|
+
|
|
343
353
|
## Requirements
|
|
344
354
|
|
|
345
355
|
- Ruby >= 3.4
|
data/lib/rubric_llm/evaluator.rb
CHANGED
|
@@ -30,7 +30,7 @@ module RubricLLM
|
|
|
30
30
|
result = metric.call(question:, answer:, context:, ground_truth:)
|
|
31
31
|
scores[name] = result[:score]
|
|
32
32
|
details[name] = result[:details]
|
|
33
|
-
rescue
|
|
33
|
+
rescue JudgeError => e
|
|
34
34
|
scores[name] = nil
|
|
35
35
|
details[name] = { error: e.message }
|
|
36
36
|
end
|
data/lib/rubric_llm/judge.rb
CHANGED
|
@@ -4,6 +4,24 @@ require "json"
|
|
|
4
4
|
|
|
5
5
|
module RubricLLM
|
|
6
6
|
class Judge
|
|
7
|
+
METRIC_RESPONSE_SCHEMA = {
|
|
8
|
+
name: "rubric_llm_metric_response",
|
|
9
|
+
strict: false,
|
|
10
|
+
schema: {
|
|
11
|
+
type: "object",
|
|
12
|
+
properties: {
|
|
13
|
+
score: { type: "number", minimum: 0.0, maximum: 1.0 },
|
|
14
|
+
reasoning: { type: "string" },
|
|
15
|
+
claims: { type: "array", items: { type: "object" } },
|
|
16
|
+
context_scores: { type: "array", items: { type: "object" } },
|
|
17
|
+
covered_facts: { type: "array", items: { type: "object" } },
|
|
18
|
+
discrepancies: { type: "array", items: { type: "object" } }
|
|
19
|
+
},
|
|
20
|
+
required: ["score"],
|
|
21
|
+
additionalProperties: true
|
|
22
|
+
}
|
|
23
|
+
}.freeze
|
|
24
|
+
|
|
7
25
|
attr_reader :config
|
|
8
26
|
|
|
9
27
|
def initialize(config:)
|
|
@@ -20,13 +38,19 @@ module RubricLLM
|
|
|
20
38
|
chat = RubyLLM.chat(model: config.judge_model, provider: config.judge_provider)
|
|
21
39
|
chat.with_temperature(config.temperature)
|
|
22
40
|
chat.with_params(max_tokens: config.max_tokens)
|
|
41
|
+
apply_response_schema(chat)
|
|
23
42
|
|
|
24
43
|
full_system_prompt = build_system_prompt(system_prompt)
|
|
25
44
|
chat.with_instructions(full_system_prompt)
|
|
26
45
|
response = chat.ask(user_prompt)
|
|
27
|
-
|
|
46
|
+
content = response.content
|
|
47
|
+
validate_response!(content.is_a?(Hash) ? content : parse_json(content))
|
|
28
48
|
rescue StandardError => e
|
|
29
|
-
|
|
49
|
+
if attempts > config.max_retries
|
|
50
|
+
raise e if e.is_a?(JudgeError)
|
|
51
|
+
|
|
52
|
+
raise JudgeError, "Judge call failed: #{e.message}"
|
|
53
|
+
end
|
|
30
54
|
|
|
31
55
|
sleep(config.retry_base_delay * (2**(attempts - 1)))
|
|
32
56
|
retry
|
|
@@ -36,25 +60,55 @@ module RubricLLM
|
|
|
36
60
|
# Parse JSON from LLM output with multiple strategies:
|
|
37
61
|
# 1. Direct JSON.parse
|
|
38
62
|
# 2. Extract from markdown code fence
|
|
39
|
-
# 3.
|
|
63
|
+
# 3. Raise JudgeError for malformed output
|
|
40
64
|
def parse_json(text)
|
|
41
|
-
|
|
65
|
+
raise JudgeError, "Judge response was empty" if text.nil? || text.strip.empty?
|
|
42
66
|
|
|
43
|
-
# Try direct parse
|
|
44
67
|
JSON.parse(text)
|
|
45
|
-
rescue JSON::ParserError
|
|
46
|
-
# Try extracting from code fence
|
|
68
|
+
rescue JSON::ParserError => e
|
|
47
69
|
if (match = text.match(/```(?:json)?\s*\n?(.*?)\n?\s*```/m))
|
|
48
70
|
begin
|
|
49
|
-
JSON.parse(match[1])
|
|
50
|
-
rescue JSON::ParserError
|
|
51
|
-
|
|
71
|
+
return JSON.parse(match[1])
|
|
72
|
+
rescue JSON::ParserError => e
|
|
73
|
+
raise JudgeError, "Judge response code fence was not valid JSON: #{e.message}"
|
|
52
74
|
end
|
|
53
75
|
end
|
|
76
|
+
|
|
77
|
+
raise JudgeError, "Judge response was not valid JSON: #{e.message}"
|
|
54
78
|
end
|
|
55
79
|
|
|
56
80
|
private
|
|
57
81
|
|
|
82
|
+
def apply_response_schema(chat)
|
|
83
|
+
return chat unless chat.respond_to?(:with_schema)
|
|
84
|
+
return chat unless structured_output_supported?(chat)
|
|
85
|
+
|
|
86
|
+
chat.with_schema(METRIC_RESPONSE_SCHEMA)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def structured_output_supported?(chat)
|
|
90
|
+
return true unless chat.respond_to?(:model)
|
|
91
|
+
return true unless chat.model.respond_to?(:structured_output?)
|
|
92
|
+
|
|
93
|
+
chat.model.structured_output?
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def validate_response!(response)
|
|
97
|
+
raise JudgeError, "Judge response must be a JSON object" unless response.is_a?(Hash)
|
|
98
|
+
raise JudgeError, "Judge response missing required score" unless response.key?("score")
|
|
99
|
+
|
|
100
|
+
score = parse_score(response["score"])
|
|
101
|
+
raise JudgeError, "Judge response score must be between 0.0 and 1.0" unless score.finite? && score.between?(0.0, 1.0)
|
|
102
|
+
|
|
103
|
+
response
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def parse_score(score)
|
|
107
|
+
Float(score)
|
|
108
|
+
rescue ArgumentError, TypeError
|
|
109
|
+
raise JudgeError, "Judge response score must be numeric"
|
|
110
|
+
end
|
|
111
|
+
|
|
58
112
|
def build_system_prompt(base_prompt)
|
|
59
113
|
return base_prompt unless config.custom_prompt
|
|
60
114
|
|
|
@@ -19,10 +19,7 @@ module RubricLLM
|
|
|
19
19
|
private
|
|
20
20
|
|
|
21
21
|
def judge_eval(system_prompt:, user_prompt:)
|
|
22
|
-
|
|
23
|
-
return { score: nil, details: { error: "No response from judge" } } if result.nil?
|
|
24
|
-
|
|
25
|
-
result
|
|
22
|
+
judge.call(system_prompt:, user_prompt:)
|
|
26
23
|
end
|
|
27
24
|
end
|
|
28
25
|
end
|
|
@@ -34,10 +34,8 @@ module RubricLLM
|
|
|
34
34
|
private
|
|
35
35
|
|
|
36
36
|
def normalize(result)
|
|
37
|
-
return { score: nil, details: result } unless result.is_a?(Hash) && result["score"]
|
|
38
|
-
|
|
39
37
|
{
|
|
40
|
-
score: Float(result["score"])
|
|
38
|
+
score: Float(result["score"]),
|
|
41
39
|
details: {
|
|
42
40
|
context_scores: result["context_scores"],
|
|
43
41
|
reasoning: result["reasoning"]
|
|
@@ -35,10 +35,8 @@ module RubricLLM
|
|
|
35
35
|
private
|
|
36
36
|
|
|
37
37
|
def normalize(result)
|
|
38
|
-
return { score: nil, details: result } unless result.is_a?(Hash) && result["score"]
|
|
39
|
-
|
|
40
38
|
{
|
|
41
|
-
score: Float(result["score"])
|
|
39
|
+
score: Float(result["score"]),
|
|
42
40
|
details: {
|
|
43
41
|
covered_facts: result["covered_facts"],
|
|
44
42
|
reasoning: result["reasoning"]
|
|
@@ -34,10 +34,8 @@ module RubricLLM
|
|
|
34
34
|
private
|
|
35
35
|
|
|
36
36
|
def normalize(result)
|
|
37
|
-
return { score: nil, details: result } unless result.is_a?(Hash) && result["score"]
|
|
38
|
-
|
|
39
37
|
{
|
|
40
|
-
score: Float(result["score"])
|
|
38
|
+
score: Float(result["score"]),
|
|
41
39
|
details: { reasoning: result["reasoning"] }
|
|
42
40
|
}
|
|
43
41
|
end
|
|
@@ -33,10 +33,8 @@ module RubricLLM
|
|
|
33
33
|
private
|
|
34
34
|
|
|
35
35
|
def normalize(result)
|
|
36
|
-
return { score: nil, details: result } unless result.is_a?(Hash) && result["score"]
|
|
37
|
-
|
|
38
36
|
{
|
|
39
|
-
score: Float(result["score"])
|
|
37
|
+
score: Float(result["score"]),
|
|
40
38
|
details: {
|
|
41
39
|
discrepancies: result["discrepancies"],
|
|
42
40
|
reasoning: result["reasoning"]
|
|
@@ -36,10 +36,8 @@ module RubricLLM
|
|
|
36
36
|
private
|
|
37
37
|
|
|
38
38
|
def normalize(result)
|
|
39
|
-
return { score: nil, details: result } unless result.is_a?(Hash) && result["score"]
|
|
40
|
-
|
|
41
39
|
{
|
|
42
|
-
score: Float(result["score"])
|
|
40
|
+
score: Float(result["score"]),
|
|
43
41
|
details: {
|
|
44
42
|
claims: result["claims"],
|
|
45
43
|
reasoning: result["reasoning"]
|
|
@@ -30,10 +30,8 @@ module RubricLLM
|
|
|
30
30
|
private
|
|
31
31
|
|
|
32
32
|
def normalize(result)
|
|
33
|
-
return { score: nil, details: result } unless result.is_a?(Hash) && result["score"]
|
|
34
|
-
|
|
35
33
|
{
|
|
36
|
-
score: Float(result["score"])
|
|
34
|
+
score: Float(result["score"]),
|
|
37
35
|
details: { reasoning: result["reasoning"] }
|
|
38
36
|
}
|
|
39
37
|
end
|
data/lib/rubric_llm/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rubric_llm
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- David Paluy
|
|
@@ -15,14 +15,14 @@ dependencies:
|
|
|
15
15
|
requirements:
|
|
16
16
|
- - "~>"
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: '1.
|
|
18
|
+
version: '1.13'
|
|
19
19
|
type: :runtime
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
23
|
- - "~>"
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
|
-
version: '1.
|
|
25
|
+
version: '1.13'
|
|
26
26
|
description: Provider-agnostic LLM evaluation with pluggable metrics, statistical
|
|
27
27
|
A/B comparison, and test framework integration. Ragas for Ruby, powered by RubyLLM.
|
|
28
28
|
email:
|