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

Inherits:
Provider
  • Object
show all
Includes:
Provider::OpenAICompatible
Defined in:
lib/legion/extensions/llm/azure_foundry/provider.rb

Overview

Azure AI Foundry and Azure OpenAI hosted provider surface.

Defined Under Namespace

Modules: Capabilities

Constant Summary collapse

DEFAULT_API_VERSION =
'2024-05-01-preview'
MODEL_INFERENCE_SURFACE =
:model_inference
OPENAI_V1_SURFACE =
:openai_v1

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.capabilitiesObject



35
# File 'lib/legion/extensions/llm/azure_foundry/provider.rb', line 35

def capabilities = Capabilities

.configuration_optionsObject



24
25
26
27
28
29
30
31
32
33
# File 'lib/legion/extensions/llm/azure_foundry/provider.rb', line 24

def configuration_options
  %i[
    azure_foundry_endpoint
    azure_foundry_api_key
    azure_foundry_bearer_token
    azure_foundry_api_version
    azure_foundry_surface
    azure_foundry_deployments
  ]
end

.configuration_requirementsObject



22
# File 'lib/legion/extensions/llm/azure_foundry/provider.rb', line 22

def configuration_requirements = %i[azure_foundry_endpoint]

.deployment_config(model_id, config:) ⇒ Object



42
43
44
45
46
47
48
49
# File 'lib/legion/extensions/llm/azure_foundry/provider.rb', line 42

def deployment_config(model_id, config:)
  deployments = config&.azure_foundry_deployments
  entries = normalize_deployments(deployments)
  entries.find do |entry|
    [value_for(entry, :deployment), value_for(entry, :model), value_for(entry, :canonical_model_alias)]
      .compact.map(&:to_s).include?(model_id.to_s)
  end
end

.normalize_deployments(deployments) ⇒ Object



51
52
53
54
55
56
57
58
59
60
61
62
# File 'lib/legion/extensions/llm/azure_foundry/provider.rb', line 51

def normalize_deployments(deployments)
  case deployments
  when Hash
    deployments.map do |name, |
      value = .to_h
      value[:deployment] ||= name
      value
    end
  else
    Array(deployments).map { |deployment| normalize_deployment_entry(deployment) }
  end
end

.resolve_model_id(model_id, config: nil) ⇒ Object



37
38
39
40
# File 'lib/legion/extensions/llm/azure_foundry/provider.rb', line 37

def resolve_model_id(model_id, config: nil)
  deployment = deployment_config(model_id, config:)
  value_for(deployment, :deployment) || value_for(deployment, :model) || model_id.to_s
end

.slugObject



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

def slug = 'azure_foundry'

Instance Method Details

#api_baseObject



117
118
119
120
121
122
123
# File 'lib/legion/extensions/llm/azure_foundry/provider.rb', line 117

