lex-llm 0.1.2 → 0.1.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 (165) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/CHANGELOG.md +12 -1
  4. data/Gemfile +1 -19
  5. data/README.md +25 -26
  6. data/lex-llm.gemspec +2 -2
  7. data/lib/legion/extensions/llm/agent.rb +366 -0
  8. data/lib/legion/extensions/llm/aliases.rb +42 -0
  9. data/lib/legion/extensions/llm/attachment.rb +229 -0
  10. data/lib/legion/extensions/llm/chat.rb +355 -0
  11. data/lib/legion/extensions/llm/chunk.rb +10 -0
  12. data/lib/legion/extensions/llm/configuration.rb +82 -0
  13. data/lib/legion/extensions/llm/connection.rb +134 -0
  14. data/lib/legion/extensions/llm/content.rb +81 -0
  15. data/lib/legion/extensions/llm/context.rb +33 -0
  16. data/lib/legion/extensions/llm/embedding.rb +33 -0
  17. data/lib/legion/extensions/llm/error.rb +116 -0
  18. data/lib/legion/extensions/llm/image.rb +109 -0
  19. data/lib/legion/extensions/llm/message.rb +111 -0
  20. data/lib/legion/extensions/llm/mime_type.rb +75 -0
  21. data/lib/legion/extensions/llm/model/info.rb +117 -0
  22. data/lib/legion/extensions/llm/model/modalities.rb +26 -0
  23. data/lib/legion/extensions/llm/model/pricing.rb +52 -0
  24. data/lib/legion/extensions/llm/model/pricing_category.rb +50 -0
  25. data/lib/legion/extensions/llm/model/pricing_tier.rb +37 -0
  26. data/lib/legion/extensions/llm/model.rb +11 -0
  27. data/lib/legion/extensions/llm/models.rb +514 -0
  28. data/lib/{lex_llm → legion/extensions/llm}/models_schema.json +1 -1
  29. data/lib/legion/extensions/llm/moderation.rb +60 -0
  30. data/lib/legion/extensions/llm/provider/open_ai_compatible.rb +276 -0
  31. data/lib/legion/extensions/llm/provider.rb +337 -0
  32. data/lib/legion/extensions/llm/routing/lane_key.rb +57 -0
  33. data/lib/legion/extensions/llm/routing/model_offering.rb +173 -0
  34. data/lib/legion/extensions/llm/routing.rb +11 -0
  35. data/lib/legion/extensions/llm/stream_accumulator.rb +209 -0
  36. data/lib/legion/extensions/llm/streaming.rb +181 -0
  37. data/lib/legion/extensions/llm/thinking.rb +53 -0
  38. data/lib/legion/extensions/llm/tokens.rb +51 -0
  39. data/lib/legion/extensions/llm/tool.rb +258 -0
  40. data/lib/legion/extensions/llm/tool_call.rb +29 -0
  41. data/lib/legion/extensions/llm/transcription.rb +39 -0
  42. data/lib/legion/extensions/llm/utils.rb +95 -0
  43. data/lib/legion/extensions/llm/version.rb +9 -0
  44. data/lib/legion/extensions/llm.rb +85 -6
  45. metadata +40 -122
  46. data/lib/generators/lex_llm/agent/agent_generator.rb +0 -36
  47. data/lib/generators/lex_llm/agent/templates/agent.rb.tt +0 -6
  48. data/lib/generators/lex_llm/agent/templates/instructions.txt.erb.tt +0 -0
  49. data/lib/generators/lex_llm/chat_ui/chat_ui_generator.rb +0 -256
  50. data/lib/generators/lex_llm/chat_ui/templates/controllers/chats_controller.rb.tt +0 -38
  51. data/lib/generators/lex_llm/chat_ui/templates/controllers/messages_controller.rb.tt +0 -21
  52. data/lib/generators/lex_llm/chat_ui/templates/controllers/models_controller.rb.tt +0 -14
  53. data/lib/generators/lex_llm/chat_ui/templates/helpers/messages_helper.rb.tt +0 -25
  54. data/lib/generators/lex_llm/chat_ui/templates/jobs/chat_response_job.rb.tt +0 -12
  55. data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/chats/_chat.html.erb.tt +0 -16
  56. data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/chats/_form.html.erb.tt +0 -31
  57. data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/chats/index.html.erb.tt +0 -31
  58. data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/chats/new.html.erb.tt +0 -9
  59. data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/chats/show.html.erb.tt +0 -27
  60. data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/messages/_assistant.html.erb.tt +0 -14
  61. data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/messages/_content.html.erb.tt +0 -1
  62. data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/messages/_error.html.erb.tt +0 -13
  63. data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/messages/_form.html.erb.tt +0 -23
  64. data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/messages/_system.html.erb.tt +0 -10
  65. data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/messages/_tool.html.erb.tt +0 -2
  66. data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/messages/_tool_calls.html.erb.tt +0 -4
  67. data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/messages/_user.html.erb.tt +0 -14
  68. data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/messages/tool_calls/_default.html.erb.tt +0 -13
  69. data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/messages/tool_results/_default.html.erb.tt +0 -21
  70. data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/models/_model.html.erb.tt +0 -17
  71. data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/models/index.html.erb.tt +0 -40
  72. data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/models/show.html.erb.tt +0 -27
  73. data/lib/generators/lex_llm/chat_ui/templates/views/chats/_chat.html.erb.tt +0 -16
  74. data/lib/generators/lex_llm/chat_ui/templates/views/chats/_form.html.erb.tt +0 -29
  75. data/lib/generators/lex_llm/chat_ui/templates/views/chats/index.html.erb.tt +0 -28
  76. data/lib/generators/lex_llm/chat_ui/templates/views/chats/new.html.erb.tt +0 -11
  77. data/lib/generators/lex_llm/chat_ui/templates/views/chats/show.html.erb.tt +0 -25
  78. data/lib/generators/lex_llm/chat_ui/templates/views/messages/_assistant.html.erb.tt +0 -9
  79. data/lib/generators/lex_llm/chat_ui/templates/views/messages/_content.html.erb.tt +0 -1
  80. data/lib/generators/lex_llm/chat_ui/templates/views/messages/_error.html.erb.tt +0 -8
  81. data/lib/generators/lex_llm/chat_ui/templates/views/messages/_form.html.erb.tt +0 -21
  82. data/lib/generators/lex_llm/chat_ui/templates/views/messages/_system.html.erb.tt +0 -6
  83. data/lib/generators/lex_llm/chat_ui/templates/views/messages/_tool.html.erb.tt +0 -2
  84. data/lib/generators/lex_llm/chat_ui/templates/views/messages/_tool_calls.html.erb.tt +0 -4
  85. data/lib/generators/lex_llm/chat_ui/templates/views/messages/_user.html.erb.tt +0 -9
  86. data/lib/generators/lex_llm/chat_ui/templates/views/messages/create.turbo_stream.erb.tt +0 -7
  87. data/lib/generators/lex_llm/chat_ui/templates/views/messages/tool_calls/_default.html.erb.tt +0 -8
  88. data/lib/generators/lex_llm/chat_ui/templates/views/messages/tool_results/_default.html.erb.tt +0 -16
  89. data/lib/generators/lex_llm/chat_ui/templates/views/models/_model.html.erb.tt +0 -15
  90. data/lib/generators/lex_llm/chat_ui/templates/views/models/index.html.erb.tt +0 -38
  91. data/lib/generators/lex_llm/chat_ui/templates/views/models/show.html.erb.tt +0 -17
  92. data/lib/generators/lex_llm/generator_helpers.rb +0 -214
  93. data/lib/generators/lex_llm/install/install_generator.rb +0 -109
  94. data/lib/generators/lex_llm/install/templates/add_references_to_chats_tool_calls_and_messages_migration.rb.tt +0 -9
  95. data/lib/generators/lex_llm/install/templates/chat_model.rb.tt +0 -3
  96. data/lib/generators/lex_llm/install/templates/create_chats_migration.rb.tt +0 -7
  97. data/lib/generators/lex_llm/install/templates/create_messages_migration.rb.tt +0 -19
  98. data/lib/generators/lex_llm/install/templates/create_models_migration.rb.tt +0 -39
  99. data/lib/generators/lex_llm/install/templates/create_tool_calls_migration.rb.tt +0 -21
  100. data/lib/generators/lex_llm/install/templates/initializer.rb.tt +0 -20
  101. data/lib/generators/lex_llm/install/templates/message_model.rb.tt +0 -4
  102. data/lib/generators/lex_llm/install/templates/model_model.rb.tt +0 -3
  103. data/lib/generators/lex_llm/install/templates/tool_call_model.rb.tt +0 -3
  104. data/lib/generators/lex_llm/schema/schema_generator.rb +0 -26
  105. data/lib/generators/lex_llm/schema/templates/schema.rb.tt +0 -2
  106. data/lib/generators/lex_llm/tool/templates/tool.rb.tt +0 -9
  107. data/lib/generators/lex_llm/tool/templates/tool_call.html.erb.tt +0 -13
  108. data/lib/generators/lex_llm/tool/templates/tool_result.html.erb.tt +0 -13
  109. data/lib/generators/lex_llm/tool/tool_generator.rb +0 -96
  110. data/lib/generators/lex_llm/upgrade_to_v1_10/templates/add_v1_10_message_columns.rb.tt +0 -19
  111. data/lib/generators/lex_llm/upgrade_to_v1_10/upgrade_to_v1_10_generator.rb +0 -50
  112. data/lib/generators/lex_llm/upgrade_to_v1_14/templates/add_v1_14_tool_call_columns.rb.tt +0 -7
  113. data/lib/generators/lex_llm/upgrade_to_v1_14/upgrade_to_v1_14_generator.rb +0 -49
  114. data/lib/generators/lex_llm/upgrade_to_v1_7/templates/migration.rb.tt +0 -145
  115. data/lib/generators/lex_llm/upgrade_to_v1_7/upgrade_to_v1_7_generator.rb +0 -122
  116. data/lib/generators/lex_llm/upgrade_to_v1_9/templates/add_v1_9_message_columns.rb.tt +0 -15
  117. data/lib/generators/lex_llm/upgrade_to_v1_9/upgrade_to_v1_9_generator.rb +0 -49
  118. data/lib/lex_llm/active_record/acts_as.rb +0 -180
  119. data/lib/lex_llm/active_record/acts_as_legacy.rb +0 -503
  120. data/lib/lex_llm/active_record/chat_methods.rb +0 -468
  121. data/lib/lex_llm/active_record/message_methods.rb +0 -131
  122. data/lib/lex_llm/active_record/model_methods.rb +0 -76
  123. data/lib/lex_llm/active_record/payload_helpers.rb +0 -26
  124. data/lib/lex_llm/active_record/tool_call_methods.rb +0 -15
  125. data/lib/lex_llm/agent.rb +0 -365
  126. data/lib/lex_llm/aliases.rb +0 -38
  127. data/lib/lex_llm/attachment.rb +0 -223
  128. data/lib/lex_llm/chat.rb +0 -351
  129. data/lib/lex_llm/chunk.rb +0 -6
  130. data/lib/lex_llm/configuration.rb +0 -81
  131. data/lib/lex_llm/connection.rb +0 -130
  132. data/lib/lex_llm/content.rb +0 -77
  133. data/lib/lex_llm/context.rb +0 -29
  134. data/lib/lex_llm/embedding.rb +0 -29
  135. data/lib/lex_llm/error.rb +0 -112
  136. data/lib/lex_llm/image.rb +0 -105
  137. data/lib/lex_llm/message.rb +0 -107
  138. data/lib/lex_llm/mime_type.rb +0 -71
  139. data/lib/lex_llm/model/info.rb +0 -113
  140. data/lib/lex_llm/model/modalities.rb +0 -22
  141. data/lib/lex_llm/model/pricing.rb +0 -48
  142. data/lib/lex_llm/model/pricing_category.rb +0 -46
  143. data/lib/lex_llm/model/pricing_tier.rb +0 -33
  144. data/lib/lex_llm/model.rb +0 -7
  145. data/lib/lex_llm/models.rb +0 -506
  146. data/lib/lex_llm/moderation.rb +0 -56
  147. data/lib/lex_llm/provider/open_ai_compatible.rb +0 -219
  148. data/lib/lex_llm/provider.rb +0 -278
  149. data/lib/lex_llm/railtie.rb +0 -35
  150. data/lib/lex_llm/routing/lane_key.rb +0 -51
  151. data/lib/lex_llm/routing/model_offering.rb +0 -169
  152. data/lib/lex_llm/routing.rb +0 -7
  153. data/lib/lex_llm/stream_accumulator.rb +0 -203
  154. data/lib/lex_llm/streaming.rb +0 -175
  155. data/lib/lex_llm/thinking.rb +0 -49
  156. data/lib/lex_llm/tokens.rb +0 -47
  157. data/lib/lex_llm/tool.rb +0 -254
  158. data/lib/lex_llm/tool_call.rb +0 -25
  159. data/lib/lex_llm/transcription.rb +0 -35
  160. data/lib/lex_llm/utils.rb +0 -91
  161. data/lib/lex_llm/version.rb +0 -5
  162. data/lib/lex_llm.rb +0 -96
  163. data/lib/tasks/lex_llm.rake +0 -23
  164. /data/lib/{lex_llm → legion/extensions/llm}/aliases.json +0 -0
  165. /data/lib/{lex_llm → legion/extensions/llm}/models.json +0 -0
