squishling 0.3.0 → 0.5.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 +133 -1
- data/README.md +63 -15
- data/docs/adapters.md +189 -0
- data/docs/configuration.md +89 -24
- data/docs/coolhand.md +66 -0
- data/docs/failures.md +129 -20
- data/docs/harnesses.md +186 -48
- data/docs/measuring-tokens.md +36 -42
- data/docs/observers.md +80 -0
- data/docs/routing.md +7 -2
- data/docs/schemas.md +33 -6
- data/docs/testing.md +183 -0
- data/docs/verifying.md +84 -0
- data/lib/squishling/adapter/error_body.rb +47 -0
- data/lib/squishling/adapter.rb +275 -0
- data/lib/squishling/adapters/openai_compatible.rb +304 -0
- data/lib/squishling/adapters/openai_options.rb +72 -0
- data/lib/squishling/adapters/ruby_llm.rb +193 -0
- data/lib/squishling/budget.rb +71 -0
- data/lib/squishling/class_methods.rb +43 -7
- data/lib/squishling/class_registry.rb +63 -0
- data/lib/squishling/configuration.rb +72 -8
- data/lib/squishling/definition.rb +95 -17
- data/lib/squishling/errors.rb +97 -12
- data/lib/squishling/harness/base.rb +136 -0
- data/lib/squishling/harness/escalation.rb +22 -0
- data/lib/squishling/harness/judged_squishsum.rb +83 -0
- data/lib/squishling/harness/option_types.rb +94 -0
- data/lib/squishling/harness/registry.rb +41 -0
- data/lib/squishling/harness/squishsum.rb +22 -0
- data/lib/squishling/harness.rb +25 -99
- data/lib/squishling/invoker.rb +28 -233
- data/lib/squishling/judge.rb +78 -21
- data/lib/squishling/llm_client.rb +73 -109
- data/lib/squishling/model_path.rb +117 -27
- data/lib/squishling/named_registry.rb +64 -0
- data/lib/squishling/observer.rb +109 -0
- data/lib/squishling/observers/coolhand.rb +86 -0
- data/lib/squishling/output_check.rb +13 -9
- data/lib/squishling/params.rb +15 -27
- data/lib/squishling/railtie.rb +10 -0
- data/lib/squishling/result.rb +37 -16
- data/lib/squishling/rotation.rb +31 -0
- data/lib/squishling/rspec.rb +18 -0
- data/lib/squishling/run/messages.rb +79 -0
- data/lib/squishling/run/rejections.rb +43 -0
- data/lib/squishling/run.rb +320 -0
- data/lib/squishling/schema.rb +10 -9
- data/lib/squishling/squawk.rb +33 -3
- data/lib/squishling/tasks/squishling.rake +18 -0
- data/lib/squishling/testing/adapter.rb +194 -0
- data/lib/squishling/testing/checks.rb +39 -0
- data/lib/squishling/testing/direct.rb +41 -0
- data/lib/squishling/testing/errors.rb +18 -0
- data/lib/squishling/testing/fixtures.rb +206 -0
- data/lib/squishling/testing/helpers.rb +71 -0
- data/lib/squishling/testing/minitest.rb +32 -0
- data/lib/squishling/testing/observer.rb +31 -0
- data/lib/squishling/testing/reply.rb +60 -0
- data/lib/squishling/testing/request.rb +75 -0
- data/lib/squishling/testing/rspec.rb +30 -0
- data/lib/squishling/testing/script.rb +189 -0
- data/lib/squishling/testing/state.rb +73 -0
- data/lib/squishling/testing/stubs.rb +112 -0
- data/lib/squishling/testing.rb +126 -0
- data/lib/squishling/verification.rb +39 -0
- data/lib/squishling/verifier.rb +179 -0
- data/lib/squishling/version.rb +1 -1
- data/lib/squishling.rb +89 -4
- metadata +53 -21
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Squishling
|
|
4
|
+
# Checks squished classes without making a request: what can only otherwise fail on a method's first call (its
|
|
5
|
+
# output schema and typed result, its callables' keywords) and the settings the layers resolve to. Collects every
|
|
6
|
+
# problem rather than stopping at the first. See Squishling.verify.
|
|
7
|
+
class Verifier
|
|
8
|
+
# Checks one squished method's Definition. Each check returns messages, or raises what the call would.
|
|
9
|
+
class MethodCheck
|
|
10
|
+
CHECKS = %i[schema declarations harness callables].freeze
|
|
11
|
+
|
|
12
|
+
def initialize(definition)
|
|
13
|
+
@definition = definition
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def problems
|
|
17
|
+
CHECKS.flat_map { |check| run(check) }
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def run(check)
|
|
23
|
+
Array(send(check)).map { |message| problem(message) }
|
|
24
|
+
rescue ConfigurationError, ArgumentError => e
|
|
25
|
+
[problem(e.message)]
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def problem(message)
|
|
29
|
+
Verification::Problem.new(label: @definition.label, message:)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# The output schema, its typed result (the Data class a call returns), and the JSON Schema itself.
|
|
33
|
+
def schema
|
|
34
|
+
schema = @definition.schema
|
|
35
|
+
messages = []
|
|
36
|
+
messages << "has no output_schema" unless schema
|
|
37
|
+
messages << "has no purpose" unless @definition.purpose_declared?
|
|
38
|
+
return messages unless schema
|
|
39
|
+
|
|
40
|
+
schema.result_class
|
|
41
|
+
messages + JSONSchemer.schema(schema.json_schema).validate_schema.map { |error| error["error"] }
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# The escalation, generation params, and squawk this method resolves to across method, class, and config.
|
|
45
|
+
def declarations
|
|
46
|
+
steps = @definition.declared_path.first.reject { |step| step[:model].nil? }
|
|
47
|
+
ModelPath.from_escalation(steps, "#{@definition.label} escalation") if steps.any?
|
|
48
|
+
Params.normalize(@definition.params, "#{@definition.label} params")
|
|
49
|
+
Squawk.validate(@definition.squawk, @definition.label)
|
|
50
|
+
[]
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def harness
|
|
54
|
+
harness = @definition.harness
|
|
55
|
+
messages = []
|
|
56
|
+
unless Harness::Registry.fetch(harness.type).equal?(harness.klass)
|
|
57
|
+
messages << "harness #{harness.type.inspect} was unregistered or replaced after it was declared"
|
|
58
|
+
end
|
|
59
|
+
messages << "the judge needs an adapter that supports judgments" if unsupported_judgment?(harness)
|
|
60
|
+
messages
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def unsupported_judgment?(harness)
|
|
64
|
+
judge = harness.options[:judge]
|
|
65
|
+
judge.is_a?(Hash) && judge[:type] == :judgment && !Squishling.config.resolved_adapter.judgments?
|
|
66
|
+
rescue ConfigurationError
|
|
67
|
+
false # no usable adapter: reported once, as the adapter's own problem
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Callables the method's inputs are passed to as keywords: include_when: of each step, and squish_when.
|
|
71
|
+
def callables
|
|
72
|
+
names, open = @definition.input_names
|
|
73
|
+
gates = @definition.declared_path.first.filter_map do |step|
|
|
74
|
+
[step[:include_when], "escalation step #{step[:model].inspect} include_when:"] if step[:include_when]
|
|
75
|
+
end
|
|
76
|
+
gates << [@definition.predicate, "squish_when"] if @definition.predicate
|
|
77
|
+
gates.flat_map { |callable, what| KeywordCheck.messages(callable, names, open).map { "#{what} #{_1}" } }
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Whether a Proc called as instance_exec(**inputs, &proc) accepts the inputs.
|
|
82
|
+
module KeywordCheck
|
|
83
|
+
module_function
|
|
84
|
+
|
|
85
|
+
# Messages for how the proc can't be called with the inputs. open: any keyword may be an input (the method
|
|
86
|
+
# takes **), so none can be known.
|
|
87
|
+
def messages(callable, inputs, open)
|
|
88
|
+
return ["must be a Proc, got #{callable.inspect}"] unless callable.is_a?(Proc)
|
|
89
|
+
return [] if open
|
|
90
|
+
|
|
91
|
+
parameters = callable.parameters
|
|
92
|
+
[missing_keywords(parameters, inputs), unaccepted_keywords(callable, parameters, inputs),
|
|
93
|
+
missing_positionals(callable, parameters, inputs)].compact
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def missing_keywords(parameters, inputs)
|
|
97
|
+
missing = parameters.filter_map { |type, name| name if type == :keyreq } - inputs
|
|
98
|
+
return if missing.empty?
|
|
99
|
+
|
|
100
|
+
"requires #{keywords(missing)}, which #{missing.one? ? 'is not an input' : 'are not inputs'} of the method"
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# A lambda with no positional parameters, or a proc that names keywords, raises on a keyword it doesn't take.
|
|
104
|
+
# Other procs and positional lambdas receive the keywords as a Hash, or ignore them.
|
|
105
|
+
def unaccepted_keywords(callable, parameters, inputs)
|
|
106
|
+
types = parameters.map(&:first)
|
|
107
|
+
return if types.include?(:keyrest) || !strict?(callable, types)
|
|
108
|
+
|
|
109
|
+
unaccepted = inputs - parameters.filter_map { |type, name| name if Squawk::KEYWORD_TYPES.include?(type) }
|
|
110
|
+
return if unaccepted.empty?
|
|
111
|
+
|
|
112
|
+
"doesn't accept #{keywords(unaccepted)}, which the method takes as input when a caller passes it (add **)"
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# A lambda's required positional parameters get the keywords as one Hash, or nothing when there are none or
|
|
116
|
+
# the lambda names keywords itself.
|
|
117
|
+
def missing_positionals(callable, parameters, inputs)
|
|
118
|
+
return unless callable.lambda?
|
|
119
|
+
|
|
120
|
+
types = parameters.map(&:first)
|
|
121
|
+
required = types.count(:req)
|
|
122
|
+
given = types.intersect?(%i[key keyreq keyrest]) || inputs.empty? ? 0 : 1
|
|
123
|
+
return unless required > given
|
|
124
|
+
|
|
125
|
+
"takes #{required} positional argument(s), but is called with keywords only"
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def strict?(callable, types)
|
|
129
|
+
keywords = types.intersect?(Squawk::KEYWORD_TYPES)
|
|
130
|
+
callable.lambda? ? keywords || !types.intersect?(%i[req opt rest]) : keywords
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def keywords(names)
|
|
134
|
+
names.map { |name| "#{name}:" }.join(", ")
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def initialize(classes)
|
|
139
|
+
classes.each { |klass| check_squished!(klass) }
|
|
140
|
+
@classes = classes
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def call
|
|
144
|
+
Verification::Report.new(adapter_problems + @classes.flat_map { |klass| class_problems(klass) })
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
private
|
|
148
|
+
|
|
149
|
+
def check_squished!(klass)
|
|
150
|
+
return if klass.is_a?(Class) && klass.include?(Squishling)
|
|
151
|
+
|
|
152
|
+
raise ConfigurationError, "#{klass.inspect} doesn't include Squishling"
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# The configured adapter, or RubyLLM's, must be usable.
|
|
156
|
+
def adapter_problems
|
|
157
|
+
Squishling.config.resolved_adapter
|
|
158
|
+
[]
|
|
159
|
+
rescue ConfigurationError => e
|
|
160
|
+
[Verification::Problem.new(label: "adapter", message: e.message)]
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def class_problems(klass)
|
|
164
|
+
klass.squished_methods.keys.flat_map do |name|
|
|
165
|
+
definition = klass.squishling_definition(name)
|
|
166
|
+
next [] if unused_default?(definition)
|
|
167
|
+
|
|
168
|
+
MethodCheck.new(definition).problems
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# Every class has the default method (call) squished, whether or not it uses it.
|
|
173
|
+
def unused_default?(definition)
|
|
174
|
+
definition.name == ClassMethods::DEFAULT_METHOD && !definition.implemented? && definition.schema.nil?
|
|
175
|
+
rescue ConfigurationError, ArgumentError
|
|
176
|
+
false
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
end
|
data/lib/squishling/version.rb
CHANGED
data/lib/squishling.rb
CHANGED
|
@@ -2,29 +2,51 @@
|
|
|
2
2
|
|
|
3
3
|
require "json"
|
|
4
4
|
require "json_schemer"
|
|
5
|
-
require "ruby_llm"
|
|
6
5
|
require "schematist"
|
|
7
6
|
|
|
8
7
|
require_relative "squishling/version"
|
|
9
8
|
require_relative "squishling/errors"
|
|
10
9
|
require_relative "squishling/configuration"
|
|
11
10
|
require_relative "squishling/params"
|
|
11
|
+
require_relative "squishling/named_registry"
|
|
12
|
+
require_relative "squishling/adapter/error_body"
|
|
13
|
+
require_relative "squishling/adapter"
|
|
14
|
+
require_relative "squishling/adapters/ruby_llm"
|
|
15
|
+
require_relative "squishling/adapters/openai_options"
|
|
16
|
+
require_relative "squishling/adapters/openai_compatible"
|
|
12
17
|
require_relative "squishling/squawk"
|
|
18
|
+
require_relative "squishling/observer"
|
|
19
|
+
require_relative "squishling/observers/coolhand"
|
|
13
20
|
require_relative "squishling/model_path"
|
|
21
|
+
require_relative "squishling/rotation"
|
|
22
|
+
require_relative "squishling/harness/option_types"
|
|
23
|
+
require_relative "squishling/harness/registry"
|
|
24
|
+
require_relative "squishling/harness/base"
|
|
25
|
+
require_relative "squishling/harness/escalation"
|
|
26
|
+
require_relative "squishling/harness/squishsum"
|
|
27
|
+
require_relative "squishling/harness/judged_squishsum"
|
|
14
28
|
require_relative "squishling/harness"
|
|
15
29
|
require_relative "squishling/result"
|
|
16
30
|
require_relative "squishling/schema"
|
|
17
31
|
require_relative "squishling/source"
|
|
18
32
|
require_relative "squishling/appendices"
|
|
19
33
|
require_relative "squishling/definition"
|
|
34
|
+
require_relative "squishling/budget"
|
|
20
35
|
require_relative "squishling/llm_client"
|
|
21
36
|
require_relative "squishling/output_check"
|
|
37
|
+
require_relative "squishling/run/messages"
|
|
38
|
+
require_relative "squishling/run/rejections"
|
|
39
|
+
require_relative "squishling/run"
|
|
22
40
|
require_relative "squishling/invoker"
|
|
23
41
|
require_relative "squishling/judge"
|
|
24
42
|
require_relative "squishling/router"
|
|
25
43
|
require_relative "squishling/wrapper"
|
|
26
44
|
require_relative "squishling/class_methods"
|
|
27
45
|
require_relative "squishling/collisions"
|
|
46
|
+
require_relative "squishling/class_registry"
|
|
47
|
+
require_relative "squishling/verification"
|
|
48
|
+
require_relative "squishling/verifier"
|
|
49
|
+
require_relative "squishling/railtie" if defined?(Rails::Railtie)
|
|
28
50
|
|
|
29
51
|
# Include Squishling in a class to make it elastic: its squished methods either run their
|
|
30
52
|
# Ruby implementation or send their inputs through an LLM and return schema-validated results.
|
|
@@ -42,6 +64,68 @@ module Squishling
|
|
|
42
64
|
@config = Configuration.new
|
|
43
65
|
end
|
|
44
66
|
|
|
67
|
+
# Forgets the registered harnesses (the built-ins stay), e.g. between specs. Kept apart from reset_config!
|
|
68
|
+
# because harnesses are registered once at boot, and a suite that resets its config between examples
|
|
69
|
+
# shouldn't lose them.
|
|
70
|
+
def reset_harnesses!
|
|
71
|
+
Harness::Registry.reset!
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Registers a custom harness under a name, usable anywhere a built-in type is (harness: :name, or
|
|
75
|
+
# harness: { type: :name, option: value }, on a class, a method, a squish! call, or config.default_harness).
|
|
76
|
+
# Pass a Harness::Base subclass, or a block that becomes the body of one (see Harness::Base). Registered
|
|
77
|
+
# globally, so do it before the classes that use it load (an initializer). Raises ConfigurationError for a
|
|
78
|
+
# name that is invalid or already taken, including the built-in :escalation, :squishsum, :judged_squishsum.
|
|
79
|
+
def register_harness(name, klass = nil, &)
|
|
80
|
+
Harness::Registry.register(name, klass, &)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# The names of the harnesses that can be declared: the built-ins, then those registered.
|
|
84
|
+
def harnesses
|
|
85
|
+
Harness.types
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Registers a custom adapter class (see Adapter::Base) under a name, usable as config.adapter = :name, or
|
|
89
|
+
# config.adapter = { type: :name, option: value } for an adapter whose initialize takes options. To use an adapter
|
|
90
|
+
# without registering it, assign an instance: config.adapter = MyAdapter.new. Raises ConfigurationError for a name
|
|
91
|
+
# that is invalid or already taken, including the built-in :ruby_llm and :openai_compatible.
|
|
92
|
+
def register_adapter(name, klass)
|
|
93
|
+
Adapter::Registry.register(name, klass)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# The names of the adapters that can be configured: the built-ins, then those registered.
|
|
97
|
+
def adapters
|
|
98
|
+
Adapter::Registry.names
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Forgets the registered adapters (the built-ins stay), e.g. between specs.
|
|
102
|
+
def reset_adapters!
|
|
103
|
+
Adapter::Registry.reset!
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Every class that includes Squishling, and each subclass of one, that is loaded and hasn't been replaced by a
|
|
107
|
+
# reload. Classes are held weakly, so listing them doesn't keep reloaded ones alive. In Rails, eager load first
|
|
108
|
+
# (Rails.application.eager_load!) to include classes not yet autoloaded.
|
|
109
|
+
def squished_classes
|
|
110
|
+
ClassRegistry.classes
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Checks squished classes without making a request: each method's output schema and typed result, its resolved
|
|
114
|
+
# escalation, params, squawk and harness, and the keywords its include_when: and squish_when procs take. Checks
|
|
115
|
+
# the given classes, or every squished_classes when none are given. Returns a Verification::Report listing every
|
|
116
|
+
# problem found (ok? when none).
|
|
117
|
+
def verify(*classes)
|
|
118
|
+
Verifier.new(classes.empty? ? squished_classes : classes.flatten).call
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Like verify, but raises a VerificationError (a ConfigurationError) naming every problem. For CI and boot checks.
|
|
122
|
+
def verify!(*classes)
|
|
123
|
+
report = verify(*classes)
|
|
124
|
+
raise VerificationError, report unless report.ok?
|
|
125
|
+
|
|
126
|
+
report
|
|
127
|
+
end
|
|
128
|
+
|
|
45
129
|
def included(base)
|
|
46
130
|
raise ConfigurationError, "Squishling can only be included in a class" unless base.is_a?(Class)
|
|
47
131
|
|
|
@@ -76,10 +160,11 @@ module Squishling
|
|
|
76
160
|
# it); return it from the method. The overrides apply to this call only: append_to_purpose adds to (or,
|
|
77
161
|
# with false, replaces) the declared sections, context is sent alongside the declared squish_context,
|
|
78
162
|
# model/escalation (one or the other)/provider/purpose/harness replace the declared ones, and params merge
|
|
79
|
-
# key by key over them.
|
|
163
|
+
# key by key over them. max_total_output_tokens/max_total_input_tokens cap what the call may spend across all its
|
|
164
|
+
# requests (false lifts a declared cap).
|
|
80
165
|
def squish!(append_to_purpose: nil, context: nil, purpose: nil, model: nil, escalation: nil, provider: nil,
|
|
81
|
-
params: nil, harness: nil)
|
|
166
|
+
params: nil, harness: nil, max_total_output_tokens: nil, max_total_input_tokens: nil)
|
|
82
167
|
Router.hand_off(self, { append_to_purpose:, context:, purpose:, model:, escalation:, provider:, params:,
|
|
83
|
-
harness: })
|
|
168
|
+
harness:, max_total_output_tokens:, max_total_input_tokens: })
|
|
84
169
|
end
|
|
85
170
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: squishling
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Michael Carroll
|
|
@@ -23,20 +23,6 @@ dependencies:
|
|
|
23
23
|
- - "~>"
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
25
|
version: '2.0'
|
|
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: '2.1'
|
|
33
|
-
type: :runtime
|
|
34
|
-
prerelease: false
|
|
35
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
-
requirements:
|
|
37
|
-
- - "~>"
|
|
38
|
-
- !ruby/object:Gem::Version
|
|
39
|
-
version: '2.1'
|
|
40
26
|
- !ruby/object:Gem::Dependency
|
|
41
27
|
name: schematist
|
|
42
28
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -53,12 +39,13 @@ dependencies:
|
|
|
53
39
|
version: '1.1'
|
|
54
40
|
description: 'Squishling makes Ruby classes elastic. Add a squishling to a class and
|
|
55
41
|
it can send inputs straight to an LLM (OpenAI, Anthropic Claude, Google Gemini,
|
|
56
|
-
or any provider RubyLLM supports
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
the
|
|
61
|
-
|
|
42
|
+
or any provider RubyLLM supports, or any OpenAI-compatible endpoint, or your own
|
|
43
|
+
adapter) and return strict-schema-validated, typed results: the same type your Ruby
|
|
44
|
+
code returns. Use it to gracefully maintain fickle integrations, build interfaces
|
|
45
|
+
for unknown data formats, and rescue production errors by handing the failing call
|
|
46
|
+
to the LLM with the source class as context. Write Ruby only for the paths where
|
|
47
|
+
token costs justify maintaining it. Per-input routing, validated retries, and fallbacks
|
|
48
|
+
for your LLM calls included.'
|
|
62
49
|
email:
|
|
63
50
|
- mc@coolhandlabs.com
|
|
64
51
|
executables: []
|
|
@@ -70,32 +57,77 @@ files:
|
|
|
70
57
|
- README.md
|
|
71
58
|
- Rakefile
|
|
72
59
|
- SECURITY.md
|
|
60
|
+
- docs/adapters.md
|
|
73
61
|
- docs/configuration.md
|
|
62
|
+
- docs/coolhand.md
|
|
74
63
|
- docs/failures.md
|
|
75
64
|
- docs/harnesses.md
|
|
76
65
|
- docs/measuring-tokens.md
|
|
77
66
|
- docs/naming.md
|
|
67
|
+
- docs/observers.md
|
|
78
68
|
- docs/routing.md
|
|
79
69
|
- docs/schemas.md
|
|
70
|
+
- docs/testing.md
|
|
71
|
+
- docs/verifying.md
|
|
80
72
|
- lib/squishling.rb
|
|
73
|
+
- lib/squishling/adapter.rb
|
|
74
|
+
- lib/squishling/adapter/error_body.rb
|
|
75
|
+
- lib/squishling/adapters/openai_compatible.rb
|
|
76
|
+
- lib/squishling/adapters/openai_options.rb
|
|
77
|
+
- lib/squishling/adapters/ruby_llm.rb
|
|
81
78
|
- lib/squishling/appendices.rb
|
|
79
|
+
- lib/squishling/budget.rb
|
|
82
80
|
- lib/squishling/class_methods.rb
|
|
81
|
+
- lib/squishling/class_registry.rb
|
|
83
82
|
- lib/squishling/collisions.rb
|
|
84
83
|
- lib/squishling/configuration.rb
|
|
85
84
|
- lib/squishling/definition.rb
|
|
86
85
|
- lib/squishling/errors.rb
|
|
87
86
|
- lib/squishling/harness.rb
|
|
87
|
+
- lib/squishling/harness/base.rb
|
|
88
|
+
- lib/squishling/harness/escalation.rb
|
|
89
|
+
- lib/squishling/harness/judged_squishsum.rb
|
|
90
|
+
- lib/squishling/harness/option_types.rb
|
|
91
|
+
- lib/squishling/harness/registry.rb
|
|
92
|
+
- lib/squishling/harness/squishsum.rb
|
|
88
93
|
- lib/squishling/invoker.rb
|
|
89
94
|
- lib/squishling/judge.rb
|
|
90
95
|
- lib/squishling/llm_client.rb
|
|
91
96
|
- lib/squishling/model_path.rb
|
|
97
|
+
- lib/squishling/named_registry.rb
|
|
98
|
+
- lib/squishling/observer.rb
|
|
99
|
+
- lib/squishling/observers/coolhand.rb
|
|
92
100
|
- lib/squishling/output_check.rb
|
|
93
101
|
- lib/squishling/params.rb
|
|
102
|
+
- lib/squishling/railtie.rb
|
|
94
103
|
- lib/squishling/result.rb
|
|
104
|
+
- lib/squishling/rotation.rb
|
|
95
105
|
- lib/squishling/router.rb
|
|
106
|
+
- lib/squishling/rspec.rb
|
|
107
|
+
- lib/squishling/run.rb
|
|
108
|
+
- lib/squishling/run/messages.rb
|
|
109
|
+
- lib/squishling/run/rejections.rb
|
|
96
110
|
- lib/squishling/schema.rb
|
|
97
111
|
- lib/squishling/source.rb
|
|
98
112
|
- lib/squishling/squawk.rb
|
|
113
|
+
- lib/squishling/tasks/squishling.rake
|
|
114
|
+
- lib/squishling/testing.rb
|
|
115
|
+
- lib/squishling/testing/adapter.rb
|
|
116
|
+
- lib/squishling/testing/checks.rb
|
|
117
|
+
- lib/squishling/testing/direct.rb
|
|
118
|
+
- lib/squishling/testing/errors.rb
|
|
119
|
+
- lib/squishling/testing/fixtures.rb
|
|
120
|
+
- lib/squishling/testing/helpers.rb
|
|
121
|
+
- lib/squishling/testing/minitest.rb
|
|
122
|
+
- lib/squishling/testing/observer.rb
|
|
123
|
+
- lib/squishling/testing/reply.rb
|
|
124
|
+
- lib/squishling/testing/request.rb
|
|
125
|
+
- lib/squishling/testing/rspec.rb
|
|
126
|
+
- lib/squishling/testing/script.rb
|
|
127
|
+
- lib/squishling/testing/state.rb
|
|
128
|
+
- lib/squishling/testing/stubs.rb
|
|
129
|
+
- lib/squishling/verification.rb
|
|
130
|
+
- lib/squishling/verifier.rb
|
|
99
131
|
- lib/squishling/version.rb
|
|
100
132
|
- lib/squishling/wrapper.rb
|
|
101
133
|
homepage: https://github.com/Coolhand-Labs/squishling
|