inquirex-llm 0.12.1 → 0.12.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ac542fe51e67d8477ceb8b7b45facb1af3e79b4794cd69d9e62a158d40931df3
4
- data.tar.gz: 0a995561405b2cf3838c72a696ba2c20e55d9fb185cbd44bbf8f0a9dd02fffe2
3
+ metadata.gz: 1261cf90cb18c935731001fc135e896e271d63e64f84aba6fc5b45105bfcaa8c
4
+ data.tar.gz: 74ddcc8813674ae65e5979a3becc1c9ecbd7bdf4b0e64e71161a6ddc15c86a01
5
5
  SHA512:
6
- metadata.gz: 3aa789c543358cd2a3cf12423e252fa07673fcedf6cdd950db4a205d3cea62eaebf2f493207989c3aaf4b89c8a89cc714b5d54fb5a9bfa50fc24e7737f5cded4
7
- data.tar.gz: cde3f862aa1fe7f3bac1988290c93595e997df98e3fc22798e8ff146b757f3552f39d7f24129b95a19fa15ce71236f21332d290cfdbe98e8633d203aeff480f7
6
+ metadata.gz: 1eda2a5ae132b1027700dc058f90ff47fe3a29e5e8804669b5ad4c9c1771f76b8c01484963e8f3d63d5e3fc9f3311e568d67d73b0f35f7e3cff9c1723a4d723c
7
+ data.tar.gz: 7af624fbd2546c4cf1404f3ac51aecc72c670e013888fb7fdb654be5f2f3ee455023e08737dfea510295bdfa9fd783c4ffe801f2e383004f8491ed463ad02955
data/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.12.2 (unreleased)
4
+
5
+ - **The eval suite now runs on llm-evals.** `inquirex/llm/evals` requires `llm/evals` and subclasses `::LLM::Evals`, because `agent-evals` was renamed and is no longer on RubyGems. The config file is `evals/.llm-evals.config.yml` and the executable is `evals`. Inside `Inquirex::LLM`, a bare `LLM` means this gem, so the leading `::` is required.
6
+
3
7
  ## 0.12.0 (unreleased)
4
8
 
5
9
  Hand-written until `github_changelog_generator` regenerates this file at release. The family-wide notes are in inquirex's `INQUIREX_VERSION_LOG.md`.
data/README.md CHANGED
@@ -487,15 +487,15 @@ end
487
487
 
488
488
  ## Evals
489
489
 
490
- Which model extracts best, measured with this gem's own prompts: ten flows, a complete and a vague text each, run on [agent-evals](https://github.com/kigster/agent-evals). The lineup and the data live in [`evals/`](evals/README.md); the adapter and the scorer are `Inquirex::LLM::Evals`, loaded only by `require "inquirex/llm/evals"`.
490
+ Which model extracts best, measured with this gem's own prompts: ten flows, a complete and a vague text each, run on [llm-evals](https://github.com/kigster/llm-evals). The lineup and the data live in [`evals/`](evals/README.md); the adapter and the scorer are `Inquirex::LLM::Evals`, loaded only by `require "inquirex/llm/evals"`.
491
491
 
492
492
  How a run works:
493
493
 
494
494
  1. **Verify** (offline): each dataset's `schema.rb` must match the `extract` step in its `dsl.rb`, and every `expected.json` must fit that schema.
495
- 1. **Expand the lineup**: every model under `vendors:` in `evals/.agent-eval.config.yml`, times each `effort`/`temperature` it lists, becomes one contender. Each contender runs every case.
495
+ 1. **Expand the lineup**: every model under `vendors:` in `evals/.llm-evals.config.yml`, times each `effort`/`temperature` it lists, becomes one contender. Each contender runs every case.
496
496
  1. **Call the model**: one OpenRouter chat completion per case, built from this gem's production prompt and the step's schema.
497
497
  1. **Score the raw reply** field by field: `correct` 1.0, `missed` 0.2, `hallucinated` 0.15, `incorrect` 0.1, `invalid` 0. A case's score is the mean over its fields. The rules are in [`evals/README.md`](evals/README.md#scoring).