@@ -0,0 +1,258 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ruby_llm/schema'
4
+
5
+ module Legion
6
+ module Extensions
7
+ module Llm
8
+ # Parameter definition for Tool methods.
9
+ class Parameter
10
+ attr_reader :name, :type, :description, :required
11
+
12
+ def initialize(name, type: 'string', desc: nil, required: true)
13
+ @name = name
14
+ @type = type
15
+ @description = desc
16
+ @required = required
17
+ end
18
+ end
19
+
20
+ # Base class for creating tools that AI models can use
21
+ class Tool
22
+ # Stops conversation continuation after tool execution
23
+ class Halt
24
+ attr_reader :content
25
+
26
+ def initialize(content)
27
+ @content = content
28
+ end
29
+
30
+ def to_s
31
+ @content.to_s
32
+ end
33
+ end
34
+
35
+ class << self
36
+ attr_reader :params_schema_definition
37
+
38
+ def description(text = nil)
39
+ return @description unless text
40
+
41
+ @description = text
42
+ end
43
+
44
+ def param(name, **)
45
+ parameters[name] = Parameter.new(name, **)
46
+ end
47
+
48
+ def parameters
49
+ @parameters ||= {}
50
+ end
51
+
52
+ def params(schema = nil, &block)
53
+ @params_schema_definition = SchemaDefinition.new(schema:, block:)
54
+ self
55
+ end
56
+
57
+ def with_params(**params)
58
+ @provider_params = params
59
+ self
60
+ end
61
+
62
+ def provider_params
63
+ @provider_params ||= {}
64
+ end
65
+ end
66
+
67
+ def name
68
+ klass_name = self.class.name
69
+ normalized = klass_name.to_s.dup.force_encoding('UTF-8').unicode_normalize(:nfkd)
70
+ normalized.encode('ASCII', replace: '')
71
+ .gsub(/[^a-zA-Z0-9_-]/, '-')
72
+ .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
73
+ .gsub(/([a-z\d])([A-Z])/, '\1_\2')
74
+ .downcase
75
+ .delete_suffix('_tool')
76
+ end
77
+
78
+ def description
79
+ self.class.description
80
+ end
81
+
82
+ def parameters
83
+ self.class.parameters
84
+ end
85
+
86
+ def provider_params
87
+ self.class.provider_params
88
+ end
89
+
90
+ def params_schema
91
+ return @params_schema if defined?(@params_schema)
92
+
93
+ @params_schema = begin
94
+ definition = self.class.params_schema_definition
95
+ if definition&.present?
96
+ definition.json_schema
97
+ elsif parameters.any?
98
+ SchemaDefinition.from_parameters(parameters)&.json_schema
99
+ end
100
+ end
101
+ end
102
+
103
+ def call(args)
104
+ normalized_args = normalize_args(args)
105
+ validation_error = validate_keyword_arguments(normalized_args)
106
+ return { error: "Invalid tool arguments: #{validation_error}" } if validation_error
107
+
108
+ Legion::Extensions::Llm.logger.debug { "Tool #{name} called with: #{normalized_args.inspect}" }
109
+ result = execute(**normalized_args)
110
+ Legion::Extensions::Llm.logger.debug { "Tool #{name} returned: #{result.inspect}" }
111
+ result
112
+ end
113
+
114
+ def execute(...)
115
+ raise NotImplementedError, 'Subclasses must implement #execute'
116
+ end
117
+
118
+ protected
119
+
120
+ def halt(message)
121
+ Halt.new(message)
122
+ end
123
+
124
+ def normalize_args(args)
125
+ return {} if args.nil?
126
+ return args.transform_keys(&:to_sym) if args.respond_to?(:transform_keys)
127
+
128
+ {}
129
+ end
130
+
131
+ def validate_keyword_arguments(arguments)
132
+ required_keywords, optional_keywords, accepts_extra_keywords = execute_keyword_signature
133
+
134
+ return nil if required_keywords.empty? && optional_keywords.empty?
135
+
136
+ argument_keys = arguments.keys
137
+ missing_keyword = first_missing_keyword(required_keywords, argument_keys)
138
+ return "missing keyword: #{missing_keyword}" if missing_keyword
139
+ return nil if accepts_extra_keywords
140
+
141
+ allowed_keywords = required_keywords + optional_keywords
142
+ unknown_keyword = first_unknown_keyword(argument_keys, allowed_keywords)
143
+ return "unknown keyword: #{unknown_keyword}" if unknown_keyword
144
+
145
+ nil
146
+ end
147
+
148
+ def execute_keyword_signature
149
+ keyword_signature = method(:execute).parameters
150
+ required_keywords = keyword_signature.filter_map { |kind, name| name if kind == :keyreq }
151
+ optional_keywords = keyword_signature.filter_map { |kind, name| name if kind == :key }
152
+ accepts_extra_keywords = keyword_signature.any? { |kind, _| kind == :keyrest }
153
+
154
+ [required_keywords, optional_keywords, accepts_extra_keywords]
155
+ end
156
+
157
+ def first_missing_keyword(required_keywords, argument_keys)
158
+ (required_keywords - argument_keys).first
159
+ end
160
+
161
+ def first_unknown_keyword(argument_keys, allowed_keywords)
162
+ (argument_keys - allowed_keywords).first
163
+ end
164
+
165
+ # Wraps schema handling for tool parameters, supporting JSON Schema hashes,
166
+ # Legion::Extensions::Llm::Schema instances/classes, and DSL blocks.
167
+ class SchemaDefinition
168
+ def self.from_parameters(parameters)
169
+ return nil if parameters.nil? || parameters.empty?
170
+
171
+ properties = parameters.to_h do |name, param|
172
+ schema = {
173
+ type: map_type(param.type),
174
+ description: param.description
175
+ }.compact
176
+
177
+ schema[:items] = default_items_schema if schema[:type] == 'array'
178
+
179
+ [name.to_s, schema]
180
+ end
181
+
182
+ required = parameters.select { |_, param| param.required }.keys.map(&:to_s)
183
+
184
+ json_schema = {
185
+ type: 'object',
186
+ properties: properties,
187
+ required: required,
188
+ additionalProperties: false,
189
+ strict: true
190
+ }
191
+
192
+ new(schema: json_schema)
193
+ end
194
+
195
+ def self.map_type(type)
196
+ case type.to_s
197
+ when 'integer', 'int' then 'integer'
198
+ when 'number', 'float', 'double' then 'number'
199
+ when 'boolean' then 'boolean'
200
+ when 'array' then 'array'
201
+ when 'object' then 'object'
202
+ else
203
+ 'string'
204
+ end
205
+ end
206
+
207
+ def self.default_items_schema
208
+ { type: 'string' }
209
+ end
210
+
211
+ def initialize(schema: nil, block: nil)
212
+ @schema = schema
213
+ @block = block
214
+ end
215
+
216
+ def present?
217
+ @schema || @block
218
+ end
219
+
220
+ def json_schema
221
+ @json_schema ||= Legion::Extensions::Llm::Utils.deep_stringify_keys(resolve_schema)
222
+ end
223
+
224
+ private
225
+
226
+ def resolve_schema
227
+ return resolve_direct_schema(@schema) if @schema
228
+ return build_from_block(&@block) if @block
229
+
230
+ nil
231
+ end
232
+
233
+ def resolve_direct_schema(schema)
234
+ return extract_schema(schema.to_json_schema) if schema.respond_to?(:to_json_schema)
235
+ return Legion::Extensions::Llm::Utils.deep_dup(schema) if schema.is_a?(Hash)
236
+ if schema.is_a?(Class) && schema.method_defined?(:to_json_schema)
237
+ return extract_schema(schema.new.to_json_schema)
238
+ end
239
+
240
+ nil
241
+ end
242
+
243
+ def build_from_block(&)
244
+ schema_class = Legion::Extensions::Llm::Schema.create(&)
245
+ extract_schema(schema_class.new.to_json_schema)
246
+ end
247
+
248
+ def extract_schema(schema_hash)
249
+ return nil unless schema_hash.is_a?(Hash)
250
+
251
+ schema = schema_hash[:schema] || schema_hash['schema'] || schema_hash
252
+ Legion::Extensions::Llm::Utils.deep_dup(schema)
253
+ end
254
+ end
255
+ end
256
+ end
257
+ end
258
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Llm
6
+ # Represents a function call from an AI model to a Tool.
7
+ class ToolCall
8
+ attr_reader :id, :name, :arguments
9
+ attr_accessor :thought_signature
10
+
11
+ def initialize(id:, name:, arguments: {}, thought_signature: nil)
12
+ @id = id
13
+ @name = name
14
+ @arguments = arguments
15
+ @thought_signature = thought_signature
16
+ end
17
+
18
+ def to_h
19
+ {
20
+ id: @id,
21
+ name: @name,
22
+ arguments: @arguments,
23
+ thought_signature: @thought_signature
24
+ }.compact
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Llm
6
+ # Represents a transcription of audio content.
7
+ class Transcription
8
+ attr_reader :text, :model, :language, :duration, :segments, :input_tokens, :output_tokens
9
+
10
+ def initialize(text:, model:, **attributes)
11
+ @text = text
12
+ @model = model
13
+ @language = attributes[:language]
14
+ @duration = attributes[:duration]
15
+ @segments = attributes[:segments]
16
+ @input_tokens = attributes[:input_tokens]
17
+ @output_tokens = attributes[:output_tokens]
18
+ end
19
+
20
+ def self.transcribe(audio_file, **kwargs)
21
+ model = kwargs.delete(:model)
22
+ language = kwargs.delete(:language)
23
+ provider = kwargs.delete(:provider)
24
+ assume_model_exists = kwargs.delete(:assume_model_exists) { false }
25
+ context = kwargs.delete(:context)
26
+ options = kwargs
27
+
28
+ config = context&.config || Legion::Extensions::Llm.config
29
+ model ||= config.default_transcription_model
30
+ model, provider_instance = Models.resolve(model, provider: provider, assume_exists: assume_model_exists,
31
+ config: config)
32
+ model_id = model.id
33
+
34
+ provider_instance.transcribe(audio_file, model: model_id, language:, **options)
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Llm
6
+ # Provides utility functions for data manipulation within the Legion::Extensions::Llm library
7
+ module Utils
8
+ module_function
9
+
10
+ def hash_get(hash, key)
11
+ hash[key.to_sym] || hash[key.to_s]
12
+ end
13
+
14
+ def to_safe_array(item)
15
+ case item
16
+ when Array
17
+ item
18
+ when Hash
19
+ [item]
20
+ else
21
+ Array(item)
22
+ end
23
+ end
24
+
25
+ def to_time(value)
26
+ return unless value
27
+
28
+ value.is_a?(Time) ? value : Time.parse(value.to_s)
29
+ end
30
+
31
+ def to_date(value)
32
+ return unless value
33
+
34
+ value.is_a?(Date) ? value : Date.parse(value.to_s)
35
+ end
36
+
37
+ def deep_merge(original, overrides)
38
+ original.merge(overrides) do |_key, original_value, overrides_value|
39
+ if original_value.is_a?(Hash) && overrides_value.is_a?(Hash)
40
+ deep_merge(original_value, overrides_value)
41
+ else
42
+ overrides_value
43
+ end
44
+ end
45
+ end
46
+
47
+ def deep_dup(value)
48
+ case value
49
+ when Hash
50
+ value.each_with_object({}) do |(key, val), duped|
51
+ duped[deep_dup(key)] = deep_dup(val)
52
+ end
53
+ when Array
54
+ value.map { |item| deep_dup(item) }
55
+ else
56
+ begin
57
+ value.dup
58
+ rescue TypeError
59
+ value
60
+ end
61
+ end
62
+ end
63
+
64
+ def deep_stringify_keys(value)
65
+ case value
66
+ when Hash
67
+ value.each_with_object({}) do |(key, val), result|
68
+ result[key.to_s] = deep_stringify_keys(val)
69
+ end
70
+ when Array
71
+ value.map { |item| deep_stringify_keys(item) }
72
+ when Symbol
73
+ value.to_s
74
+ else
75
+ value
76
+ end
77
+ end
78
+
79
+ def deep_symbolize_keys(value)
80
+ case value
81
+ when Hash
82
+ value.each_with_object({}) do |(key, val), result|
83
+ symbolized_key = key.respond_to?(:to_sym) ? key.to_sym : key
84
+ result[symbolized_key] = deep_symbolize_keys(val)
85
+ end
86
+ when Array
87
+ value.map { |item| deep_symbolize_keys(item) }
88
+ else
89
+ value
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Llm
6
+ VERSION = '0.1.4'
7
+ end
8
+ end
9
+ end
@@ -1,23 +1,100 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'lex_llm'
4
- require 'legion/extensions/llm/provider_settings'
5
- require 'legion/extensions/llm/transport/fleet_lane'
3
+ require 'base64'
4
+ require 'date'
5
+ require 'digest/sha1'
6
+ require 'event_stream_parser'
7
+ require 'faraday'
8
+ require 'faraday/multipart'
9
+ require 'faraday/retry'
10
+ require 'legion/json'
11
+ require 'logger'
12
+ require 'marcel'
13
+ require 'ruby_llm/schema'
14
+ require 'securerandom'
15
+ require 'time'
16
+ require 'zeitwerk'
17
+ require_relative 'llm/version'
6
18
 
