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 Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Class Attribute Details

.registry_publisherObject



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

def registry_publisher
  @registry_publisher ||= RegistryPublisher.new
end

Class Method Details

.capabilitiesObject



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

def capabilities = Capabilities

.configuration_optionsObject



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

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



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

def configuration_requirements = %i[azure_foundry_endpoint]

.deployment_config(model_id, config:) ⇒ Object



48
49
50
51
52
53
54
55
# File 'lib/legion/extensions/llm/azure_foundry/provider.rb', line 48

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



57
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/legion/extensions/llm/azure_foundry/provider.rb', line 57

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



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

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



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

def slug = 'azure_foundry'

Instance Method Details

#api_baseObject



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

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



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

def chat_url = completion_url

#completion_urlObject



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

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



145
146
147
148
149
150
151
152
153
154
# File 'lib/legion/extensions/llm/azure_foundry/provider.rb', line 145

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



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

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

#headersObject



131
132
133
134
135
136
# File 'lib/legion/extensions/llm/azure_foundry/provider.rb', line 131

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

#health(live: false) ⇒ Object



173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
# File 'lib/legion/extensions/llm/azure_foundry/provider.rb', line 173

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



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

def health_url = models_url

#list_modelsObject



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

def list_models
  models = discover_offerings(live: false).map { |offering| model_info_from_offering(offering) }
  self.class.registry_publisher.publish_models_async(models, readiness: readiness(live: false))
  models
end

#models_urlObject



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

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



156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
# File 'lib/legion/extensions/llm/azure_foundry/provider.rb', line 156

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



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

def readiness(live: false)
  health(live: live).merge(local: false, remote: true, endpoints: endpoint_manifest).tap do ||
    self.class.registry_publisher.publish_readiness_async() if live
  end
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



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

def stream_url = completion_url