498
- 1. **Report**: a leaderboard on screen and a JSON log under `log/agent-evals/`. If `BRAINTRUST_API_KEY` or `LOGFIRE_TOKEN` is set, each case is also sent to that one tracker; `--local` turns that off.
498
+ 1. **Report**: a leaderboard on screen and a JSON log under `log/llm-evals/`. If `BRAINTRUST_API_KEY` or `LOGFIRE_TOKEN` is set, each case is also sent to that one tracker; `--local` turns that off.
499
499
 
500
500
  ```bash
501
501
  just evals -n # the whole matrix, calling nothing
@@ -503,8 +503,8 @@ just evals # every configured model on every dataset
503
503
  just evals --local -d 05-managed-it-services # one dataset, no remote tracker
504
504
  just evals-verify # step 1 only
505
505
 
506
- bundle exec agent-evals cases # the 20 cases
507
- bundle exec agent-evals run -m openai/gpt-4o-mini # one model, per-case breakdown
506
+ bundle exec evals cases # the 20 cases
507
+ bundle exec evals run -m openai/gpt-4o-mini # one model, per-case breakdown
508
508
  ```
509
509
 
510
510
  `just evals` needs `OPENROUTER_API_KEY` and the `:evals` bundle group. It is a paid run: 14 contenders × 20 cases = 280 calls.
@@ -9,9 +9,9 @@ module Inquirex
9
9
  # canonicalized by the gem. It never scores; {Scorer} does that.
10
10
  #
11
11
  # A reply that is not a JSON object is the model's failure and comes
12
- # back as a {Agent::Evals::Reply} with `error` set. A transport failure
12
+ # back as a {LLM::Evals::Reply} with `error` set. A transport failure
13
13
  # raises, and the run counts it as an error rather than a wrong answer.
14
- class Adapter < Agent::Evals::Adapter
14
+ class Adapter < ::LLM::Evals::Adapter
15
15
  # Recorded with every experiment: which prompt the models saw.
16
16
  PROMPT = "Inquirex::LLM::OpenAIAdapter"
17
17
 
@@ -24,7 +24,7 @@ module Inquirex
24
24
  end
25
25
 
26
26
  # @return [String]
27
- def root = @root || Agent::Evals.configuration.root
27
+ def root = @root || ::LLM::Evals.configuration.root
28
28
 
29
29
  # Loads the dataset's flow and schema once, before its cases run in threads.
30
30
  #
@@ -36,14 +36,14 @@ module Inquirex
36
36
  set.schema
37
37
  end
38
38
 
39
- # @param kase [Agent::Evals::Case]
40
- # @param model [Agent::Evals::Model]
41
- # @return [Agent::Evals::Reply]
39
+ # @param kase [LLM::Evals::Case]
40
+ # @param model [LLM::Evals::Model]
41
+ # @return [LLM::Evals::Reply]
42
42
  def call(kase, model:)
43
43
  set = dataset(kase.dataset)
44
44
  adapter = ModelAdapter.new(model)
45
45
  extraction = adapter.call(set.node, { set.source_step => kase.input })
