Module: Ask::Eval::DSL
- Defined in:
- lib/ask/eval/dsl.rb
Overview
Minitest DSL mixin. Include this in your test class to get all ask-eval assertion methods, including session evaluation.
Instance Method Summary collapse
-
#assert_contains(output, value, msg = nil) ⇒ Object
--- Deterministic Assertions ---.
- #assert_correctness(output, expected:, model: nil, msg: nil) ⇒ Object
-
#assert_cost_under(max_cost, msg = nil) ⇒ Object
Assert that no more than
max_costwas spent on LLM calls. - #assert_email(output, msg = nil) ⇒ Object
- #assert_ends_with(output, suffix, msg = nil) ⇒ Object
- #assert_equals(output, value, msg = nil) ⇒ Object
-
#assert_faithful(output, context:, model: nil, threshold: 0.7, msg: nil) ⇒ Object
--- LLM Judge Assertions ---.
- #assert_json(output, msg = nil) ⇒ Object
- #assert_max_length(output, max, msg = nil) ⇒ Object
- #assert_max_tokens(output, max, msg = nil) ⇒ Object
- #assert_min_length(output, min, msg = nil) ⇒ Object
- #assert_not_contains(output, value, msg = nil) ⇒ Object
- #assert_not_hallucinating(output, context:, model: nil, threshold: 0.7, msg: nil) ⇒ Object
- #assert_regex(output, pattern, msg = nil) ⇒ Object
- #assert_starts_with(output, prefix, msg = nil) ⇒ Object
-
#assert_tool_called(tool_name, msg = nil) ⇒ Object
Assert that a specific tool was called during the session.
- #assert_url(output, msg = nil) ⇒ Object
-
#eval_output(output, recorder: nil, scenario: nil) ⇒ Object
Eval an agent session with recording support.
-
#eval_session(model:, tools: [], system_prompt: nil, recordings_dir: nil, **session_opts) {|SessionEval| ... } ⇒ Object
Evaluate an agent session within a test.
- #refute_bias(output, model: nil, msg: nil) ⇒ Object
- #refute_toxicity(output, model: nil, msg: nil) ⇒ Object
Instance Method Details
#assert_contains(output, value, msg = nil) ⇒ Object
--- Deterministic Assertions ---
108 109 110 111 |
# File 'lib/ask/eval/dsl.rb', line 108 def assert_contains(output, value, msg = nil) result = Assertions::Deterministic.contains(output, value: value) assert result[:passed], msg || result[:reason] end |
#assert_correctness(output, expected:, model: nil, msg: nil) ⇒ Object
204 205 206 207 208 209 210 |
# File 'lib/ask/eval/dsl.rb', line 204 def assert_correctness(output, expected:, model: nil, msg: nil) tc = TestCase.new(actual_output: output, expected_output: expected) judge = Judges::Correctness.new(model: model) result = judge.call(tc) assert result.passed, msg || "Correctness check failed: #{result.reason}" log_cost(result) if Ask::Eval.configuration.track_cost end |
#assert_cost_under(max_cost, msg = nil) ⇒ Object
Assert that no more than max_cost was spent on LLM calls.
87 88 89 90 91 92 93 94 |
# File 'lib/ask/eval/dsl.rb', line 87 def assert_cost_under(max_cost, msg = nil) session_eval = @_ask_eval_session return flunk(msg || "No session eval context") unless session_eval cost = session_eval.total_cost assert cost <= max_cost, msg || "Expected cost <= #{max_cost}, got $#{'%.6f' % cost}" end |
#assert_email(output, msg = nil) ⇒ Object
163 164 165 166 |
# File 'lib/ask/eval/dsl.rb', line 163 def assert_email(output, msg = nil) result = Assertions::Deterministic.email(output) assert result[:passed], msg || result[:reason] end |
#assert_ends_with(output, suffix, msg = nil) ⇒ Object
138 139 140 141 |
# File 'lib/ask/eval/dsl.rb', line 138 def assert_ends_with(output, suffix, msg = nil) result = Assertions::Deterministic.ends_with(output, suffix: suffix) assert result[:passed], msg || result[:reason] end |
#assert_equals(output, value, msg = nil) ⇒ Object
143 144 145 146 |
# File 'lib/ask/eval/dsl.rb', line 143 def assert_equals(output, value, msg = nil) result = Assertions::Deterministic.equals(output, value: value) assert result[:passed], msg || result[:reason] end |
#assert_faithful(output, context:, model: nil, threshold: 0.7, msg: nil) ⇒ Object
--- LLM Judge Assertions ---
170 171 172 173 174 175 176 177 |
# File 'lib/ask/eval/dsl.rb', line 170 def assert_faithful(output, context:, model: nil, threshold: 0.7, msg: nil) tc = TestCase.new(actual_output: output, context: context) judge = Judges::Faithful.new(model: model) result = judge.call(tc) passed = result.score >= threshold assert passed, msg || "Faithfulness check failed: #{result.reason} (score: #{result.score})" log_cost(result) if Ask::Eval.configuration.track_cost end |
#assert_json(output, msg = nil) ⇒ Object
123 124 125 126 |
# File 'lib/ask/eval/dsl.rb', line 123 def assert_json(output, msg = nil) result = Assertions::Deterministic.is_json(output) assert result[:passed], msg || result[:reason] end |
#assert_max_length(output, max, msg = nil) ⇒ Object
153 154 155 156 |
# File 'lib/ask/eval/dsl.rb', line 153 def assert_max_length(output, max, msg = nil) result = Assertions::Deterministic.max_length(output, max: max) assert result[:passed], msg || result[:reason] end |
#assert_max_tokens(output, max, msg = nil) ⇒ Object
128 129 130 131 |
# File 'lib/ask/eval/dsl.rb', line 128 def assert_max_tokens(output, max, msg = nil) result = Assertions::Deterministic.max_tokens(output, max: max) assert result[:passed], msg || result[:reason] end |
#assert_min_length(output, min, msg = nil) ⇒ Object
148 149 150 151 |
# File 'lib/ask/eval/dsl.rb', line 148 def assert_min_length(output, min, msg = nil) result = Assertions::Deterministic.min_length(output, min: min) assert result[:passed], msg || result[:reason] end |
#assert_not_contains(output, value, msg = nil) ⇒ Object
113 114 115 116 |
# File 'lib/ask/eval/dsl.rb', line 113 def assert_not_contains(output, value, msg = nil) result = Assertions::Deterministic.not_contains(output, value: value) assert result[:passed], msg || result[:reason] end |
#assert_not_hallucinating(output, context:, model: nil, threshold: 0.7, msg: nil) ⇒ Object
179 180 181 182 183 184 185 186 |
# File 'lib/ask/eval/dsl.rb', line 179 def assert_not_hallucinating(output, context:, model: nil, threshold: 0.7, msg: nil) tc = TestCase.new(actual_output: output, context: context) judge = Judges::Hallucination.new(model: model) result = judge.call(tc) passed = result.score >= threshold assert passed, msg || "Hallucination check failed: #{result.reason} (score: #{result.score})" log_cost(result) if Ask::Eval.configuration.track_cost end |
#assert_regex(output, pattern, msg = nil) ⇒ Object
118 119 120 121 |
# File 'lib/ask/eval/dsl.rb', line 118 def assert_regex(output, pattern, msg = nil) result = Assertions::Deterministic.regex(output, pattern: pattern) assert result[:passed], msg || result[:reason] end |
#assert_starts_with(output, prefix, msg = nil) ⇒ Object
133 134 135 136 |
# File 'lib/ask/eval/dsl.rb', line 133 def assert_starts_with(output, prefix, msg = nil) result = Assertions::Deterministic.starts_with(output, prefix: prefix) assert result[:passed], msg || result[:reason] end |
#assert_tool_called(tool_name, msg = nil) ⇒ Object
Assert that a specific tool was called during the session.
74 75 76 77 78 79 80 81 |
# File 'lib/ask/eval/dsl.rb', line 74 def assert_tool_called(tool_name, msg = nil) session_eval = @_ask_eval_session return flunk(msg || "No session eval context") unless session_eval assert session_eval.tool_called?(tool_name), msg || "Expected tool #{tool_name.inspect} to have been called. " \ "Tools called: #{session_eval.tool_names.inspect}" end |
#assert_url(output, msg = nil) ⇒ Object
158 159 160 161 |
# File 'lib/ask/eval/dsl.rb', line 158 def assert_url(output, msg = nil) result = Assertions::Deterministic.url(output) assert result[:passed], msg || result[:reason] end |
#eval_output(output, recorder: nil, scenario: nil) ⇒ Object
Eval an agent session with recording support. This is the low-level method that eval_session delegates to.
100 101 102 103 104 |
# File 'lib/ask/eval/dsl.rb', line 100 def eval_output(output, recorder: nil, scenario: nil, **) if recorder&.recording? recorder.record_assertion(name: caller_locations(1, 1)&.label, passed: true, score: 1.0, reason: "recorded") end end |
#eval_session(model:, tools: [], system_prompt: nil, recordings_dir: nil, **session_opts) {|SessionEval| ... } ⇒ Object
Evaluate an agent session within a test.
The session runs with regression recording enabled. On first run,
interactions are recorded to test/recordings/<test_name>/.
On subsequent runs with ASK_EVAL_MODE=replay, recorded responses
are used instead of real LLM calls.
40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
# File 'lib/ask/eval/dsl.rb', line 40 def eval_session(model:, tools: [], system_prompt: nil, recordings_dir: nil, **session_opts) test_name = self.name recorder = Recorder.new( test_name: test_name, recordings_dir: recordings_dir ) session = Ask::Agent::Session.new( model: model, tools: tools, system_prompt: system_prompt, **session_opts ) unless recorder. recorder.wrap(session) end eval_runner = SessionEval.new(session, recorder: recorder) # Make session eval available to assert_tool_called / assert_cost_under @_ask_eval_session = eval_runner yield eval_runner ensure @_ask_eval_session = nil recorder&.save end |
#refute_bias(output, model: nil, msg: nil) ⇒ Object
188 189 190 191 192 193 194 |
# File 'lib/ask/eval/dsl.rb', line 188 def refute_bias(output, model: nil, msg: nil) tc = TestCase.new(actual_output: output) judge = Judges::Bias.new(model: model) result = judge.call(tc) assert result.passed, msg || "Bias detected: #{result.reason}" log_cost(result) if Ask::Eval.configuration.track_cost end |
#refute_toxicity(output, model: nil, msg: nil) ⇒ Object
196 197 198 199 200 201 202 |
# File 'lib/ask/eval/dsl.rb', line 196 def refute_toxicity(output, model: nil, msg: nil) tc = TestCase.new(actual_output: output) judge = Judges::Toxicity.new(model: model) result = judge.call(tc) assert result.passed, msg || "Toxicity detected: #{result.reason}" log_cost(result) if Ask::Eval.configuration.track_cost end |