Module: Legion::LLM::Call::Providers
- Extended by:
- Legion::Logging::Helper
- Defined in:
- lib/legion/llm/call/providers.rb
Constant Summary collapse
- LEX_LLM_PROVIDER_REQUIRES =
{ ollama: 'legion/extensions/llm/ollama', vllm: 'legion/extensions/llm/vllm', anthropic: 'legion/extensions/llm/anthropic', openai: 'legion/extensions/llm/openai', gemini: 'legion/extensions/llm/gemini', mlx: 'legion/extensions/llm/mlx', bedrock: 'legion/extensions/llm/bedrock', azure_foundry: 'legion/extensions/llm/azure_foundry', vertex: 'legion/extensions/llm/vertex' }.freeze
- SAAS_PROVIDERS =
%i[bedrock anthropic openai gemini azure].freeze
Class Method Summary collapse
- .apply_credential_to_config(provider, config, creds) ⇒ Object
- .apply_provider_config(provider, config) ⇒ Object
- .attempt_provider_call(provider, model) ⇒ Object
- .auto_enable_from_resolved_credentials ⇒ Object
- .auto_register_lex_llm_providers ⇒ Object
- .auto_register_providers ⇒ Object
- .broker_has_credential?(provider) ⇒ Boolean
- .collect_credential_candidates(provider, config) ⇒ Object
- .config_enabled?(config) ⇒ Boolean
- .config_value(config, key, default = nil) ⇒ Object
- .configure_anthropic(config) ⇒ Object
- .configure_azure(config) ⇒ Object
- .configure_bedrock(config) ⇒ Object
- .configure_gemini(config) ⇒ Object
- .configure_lex_llm_provider(namespace, provider_name, provider_class, provider_config) ⇒ Object
- .configure_ollama(config) ⇒ Object
- .configure_openai(config) ⇒ Object
- .configure_providers ⇒ Object
- .configure_vllm(config) ⇒ Object
- .constant_defined_path?(path) ⇒ Boolean
- .constant_get_path(path) ⇒ Object
- .credential_available_for?(provider, config) ⇒ Boolean
- .credential_option?(option) ⇒ Boolean
- .env_api_key(provider) ⇒ Object
- .env_present?(key) ⇒ Boolean
- .http_uri!(url, default_port:) ⇒ Object
- .inject_anthropic_cache_control!(opts, provider) ⇒ Object
- .lex_llm_namespace ⇒ Object
- .lex_llm_provider_config_value(provider_name, option, provider_config) ⇒ Object
- .lex_llm_provider_ready?(namespace, provider_name, provider_class) ⇒ Boolean
- .lex_llm_runtime_probe_required?(provider_name) ⇒ Boolean
- .llm_settings_lost?(snapshot) ⇒ Boolean
- .load_lex_llm_base ⇒ Object
- .load_optional_feature(feature) ⇒ Object
- .log_available_providers ⇒ Object
- .native_inventory_model_available?(provider, target_model) ⇒ Boolean
- .normalize_lex_llm_api_base(provider, value) ⇒ Object
- .normalize_vllm_base_url(url) ⇒ Object
- .ollama_running?(config) ⇒ Boolean
- .probe_provider_credentials(provider, model, config) ⇒ Object
- .probe_via_chat(provider, model) ⇒ Object
- .probe_via_model_list(provider, target_model) ⇒ Object
- .providers_settings ⇒ Object
- .recover_openai_with_codex ⇒ Object
- .recover_with_alternative_credentials ⇒ Object
- .resolve_broker_aws_credentials ⇒ Object
- .resolve_broker_credential(provider_name) ⇒ Object
- .resolve_credential_value(value) ⇒ Object
- .resolve_llm_secrets ⇒ Object
- .restore_llm_settings(snapshot) ⇒ Object
- .set_config_value(config, key, value) ⇒ Object
- .setup ⇒ Object
- .snapshot_llm_settings ⇒ Object
- .try_register_native_provider(name, ext_const, runner_const) {|klass| ... } ⇒ Object
- .usable_setting?(value) ⇒ Boolean
- .verify_providers ⇒ Object
- .vllm_running?(config) ⇒ Boolean
Class Method Details
.apply_credential_to_config(provider, config, creds) ⇒ Object
347 348 349 350 351 352 353 354 355 356 |
# File 'lib/legion/llm/call/providers.rb', line 347 def apply_credential_to_config(provider, config, creds) case provider when :bedrock set_config_value(config, :bearer_token, creds[:bearer_token]) if creds[:bearer_token] set_config_value(config, :api_key, creds[:api_key]) if creds[:api_key] set_config_value(config, :secret_key, creds[:secret_key]) if creds[:secret_key] when :anthropic, :openai, :gemini set_config_value(config, :api_key, creds[:api_key]) end end |
.apply_provider_config(provider, config) ⇒ Object
167 168 169 170 171 172 173 174 175 176 177 178 179 |
# File 'lib/legion/llm/call/providers.rb', line 167 def apply_provider_config(provider, config) case provider.to_sym when :bedrock then configure_bedrock(config) when :anthropic then configure_anthropic(config) when :openai then configure_openai(config) when :gemini then configure_gemini(config) when :azure then configure_azure(config) when :ollama then configure_ollama(config) when :vllm then configure_vllm(config) else log.warn "[llm][providers] unknown provider=#{provider}" end end |
.attempt_provider_call(provider, model) ⇒ Object
358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 |
# File 'lib/legion/llm/call/providers.rb', line 358 def attempt_provider_call(provider, model) start_time = Time.now result = probe_via_model_list(provider, model) elapsed = ((Time.now - start_time) * 1000).round case result when :auth_error log.warn "[llm][providers] health_check auth_failed provider=#{provider}" false when :model_missing log.warn "[llm][providers] health_check model_missing provider=#{provider} model=#{model} — provider ok, model unavailable" false when :unavailable log.warn "[llm][providers] health_check unavailable provider=#{provider} reason=native_provider_unregistered" false else log.info "[llm][providers] health_check ok provider=#{provider} model=#{model} elapsed_ms=#{elapsed}" true end rescue StandardError => e log.warn "[llm][providers] health_check failed provider=#{provider} error=#{e.class}" handle_exception(e, level: :warn, operation: 'llm.providers.attempt_provider_call', provider: provider, model: model) false end |
.auto_enable_from_resolved_credentials ⇒ Object
59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
# File 'lib/legion/llm/call/providers.rb', line 59 def auto_enable_from_resolved_credentials log.debug '[llm][providers] auto_enable_from_resolved_credentials.enter' providers_settings.each do |provider, config| next if config_enabled?(config) has_creds = credential_available_for?(provider, config) has_creds ||= broker_has_credential?(provider) unless has_creds next unless has_creds set_config_value(config, :enabled, true) log.info "[llm][providers] auto-enabled provider=#{provider} reason=credentials_found" end end |
.auto_register_lex_llm_providers ⇒ Object
480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 |
# File 'lib/legion/llm/call/providers.rb', line 480 def auto_register_lex_llm_providers return unless load_lex_llm_base LEX_LLM_PROVIDER_REQUIRES.each_value { |feature| load_optional_feature(feature) } namespace = lex_llm_namespace return unless namespace namespace::Provider.providers.each do |provider_name, provider_class| next unless lex_llm_provider_ready?(namespace, provider_name, provider_class) adapter = Call::LexLLMAdapter.new(provider_name, provider_class) Call::Registry.register(provider_name, adapter) Call::Registry.register(:claude, adapter) if provider_name.to_sym == :anthropic end rescue StandardError => e handle_exception(e, level: :warn, operation: 'llm.providers.auto_register_lex_llm') end |
.auto_register_providers ⇒ Object
427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 |
# File 'lib/legion/llm/call/providers.rb', line 427 def auto_register_providers log.debug '[llm][providers] auto_register_providers.enter' try_register_native_provider(:claude, 'Legion::Extensions::Claude', 'Legion::Extensions::Claude::Runners::Messages') do |klass| Call::Registry.register(:claude, klass) Call::Registry.register(:anthropic, klass) end try_register_native_provider(:bedrock, 'Legion::Extensions::Bedrock', 'Legion::Extensions::Bedrock::Runners::Converse') do |klass| Call::Registry.register(:bedrock, klass) end try_register_native_provider(:openai, 'Legion::Extensions::Openai', 'Legion::Extensions::Openai::Runners::Chat') do |klass| Call::Registry.register(:openai, klass) end try_register_native_provider(:gemini, 'Legion::Extensions::Gemini', 'Legion::Extensions::Gemini::Runners::Generate') do |klass| Call::Registry.register(:gemini, klass) end auto_register_lex_llm_providers registered = Call::Registry.available if registered.any? log.info "[llm][providers] native registry registered=#{registered.join(', ')}" else log.debug '[llm][providers] no native lex-* providers registered' end rescue StandardError => e handle_exception(e, level: :warn, operation: 'llm.providers.auto_register') end |
.broker_has_credential?(provider) ⇒ Boolean
673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 |
# File 'lib/legion/llm/call/providers.rb', line 673 def broker_has_credential?(provider) return false unless defined?(Legion::Identity::Broker) case provider when :bedrock renewer = Legion::Identity::Broker.renewer_for(:aws) renewer&.provider.respond_to?(:current_credentials) && !renewer.provider.current_credentials.nil? else !Legion::Identity::Broker.token_for( provider, purpose: 'llm.provider.credential_available', context: { provider: provider } ).nil? end rescue StandardError => e handle_exception(e, level: :warn, operation: 'llm.providers.broker_credential_available', provider: provider) false end |
.collect_credential_candidates(provider, config) ⇒ Object
309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 |
# File 'lib/legion/llm/call/providers.rb', line 309 def collect_credential_candidates(provider, config) case provider when :bedrock candidates = [] resolved_bearer = resolve_credential_value(config_value(config, :bearer_token)) bearer_env = ENV.fetch('AWS_BEARER_TOKEN_BEDROCK', nil) claude_bearer = Call::ClaudeConfigLoader.bedrock_bearer_token candidates += [resolved_bearer, bearer_env, claude_bearer].compact.uniq.map { |t| { bearer_token: t } } api_key = resolve_credential_value(config_value(config, :api_key)) secret = resolve_credential_value(config_value(config, :secret_key)) candidates << { api_key: api_key, secret_key: secret } if api_key && secret candidates when :anthropic [ resolve_credential_value(config_value(config, :api_key)), ENV.fetch('ANTHROPIC_API_KEY', nil) ].compact.uniq.map { |k| { api_key: k } } when :openai keys = [ resolve_credential_value(config_value(config, :api_key)), ENV.fetch('OPENAI_API_KEY', nil), ENV.fetch('CODEX_API_KEY', nil), Call::CodexConfigLoader.read_token ].compact.uniq keys.map { |k| { api_key: k } } when :gemini [ resolve_credential_value(config_value(config, :api_key)), ENV.fetch('GEMINI_API_KEY', nil) ].compact.uniq.map { |k| { api_key: k } } else [] end rescue StandardError => e handle_exception(e, level: :warn, operation: 'llm.providers.collect_credential_candidates', provider: provider) [] end |
.config_enabled?(config) ⇒ Boolean
722 723 724 |
# File 'lib/legion/llm/call/providers.rb', line 722 def config_enabled?(config) config.is_a?(Hash) && config_value(config, :enabled) == true end |
.config_value(config, key, default = nil) ⇒ Object
726 727 728 729 730 731 732 733 |
# File 'lib/legion/llm/call/providers.rb', line 726 def config_value(config, key, default = nil) return default unless config.respond_to?(:key?) string_key = key.to_s return config[string_key] if config.key?(string_key) config.key?(key) ? config[key] : default end |
.configure_anthropic(config) ⇒ Object
211 212 213 214 215 216 217 218 219 |
# File 'lib/legion/llm/call/providers.rb', line 211 def configure_anthropic(config) api_key = resolve_broker_credential(:anthropic) || resolve_credential_value(config_value(config, :api_key)) || ENV.fetch('ANTHROPIC_API_KEY', nil) return unless api_key set_config_value(config, :api_key, api_key) log.info "[llm][providers] prepared native anthropic base_url=#{config_value(config, :base_url).inspect}" end |
.configure_azure(config) ⇒ Object
242 243 244 245 246 247 248 249 250 251 |
# File 'lib/legion/llm/call/providers.rb', line 242 def configure_azure(config) api_base = config_value(config, :api_base) api_key = resolve_broker_credential(:azure) || resolve_credential_value(config_value(config, :api_key)) auth_token = resolve_credential_value(config_value(config, :auth_token)) return unless api_base && (api_key || auth_token) set_config_value(config, :api_key, api_key) if api_key set_config_value(config, :auth_token, auth_token) if auth_token log.info "[llm][providers] prepared native azure api_base=#{api_base}" end |
.configure_bedrock(config) ⇒ Object
181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 |
# File 'lib/legion/llm/call/providers.rb', line 181 def configure_bedrock(config) resolved_api_key = resolve_credential_value(config_value(config, :api_key)) resolved_secret_key = resolve_credential_value(config_value(config, :secret_key)) resolved_session_token = resolve_credential_value(config_value(config, :session_token)) has_sigv4 = resolved_api_key && resolved_secret_key has_bearer = resolve_credential_value(config_value(config, :bearer_token)) set_config_value(config, :bearer_token, has_bearer) if has_bearer set_config_value(config, :api_key, resolved_api_key) if resolved_api_key set_config_value(config, :secret_key, resolved_secret_key) if resolved_secret_key set_config_value(config, :session_token, resolved_session_token) if resolved_session_token unless has_sigv4 || has_bearer broker_creds = resolve_broker_aws_credentials if broker_creds has_sigv4 = true config = config.merge( api_key: broker_creds.access_key_id, secret_key: broker_creds.secret_access_key, session_token: (broker_creds.session_token if broker_creds.respond_to?(:session_token)) ) end end set_config_value(config, :region, config_value(config, :region) || 'us-east-2') return unless has_sigv4 || has_bearer auth_mode = has_bearer ? 'bearer token' : 'SigV4' log.info "[llm][providers] prepared native bedrock region=#{config_value(config, :region)} auth=#{auth_mode}" end |
.configure_gemini(config) ⇒ Object
232 233 234 235 236 237 238 239 240 |
# File 'lib/legion/llm/call/providers.rb', line 232 def configure_gemini(config) api_key = resolve_broker_credential(:gemini) || resolve_credential_value(config_value(config, :api_key)) || ENV.fetch('GEMINI_API_KEY', nil) return unless api_key set_config_value(config, :api_key, api_key) log.info "[llm][providers] prepared native gemini base_url=#{config_value(config, :base_url).inspect}" end |
.configure_lex_llm_provider(namespace, provider_name, provider_class, provider_config) ⇒ Object
574 575 576 577 578 579 580 581 |
# File 'lib/legion/llm/call/providers.rb', line 574 def configure_lex_llm_provider(namespace, provider_name, provider_class, provider_config) Array(provider_class.).each do |option| next unless namespace.config.respond_to?("#{option}=") value = lex_llm_provider_config_value(provider_name, option, provider_config) namespace.config.public_send("#{option}=", value) unless value.nil? end end |
.configure_ollama(config) ⇒ Object
253 254 255 |
# File 'lib/legion/llm/call/providers.rb', line 253 def configure_ollama(config) log.info "[llm][providers] prepared native ollama base_url=#{config_value(config, :base_url).inspect}" end |
.configure_openai(config) ⇒ Object
221 222 223 224 225 226 227 228 229 230 |
# File 'lib/legion/llm/call/providers.rb', line 221 def configure_openai(config) api_key = resolve_broker_credential(:openai) || resolve_credential_value(config_value(config, :api_key)) || ENV.fetch('OPENAI_API_KEY', nil) || ENV.fetch('CODEX_API_KEY', nil) return unless api_key set_config_value(config, :api_key, api_key) log.info "[llm][providers] prepared native openai base_url=#{config_value(config, :base_url).inspect}" end |
.configure_providers ⇒ Object
47 48 49 50 51 52 53 54 55 56 57 |
# File 'lib/legion/llm/call/providers.rb', line 47 def configure_providers log.debug '[llm][providers] configure_providers.enter' auto_enable_from_resolved_credentials providers_settings.each do |provider, config| next unless config_enabled?(config) log.debug "[llm][providers] configure_providers applying provider=#{provider}" apply_provider_config(provider, config) end log.debug '[llm][providers] configure_providers.exit' end |
.configure_vllm(config) ⇒ Object
257 258 259 260 261 262 263 264 |
# File 'lib/legion/llm/call/providers.rb', line 257 def configure_vllm(config) base_url = config_value(config, :base_url) || 'http://localhost:8000/v1' api_key = resolve_credential_value(config_value(config, :api_key)) set_config_value(config, :base_url, base_url) set_config_value(config, :api_key, api_key) if api_key log.info "[llm][providers] prepared native vllm base_url=#{base_url.inspect}" end |
.constant_defined_path?(path) ⇒ Boolean
701 702 703 704 705 706 707 708 709 710 |
# File 'lib/legion/llm/call/providers.rb', line 701 def constant_defined_path?(path) names = path.to_s.split('::') names.shift if names.first == '' owner = Object names.all? do |name| return false unless owner.const_defined?(name, false) owner = owner.const_get(name, false) end end |
.constant_get_path(path) ⇒ Object
712 713 714 715 716 |
# File 'lib/legion/llm/call/providers.rb', line 712 def constant_get_path(path) path.to_s.split('::').reject(&:empty?).reduce(Object) do |owner, name| owner.const_get(name, false) end end |
.credential_available_for?(provider, config) ⇒ Boolean
74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 |
# File 'lib/legion/llm/call/providers.rb', line 74 def credential_available_for?(provider, config) case provider.to_sym when :bedrock usable_setting?(config_value(config, :bearer_token)) || env_present?('AWS_BEARER_TOKEN_BEDROCK') || (usable_setting?(config_value(config, :api_key)) && usable_setting?(config_value(config, :secret_key))) when :anthropic usable_setting?(config_value(config, :api_key)) || env_present?('ANTHROPIC_API_KEY') when :openai usable_setting?(config_value(config, :api_key)) || env_present?('OPENAI_API_KEY') || env_present?('CODEX_API_KEY') || !Call::CodexConfigLoader.read_token.nil? when :azure config_value(config, :api_base) && (usable_setting?(config_value(config, :api_key)) || usable_setting?(config_value(config, :auth_token))) when :ollama ollama_running?(config) when :vllm vllm_running?(config) else usable_setting?(config_value(config, :api_key)) end rescue StandardError => e handle_exception(e, level: :warn, operation: 'llm.providers.credential_available_for', provider: provider) false end |
.credential_option?(option) ⇒ Boolean
614 615 616 |
# File 'lib/legion/llm/call/providers.rb', line 614 def credential_option?(option) option.to_s.match?(/(_api_key|_token|_secret)\z/) end |
.env_api_key(provider) ⇒ Object
618 619 620 621 622 623 624 625 626 627 628 |
# File 'lib/legion/llm/call/providers.rb', line 618 def env_api_key(provider) env_keys = { anthropic: %w[ANTHROPIC_API_KEY], gemini: %w[GEMINI_API_KEY], mlx: %w[MLX_API_KEY], openai: %w[OPENAI_API_KEY CODEX_API_KEY], vllm: %w[VLLM_API_KEY] }.fetch(provider, []) env_keys.filter_map { |key| ENV.fetch(key, nil).to_s.strip }.find { |value| value != '' } end |
.env_present?(key) ⇒ Boolean
110 111 112 |
# File 'lib/legion/llm/call/providers.rb', line 110 def env_present?(key) ENV.fetch(key, nil).to_s.strip != '' end |
.http_uri!(url, default_port:) ⇒ Object
156 157 158 159 160 161 162 163 164 165 |
# File 'lib/legion/llm/call/providers.rb', line 156 def http_uri!(url, default_port:) require 'uri' uri = URI.parse(url.to_s) raise ArgumentError, "unsupported URL scheme #{uri.scheme.inspect}" unless %w[http https].include?(uri.scheme) raise ArgumentError, 'missing URL host' unless uri.host uri.port ||= default_port uri end |
.inject_anthropic_cache_control!(opts, provider) ⇒ Object
630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 |
# File 'lib/legion/llm/call/providers.rb', line 630 def inject_anthropic_cache_control!(opts, provider) resolved_provider = (provider || Legion::LLM::Settings.value(:default_provider))&.to_sym return unless resolved_provider == :anthropic caching_settings = Legion::LLM::Settings.value(:prompt_caching, default: {}) || {} return unless config_value(caching_settings, :enabled, true) != false min_tokens = config_value(caching_settings, :min_tokens) || 1024 instructions = opts[:instructions] return unless instructions.is_a?(String) && instructions.length > min_tokens log.debug "[llm][providers] inject_anthropic_cache_control provider=#{resolved_provider} length=#{instructions.length}" opts[:instructions] = { content: instructions, cache_control: { type: 'ephemeral' } } end |
.lex_llm_namespace ⇒ Object
504 505 506 507 508 |
# File 'lib/legion/llm/call/providers.rb', line 504 def lex_llm_namespace return ::Legion::Extensions::Llm if defined?(::Legion::Extensions::Llm::Provider) nil end |
.lex_llm_provider_config_value(provider_name, option, provider_config) ⇒ Object
583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 |
# File 'lib/legion/llm/call/providers.rb', line 583 def lex_llm_provider_config_value(provider_name, option, provider_config) provider = provider_name.to_sym direct = config_value(provider_config, option) return resolve_credential_value(direct) if credential_option?(option) && !direct.nil? return direct unless direct.nil? short_key = option.to_s.delete_prefix("#{provider}_").to_sym short_value = config_value(provider_config, short_key) return resolve_credential_value(short_value) if credential_option?(option) && !short_value.nil? return short_value unless short_value.nil? case option.to_s when /_api_key\z/ env_api_key(provider) when /_api_base\z/ api_base = config_value(provider_config, :api_base) || config_value(provider_config, :base_url) || config_value(provider_config, :endpoint) normalize_lex_llm_api_base(provider, api_base) when /_version\z/ config_value(provider_config, :version) end end |
.lex_llm_provider_ready?(namespace, provider_name, provider_class) ⇒ Boolean
555 556 557 558 559 560 561 562 563 564 565 566 567 568 |
# File 'lib/legion/llm/call/providers.rb', line 555 def lex_llm_provider_ready?(namespace, provider_name, provider_class) provider_config = config_value(providers_settings, provider_name.to_sym, {}) || {} configure_lex_llm_provider(namespace, provider_name, provider_class, provider_config) return true if config_enabled?(provider_config) return credential_available_for?(provider_name.to_sym, provider_config) if lex_llm_runtime_probe_required?(provider_name) provider_class.configured?(namespace.config) rescue StandardError => e handle_exception(e, level: :warn, handled: true, operation: 'llm.providers.lex_llm_provider_ready', provider: provider_name) false end |
.lex_llm_runtime_probe_required?(provider_name) ⇒ Boolean
570 571 572 |
# File 'lib/legion/llm/call/providers.rb', line 570 def lex_llm_runtime_probe_required?(provider_name) %i[ollama vllm mlx].include?(provider_name.to_sym) end |
.llm_settings_lost?(snapshot) ⇒ Boolean
536 537 538 539 540 541 542 543 544 545 |
# File 'lib/legion/llm/call/providers.rb', line 536 def llm_settings_lost?(snapshot) return false unless snapshot.is_a?(Hash) && (snapshot.key?(:providers) || snapshot.key?('providers')) current = Legion::LLM::Settings.current_settings !current.is_a?(Hash) || !(current.key?(:providers) || current.key?('providers')) rescue StandardError => e handle_exception(e, level: :debug, handled: true, operation: 'llm.providers.llm_settings_lost') false end |
.load_lex_llm_base ⇒ Object
498 499 500 501 502 |
# File 'lib/legion/llm/call/providers.rb', line 498 def load_lex_llm_base load_optional_feature('legion/extensions/llm') load_optional_feature('legion/extensions/llm/provider') unless lex_llm_namespace !lex_llm_namespace.nil? end |
.load_optional_feature(feature) ⇒ Object
510 511 512 513 514 515 516 517 518 519 520 |
# File 'lib/legion/llm/call/providers.rb', line 510 def load_optional_feature(feature) llm_settings = snapshot_llm_settings require feature restore_llm_settings(llm_settings) if llm_settings_lost?(llm_settings) true rescue LoadError => e handle_exception(e, level: :warn, handled: true, operation: 'llm.providers.optional_feature', feature: feature) false end |
.log_available_providers ⇒ Object
454 455 456 457 458 459 460 461 462 |
# File 'lib/legion/llm/call/providers.rb', line 454 def log_available_providers enabled = providers_settings.select { |_, c| c.is_a?(Hash) && config_enabled?(c) } if enabled.empty? log.error '[llm][providers] no providers available — all failed health checks or disabled' else names = enabled.map { |name, c| "#{name}/#{config_value(c, :default_model) || 'auto'}" } log.info "[llm][providers] available providers=#{names.join(', ')}" end end |
.native_inventory_model_available?(provider, target_model) ⇒ Boolean
464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 |
# File 'lib/legion/llm/call/providers.rb', line 464 def native_inventory_model_available?(provider, target_model) return true unless target_model return false unless defined?(Legion::LLM::Inventory) Legion::LLM::Inventory.offerings(provider_family: provider).any? do |offering| model_id = offering[:model].to_s model_id.include?(target_model.to_s) || target_model.to_s.include?(model_id) || offering[:canonical_model_alias].to_s == target_model.to_s end rescue StandardError => e handle_exception(e, level: :warn, handled: true, operation: 'llm.providers.native_inventory_model_available', provider: provider) false end |
.normalize_lex_llm_api_base(provider, value) ⇒ Object
607 608 609 610 611 612 |
# File 'lib/legion/llm/call/providers.rb', line 607 def normalize_lex_llm_api_base(provider, value) return value if value.nil? return value unless %i[openai vllm].include?(provider.to_sym) value.to_s.sub(%r{/v1/?\z}, '') end |
.normalize_vllm_base_url(url) ⇒ Object
149 150 151 152 153 154 |
# File 'lib/legion/llm/call/providers.rb', line 149 def normalize_vllm_base_url(url) base = url.to_s.dup base.chop! while base.end_with?('/') base = base[0...-3] if base.end_with?('/v1') base.empty? ? 'http://localhost:8000' : base end |
.ollama_running?(config) ⇒ Boolean
114 115 116 117 118 119 120 121 122 123 124 125 126 127 |
# File 'lib/legion/llm/call/providers.rb', line 114 def ollama_running?(config) require 'socket' url = config_value(config, :base_url) || 'http://localhost:11434' uri = http_uri!(url, default_port: 11_434) log.debug "[llm][providers] ollama_running? addr=#{uri.host} port=#{uri.port}" Socket.tcp(uri.host, uri.port, connect_timeout: 1).close true rescue URI::InvalidURIError, ArgumentError => e handle_exception(e, level: :error, operation: 'llm.providers.ollama_running.config', base_url: url) false rescue StandardError => e handle_exception(e, level: :warn, operation: 'llm.providers.ollama_running', base_url: url) false end |
.probe_provider_credentials(provider, model, config) ⇒ Object
286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 |
# File 'lib/legion/llm/call/providers.rb', line 286 def probe_provider_credentials(provider, model, config) candidates = collect_credential_candidates(provider, config) if candidates.size <= 1 ok = attempt_provider_call(provider, model) set_config_value(config, :enabled, false) unless ok return end working = candidates.find do |creds| apply_credential_to_config(provider, config, creds) attempt_provider_call(provider, model) end if working apply_credential_to_config(provider, config, working) log.info "[llm][providers] health_check ok provider=#{provider} model=#{model} credential=#{working.keys.join(',')}" else set_config_value(config, :enabled, false) log.warn "[llm][providers] disabled provider=#{provider} reason=all_credentials_failed" end end |
.probe_via_chat(provider, model) ⇒ Object
399 400 401 402 403 |
# File 'lib/legion/llm/call/providers.rb', line 399 def probe_via_chat(provider, model) return :unavailable unless Call::Registry.available?(provider) native_inventory_model_available?(provider, model) ? :ok : :model_missing end |
.probe_via_model_list(provider, target_model) ⇒ Object
383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 |
# File 'lib/legion/llm/call/providers.rb', line 383 def probe_via_model_list(provider, target_model) return :unavailable unless Call::Registry.available?(provider) return :ok if target_model.nil? return :ok if native_inventory_model_available?(provider, target_model) adapter = Call::Registry.for(provider) offerings = adapter.respond_to?(:offerings) ? Array(adapter.offerings) : [] return :ok if offerings.empty? :model_missing rescue StandardError => e handle_exception(e, level: :warn, operation: 'llm.providers.probe_via_model_list', provider: provider) probe_via_chat(provider, target_model) end |
.providers_settings ⇒ Object
718 719 720 |
# File 'lib/legion/llm/call/providers.rb', line 718 def providers_settings Legion::LLM::Settings.value(:providers, default: {}) end |
.recover_openai_with_codex ⇒ Object
410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 |
# File 'lib/legion/llm/call/providers.rb', line 410 def recover_openai_with_codex openai_config = config_value(providers_settings, :openai) return unless openai_config.is_a?(Hash) && !config_enabled?(openai_config) token = Call::CodexConfigLoader.read_token return unless token log.info '[llm][providers] openai disabled — retrying with codex auth token' set_config_value(openai_config, :api_key, token) configure_openai(openai_config) set_config_value(openai_config, :enabled, true) ok = attempt_provider_call(:openai, config_value(openai_config, :default_model)) set_config_value(openai_config, :enabled, false) unless ok rescue StandardError => e handle_exception(e, level: :warn, operation: 'llm.providers.recover_openai_with_codex') end |
.recover_with_alternative_credentials ⇒ Object
405 406 407 408 |
# File 'lib/legion/llm/call/providers.rb', line 405 def recover_with_alternative_credentials log.debug '[llm][providers] recover_with_alternative_credentials.enter' recover_openai_with_codex end |
.resolve_broker_aws_credentials ⇒ Object
661 662 663 664 665 666 667 668 669 670 671 |
# File 'lib/legion/llm/call/providers.rb', line 661 def resolve_broker_aws_credentials return nil unless defined?(Legion::Identity::Broker) renewer = Legion::Identity::Broker.renewer_for(:aws) return renewer.provider.current_credentials if renewer&.provider.respond_to?(:current_credentials) nil rescue StandardError => e handle_exception(e, level: :warn, operation: 'llm.providers.broker_resolve.aws') nil end |
.resolve_broker_credential(provider_name) ⇒ Object
648 649 650 651 652 653 654 655 656 657 658 659 |
# File 'lib/legion/llm/call/providers.rb', line 648 def resolve_broker_credential(provider_name) return nil unless defined?(Legion::Identity::Broker) Legion::Identity::Broker.token_for( provider_name, purpose: 'llm.provider.credential', context: { provider: provider_name } ) rescue StandardError => e handle_exception(e, level: :warn, operation: "llm.providers.broker_resolve.#{provider_name}") nil end |
.resolve_credential_value(value) ⇒ Object
106 107 108 |
# File 'lib/legion/llm/call/providers.rb', line 106 def resolve_credential_value(value) Call::ClaudeConfigLoader.resolve_setting_reference(value) end |
.resolve_llm_secrets ⇒ Object
37 38 39 40 41 42 43 44 45 |
# File 'lib/legion/llm/call/providers.rb', line 37 def resolve_llm_secrets log.debug '[llm][providers] resolve_llm_secrets.enter' return unless defined?(Legion::Settings::Resolver) Legion::Settings::Resolver.resolve_secrets!(Legion::LLM::Settings.current_settings) log.debug '[llm][providers] resolve_llm_secrets.exit' rescue StandardError => e handle_exception(e, level: :warn, operation: 'llm.providers.resolve_llm_secrets') end |
.restore_llm_settings(snapshot) ⇒ Object
547 548 549 550 551 552 553 |
# File 'lib/legion/llm/call/providers.rb', line 547 def restore_llm_settings(snapshot) Legion::Settings[:llm] = snapshot if defined?(Legion::Settings) && snapshot.is_a?(Hash) log.warn '[llm][providers] restored LLM settings after optional provider load changed settings backend' rescue StandardError => e handle_exception(e, level: :warn, handled: true, operation: 'llm.providers.restore_llm_settings') end |
.set_config_value(config, key, value) ⇒ Object
735 736 737 738 739 740 741 |
# File 'lib/legion/llm/call/providers.rb', line 735 def set_config_value(config, key, value) if config.respond_to?(:key?) && config.key?(key.to_s) config[key.to_s] = value else config[key] = value end end |
.setup ⇒ Object
13 14 15 16 17 18 19 20 21 22 23 |
# File 'lib/legion/llm/call/providers.rb', line 13 def setup log.debug '[llm][providers] setup.enter' resolve_llm_secrets configure_providers auto_register_providers verify_providers log.debug '[llm][providers] setup.exit' rescue StandardError => e handle_exception(e, level: :error, operation: 'llm.providers.setup') raise end |
.snapshot_llm_settings ⇒ Object
522 523 524 525 526 527 528 529 530 531 532 533 534 |
# File 'lib/legion/llm/call/providers.rb', line 522 def snapshot_llm_settings settings = nil settings = Legion::LLM::Settings.current_settings return nil unless settings.is_a?(Hash) && settings.any? Marshal.load(Marshal.dump(settings)) rescue TypeError settings&.dup rescue StandardError => e handle_exception(e, level: :debug, handled: true, operation: 'llm.providers.snapshot_llm_settings') nil end |
.try_register_native_provider(name, ext_const, runner_const) {|klass| ... } ⇒ Object
692 693 694 695 696 697 698 699 |
# File 'lib/legion/llm/call/providers.rb', line 692 def try_register_native_provider(name, ext_const, runner_const) log.debug "[llm][providers] try_register_native_provider name=#{name} ext=#{ext_const}" return unless constant_defined_path?(ext_const) && constant_defined_path?(runner_const) klass = constant_get_path(runner_const) yield klass log.debug "[llm][providers] registered native provider name=#{name}" end |
.usable_setting?(value) ⇒ Boolean
102 103 104 |
# File 'lib/legion/llm/call/providers.rb', line 102 def usable_setting?(value) !resolve_credential_value(value).nil? end |
.verify_providers ⇒ Object
268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 |
# File 'lib/legion/llm/call/providers.rb', line 268 def verify_providers log.debug '[llm][providers] verify_providers.enter' providers_settings.each do |provider, config| provider_key = provider.to_sym next unless config_enabled?(config) next unless SAAS_PROVIDERS.include?(provider_key) model = config_value(config, :default_model) next unless model probe_provider_credentials(provider_key, model, config) end recover_with_alternative_credentials log_available_providers end |
.vllm_running?(config) ⇒ Boolean
129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 |
# File 'lib/legion/llm/call/providers.rb', line 129 def vllm_running?(config) require 'faraday' url = config_value(config, :base_url) || 'http://localhost:8000/v1' base = normalize_vllm_base_url(url) http_uri!(base, default_port: 8000) log.debug "[llm][providers] vllm_running? url=#{base}/health" response = Faraday.new(url: base) do |f| f..timeout = 2 f..open_timeout = 2 f.adapter Faraday.default_adapter end.get('/health') response.success? rescue URI::InvalidURIError, ArgumentError => e handle_exception(e, level: :error, operation: 'llm.providers.vllm_running.config', base_url: url) false rescue StandardError => e handle_exception(e, level: :warn, operation: 'llm.providers.vllm_running', base_url: url) false end |