46
- Agent::Evals::Reply.new(
46
+ ::LLM::Evals::Reply.new(
47
47
  output: extraction.output.transform_keys(&:to_s),
48
48
  raw: extraction.raw.transform_keys(&:to_s),
49
49
  usage: adapter.completion.usage,
@@ -52,13 +52,13 @@ module Inquirex
52
52
  rescue Errors::AdapterError => e
53
53
  raise if adapter.completion.nil? # nothing was asked: not the model's doing
54
54
 
55
- Agent::Evals::Reply.new(error: e.message.lines.first.strip,
55
+ ::LLM::Evals::Reply.new(error: e.message.lines.first.strip,
56
56
  raw: adapter.completion.text,
57
57
  usage: adapter.completion.usage,
58
58
  metadata: { prompt: adapter.prompt })
59
59
  end
60
60
 
61
- # @param kase [Agent::Evals::Case]
61
+ # @param kase [LLM::Evals::Case]
62
62
  # @return [Hash] the schema the scorer grades against
63
63
  def context(kase) = { schema: dataset(kase.dataset).schema }
64
64
 
@@ -6,7 +6,7 @@ module Inquirex
6
6
  # What one dataset folder shares across its cases: the flow in dsl.rb,
7
7
  # whose single `extract` step supplies the prompt and the schema, and
8
8
  # schema.rb, that schema copied out verbatim so a change to either is
9
- # caught by `agent-evals verify` instead of silently moving the scores.
9
+ # caught by `evals verify` instead of silently moving the scores.
10
10
  #
11
11
  # evals/datasets/05-managed-it-services/
12
12
  # dsl.rb the flow, loaded with unsafe: true (a file we author)
@@ -61,7 +61,7 @@ module Inquirex
61
61
  # @return [Symbol]
62
62
  def source_step = node.from_steps.first
63
63
 
64
- # Offline checks of the shared files, for `agent-evals verify`.
64
+ # Offline checks of the shared files, for `evals verify`.
65
65
  #
66
66
  # @return [Array<String>] problems; empty when the dataset is sound
67
67
  def problems
@@ -4,7 +4,7 @@ module Inquirex
4
4
  module LLM
5
5
  module Evals
6
6
  # The gem's {OpenAIAdapter} with its transport swapped for an
7
- # agent-evals {Agent::Evals::Model}. Everything the model sees and
7
+ # llm-evals {LLM::Evals::Model}. Everything the model sees and
8
8
  # everything done to its answer is production's: the system prompt,
9
9
  # the schema contract, the user prompt built from the step's `from`
10
10
  # answers, fence stripping, JSON parsing and the canonicalization of
@@ -18,13 +18,13 @@ module Inquirex
18
18
  # OpenAIAdapter insists on a key; the eval model holds the real one.
19
19
  NO_KEY = "(unused: the eval model carries its own key)"
20
20
 
21
- # @return [Agent::Evals::Completion, nil] what the model answered, once asked
21
+ # @return [LLM::Evals::Completion, nil] what the model answered, once asked
22
22
  attr_reader :completion
23
23
 
24
24
  # @return [Hash{Symbol => String}, nil] the system and user prompts, once sent
25
25
  attr_reader :prompt
26
26
 
27
- # @param model [Agent::Evals::Model]
27
+ # @param model [LLM::Evals::Model]
28
28
  def initialize(model)
29
29
  super(api_key: NO_KEY, model: model.id)
30
30
  @model = model
@@ -13,36 +13,36 @@ module Inquirex
13
13
  # share the model answered anyway; it is nil when the truth has no
14
14
  # null, so a dataset of complete texts neither earns nor loses by it.
15
15
  # `invalid_rate` is the share of fields the schema would reject.
16
- class Scorer < Agent::Evals::Scorer
16
+ class Scorer < ::LLM::Evals::Scorer
17
17
  METRICS = [
18
- Agent::Evals::Metric.new(name: "score",
18
+ ::LLM::Evals::Metric.new(name: "score",
19
19
  primary: true,
20
20
  description: "Mean field value: correct 1.0, missed 0.2, hallucinated 0.15, incorrect 0.1, invalid 0; " \
21
21
  "a multi_enum is the mean of its elements."),
22
- Agent::Evals::Metric.new(name: "exact", description: "1 when every field is correct."),
23
- Agent::Evals::Metric.new(name: "hallucinated_rate",
22
+ ::LLM::Evals::Metric.new(name: "exact", description: "1 when every field is correct."),
23
+ ::LLM::Evals::Metric.new(name: "hallucinated_rate",
24
24
  description: "Of the fields the text never answers (truth null), the share the model answered anyway; " \
25
25
  "nothing recorded when the truth has no null."),
26
- Agent::Evals::Metric.new(name: "invalid_rate",
26
+ ::LLM::Evals::Metric.new(name: "invalid_rate",
27
27
  description: "Share of fields whose value the schema does not allow: off the list, wrong type.")
28
28
  ].freeze
29
29
 
30
- # How the gem's field types read to {Agent::Evals::SchemaValidator}.
30
+ # How the gem's field types read to {LLM::Evals::SchemaValidator}.
31
31
  VALIDATOR_TYPES = { enum: :enum, multi_enum: :multi_enum, integer: :integer, boolean: :boolean,
32
32
  decimal: :number, currency: :number }.freeze
33
33
 
34
- # @return [Array<Agent::Evals::Metric>]
34
+ # @return [Array<LLM::Evals::Metric>]
35
35
  def metrics = METRICS
36
36
 
37
- # @param kase [Agent::Evals::Case]
38
- # @param reply [Agent::Evals::Reply]
37
+ # @param kase [LLM::Evals::Case]
38
+ # @param reply [LLM::Evals::Reply]
39
39
  # @param context [Hash] `:schema`, the {Inquirex::LLM::Schema} the dataset extracts
40
- # @return [Agent::Evals::Score]
40
+ # @return [LLM::Evals::Score]
41
41
  def call(kase, reply, context:)
42
42
  schema = context.fetch(:schema)
43
43
  fields = field_scores(schema, kase.expected, reply.raw)
44
44
  open = fields.select { kase.expected[it.field].nil? }
45
- Agent::Evals::Score.new(
45
+ ::LLM::Evals::Score.new(
46
46
  value: mean(fields.map(&:value)),
47
47
  metrics: { "score" => mean(fields.map(&:value)),
48
48
  "exact" => fields.all?(&:correct?) ? 1.0 : 0.0,
@@ -74,7 +74,7 @@ module Inquirex
74
74
  # Checks expected.json against the schema, every field nullable: a
75
75
  # null is how the truth says the text never answers it.
76
76
  #
77
- # @param kase [Agent::Evals::Case]
77
+ # @param kase [LLM::Evals::Case]
78
78
  # @param context [Hash]
79
79
  # @return [Array<String>] unknown keys, missing keys, off-list values, wrong types
80
80
  def verify(kase, context:)
@@ -82,10 +82,10 @@ module Inquirex
82
82
  fields = schema.field_specs.to_h do |name, spec|
83
83
  [name.to_s, { type: VALIDATOR_TYPES.fetch(spec.type, :string), enum: spec.values, nullable: true }]
84
84
  end
85
- Agent::Evals::SchemaValidator.new(fields).problems(kase.expected)
85
+ ::LLM::Evals::SchemaValidator.new(fields).problems(kase.expected)
86
86
  end
87
87
 
88
- # @param kase [Agent::Evals::Case]
88
+ # @param kase [LLM::Evals::Case]
89
89
  # @return [String] "answered 7/10"
90
90
  def summary(kase)
91
91
  return nil unless kase.expected.is_a?(Hash)
@@ -1,15 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # The extraction eval suite's Ruby: an agent-evals Adapter that runs one
3
+ # The extraction eval suite's Ruby: an llm-evals Adapter that runs one
4
4
  # case through the gem's production prompts, and a Scorer that grades the
5
5
  # reply field by field. Loaded by `require "inquirex/llm/evals"` from
6
- # evals/.agent-eval.config.yml, never by `require "inquirex-llm"`, because
7
- # agent-evals is the suite's dependency and not the gem's.
6
+ # evals/.llm-evals.config.yml, never by `require "inquirex-llm"`, because
7
+ # llm-evals is the suite's dependency and not the gem's.
8
8
  begin
9
- require "agent/evals"
9
+ require "llm/evals"
10
10
  rescue LoadError => e
11
11
  raise LoadError,
12
- "inquirex/llm/evals needs the agent-evals gem, which is not a dependency of inquirex-llm: " \
12
+ "inquirex/llm/evals needs the llm-evals gem, which is not a dependency of inquirex-llm: " \
13
13
  "add it to the :evals group of your Gemfile (#{e.message})"
14
14
  end
15
15
 
@@ -22,7 +22,7 @@ require_relative "evals/scorer"
22
22
 
23
23
  module Inquirex
24
24
  module LLM
25
- # The pieces agent-evals needs from this gem. {Adapter} owns everything
25
+ # The pieces llm-evals needs from this gem. {Adapter} owns everything
26
26
  # the model sees and everything done to its answer; {Scorer} owns what a
27
27
  # right answer is. Neither knows the other, and only the config file
28
28
  # names them both.
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Inquirex
4
4
  module LLM
5
- VERSION = "0.12.1"
5
+ VERSION = "0.12.2"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: inquirex-llm
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.12.1
4
+ version: 0.12.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Konstantin Gredeskoul