Module: AiCli::Helpers::Llm
- Defined in:
- lib/aicli/helpers/llm.rb
Constant Summary collapse
- PROVIDERS =
%w[openai anthropic].freeze
- DEFAULT_MODELS =
{ 'openai' => 'gpt-4o-mini', 'anthropic' => 'claude-sonnet-4-6' }.freeze
Class Method Summary collapse
- .build_chat(config) ⇒ Object
- .configure!(config) ⇒ Object
- .default_model_for(provider) ⇒ Object
- .ensure_credentials!(config, provider = nil) ⇒ Object
- .list_chat_models(provider) ⇒ Object
- .model_known?(model, provider) ⇒ Boolean
- .normalize_provider(provider) ⇒ Object
- .provider_for_model(model_id) ⇒ Object
-
.resolve_provider_and_model(config) ⇒ Object
Returns [provider, model], fixing mismatches against the RubyLLM registry.
- .text_chat_model?(model) ⇒ Boolean
Class Method Details
.build_chat(config) ⇒ Object
37 38 39 40 41 42 43 44 45 46 |
# File 'lib/aicli/helpers/llm.rb', line 37 def build_chat(config) provider, model = resolve_provider_and_model(config) ensure_credentials!(config, provider) configure!(config.merge('PROVIDER' => provider, 'MODEL' => model)) opts = { model: model, provider: provider.to_sym } opts[:assume_model_exists] = true unless model_known?(model, provider) RubyLLM.chat(**opts) end |
.configure!(config) ⇒ Object
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
# File 'lib/aicli/helpers/llm.rb', line 17 def configure!(config) provider = normalize_provider(config['PROVIDER']) ensure_credentials!(config, provider) RubyLLM.configure do |c| openai_key = config['OPENAI_KEY'].to_s anthropic_key = config['ANTHROPIC_KEY'].to_s endpoint = config['OPENAI_API_ENDPOINT'].to_s c.openai_api_key = openai_key unless openai_key.empty? c.anthropic_api_key = anthropic_key unless anthropic_key.empty? if !endpoint.empty? && endpoint != 'https://api.openai.com/v1' c.openai_api_base = endpoint.sub(%r{/+\z}, '') end end provider end |
.default_model_for(provider) ⇒ Object
89 90 91 |
# File 'lib/aicli/helpers/llm.rb', line 89 def default_model_for(provider) DEFAULT_MODELS.fetch(normalize_provider(provider), DEFAULT_MODELS['openai']) end |
.ensure_credentials!(config, provider = nil) ⇒ Object
98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 |
# File 'lib/aicli/helpers/llm.rb', line 98 def ensure_credentials!(config, provider = nil) provider = normalize_provider(provider || config['PROVIDER']) case provider when 'anthropic' if config['ANTHROPIC_KEY'].to_s.empty? raise KnownError, "Please set your Anthropic API key via `#{Constants::COMMAND_NAME} config set ANTHROPIC_KEY=<your token>`" end else if config['OPENAI_KEY'].to_s.empty? raise KnownError, "Please set your OpenAI API key via `#{Constants::COMMAND_NAME} config set OPENAI_KEY=<your token>`" end end end |
.list_chat_models(provider) ⇒ Object
79 80 81 82 83 84 85 86 87 |
# File 'lib/aicli/helpers/llm.rb', line 79 def list_chat_models(provider) provider = normalize_provider(provider) RubyLLM.models.chat_models .by_provider(provider.to_sym) .select { |m| text_chat_model?(m) } .sort_by { |m| [m.name.to_s.downcase, m.id] } .map { |m| { 'id' => m.id, 'name' => m.name.to_s } } end |
.model_known?(model, provider) ⇒ Boolean
115 116 117 118 119 120 |
# File 'lib/aicli/helpers/llm.rb', line 115 def model_known?(model, provider) RubyLLM.models.find(model, provider.to_sym) true rescue StandardError false end |
.normalize_provider(provider) ⇒ Object
93 94 95 96 |
# File 'lib/aicli/helpers/llm.rb', line 93 def normalize_provider(provider) value = provider.to_s.downcase PROVIDERS.include?(value) ? value : 'openai' end |
.provider_for_model(model_id) ⇒ Object
66 67 68 69 70 71 72 73 74 75 76 77 |
# File 'lib/aicli/helpers/llm.rb', line 66 def provider_for_model(model_id) return nil if model_id.to_s.empty? info = RubyLLM.models.find(model_id) normalize_provider(info.provider) rescue StandardError # Try each known provider explicitly (aliases can be provider-specific). PROVIDERS.each do |provider| return provider if model_known?(model_id, provider) end nil end |
.resolve_provider_and_model(config) ⇒ Object
Returns [provider, model], fixing mismatches against the RubyLLM registry.
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
# File 'lib/aicli/helpers/llm.rb', line 49 def resolve_provider_and_model(config) provider = normalize_provider(config['PROVIDER']) model = config['MODEL'].to_s model = default_model_for(provider) if model.empty? model_provider = provider_for_model(model) if model_provider && model_provider != provider # Prefer the provider that actually owns the selected model. provider = model_provider elsif model_provider.nil? && !model_known?(model, provider) # Unknown id for this provider: fall back to a safe default. model = default_model_for(provider) end [provider, model] end |
.text_chat_model?(model) ⇒ Boolean
122 123 124 125 126 127 |
# File 'lib/aicli/helpers/llm.rb', line 122 def text_chat_model?(model) outputs = Array(model.modalities&.output).map(&:to_s) return false unless outputs.include?('text') !model.id.match?(/tts|transcribe|realtime|whisper|embedding|moderation|dall-e|imagen|search-preview|babbage|davinci|ada-|curie|instruct/i) end |