def api_base
  endpoint = config.azure_foundry_endpoint.to_s.sub(%r{/*\z}, '')
  return "#{endpoint}/openai/v1" if surface == OPENAI_V1_SURFACE && !endpoint.end_with?('/openai/v1')
  return endpoint.delete_suffix('/models') if surface == MODEL_INFERENCE_SURFACE

  endpoint
end

#chat(messages, model:, temperature: nil, max_tokens: nil, tools: {}, tool_prefs: nil, params: {}) ⇒ Object

rubocop:disable Metrics/ParameterLists



202
203
204
# File 'lib/legion/extensions/llm/azure_foundry/provider.rb', line 202

def chat(messages, model:, temperature: nil, max_tokens: nil, tools: {}, tool_prefs: nil, params: {}) # rubocop:disable Metrics/ParameterLists
  complete(messages, tools:, temperature:, model: model_info(model, max_tokens:), params:, tool_prefs:)
end

#chat_urlObject



133
# File 'lib/legion/extensions/llm/azure_foundry/provider.rb', line 133

def chat_url = completion_url

#completion_urlObject



132
# File 'lib/legion/extensions/llm/azure_foundry/provider.rb', line 132

def completion_url = path_for('chat/completions')

#count_tokens(messages, model:) ⇒ Object



217
218
219
220
221
222
223
224
225
# File 'lib/legion/extensions/llm/azure_foundry/provider.rb', line 217

def count_tokens(messages, model:, **)
  {
    provider_family: :azure_foundry,
    model: model_id(model),
    supported: false,
    reason: 'Azure AI Foundry REST docs do not define a portable token-counting endpoint for this surface.',
    estimated_input_characters: messages.sum { |message| message.content.to_s.length }
  }
end

#discover_offerings(live: false, **filters) ⇒ Object



139
140
141
142
143
144
145
146
147
148
# File 'lib/legion/extensions/llm/azure_foundry/provider.rb', line 139

def discover_offerings(live: false, **filters)
  offerings = configured_deployments.filter_map { |deployment| offering_from_config(deployment) }
  return filter_offerings(offerings, **filters) unless live

  filter_offerings(offerings, **filters).map do |offering|
    (offering)
  rescue StandardError => e
    with_health(offering, ready: false, checked: true, error: e)
  end
end

#embed(text, model:, dimensions: nil, input_type: nil) ⇒ Object



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

def embed(text, model:, dimensions: nil, input_type: nil)
  payload = render_embedding_payload(text, model: model_id(model), dimensions:)
  payload[:input_type] = input_type if input_type
  response = connection.post(embedding_url(model:), payload)
  parse_embedding_response(response, model: model_id(model), text:)
end

#embedding_urlObject



136
# File 'lib/legion/extensions/llm/azure_foundry/provider.rb', line 136

def embedding_url(**) = path_for('embeddings')

#headersObject



125
126
127
128
129
130
# File 'lib/legion/extensions/llm/azure_foundry/provider.rb', line 125

def headers
  {
    'api-key' => config.azure_foundry_api_key,
    'Authorization' => bearer_header
  }.compact
end

#health(live: false) ⇒ Object



167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
# File 'lib/legion/extensions/llm/azure_foundry/provider.rb', line 167

def health(live: false)
  baseline = {
    provider: :azure_foundry,
    configured: configured?,
    ready: configured?,
    live: live,
    api_base: api_base,
    surface: surface
  }
  return baseline.merge(checked: false) unless live

  response = connection.get(health_url)
  baseline.merge(checked: true, model_info: response.body)
rescue StandardError => e
  baseline.merge(checked: true, ready: false, error: e.class.name, message: e.message)
end

#health_urlObject



137
# File 'lib/legion/extensions/llm/azure_foundry/provider.rb', line 137

def health_url = models_url

#list_modelsObject



188
189
190
191
192
193
194
195
196
197
198
199
200
# File 'lib/legion/extensions/llm/azure_foundry/provider.rb', line 188

def list_models
  discover_offerings(live: false).map do |offering|
    Legion::Extensions::Llm::Model::Info.new(
      id: offering.model,
      name: offering.[:canonical_model_alias] || offering.model,
      provider: :azure_foundry,
      family: offering.[:model_family],
      capabilities: offering.capabilities.map(&:to_s),
      modalities: modalities_for_capabilities(offering.capabilities.map(&:to_s)),
      metadata: offering.to_h
    )
  end
end

#models_urlObject



135
# File 'lib/legion/extensions/llm/azure_foundry/provider.rb', line 135

def models_url = path_for('info')

#offering_for(model:, model_family: nil, canonical_model_alias: nil, instance_id: :default, usage_type: nil, **metadata) ⇒ Object

rubocop:disable Metrics/ParameterLists



150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
# File 'lib/legion/extensions/llm/azure_foundry/provider.rb', line 150

def offering_for(model:, model_family: nil, canonical_model_alias: nil, instance_id: :default, # rubocop:disable Metrics/ParameterLists
                 usage_type: nil, **)
  deployment = self.class.deployment_config(model, config:)
  model_id = self.class.resolve_model_id(model, config:)
  configured_family = value_for(deployment, :model_family)
  configured_alias = value_for(deployment, :canonical_model_alias)

  build_offering(
    model: model_id,
    instance_id: instance_id,
    model_family: normalize_family(model_family || configured_family || infer_model_family(model_id)),
    canonical_model_alias: canonical_model_alias || configured_alias,
    usage_type: usage_type || value_for(deployment, :usage_type) || usage_type_for(model_id),
    metadata: .merge((deployment))
  )
end

#readiness(live: false) ⇒ Object



184
185
186
# File 'lib/legion/extensions/llm/azure_foundry/provider.rb', line 184

def readiness(live: false)
  health(live: live).merge(local: false, remote: true, endpoints: endpoint_manifest)
end

#stream(messages, model:, temperature: nil, max_tokens: nil, tools: {}, tool_prefs: nil, params: {}) ⇒ Object

rubocop:disable Metrics/ParameterLists



206
207
208
# File 'lib/legion/extensions/llm/azure_foundry/provider.rb', line 206

def stream(messages, model:, temperature: nil, max_tokens: nil, tools: {}, tool_prefs: nil, params: {}, &) # rubocop:disable Metrics/ParameterLists
  complete(messages, tools:, temperature:, model: model_info(model, max_tokens:), params:, tool_prefs:, &)
end

#stream_urlObject



134
# File 'lib/legion/extensions/llm/azure_foundry/provider.rb', line 134

def stream_url = completion_url