ruby_llm-contract 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.
Files changed (89) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/.rubocop.yml +55 -0
  4. data/CHANGELOG.md +76 -0
  5. data/Gemfile +11 -0
  6. data/Gemfile.lock +176 -0
  7. data/LICENSE +21 -0
  8. data/README.md +154 -0
  9. data/Rakefile +8 -0
  10. data/examples/00_basics.rb +500 -0
  11. data/examples/01_classify_threads.rb +220 -0
  12. data/examples/02_generate_comment.rb +203 -0
  13. data/examples/03_target_audience.rb +201 -0
  14. data/examples/04_real_llm.rb +410 -0
  15. data/examples/05_output_schema.rb +258 -0
  16. data/examples/07_keyword_extraction.rb +239 -0
  17. data/examples/08_translation.rb +353 -0
  18. data/examples/09_eval_dataset.rb +287 -0
  19. data/examples/10_reddit_full_showcase.rb +363 -0
  20. data/examples/README.md +140 -0
  21. data/lib/ruby_llm/contract/adapters/base.rb +13 -0
  22. data/lib/ruby_llm/contract/adapters/response.rb +17 -0
  23. data/lib/ruby_llm/contract/adapters/ruby_llm.rb +94 -0
  24. data/lib/ruby_llm/contract/adapters/test.rb +44 -0
  25. data/lib/ruby_llm/contract/adapters.rb +6 -0
  26. data/lib/ruby_llm/contract/concerns/deep_symbolize.rb +17 -0
  27. data/lib/ruby_llm/contract/concerns/eval_host.rb +109 -0
  28. data/lib/ruby_llm/contract/concerns/trace_equality.rb +15 -0
  29. data/lib/ruby_llm/contract/concerns/usage_aggregator.rb +43 -0
  30. data/lib/ruby_llm/contract/configuration.rb +21 -0
  31. data/lib/ruby_llm/contract/contract/definition.rb +39 -0
  32. data/lib/ruby_llm/contract/contract/invariant.rb +23 -0
  33. data/lib/ruby_llm/contract/contract/parser.rb +143 -0
  34. data/lib/ruby_llm/contract/contract/schema_validator.rb +239 -0
  35. data/lib/ruby_llm/contract/contract/validator.rb +104 -0
  36. data/lib/ruby_llm/contract/contract.rb +7 -0
  37. data/lib/ruby_llm/contract/cost_calculator.rb +38 -0
  38. data/lib/ruby_llm/contract/dsl.rb +13 -0
  39. data/lib/ruby_llm/contract/errors.rb +19 -0
  40. data/lib/ruby_llm/contract/eval/case_result.rb +76 -0
  41. data/lib/ruby_llm/contract/eval/contract_detail_builder.rb +47 -0
  42. data/lib/ruby_llm/contract/eval/dataset.rb +53 -0
  43. data/lib/ruby_llm/contract/eval/eval_definition.rb +112 -0
  44. data/lib/ruby_llm/contract/eval/evaluation_result.rb +27 -0
  45. data/lib/ruby_llm/contract/eval/evaluator/exact.rb +20 -0
  46. data/lib/ruby_llm/contract/eval/evaluator/json_includes.rb +58 -0
  47. data/lib/ruby_llm/contract/eval/evaluator/proc_evaluator.rb +40 -0
  48. data/lib/ruby_llm/contract/eval/evaluator/regex.rb +27 -0
  49. data/lib/ruby_llm/contract/eval/model_comparison.rb +80 -0
  50. data/lib/ruby_llm/contract/eval/pipeline_result_adapter.rb +15 -0
  51. data/lib/ruby_llm/contract/eval/report.rb +115 -0
  52. data/lib/ruby_llm/contract/eval/runner.rb +162 -0
  53. data/lib/ruby_llm/contract/eval/trait_evaluator.rb +75 -0
  54. data/lib/ruby_llm/contract/eval.rb +16 -0
  55. data/lib/ruby_llm/contract/pipeline/base.rb +62 -0
  56. data/lib/ruby_llm/contract/pipeline/result.rb +131 -0
  57. data/lib/ruby_llm/contract/pipeline/runner.rb +139 -0
  58. data/lib/ruby_llm/contract/pipeline/trace.rb +72 -0
  59. data/lib/ruby_llm/contract/pipeline.rb +6 -0
  60. data/lib/ruby_llm/contract/prompt/ast.rb +38 -0
  61. data/lib/ruby_llm/contract/prompt/builder.rb +47 -0
  62. data/lib/ruby_llm/contract/prompt/node.rb +25 -0
  63. data/lib/ruby_llm/contract/prompt/nodes/example_node.rb +27 -0
  64. data/lib/ruby_llm/contract/prompt/nodes/rule_node.rb +15 -0
  65. data/lib/ruby_llm/contract/prompt/nodes/section_node.rb +26 -0
  66. data/lib/ruby_llm/contract/prompt/nodes/system_node.rb +15 -0
  67. data/lib/ruby_llm/contract/prompt/nodes/user_node.rb +15 -0
  68. data/lib/ruby_llm/contract/prompt/nodes.rb +7 -0
  69. data/lib/ruby_llm/contract/prompt/renderer.rb +76 -0
  70. data/lib/ruby_llm/contract/railtie.rb +20 -0
  71. data/lib/ruby_llm/contract/rake_task.rb +78 -0
  72. data/lib/ruby_llm/contract/rspec/pass_eval.rb +96 -0
  73. data/lib/ruby_llm/contract/rspec/satisfy_contract.rb +31 -0
  74. data/lib/ruby_llm/contract/rspec.rb +6 -0
  75. data/lib/ruby_llm/contract/step/base.rb +138 -0
  76. data/lib/ruby_llm/contract/step/dsl.rb +144 -0
  77. data/lib/ruby_llm/contract/step/limit_checker.rb +64 -0
  78. data/lib/ruby_llm/contract/step/result.rb +38 -0
  79. data/lib/ruby_llm/contract/step/retry_executor.rb +90 -0
  80. data/lib/ruby_llm/contract/step/retry_policy.rb +76 -0
  81. data/lib/ruby_llm/contract/step/runner.rb +126 -0
  82. data/lib/ruby_llm/contract/step/trace.rb +70 -0
  83. data/lib/ruby_llm/contract/step.rb +10 -0
  84. data/lib/ruby_llm/contract/token_estimator.rb +19 -0
  85. data/lib/ruby_llm/contract/types.rb +11 -0
  86. data/lib/ruby_llm/contract/version.rb +7 -0
  87. data/lib/ruby_llm/contract.rb +108 -0
  88. data/ruby_llm-contract.gemspec +33 -0
  89. metadata +172 -0
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "contract/version"
4
+ require_relative "contract/errors"
5
+ require_relative "contract/types"
6
+
7
+ module RubyLLM
8
+ module Contract
9
+ class << self
10
+ def configuration
11
+ @configuration ||= Configuration.new
12
+ end
13
+
14
+ def configure
15
+ yield(configuration)
16
+ auto_create_adapter! if configuration.default_adapter.nil?
17
+ end
18
+
19
+ def reset_configuration!
20
+ @configuration = Configuration.new
21
+ end
22
+
23
+ # --- Eval host registry ---
24
+
25
+ def register_eval_host(klass)
26
+ eval_hosts << klass unless eval_hosts.include?(klass)
27
+ end
28
+
29
+ def eval_hosts
30
+ @eval_hosts ||= []
31
+ end
32
+
33
+ def run_all_evals(context: {})
34
+ live_eval_hosts.to_h do |host|
35
+ [host, host.run_eval(context: context)]
36
+ end
37
+ end
38
+
39
+ def reset_eval_hosts!
40
+ @eval_hosts = []
41
+ end
42
+
43
+ def load_evals!(dir = nil)
44
+ dirs = if dir
45
+ [dir]
46
+ elsif defined?(::Rails)
47
+ %w[app/steps/eval app/contracts/eval].filter_map do |path|
48
+ full = ::Rails.root.join(path)
49
+ full.to_s if full.exist?
50
+ end
51
+ else
52
+ []
53
+ end
54
+
55
+ return if dirs.empty?
56
+
57
+ # Clear existing eval definitions before reload to prevent stale state.
58
+ # Thread-local flag suppresses the "redefining" warning during reload.
59
+ Thread.current[:ruby_llm_contract_reloading] = true
60
+ eval_hosts.each do |host|
61
+ host.clear_eval_definitions! if host.respond_to?(:clear_eval_definitions!)
62
+ end
63
+
64
+ dirs.each do |d|
65
+ Dir[File.join(d, "**", "*_eval.rb")].each { |f| load f }
66
+ end
67
+ ensure
68
+ Thread.current[:ruby_llm_contract_reloading] = false
69
+ end
70
+
71
+ private
72
+
73
+ # Filter out GC'd anonymous classes and classes that no longer have evals
74
+ def live_eval_hosts
75
+ eval_hosts.select do |host|
76
+ host.respond_to?(:eval_defined?) && host.eval_defined?
77
+ end
78
+ end
79
+
80
+ def auto_create_adapter!
81
+ require "ruby_llm"
82
+ configuration.default_adapter = Adapters::RubyLLM.new
83
+ rescue LoadError
84
+ nil
85
+ end
86
+ end
87
+ end
88
+ end
89
+
90
+ require_relative "contract/concerns/deep_symbolize"
91
+ require_relative "contract/concerns/eval_host"
92
+ require_relative "contract/concerns/trace_equality"
93
+ require_relative "contract/concerns/usage_aggregator"
94
+ require_relative "contract/configuration"
95
+ require_relative "contract/prompt/node"
96
+ require_relative "contract/prompt/nodes"
97
+ require_relative "contract/prompt/ast"
98
+ require_relative "contract/prompt/builder"
99
+ require_relative "contract/prompt/renderer"
100
+ require_relative "contract/contract"
101
+ require_relative "contract/cost_calculator"
102
+ require_relative "contract/token_estimator"
103
+ require_relative "contract/adapters"
104
+ require_relative "contract/step"
105
+ require_relative "contract/pipeline"
106
+ require_relative "contract/eval"
107
+ require_relative "contract/dsl"
108
+ require_relative "contract/railtie" if defined?(Rails::Railtie)
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/ruby_llm/contract/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "ruby_llm-contract"
7
+ spec.version = RubyLLM::Contract::VERSION
8
+ spec.authors = ["Justyna"]
9
+
10
+ spec.summary = "Contract-first LLM step execution for RubyLLM"
11
+ spec.description = "Turn RubyLLM calls into contracted, validated, testable steps with schema enforcement, " \
12
+ "retry with model escalation, and eval."
13
+ spec.homepage = "https://github.com/justi/ruby_llm-contract"
14
+ spec.license = "MIT"
15
+ spec.required_ruby_version = ">= 3.2.0"
16
+
17
+ spec.metadata["homepage_uri"] = spec.homepage
18
+ spec.metadata["source_code_uri"] = spec.homepage
19
+ spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/main/CHANGELOG.md"
20
+ spec.metadata["rubygems_mfa_required"] = "true"
21
+
22
+ spec.files = Dir.chdir(__dir__) do
23
+ `git ls-files -z`.split("\x0").reject do |f|
24
+ (File.expand_path(f) == __FILE__) ||
25
+ f.start_with?("spec/", "docs/", "doc/", ".ai/", ".claude/", ".git")
26
+ end
27
+ end
28
+ spec.require_paths = ["lib"]
29
+
30
+ spec.add_dependency "dry-types", "~> 1.7"
31
+ spec.add_dependency "ruby_llm", "~> 1.0"
32
+ spec.add_dependency "ruby_llm-schema", "~> 0.3"
33
+ end
metadata ADDED
@@ -0,0 +1,172 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ruby_llm-contract
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - Justyna
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: dry-types
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '1.7'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '1.7'
26
+ - !ruby/object:Gem::Dependency
27
+ name: ruby_llm
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '1.0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '1.0'
40
+ - !ruby/object:Gem::Dependency
41
+ name: ruby_llm-schema
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '0.3'
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '0.3'
54
+ description: Turn RubyLLM calls into contracted, validated, testable steps with schema
55
+ enforcement, retry with model escalation, and eval.
56
+ executables: []
57
+ extensions: []
58
+ extra_rdoc_files: []
59
+ files:
60
+ - ".rspec"
61
+ - ".rubocop.yml"
62
+ - CHANGELOG.md
63
+ - Gemfile
64
+ - Gemfile.lock
65
+ - LICENSE
66
+ - README.md
67
+ - Rakefile
68
+ - examples/00_basics.rb
69
+ - examples/01_classify_threads.rb
70
+ - examples/02_generate_comment.rb
71
+ - examples/03_target_audience.rb
72
+ - examples/04_real_llm.rb
73
+ - examples/05_output_schema.rb
74
+ - examples/07_keyword_extraction.rb
75
+ - examples/08_translation.rb
76
+ - examples/09_eval_dataset.rb
77
+ - examples/10_reddit_full_showcase.rb
78
+ - examples/README.md
79
+ - lib/ruby_llm/contract.rb
80
+ - lib/ruby_llm/contract/adapters.rb
81
+ - lib/ruby_llm/contract/adapters/base.rb
82
+ - lib/ruby_llm/contract/adapters/response.rb
83
+ - lib/ruby_llm/contract/adapters/ruby_llm.rb
84
+ - lib/ruby_llm/contract/adapters/test.rb
85
+ - lib/ruby_llm/contract/concerns/deep_symbolize.rb
86
+ - lib/ruby_llm/contract/concerns/eval_host.rb
87
+ - lib/ruby_llm/contract/concerns/trace_equality.rb
88
+ - lib/ruby_llm/contract/concerns/usage_aggregator.rb
89
+ - lib/ruby_llm/contract/configuration.rb
90
+ - lib/ruby_llm/contract/contract.rb
91
+ - lib/ruby_llm/contract/contract/definition.rb
92
+ - lib/ruby_llm/contract/contract/invariant.rb
93
+ - lib/ruby_llm/contract/contract/parser.rb
94
+ - lib/ruby_llm/contract/contract/schema_validator.rb
95
+ - lib/ruby_llm/contract/contract/validator.rb
96
+ - lib/ruby_llm/contract/cost_calculator.rb
97
+ - lib/ruby_llm/contract/dsl.rb
98
+ - lib/ruby_llm/contract/errors.rb
99
+ - lib/ruby_llm/contract/eval.rb
100
+ - lib/ruby_llm/contract/eval/case_result.rb
101
+ - lib/ruby_llm/contract/eval/contract_detail_builder.rb
102
+ - lib/ruby_llm/contract/eval/dataset.rb
103
+ - lib/ruby_llm/contract/eval/eval_definition.rb
104
+ - lib/ruby_llm/contract/eval/evaluation_result.rb
105
+ - lib/ruby_llm/contract/eval/evaluator/exact.rb
106
+ - lib/ruby_llm/contract/eval/evaluator/json_includes.rb
107
+ - lib/ruby_llm/contract/eval/evaluator/proc_evaluator.rb
108
+ - lib/ruby_llm/contract/eval/evaluator/regex.rb
109
+ - lib/ruby_llm/contract/eval/model_comparison.rb
110
+ - lib/ruby_llm/contract/eval/pipeline_result_adapter.rb
111
+ - lib/ruby_llm/contract/eval/report.rb
112
+ - lib/ruby_llm/contract/eval/runner.rb
113
+ - lib/ruby_llm/contract/eval/trait_evaluator.rb
114
+ - lib/ruby_llm/contract/pipeline.rb
115
+ - lib/ruby_llm/contract/pipeline/base.rb
116
+ - lib/ruby_llm/contract/pipeline/result.rb
117
+ - lib/ruby_llm/contract/pipeline/runner.rb
118
+ - lib/ruby_llm/contract/pipeline/trace.rb
119
+ - lib/ruby_llm/contract/prompt/ast.rb
120
+ - lib/ruby_llm/contract/prompt/builder.rb
121
+ - lib/ruby_llm/contract/prompt/node.rb
122
+ - lib/ruby_llm/contract/prompt/nodes.rb
123
+ - lib/ruby_llm/contract/prompt/nodes/example_node.rb
124
+ - lib/ruby_llm/contract/prompt/nodes/rule_node.rb
125
+ - lib/ruby_llm/contract/prompt/nodes/section_node.rb
126
+ - lib/ruby_llm/contract/prompt/nodes/system_node.rb
127
+ - lib/ruby_llm/contract/prompt/nodes/user_node.rb
128
+ - lib/ruby_llm/contract/prompt/renderer.rb
129
+ - lib/ruby_llm/contract/railtie.rb
130
+ - lib/ruby_llm/contract/rake_task.rb
131
+ - lib/ruby_llm/contract/rspec.rb
132
+ - lib/ruby_llm/contract/rspec/pass_eval.rb
133
+ - lib/ruby_llm/contract/rspec/satisfy_contract.rb
134
+ - lib/ruby_llm/contract/step.rb
135
+ - lib/ruby_llm/contract/step/base.rb
136
+ - lib/ruby_llm/contract/step/dsl.rb
137
+ - lib/ruby_llm/contract/step/limit_checker.rb
138
+ - lib/ruby_llm/contract/step/result.rb
139
+ - lib/ruby_llm/contract/step/retry_executor.rb
140
+ - lib/ruby_llm/contract/step/retry_policy.rb
141
+ - lib/ruby_llm/contract/step/runner.rb
142
+ - lib/ruby_llm/contract/step/trace.rb
143
+ - lib/ruby_llm/contract/token_estimator.rb
144
+ - lib/ruby_llm/contract/types.rb
145
+ - lib/ruby_llm/contract/version.rb
146
+ - ruby_llm-contract.gemspec
147
+ homepage: https://github.com/justi/ruby_llm-contract
148
+ licenses:
149
+ - MIT
150
+ metadata:
151
+ homepage_uri: https://github.com/justi/ruby_llm-contract
152
+ source_code_uri: https://github.com/justi/ruby_llm-contract
153
+ changelog_uri: https://github.com/justi/ruby_llm-contract/blob/main/CHANGELOG.md
154
+ rubygems_mfa_required: 'true'
155
+ rdoc_options: []
156
+ require_paths:
157
+ - lib
158
+ required_ruby_version: !ruby/object:Gem::Requirement
159
+ requirements:
160
+ - - ">="
161
+ - !ruby/object:Gem::Version
162
+ version: 3.2.0
163
+ required_rubygems_version: !ruby/object:Gem::Requirement
164
+ requirements:
165
+ - - ">="
166
+ - !ruby/object:Gem::Version
167
+ version: '0'
168
+ requirements: []
169
+ rubygems_version: 3.6.7
170
+ specification_version: 4
171
+ summary: Contract-first LLM step execution for RubyLLM
172
+ test_files: []