7
19
  module Legion
8
20
  module Extensions
9
21
  # Legion-native namespace for the shared LLM provider framework.
10
22
  module Llm
11
- VERSION = LexLLM::VERSION unless const_defined?(:VERSION, false)
23
+ loader = Zeitwerk::Loader.new
24
+ loader.tag = 'lex-llm'
25
+ loader.inflector.inflect(
26
+ 'api' => 'API',
27
+ 'llm' => 'Llm',
28
+ 'open_ai_compatible' => 'OpenAICompatible',
29
+ 'pdf' => 'PDF',
30
+ 'ui' => 'UI'
31
+ )
32
+ loader.ignore("#{__dir__}/llm/version.rb")
33
+ loader.push_dir("#{__dir__}/llm", namespace: self)
34
+ loader.setup
35
+
36
+ Schema = ::RubyLLM::Schema unless const_defined?(:Schema, false)
12
37
 
13
38
  # Provider-neutral value objects exposed under the Legion extension namespace.
14
39
  module Types
15
- ModelOffering = LexLLM::Routing::ModelOffering unless const_defined?(:ModelOffering, false)
40
+ ModelOffering = Routing::ModelOffering unless const_defined?(:ModelOffering, false)
16
41
  end
17
42
 
18
43
  # Shared routing helpers exposed under the Legion extension namespace.
