lex-llm 0.7.6 → 0.8.4

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 (168) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +139 -0
  3. data/RULES.md +97 -0
  4. data/lib/legion/extensions/llm/auto_registration.rb +4 -13
  5. data/lib/legion/extensions/llm/canonical/chunk.rb +107 -94
  6. data/lib/legion/extensions/llm/canonical/content_block.rb +65 -75
  7. data/lib/legion/extensions/llm/canonical/message.rb +53 -69
  8. data/lib/legion/extensions/llm/canonical/params.rb +58 -34
  9. data/lib/legion/extensions/llm/canonical/request.rb +68 -56
  10. data/lib/legion/extensions/llm/canonical/response.rb +62 -78
  11. data/lib/legion/extensions/llm/canonical/strict.rb +105 -0
  12. data/lib/legion/extensions/llm/canonical/thinking.rb +36 -84
  13. data/lib/legion/extensions/llm/canonical/thinking_config.rb +149 -0
  14. data/lib/legion/extensions/llm/canonical/tool_call.rb +43 -53
  15. data/lib/legion/extensions/llm/canonical/tool_definition.rb +56 -46
  16. data/lib/legion/extensions/llm/canonical/tool_schema.rb +15 -22
  17. data/lib/legion/extensions/llm/canonical/usage.rb +47 -40
  18. data/lib/legion/extensions/llm/canonical.rb +7 -5
  19. data/lib/legion/extensions/llm/configuration.rb +40 -10
  20. data/lib/legion/extensions/llm/connection.rb +8 -30
  21. data/lib/legion/extensions/llm/credential_sources.rb +32 -49
  22. data/lib/legion/extensions/llm/discovery/actor.rb +92 -0
  23. data/lib/legion/extensions/llm/discovery/pipeline.rb +604 -0
  24. data/lib/legion/extensions/llm/error.rb +0 -14
  25. data/lib/legion/extensions/llm/fleet/contract_error.rb +15 -0
  26. data/lib/legion/extensions/llm/fleet/envelope_validation.rb +7 -6
  27. data/lib/legion/extensions/llm/fleet/fleet_envelope.rb +66 -0
  28. data/lib/legion/extensions/llm/fleet/protocol.rb +18 -5
  29. data/lib/legion/extensions/llm/fleet/provider_responder.rb +58 -156
  30. data/lib/legion/extensions/llm/fleet/token_validator.rb +15 -21
  31. data/lib/legion/extensions/llm/fleet/worker_execution.rb +107 -154
  32. data/lib/legion/extensions/llm/inventory/errors.rb +0 -1
  33. data/lib/legion/extensions/llm/inventory/evidence.rb +1 -1
  34. data/lib/legion/extensions/llm/inventory/identity.rb +55 -39
  35. data/lib/legion/extensions/llm/inventory/probe_token.rb +9 -12
  36. data/lib/legion/extensions/llm/inventory/publisher.rb +15 -53
  37. data/lib/legion/extensions/llm/inventory/records.rb +36 -100
  38. data/lib/legion/extensions/llm/inventory/registry.rb +54 -48
  39. data/lib/legion/extensions/llm/inventory/snapshot.rb +7 -21
  40. data/lib/legion/extensions/llm/inventory/weight_reconciler.rb +13 -6
  41. data/lib/legion/extensions/llm/inventory/weight_schema.rb +47 -8
  42. data/lib/legion/extensions/llm/provider/open_ai_compatible.rb +178 -182
  43. data/lib/legion/extensions/llm/provider.rb +192 -315
  44. data/lib/legion/extensions/llm/provider_contract.rb +25 -8
  45. data/lib/legion/extensions/llm/provider_settings.rb +5 -26
  46. data/lib/legion/extensions/llm/responses/thinking_extractor.rb +8 -1
  47. data/lib/legion/extensions/llm/responses/tool_arguments.rb +48 -0
  48. data/lib/legion/extensions/llm/routing/provider_outcome.rb +25 -0
  49. data/lib/legion/extensions/llm/routing/records.rb +34 -17
  50. data/lib/legion/extensions/llm/stream_accumulator.rb +186 -270
  51. data/lib/legion/extensions/llm/streaming.rb +50 -35
  52. data/lib/legion/extensions/llm/taxonomies.rb +14 -26
  53. data/lib/legion/extensions/llm/transport/fleet_lane.rb +8 -10
  54. data/lib/legion/extensions/llm/transport/messages/fleet_error.rb +3 -2
  55. data/lib/legion/extensions/llm/transport/messages/fleet_request.rb +6 -8
  56. data/lib/legion/extensions/llm/transport/messages/fleet_response.rb +10 -9
  57. data/lib/legion/extensions/llm/utils.rb +23 -5
  58. data/lib/legion/extensions/llm/version.rb +1 -1
  59. data/lib/legion/extensions/llm.rb +8 -98
  60. data/spec/legion/extensions/llm/auto_registration_spec.rb +4 -9
  61. data/spec/legion/extensions/llm/canonical/chunk_spec.rb +66 -252
  62. data/spec/legion/extensions/llm/canonical/content_block_spec.rb +52 -197
  63. data/spec/legion/extensions/llm/canonical/message_spec.rb +89 -204
  64. data/spec/legion/extensions/llm/canonical/params_spec.rb +55 -136
  65. data/spec/legion/extensions/llm/canonical/request_spec.rb +81 -143
  66. data/spec/legion/extensions/llm/canonical/response_spec.rb +68 -204
  67. data/spec/legion/extensions/llm/canonical/thinking/config_spec.rb +222 -0
  68. data/spec/legion/extensions/llm/canonical/thinking_spec.rb +23 -171
  69. data/spec/legion/extensions/llm/canonical/tool_call_spec.rb +59 -162
  70. data/spec/legion/extensions/llm/canonical/tool_definition_spec.rb +55 -191
  71. data/spec/legion/extensions/llm/canonical/tool_schema_spec.rb +26 -67
  72. data/spec/legion/extensions/llm/canonical/usage_spec.rb +46 -155
  73. data/spec/legion/extensions/llm/configuration_spec.rb +31 -5
  74. data/spec/legion/extensions/llm/conformance/canonical_type_examples.rb +106 -0
  75. data/spec/legion/extensions/llm/conformance/client_translator_examples.rb +1 -2
  76. data/spec/legion/extensions/llm/conformance/conformance.rb +10 -2
  77. data/spec/legion/extensions/llm/conformance/fixtures/canonical_fleet_round_trip.json +1 -1
  78. data/spec/legion/extensions/llm/conformance/fixtures/canonical_thinking_request.json +2 -2
  79. data/spec/legion/extensions/llm/conformance/provider_translator_examples.rb +1 -1
  80. data/spec/legion/extensions/llm/conformance/ssot_contract_conformance_spec.rb +128 -0
  81. data/spec/legion/extensions/llm/conformance/ssot_contract_examples.rb +505 -0
  82. data/spec/legion/extensions/llm/conformance/ssot_provider_examples.rb +11 -10
  83. data/spec/legion/extensions/llm/credential_sources_spec.rb +12 -13
  84. data/spec/legion/extensions/llm/error_spec.rb +2 -12
  85. data/spec/legion/extensions/llm/fleet/exact_offering_spec.rb +59 -45
  86. data/spec/legion/extensions/llm/fleet/provider_responder_spec.rb +173 -95
  87. data/spec/legion/extensions/llm/fleet/token_validator_spec.rb +7 -2
  88. data/spec/legion/extensions/llm/fleet/worker_execution_spec.rb +93 -74
  89. data/spec/legion/extensions/llm/fleet_messages_spec.rb +119 -125
  90. data/spec/legion/extensions/llm/gemspec_spec.rb +1 -2
  91. data/spec/legion/extensions/llm/inventory/boot_spec.rb +4 -4
  92. data/spec/legion/extensions/llm/inventory/identity_spec.rb +127 -110
  93. data/spec/legion/extensions/llm/inventory/probe_token_spec.rb +4 -4
  94. data/spec/legion/extensions/llm/inventory/publisher_spec.rb +8 -61
  95. data/spec/legion/extensions/llm/inventory/records_spec.rb +82 -47
  96. data/spec/legion/extensions/llm/inventory/registry_activation_spec.rb +49 -16
  97. data/spec/legion/extensions/llm/inventory/registry_replacement_spec.rb +5 -4
  98. data/spec/legion/extensions/llm/inventory/snapshot_spec.rb +11 -5
  99. data/spec/legion/extensions/llm/inventory/weight_reconciler_spec.rb +7 -3
  100. data/spec/legion/extensions/llm/inventory/weight_schema_spec.rb +60 -10
  101. data/spec/legion/extensions/llm/provider/open_ai_compatible_spec.rb +128 -68
  102. data/spec/legion/extensions/llm/provider/open_ai_compatible_tool_calls_array_spec.rb +7 -31
  103. data/spec/legion/extensions/llm/provider_contract_spec.rb +10 -15
  104. data/spec/legion/extensions/llm/provider_spec.rb +97 -78
  105. data/spec/legion/extensions/llm/routing/records_spec.rb +38 -5
  106. data/spec/legion/extensions/llm/stream_accumulator_spec.rb +174 -144
  107. data/spec/legion/extensions/llm/streaming_spec.rb +27 -0
  108. data/spec/legion/extensions/llm/taxonomies_spec.rb +43 -43
  109. data/spec/legion/extensions/llm/transport/fleet_lane_spec.rb +1 -1
  110. data/spec/legion/extensions/llm/utils_spec.rb +26 -7
  111. data/spec/legion/extensions/llm_base_contract_spec.rb +55 -90
  112. data/spec/legion/extensions/llm_extension_spec.rb +5 -5
  113. data/spec/support/fake_llm_provider.rb +45 -39
  114. data/spec/support/fake_ssot_harness.rb +7 -2
  115. metadata +13 -54
  116. data/lib/legion/extensions/llm/agent.rb +0 -366
  117. data/lib/legion/extensions/llm/aliases.json +0 -436
  118. data/lib/legion/extensions/llm/aliases.rb +0 -67
  119. data/lib/legion/extensions/llm/attachment.rb +0 -229
  120. data/lib/legion/extensions/llm/chat.rb +0 -354
  121. data/lib/legion/extensions/llm/chunk.rb +0 -10
  122. data/lib/legion/extensions/llm/content.rb +0 -81
  123. data/lib/legion/extensions/llm/context.rb +0 -33
  124. data/lib/legion/extensions/llm/embedding.rb +0 -33
  125. data/lib/legion/extensions/llm/image.rb +0 -109
  126. data/lib/legion/extensions/llm/inventory/capabilities.rb +0 -40
  127. data/lib/legion/extensions/llm/inventory/scoped_refresher.rb +0 -310
  128. data/lib/legion/extensions/llm/message.rb +0 -118
  129. data/lib/legion/extensions/llm/mime_type.rb +0 -75
  130. data/lib/legion/extensions/llm/model/info.rb +0 -286
  131. data/lib/legion/extensions/llm/model/modalities.rb +0 -26
  132. data/lib/legion/extensions/llm/model/pricing.rb +0 -52
  133. data/lib/legion/extensions/llm/model/pricing_category.rb +0 -50
  134. data/lib/legion/extensions/llm/model/pricing_tier.rb +0 -37
  135. data/lib/legion/extensions/llm/model.rb +0 -11
  136. data/lib/legion/extensions/llm/models.json +0 -57313
  137. data/lib/legion/extensions/llm/models.rb +0 -530
  138. data/lib/legion/extensions/llm/models_schema.json +0 -168
  139. data/lib/legion/extensions/llm/moderation.rb +0 -60
  140. data/lib/legion/extensions/llm/registry_event_builder.rb +0 -141
  141. data/lib/legion/extensions/llm/registry_publisher.rb +0 -107
  142. data/lib/legion/extensions/llm/responses/chat_response.rb +0 -43
  143. data/lib/legion/extensions/llm/responses/embedding_response.rb +0 -38
  144. data/lib/legion/extensions/llm/responses/stream_chunk.rb +0 -43
  145. data/lib/legion/extensions/llm/routing/lane_key.rb +0 -66
  146. data/lib/legion/extensions/llm/routing/model_offering.rb +0 -241
  147. data/lib/legion/extensions/llm/routing/offering_registry.rb +0 -101
  148. data/lib/legion/extensions/llm/routing/registry_event.rb +0 -167
  149. data/lib/legion/extensions/llm/thinking.rb +0 -53
  150. data/lib/legion/extensions/llm/tokens.rb +0 -51
  151. data/lib/legion/extensions/llm/tool_call.rb +0 -34
  152. data/lib/legion/extensions/llm/transcription.rb +0 -39
  153. data/lib/legion/extensions/llm/transport/messages/registry_event.rb +0 -44
  154. data/spec/legion/extensions/llm/agent_spec.rb +0 -179
  155. data/spec/legion/extensions/llm/attachment_spec.rb +0 -25
  156. data/spec/legion/extensions/llm/conformance/fixtures/ssot_identity_vectors.json +0 -84
  157. data/spec/legion/extensions/llm/context_spec.rb +0 -127
  158. data/spec/legion/extensions/llm/inventory/capabilities_spec.rb +0 -43
  159. data/spec/legion/extensions/llm/inventory/scoped_refresher_spec.rb +0 -340
  160. data/spec/legion/extensions/llm/message_spec.rb +0 -64
  161. data/spec/legion/extensions/llm/model/info_spec.rb +0 -222
  162. data/spec/legion/extensions/llm/models_spec.rb +0 -104
  163. data/spec/legion/extensions/llm/registry_event_builder_spec.rb +0 -68
  164. data/spec/legion/extensions/llm/registry_publisher_spec.rb +0 -22
  165. data/spec/legion/extensions/llm/responses/response_objects_spec.rb +0 -75
  166. data/spec/legion/extensions/llm/routing/model_offering_spec.rb +0 -281
  167. data/spec/legion/extensions/llm/routing/offering_registry_spec.rb +0 -50
  168. data/spec/legion/extensions/llm/routing/registry_event_spec.rb +0 -120
