Class: Legion::Extensions::Llm::Provider

Inherits:
Object
  • Object
show all
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

Class Method Summary collapse

Instance Method Summary collapse

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

#configObject (readonly)

Returns the value of attribute config.



11
12
13
# File 'lib/legion/extensions/llm/provider.rb', line 11

def config
  @config
end

#connectionObject (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

Returns:

  • (Boolean)


216
217
218
# File 'lib/legion/extensions/llm/provider.rb', line 216

def assume_models_exist?
  false
end

.capabilitiesObject



196
197
198
# File 'lib/legion/extensions/llm/provider.rb', line 196

def capabilities
  nil
end

.configuration_optionsObject



204
205
206
# File 'lib/legion/extensions/llm/provider.rb', line 204

def configuration_options
  []
end

.configuration_requirementsObject



200
201
202
# File 'lib/legion/extensions/llm/provider.rb', line 200

def configuration_requirements
  []
end

.configured?(config) ⇒ Boolean

Returns:

  • (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

Returns:

  • (Boolean)


208
209
210
# File 'lib/legion/extensions/llm/provider.rb', line 208

def local?
  false
end

.local_providersObject



248
249
250
# File 'lib/legion/extensions/llm/provider.rb', line 248

def local_providers
  providers.select { |_slug, provider_class| provider_class.local? }
end

.nameObject



188
189
190
# File 'lib/legion/extensions/llm/provider.rb', line 188

def name
  to_s.split('::').last
end

.providersObject



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.register_provider_options(provider_class.configuration_options)
end

.remote?Boolean

Returns:

  • (Boolean)


212
213
214
# File 'lib/legion/extensions/llm/provider.rb', line 212

def remote?
  !local?
end

.remote_providersObject



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

.slugObject



192
193
194
# File 'lib/legion/extensions/llm/provider.rb', line 192

def slug
  name.downcase
end

Instance Method Details

#api_baseObject

Raises:

  • (NotImplementedError)


19
20
21
# File 'lib/legion/extensions/llm/provider.rb', line 19

def api_base
  raise NotImplementedError
end

#assume_models_exist?Boolean

Returns:

  • (Boolean)


113
114
115
# File 'lib/legion/extensions/llm/provider.rb', line 113

def assume_models_exist?
  self.class.assume_models_exist?
end

#capabilitiesObject



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(messages, 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(
      messages,
      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_requirementsObject



39
40
41
# File 'lib/legion/extensions/llm/provider.rb', line 39

def configuration_requirements
  self.class.configuration_requirements
end

#configured?Boolean

Returns:

  • (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 embed(text, model:, dimensions:)
  payload = render_embedding_payload(text, model:, dimensions:)
  response = @connection.post(embedding_url(model:), payload)
  parse_embedding_response(response, model:, text:)
end

#endpoint_manifestObject



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 format_messages(messages)
  messages.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

#headersObject



23
24
25
# File 'lib/legion/extensions/llm/provider.rb', line 23

def headers
  {}
end

#list_modelsObject

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

Returns:

  • (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

#nameObject



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.message })
end

#remote?Boolean

Returns:

  • (Boolean)


109
110
111
# File 'lib/legion/extensions/llm/provider.rb', line 109

def remote?
  self.class.remote?
end

#slugObject



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