Class: Legion::Extensions::Llm::Provider
- Inherits:
-
Object
- Object
- Legion::Extensions::Llm::Provider
- Includes:
- Streaming, Logging::Helper
- Defined in:
- lib/legion/extensions/llm/provider.rb,
lib/legion/extensions/llm/provider/open_ai_compatible.rb
Overview
Base class for LLM providers.
Defined Under Namespace
Modules: OpenAICompatible
Instance Attribute Summary collapse
-
#config ⇒ Object
readonly
Returns the value of attribute config.
-
#connection ⇒ Object
readonly
Returns the value of attribute connection.
Class Method Summary collapse
- .assume_models_exist? ⇒ Boolean
- .capabilities ⇒ Object
- .configuration_options ⇒ Object
- .configuration_requirements ⇒ Object
- .configured?(config) ⇒ Boolean
- .configured_providers(config) ⇒ Object
- .configured_remote_providers(config) ⇒ Object
- .for(model) ⇒ Object
- .local? ⇒ Boolean
- .local_providers ⇒ Object
- .name ⇒ Object
- .providers ⇒ Object
- .register(name, provider_class) ⇒ Object
- .remote? ⇒ Boolean
- .remote_providers ⇒ Object
- .resolve(name) ⇒ Object
-
.resolve_model_id(model_id, config: nil) ⇒ Object
rubocop:disable Lint/UnusedMethodArgument.
- .slug ⇒ Object
Instance Method Summary collapse
- #api_base ⇒ Object
- #assume_models_exist? ⇒ Boolean
- #capabilities ⇒ Object
-
#complete(messages, tools:, temperature:, model:, params: {}, headers: {}, schema: nil, thinking: nil, tool_prefs: nil) ⇒ Object
rubocop:disable Metrics/ParameterLists.
- #configuration_requirements ⇒ Object
- #configured? ⇒ Boolean
- #embed(text, model:, dimensions:) ⇒ Object
- #endpoint_manifest ⇒ Object
- #format_messages(messages) ⇒ Object
- #format_tool_calls(_tool_calls) ⇒ Object
- #headers ⇒ Object
-
#initialize(config) ⇒ Provider
constructor
A new instance of Provider.
-
#list_models ⇒ Object
rubocop:enable Metrics/ParameterLists.
- #local? ⇒ Boolean
- #moderate(input, model:) ⇒ Object
- #name ⇒ Object
-
#paint(prompt, model:, size:, with: nil, mask: nil, params: {}) ⇒ Object
rubocop:disable Metrics/ParameterLists.
- #parse_error(response) ⇒ Object
- #parse_tool_calls(_tool_calls) ⇒ Object
- #readiness(live: false) ⇒ Object
- #remote? ⇒ Boolean
- #slug ⇒ Object
- #transcribe(audio_file, model:, language:) ⇒ Object
Methods included from Streaming
build_on_data_handler, build_stream_callback, build_stream_error_response, error_chunk?, faraday_1?, handle_data, handle_error_chunk, handle_error_event, handle_failed_response, handle_json_error_chunk, handle_parsed_error, handle_sse, handle_stream, json_error_payload?, parse_error_from_json, parse_streaming_error, process_stream_chunk, stream_response
Constructor Details
#initialize(config) ⇒ Provider
Returns a new instance of Provider.
13 14 15 16 17 |
# File 'lib/legion/extensions/llm/provider.rb', line 13 def initialize(config) @config = config ensure_configured! @connection = Connection.new(self, @config) end |
Instance Attribute Details
#config ⇒ Object (readonly)
Returns the value of attribute config.
11 12 13 |
# File 'lib/legion/extensions/llm/provider.rb', line 11 def config @config end |
#connection ⇒ Object (readonly)
Returns the value of attribute connection.
11 12 13 |
# File 'lib/legion/extensions/llm/provider.rb', line 11 def connection @connection end |
Class Method Details
.assume_models_exist? ⇒ Boolean
216 217 218 |
# File 'lib/legion/extensions/llm/provider.rb', line 216 def assume_models_exist? false end |
.capabilities ⇒ Object
196 197 198 |
# File 'lib/legion/extensions/llm/provider.rb', line 196 def capabilities nil end |
.configuration_options ⇒ Object
204 205 206 |
# File 'lib/legion/extensions/llm/provider.rb', line 204 def [] end |
.configuration_requirements ⇒ Object
200 201 202 |
# File 'lib/legion/extensions/llm/provider.rb', line 200 def configuration_requirements [] end |
.configured?(config) ⇒ Boolean
224 225 226 |
# File 'lib/legion/extensions/llm/provider.rb', line 224 def configured?(config) configuration_requirements.all? { |req| config.send(req) } end |
.configured_providers(config) ⇒ Object
256 257 258 259 260 |
# File 'lib/legion/extensions/llm/provider.rb', line 256 def configured_providers(config) providers.select do |_slug, provider_class| provider_class.configured?(config) end.values end |
.configured_remote_providers(config) ⇒ Object
262 263 264 265 266 |
# File 'lib/legion/extensions/llm/provider.rb', line 262 def configured_remote_providers(config) providers.select do |_slug, provider_class| provider_class.remote? && provider_class.configured?(config) end.values end |
.for(model) ⇒ Object
239 240 241 242 |
# File 'lib/legion/extensions/llm/provider.rb', line 239 def for(model) model_info = Models.find(model) resolve model_info.provider end |
.local? ⇒ Boolean
208 209 210 |
# File 'lib/legion/extensions/llm/provider.rb', line 208 def local? false end |
.local_providers ⇒ Object
248 249 250 |
# File 'lib/legion/extensions/llm/provider.rb', line 248 def local_providers providers.select { |_slug, provider_class| provider_class.local? } end |
.name ⇒ Object
188 189 190 |
# File 'lib/legion/extensions/llm/provider.rb', line 188 def name to_s.split('::').last end |
.providers ⇒ Object
244 245 246 |
# File 'lib/legion/extensions/llm/provider.rb', line 244 def providers @providers ||= {} end |
.register(name, provider_class) ⇒ Object
228 229 230 231 |
# File 'lib/legion/extensions/llm/provider.rb', line 228 def register(name, provider_class) providers[name.to_sym] = provider_class Legion::Extensions::Llm::Configuration.(provider_class.) end |
.remote? ⇒ Boolean
212 213 214 |
# File 'lib/legion/extensions/llm/provider.rb', line 212 def remote? !local? end |
.remote_providers ⇒ Object
252 253 254 |
# File 'lib/legion/extensions/llm/provider.rb', line 252 def remote_providers providers.select { |_slug, provider_class| provider_class.remote? } end |
.resolve(name) ⇒ Object
233 234 235 236 237 |
# File 'lib/legion/extensions/llm/provider.rb', line 233 def resolve(name) return nil if name.nil? providers[name.to_sym] end |
.resolve_model_id(model_id, config: nil) ⇒ Object
rubocop:disable Lint/UnusedMethodArgument
220 221 222 |
# File 'lib/legion/extensions/llm/provider.rb', line 220 def resolve_model_id(model_id, config: nil) # rubocop:disable Lint/UnusedMethodArgument model_id end |
.slug ⇒ Object
192 193 194 |
# File 'lib/legion/extensions/llm/provider.rb', line 192 def slug name.downcase end |
Instance Method Details
#api_base ⇒ Object
19 20 21 |
# File 'lib/legion/extensions/llm/provider.rb', line 19 def api_base raise NotImplementedError end |
#assume_models_exist? ⇒ Boolean
113 114 115 |
# File 'lib/legion/extensions/llm/provider.rb', line 113 def assume_models_exist? self.class.assume_models_exist? end |
#capabilities ⇒ Object
35 36 37 |
# File 'lib/legion/extensions/llm/provider.rb', line 35 def capabilities self.class.capabilities end |
#complete(messages, tools:, temperature:, model:, params: {}, headers: {}, schema: nil, thinking: nil, tool_prefs: nil) ⇒ Object
rubocop:disable Metrics/ParameterLists
44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
# File 'lib/legion/extensions/llm/provider.rb', line 44 def complete(, tools:, temperature:, model:, params: {}, headers: {}, schema: nil, thinking: nil, tool_prefs: nil, &) normalized_temperature = maybe_normalize_temperature(temperature, model) payload = Utils.deep_merge( render_payload( , tools: tools, tool_prefs: tool_prefs, temperature: normalized_temperature, model: model, stream: block_given?, schema: schema, thinking: thinking ), params ) if block_given? stream_response @connection, payload, headers, & else sync_response @connection, payload, headers end end |
#configuration_requirements ⇒ Object
39 40 41 |
# File 'lib/legion/extensions/llm/provider.rb', line 39 def configuration_requirements self.class.configuration_requirements end |
#configured? ⇒ Boolean
101 102 103 |
# File 'lib/legion/extensions/llm/provider.rb', line 101 def configured? configuration_requirements.all? { |req| @config.send(req) } end |
#embed(text, model:, dimensions:) ⇒ Object
75 76 77 78 79 |
# File 'lib/legion/extensions/llm/provider.rb', line 75 def (text, model:, dimensions:) payload = (text, model:, dimensions:) response = @connection.post((model:), payload) (response, model:, text:) end |
#endpoint_manifest ⇒ Object
139 140 141 142 143 144 145 146 147 148 |
# File 'lib/legion/extensions/llm/provider.rb', line 139 def endpoint_manifest endpoint_methods.each_with_object({}) do |(key, method_name), result| next unless respond_to?(method_name) value = public_send(method_name) result[key] = value unless value.nil? rescue ArgumentError, NotImplementedError next end end |
#format_messages(messages) ⇒ Object
170 171 172 173 174 175 176 177 |
# File 'lib/legion/extensions/llm/provider.rb', line 170 def () .map do |msg| { role: msg.role.to_s, content: msg.content } end end |
#format_tool_calls(_tool_calls) ⇒ Object
179 180 181 |
# File 'lib/legion/extensions/llm/provider.rb', line 179 def format_tool_calls(_tool_calls) nil end |
#headers ⇒ Object
23 24 25 |
# File 'lib/legion/extensions/llm/provider.rb', line 23 def headers {} end |
#list_models ⇒ Object
rubocop:enable Metrics/ParameterLists
70 71 72 73 |
# File 'lib/legion/extensions/llm/provider.rb', line 70 def list_models response = @connection.get models_url parse_list_models_response response, slug, capabilities end |
#local? ⇒ Boolean
105 106 107 |
# File 'lib/legion/extensions/llm/provider.rb', line 105 def local? self.class.local? end |
#moderate(input, model:) ⇒ Object
81 82 83 84 85 |
# File 'lib/legion/extensions/llm/provider.rb', line 81 def moderate(input, model:) payload = render_moderation_payload(input, model:) response = @connection.post moderation_url, payload parse_moderation_response(response, model:) end |
#name ⇒ Object
31 32 33 |
# File 'lib/legion/extensions/llm/provider.rb', line 31 def name self.class.name end |
#paint(prompt, model:, size:, with: nil, mask: nil, params: {}) ⇒ Object
rubocop:disable Metrics/ParameterLists
87 88 89 90 91 92 |
# File 'lib/legion/extensions/llm/provider.rb', line 87 def paint(prompt, model:, size:, with: nil, mask: nil, params: {}) # rubocop:disable Metrics/ParameterLists validate_paint_inputs!(with:, mask:) payload = render_image_payload(prompt, model:, size:, with:, mask:, params:) response = @connection.post images_url(with:, mask:), payload parse_image_response(response, model:) end |
#parse_error(response) ⇒ Object
150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 |
# File 'lib/legion/extensions/llm/provider.rb', line 150 def parse_error(response) return if response.body.empty? body = try_parse_json(response.body) case body when Hash error = body['error'] return error if error.is_a?(String) body.dig('error', 'message') when Array body.map do |part| error = part['error'] error.is_a?(String) ? error : part.dig('error', 'message') end.join('. ') else body end end |
#parse_tool_calls(_tool_calls) ⇒ Object
183 184 185 |
# File 'lib/legion/extensions/llm/provider.rb', line 183 def parse_tool_calls(_tool_calls) nil end |
#readiness(live: false) ⇒ Object
117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 |
# File 'lib/legion/extensions/llm/provider.rb', line 117 def readiness(live: false) = { provider: slug.to_sym, name: name, configured: configured?, ready: configured?, local: local?, remote: remote?, api_base: api_base, endpoints: endpoint_manifest, live: live } return .merge(health: { checked: false }) unless live && [:endpoints][:health] response = @connection.get([:endpoints][:health]) .merge(ready: configured? && health_ready?(response.body), health: response.body) rescue StandardError => e handle_exception(e, level: :warn, handled: true, operation: 'llm.provider.readiness') .merge(ready: false, health: { error: e.class.name, message: e. }) end |
#remote? ⇒ Boolean
109 110 111 |
# File 'lib/legion/extensions/llm/provider.rb', line 109 def remote? self.class.remote? end |
#slug ⇒ Object
27 28 29 |
# File 'lib/legion/extensions/llm/provider.rb', line 27 def slug self.class.slug end |
#transcribe(audio_file, model:, language:) ⇒ Object
94 95 96 97 98 99 |
# File 'lib/legion/extensions/llm/provider.rb', line 94 def transcribe(audio_file, model:, language:, **) file_part = build_audio_file_part(audio_file) payload = render_transcription_payload(file_part, model:, language:, **) response = @connection.post transcription_url, payload parse_transcription_response(response, model:) end |