19
44
  module Routing
20
- LaneKey = LexLLM::Routing::LaneKey unless const_defined?(:LaneKey, false)
45
+ LaneKey = ::Legion::Extensions::Llm::Routing::LaneKey unless const_defined?(:LaneKey, false)
46
+ end
47
+
48
+ class << self
49
+ def context
50
+ context_config = config.dup
51
+ yield context_config if block_given?
52
+ Context.new(context_config)
53
+ end
54
+
55
+ def chat(...)
56
+ Chat.new(...)
57
+ end
58
+
59
+ def embed(...)
60
+ Embedding.embed(...)
61
+ end
62
+
63
+ def moderate(...)
64
+ Moderation.moderate(...)
65
+ end
66
+
67
+ def paint(...)
68
+ Image.paint(...)
69
+ end
70
+
71
+ def transcribe(...)
72
+ Transcription.transcribe(...)
73
+ end
74
+
75
+ def models
76
+ Models.instance
77
+ end
78
+
79
+ def providers
80
+ Provider.providers.values
81
+ end
82
+
83
+ def configure
84
+ yield config
85
+ end
86
+
87
+ def config
88
+ @config ||= Configuration.new
89
+ end
90
+
91
+ def logger
92
+ @logger ||= config.logger || Logger.new(
93
+ config.log_file,
94
+ progname: 'Legion::Extensions::Llm',
95
+ level: config.log_level
96
+ )
97
+ end
21
98
  end
22
99
 
23
100
  def self.default_settings
@@ -45,6 +122,8 @@ module Legion
45
122
  def self.provider_settings(...)
46
123
  ProviderSettings.build(...)
47
124
  end
125
+
126
+ loader.eager_load
48
127
  end
49
128
  end
50
129
  end