Class: Legion::Extensions::Llm::AzureFoundry::Provider
- Inherits:
-
Provider
- Object
- Provider
- Legion::Extensions::Llm::AzureFoundry::Provider
- 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
- .capabilities ⇒ Object
- .configuration_options ⇒ Object
- .configuration_requirements ⇒ Object
- .default_tier ⇒ Object
- .default_transport ⇒ Object
- .deployment_config(model_id, config:) ⇒ Object
- .normalize_deployments(deployments) ⇒ Object
- .registry_publisher ⇒ Object
- .resolve_model_id(model_id, config: nil) ⇒ Object
- .slug ⇒ Object
Instance Method Summary collapse
- #api_base ⇒ Object
- #chat(messages:, model:, **options) ⇒ Object
- #chat_url ⇒ Object
- #completion_url ⇒ Object
- #count_tokens(messages:, model:, **_provider_options) ⇒ Object
- #discover_offerings(live: false, raise_on_unreachable: false, **filters) ⇒ Object
- #embed(text:, model:, **options) ⇒ Object
- #embedding_url ⇒ Object
- #headers ⇒ Object
- #health(live: false) ⇒ Object
- #health_url ⇒ Object
- #list_models ⇒ Object
- #models_url ⇒ Object
-
#offering_for(model:, model_family: nil, canonical_model_alias: nil, instance_id: nil, usage_type: nil, **metadata) ⇒ Object
rubocop:disable Metrics/ParameterLists, Metrics/AbcSize.
- #readiness(live: false) ⇒ Object
- #settings ⇒ Object
- #stream(messages:, model:, **options) ⇒ Object
- #stream_url ⇒ Object
- #stream_usage_supported? ⇒ Boolean
Class Method Details
.capabilities ⇒ Object
36 |
# File 'lib/legion/extensions/llm/azure_foundry/provider.rb', line 36 def capabilities = Capabilities |
.configuration_options ⇒ Object
25 26 27 28 29 30 31 32 33 34 |
# File 'lib/legion/extensions/llm/azure_foundry/provider.rb', line 25 def %i[ azure_foundry_endpoint azure_foundry_api_key azure_foundry_bearer_token azure_foundry_api_version azure_foundry_surface azure_foundry_deployments ] end |
.configuration_requirements ⇒ Object
23 |
# File 'lib/legion/extensions/llm/azure_foundry/provider.rb', line 23 def configuration_requirements = %i[azure_foundry_endpoint] |
.default_tier ⇒ Object
22 |
# File 'lib/legion/extensions/llm/azure_foundry/provider.rb', line 22 def default_tier = :cloud |
.default_transport ⇒ Object
21 |
# File 'lib/legion/extensions/llm/azure_foundry/provider.rb', line 21 def default_transport = :http |
.deployment_config(model_id, config:) ⇒ Object
47 48 49 50 51 52 53 54 |
# File 'lib/legion/extensions/llm/azure_foundry/provider.rb', line 47 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
56 57 58 59 60 61 62 63 64 65 66 67 |
# File 'lib/legion/extensions/llm/azure_foundry/provider.rb', line 56 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 |
.registry_publisher ⇒ Object
38 39 40 |
# File 'lib/legion/extensions/llm/azure_foundry/provider.rb', line 38 def registry_publisher AzureFoundry.registry_publisher end |
.resolve_model_id(model_id, config: nil) ⇒ Object
42 43 44 45 |
# File 'lib/legion/extensions/llm/azure_foundry/provider.rb', line 42 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 |
.slug ⇒ Object
20 |
# File 'lib/legion/extensions/llm/azure_foundry/provider.rb', line 20 def slug = 'azure_foundry' |
Instance Method Details
#api_base ⇒ Object
128 129 130 131 132 133 134 |
# File 'lib/legion/extensions/llm/azure_foundry/provider.rb', line 128 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:, **options) ⇒ Object
196 197 198 199 200 201 202 203 204 205 |
# File 'lib/legion/extensions/llm/azure_foundry/provider.rb', line 196 def chat( messages:, model:, ** ) log.info { "chat request model=#{model} messages=#{.size}" } complete(, tools: .fetch(:tools, {}), temperature: [:temperature], model: model_info(model, max_tokens: [:max_tokens]), params: .fetch(:params, {}), tool_prefs: [:tool_prefs]) end |
#chat_url ⇒ Object
144 |
# File 'lib/legion/extensions/llm/azure_foundry/provider.rb', line 144 def chat_url = completion_url |
#completion_url ⇒ Object
143 |
# File 'lib/legion/extensions/llm/azure_foundry/provider.rb', line 143 def completion_url = path_for('chat/completions') |
#count_tokens(messages:, model:, **_provider_options) ⇒ Object
234 235 236 237 238 239 240 241 242 243 244 245 246 |
# File 'lib/legion/extensions/llm/azure_foundry/provider.rb', line 234 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: .sum { || .content.to_s.length } } end |
#discover_offerings(live: false, raise_on_unreachable: false, **filters) ⇒ Object
299 300 301 302 303 304 305 306 307 308 309 310 |
# File 'lib/legion/extensions/llm/azure_foundry/provider.rb', line 299 def discover_offerings(live: false, raise_on_unreachable: false, **filters) offerings = allowed_offerings return filter_offerings(offerings, **filters) unless live resolved = offerings.map { |offering| (offering) } filter_offerings(resolved, **filters) rescue Faraday::ConnectionFailed, Faraday::TimeoutError => e log.warn("[#{slug}] instance=#{provider_instance_id} unreachable: #{e.}") raise if raise_on_unreachable [] end |
#embed(text:, model:, **options) ⇒ Object
219 220 221 222 223 224 225 226 227 228 229 230 231 232 |
# File 'lib/legion/extensions/llm/azure_foundry/provider.rb', line 219 def ( text:, model:, ** ) log.info { "embed request model=#{model}" } payload = Utils.deep_merge( (text, model: model_id(model), dimensions: [:dimensions]), .fetch(:params, {}) ) payload[:input_type] = [:input_type] if [:input_type] response = connection.post((model:), payload) (response, model: model_id(model), text:) end |
#embedding_url ⇒ Object
147 |
# File 'lib/legion/extensions/llm/azure_foundry/provider.rb', line 147 def (**) = path_for('embeddings') |
#headers ⇒ Object
136 137 138 139 140 141 |
# File 'lib/legion/extensions/llm/azure_foundry/provider.rb', line 136 def headers identity_headers.merge({ 'api-key' => config.azure_foundry_api_key, 'Authorization' => bearer_header }.compact) end |
#health(live: false) ⇒ Object
168 169 170 171 172 173 174 175 176 177 178 179 180 |
# File 'lib/legion/extensions/llm/azure_foundry/provider.rb', line 168 def health(live: false) log.info { "checking health live=#{live} at #{api_base}" } baseline = health_baseline(live) return baseline.merge(checked: false) unless live response = connection.get(health_url) baseline.merge(checked: true, ready: true, status: 'healthy', circuit_state: 'closed', raw: response.body) rescue StandardError => e handle_exception(e, level: :warn, handled: true, operation: 'azure_foundry.health') baseline.merge(checked: true, ready: false, status: 'unhealthy', circuit_state: 'open', error: e.class.name, message: e.) end |
#health_url ⇒ Object
148 |
# File 'lib/legion/extensions/llm/azure_foundry/provider.rb', line 148 def health_url = models_url |
#list_models ⇒ Object
189 190 191 192 193 194 |
# File 'lib/legion/extensions/llm/azure_foundry/provider.rb', line 189 def list_models log.info { "listing configured deployment models from #{api_base}" } 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_url ⇒ Object
146 |
# File 'lib/legion/extensions/llm/azure_foundry/provider.rb', line 146 def models_url = path_for('info') |
#offering_for(model:, model_family: nil, canonical_model_alias: nil, instance_id: nil, usage_type: nil, **metadata) ⇒ Object
rubocop:disable Metrics/ParameterLists, Metrics/AbcSize
150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 |
# File 'lib/legion/extensions/llm/azure_foundry/provider.rb', line 150 def offering_for(model:, model_family: nil, canonical_model_alias: nil, instance_id: nil, # rubocop:disable Metrics/ParameterLists, Metrics/AbcSize 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) resolved_instance_id = instance_id || provider_instance_id build_offering( model: model_id, instance_id: resolved_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
182 183 184 185 186 187 |
# File 'lib/legion/extensions/llm/azure_foundry/provider.rb', line 182 def readiness(live: false) log.info { "checking readiness live=#{live} at #{api_base}" } health(live: live).merge(local: false, remote: true, endpoints: endpoint_manifest).tap do || self.class.registry_publisher.publish_readiness_async() if live end end |
#settings ⇒ Object
124 125 126 |
# File 'lib/legion/extensions/llm/azure_foundry/provider.rb', line 124 def settings AzureFoundry.default_settings.dig(:instances, :default) end |
#stream(messages:, model:, **options) ⇒ Object
207 208 209 210 211 212 213 214 215 216 217 |
# File 'lib/legion/extensions/llm/azure_foundry/provider.rb', line 207 def stream( messages:, model:, **, & ) log.info { "stream request model=#{model} messages=#{.size}" } complete(, tools: .fetch(:tools, {}), temperature: [:temperature], model: model_info(model, max_tokens: [:max_tokens]), params: .fetch(:params, {}), tool_prefs: [:tool_prefs], &) end |
#stream_url ⇒ Object
145 |
# File 'lib/legion/extensions/llm/azure_foundry/provider.rb', line 145 def stream_url = completion_url |
#stream_usage_supported? ⇒ Boolean
122 |
# File 'lib/legion/extensions/llm/azure_foundry/provider.rb', line 122 def stream_usage_supported? = true |