@@ -1,366 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'erb'
4
- require 'forwardable'
5
- require 'pathname'
6
- require 'ruby_llm/schema'
7
-
8
- module Legion
9
- module Extensions
10
- module Llm
11
- # Base class for simple, class-configured agents.
12
- class Agent
13
- extend Forwardable
14
- include Enumerable
15
-
16
- class << self
17
- def inherited(subclass)
18
- super
19
- subclass.instance_variable_set(:@chat_kwargs, (@chat_kwargs || {}).dup)
20
- subclass.instance_variable_set(:@tools, (@tools || []).dup)
21
- subclass.instance_variable_set(:@instructions, @instructions)
22
- subclass.instance_variable_set(:@temperature, @temperature)
23
- subclass.instance_variable_set(:@thinking, @thinking)
24
- subclass.instance_variable_set(:@params, (@params || {}).dup)
25
- subclass.instance_variable_set(:@headers, (@headers || {}).dup)
26
- subclass.instance_variable_set(:@schema, @schema)
27
- subclass.instance_variable_set(:@context, @context)
28
- subclass.instance_variable_set(:@chat_model, @chat_model)
29
- subclass.instance_variable_set(:@input_names, (@input_names || []).dup)
30
- end
31
-
32
- def model(model_id = nil, **options)
33
- options[:model] = model_id unless model_id.nil?
34
- @chat_kwargs = options
35
- end
36
-
37
- def tools(*tools, &block)
38
- return @tools || [] if tools.empty? && !block_given?
39
-
40
- @tools = block_given? ? block : tools.flatten
41
- end
42
-
43
- def instructions(text = nil, **prompt_locals, &block)
44
- if text.nil? && prompt_locals.empty? && !block_given?
45
- @instructions ||= { prompt: 'instructions', locals: {} }
46
- return @instructions
47
- end
48
-
49
- @instructions = block || text || { prompt: 'instructions', locals: prompt_locals }
50
- end
51
-
52
- def temperature(value = nil)
53
- return @temperature if value.nil?
54
-
55
- @temperature = value
56
- end
57
-
58
- def thinking(effort: nil, budget: nil)
59
- return @thinking if effort.nil? && budget.nil?
60
-
61
- @thinking = { effort: effort, budget: budget }
62
- end
63
-
64
- def params(**params, &block)
65
- return @params || {} if params.empty? && !block_given?
66
-
67
- @params = block_given? ? block : params
68
- end
69
-
70
- def headers(**headers, &block)
71
- return @headers || {} if headers.empty? && !block_given?
72
-
73
- @headers = block_given? ? block : headers
74
- end
75
-
76
- def schema(value = nil, &block)
77
- return @schema if value.nil? && !block_given?
78
-
79
- @schema = block_given? ? block : value
80
- end
81
-
82
- def context(value = nil)
83
- return @context if value.nil?
84
-
85
- @context = value
86
- end
87
-
88
- def chat_model(value = nil)
89
- return @chat_model if value.nil?
90
-
91
- @chat_model = value
92
- remove_instance_variable(:@resolved_chat_model) if instance_variable_defined?(:@resolved_chat_model)
93
- end
94
-
95
- def inputs(*names)
96
- return @input_names || [] if names.empty?
97
-
98
- @input_names = names.flatten.map(&:to_sym)
99
- end
100
-
101
- def chat_kwargs
102
- @chat_kwargs || {}
103
- end
104
-
105
- def chat(**kwargs)
106
- input_values, chat_options = partition_inputs(kwargs)
107
- chat = Legion::Extensions::Llm.chat(**chat_kwargs, **chat_options)
108
- apply_configuration(chat, input_values:, persist_instructions: true)
109
- chat
110
- end
111
-
112
- def create(**)
113
- with_chat_record(:create, **)
114
- end
115
-
116
- def create!(**)
117
- with_chat_record(:create!, **)
118
- end
119
-
120
- def find(id, **kwargs)
121
- raise ArgumentError, 'chat_model must be configured to use find' unless resolved_chat_model
122
-
123
- input_values, = partition_inputs(kwargs)
124
- record = resolved_chat_model.find(id)
125
- apply_configuration(record, input_values:, persist_instructions: false)
126
-
127
- record
128
- end
129
-
130
- def sync_instructions!(chat_or_id, **kwargs)
131
- raise ArgumentError, 'chat_model must be configured to use sync_instructions!' unless resolved_chat_model
132
-
133
- input_values, = partition_inputs(kwargs)
134
- record = chat_or_id.is_a?(resolved_chat_model) ? chat_or_id : resolved_chat_model.find(chat_or_id)
135
- apply_assume_model_exists(record)
136
- runtime = runtime_context(chat: record, inputs: input_values)
137
- instructions_value = resolved_instructions_value(record, runtime, inputs: input_values)
138
- return record if instructions_value.nil?
139
-
140
- record.with_instructions(instructions_value)
141
- record
142
- end
143
-
144
- def render_prompt(name, chat:, inputs:, locals:)
145
- path = prompt_path_for(name)
146
- unless File.exist?(path)
147
- raise Legion::Extensions::Llm::PromptNotFoundError,
148
- "Prompt file not found for #{self}: #{path}. Create the file or use inline instructions."
149
- end
150
-
151
- resolved_locals = resolve_prompt_locals(locals, runtime: runtime_context(chat:, inputs:), chat:, inputs:)
152
- ERB.new(File.read(path)).result_with_hash(resolved_locals)
153
- end
154
-
155
- private
156
-
157
- def with_chat_record(method_name, **kwargs)
158
- raise ArgumentError, 'chat_model must be configured to use create/create!' unless resolved_chat_model
159
-
160
- input_values, chat_options = partition_inputs(kwargs)
161
- record = resolved_chat_model.public_send(method_name, **chat_kwargs, **chat_options)
162
- apply_configuration(record, input_values:, persist_instructions: true) if record
163
- record
164
- end
165
-
166
- def apply_configuration(chat_object, input_values:, persist_instructions:)
167
- runtime = runtime_context(chat: chat_object, inputs: input_values)
168
- llm_chat = llm_chat_for(chat_object)
169
-
170
- apply_context(llm_chat)
171
- apply_instructions(chat_object, runtime, inputs: input_values, persist: persist_instructions)
172
- apply_tools(llm_chat, runtime)
173
- apply_temperature(llm_chat)
174
- apply_thinking(llm_chat)
175
- apply_params(llm_chat, runtime)
176
- apply_headers(llm_chat, runtime)
177
- apply_schema(llm_chat, runtime)
178
- end
179
-
180
- def apply_context(llm_chat)
181
- llm_chat.with_context(context) if context
182
- end
183
-
184
- def apply_instructions(chat_object, runtime, inputs:, persist:)
185
- value = resolved_instructions_value(chat_object, runtime, inputs:)
186
- return if value.nil?
187
-
188
- target = instruction_target(chat_object, persist:)
189
- return target.with_runtime_instructions(value) if use_runtime_instructions?(target, persist:)
190
-
191
- target.with_instructions(value)
192
- end
193
-
194
- def apply_tools(llm_chat, runtime)
195
- tools_to_apply = Array(evaluate(tools, runtime))
196
- llm_chat.with_tools(*tools_to_apply) unless tools_to_apply.empty?
197
- end
198
-
199
- def apply_temperature(llm_chat)
200
- llm_chat.with_temperature(temperature) unless temperature.nil?
201
- end
202
-
203
- def apply_thinking(llm_chat)
204
- llm_chat.with_thinking(**thinking) if thinking
205
- end
206
-
207
- def apply_params(llm_chat, runtime)
208
- value = evaluate(params, runtime)
209
- llm_chat.with_params(**value) if value && !value.empty?
210
- end
211
-
212
- def apply_headers(llm_chat, runtime)
213
- value = evaluate(headers, runtime)
214
- llm_chat.with_headers(**value) if value && !value.empty?
215
- end
216
-
217
- def apply_schema(llm_chat, runtime)
218
- value = resolved_schema_value(runtime)
219
- llm_chat.with_schema(value) if value
220
- end
221
-
222
- def resolved_schema_value(runtime)
223
- value = schema
224
- return value unless value.is_a?(Proc)
225
-
226
- evaluate(value, runtime)
227
- rescue NoMethodError => e
228
- raise unless e.receiver.equal?(runtime)
229
-
230
- Legion::Extensions::Llm::Schema.create(&value)
231
- end
232
-
233
- def llm_chat_for(chat_object)
234
- apply_assume_model_exists(chat_object)
235
- chat_object.respond_to?(:to_llm) ? chat_object.to_llm : chat_object
236
- end
237
-
238
- def apply_assume_model_exists(chat_object)
239
- return unless chat_kwargs.key?(:assume_model_exists) &&
240
- resolved_chat_model &&
241
- chat_object.is_a?(resolved_chat_model)
242
-
243
- chat_object.assume_model_exists = chat_kwargs[:assume_model_exists]
244
- end
245
-
246
- def evaluate(value, runtime)
247
- value.is_a?(Proc) ? runtime.instance_exec(&value) : value
248
- end
249
-
250
- def resolved_instructions_value(chat_object, runtime, inputs:)
251
- value = evaluate(@instructions, runtime)
252
- return value unless prompt_instruction?(value)
253
-
254
- runtime.prompt(
255
- value[:prompt],
256
- **resolve_prompt_locals(value[:locals] || {}, runtime:, chat: chat_object, inputs:)
257
- )
258
- end
259
-
260
- def prompt_instruction?(value)
261
- value.is_a?(Hash) && value[:prompt]
262
- end
263
-
264
- def instruction_target(chat_object, persist:)
265
- if persist || !chat_object.respond_to?(:to_llm)
266
- chat_object
267
- else
268
- runtime_instruction_target(chat_object)
269
- end
270
- end
271
-
272
- def runtime_instruction_target(chat_object)
273
- return chat_object if chat_object.respond_to?(:with_runtime_instructions)
274
-
275
- chat_object.to_llm
276
- end
277
-
278
- def use_runtime_instructions?(target, persist:)
279
- !persist && target.respond_to?(:with_runtime_instructions)
280
- end
281
-
282
- def resolve_prompt_locals(locals, runtime:, chat:, inputs:)
283
- base = { chat: chat }.merge(inputs)
284
- evaluated = locals.each_with_object({}) do |(key, value), acc|
285
- acc[key.to_sym] = value.is_a?(Proc) ? runtime.instance_exec(&value) : value
286
- end
287
- base.merge(evaluated)
288
- end
289
-
290
- def partition_inputs(kwargs)
291
- input_values = {}
292
- chat_options = {}
293
-
294
- kwargs.each do |key, value|
295
- symbolized_key = key.to_sym
296
- if inputs.include?(symbolized_key)
297
- input_values[symbolized_key] = value
298
- else
299
- chat_options[symbolized_key] = value
300
- end
301
- end
302
-
303
- [input_values, chat_options]
304
- end
305
-
306
- def runtime_context(chat:, inputs:)
307
- agent_class = self
308
- Object.new.tap do |runtime|
309
- runtime.define_singleton_method(:chat) { chat }
310
- runtime.define_singleton_method(:prompt) do |name, **locals|
311
- agent_class.render_prompt(name, chat:, inputs:, locals:)
312
- end
313
-
314
- inputs.each do |name, value|
315
- runtime.define_singleton_method(name) { value }
316
- end
317
- end
318
- end
319
-
320
- def prompt_path_for(name)
321
- filename = name.to_s
322
- filename += '.txt.erb' unless filename.end_with?('.txt.erb')
323
- prompt_root.join(prompt_agent_path, filename)
324
- end
325
-
326
- def prompt_agent_path
327
- class_name = name || 'agent'
328
- class_name.gsub('::', '/')
329
- .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
330
- .gsub(/([a-z\d])([A-Z])/, '\1_\2')
331
- .tr('-', '_')
332
- .downcase
333
- end
334
-
335
- def prompt_root
336
- Pathname.new(Dir.pwd).join('app/prompts')
337
- end
338
-
339
- def resolved_chat_model
340
- return @resolved_chat_model if defined?(@resolved_chat_model)
341
-
342
- @resolved_chat_model = case @chat_model
343
- when String then Object.const_get(@chat_model)
344
- else @chat_model
345
- end
346
- end
347
- end
348
-
349
- def initialize(chat: nil, inputs: nil, persist_instructions: true, **kwargs)
350
- input_values, chat_options = self.class.send(:partition_inputs, kwargs)
351
- @chat = chat || Legion::Extensions::Llm.chat(**self.class.chat_kwargs, **chat_options)
352
- self.class.send(:apply_configuration, @chat, input_values: input_values.merge(inputs || {}),
353
- persist_instructions:)
354
- end
355
-
356
- attr_reader :chat
357
-
358
- def_delegators :chat, :model, :messages, :tools, :params, :headers, :schema, :ask, :say,
359
- :with_tool, :with_tools,
360
- :with_model, :with_temperature, :with_thinking, :with_context, :with_params, :with_headers,
361
- :with_schema, :on_new_message, :on_end_message, :on_tool_call, :on_tool_result, :each, :complete,
362
- :add_message, :reset_messages!
363
- end
364
- end
365
- end
366
- end