dify_llm 1.8.2 → 1.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
 - data/README.md +8 -3
 - data/lib/generators/ruby_llm/install/templates/create_messages_migration.rb.tt +3 -0
 - data/lib/generators/ruby_llm/upgrade_to_v1_7/upgrade_to_v1_7_generator.rb +1 -1
 - data/lib/generators/ruby_llm/upgrade_to_v1_9/templates/add_v1_9_message_columns.rb.tt +15 -0
 - data/lib/generators/ruby_llm/upgrade_to_v1_9/upgrade_to_v1_9_generator.rb +49 -0
 - data/lib/ruby_llm/active_record/acts_as.rb +6 -6
 - data/lib/ruby_llm/active_record/chat_methods.rb +41 -13
 - data/lib/ruby_llm/active_record/message_methods.rb +11 -2
 - data/lib/ruby_llm/active_record/model_methods.rb +1 -1
 - data/lib/ruby_llm/aliases.json +62 -20
 - data/lib/ruby_llm/attachment.rb +8 -0
 - data/lib/ruby_llm/chat.rb +13 -2
 - data/lib/ruby_llm/configuration.rb +6 -1
 - data/lib/ruby_llm/connection.rb +3 -3
 - data/lib/ruby_llm/content.rb +23 -0
 - data/lib/ruby_llm/message.rb +11 -6
 - data/lib/ruby_llm/model/info.rb +4 -0
 - data/lib/ruby_llm/models.json +9410 -7793
 - data/lib/ruby_llm/models.rb +14 -22
 - data/lib/ruby_llm/provider.rb +23 -1
 - data/lib/ruby_llm/providers/anthropic/chat.rb +22 -3
 - data/lib/ruby_llm/providers/anthropic/content.rb +44 -0
 - data/lib/ruby_llm/providers/anthropic/media.rb +2 -1
 - data/lib/ruby_llm/providers/anthropic/models.rb +15 -0
 - data/lib/ruby_llm/providers/anthropic/streaming.rb +2 -0
 - data/lib/ruby_llm/providers/anthropic/tools.rb +20 -18
 - data/lib/ruby_llm/providers/bedrock/media.rb +2 -1
 - data/lib/ruby_llm/providers/bedrock/streaming/content_extraction.rb +15 -0
 - data/lib/ruby_llm/providers/bedrock/streaming/payload_processing.rb +2 -0
 - data/lib/ruby_llm/providers/dify/chat.rb +16 -5
 - data/lib/ruby_llm/providers/gemini/chat.rb +352 -69
 - data/lib/ruby_llm/providers/gemini/media.rb +59 -1
 - data/lib/ruby_llm/providers/gemini/tools.rb +146 -25
 - data/lib/ruby_llm/providers/gemini/transcription.rb +116 -0
 - data/lib/ruby_llm/providers/gemini.rb +2 -1
 - data/lib/ruby_llm/providers/gpustack/media.rb +1 -0
 - data/lib/ruby_llm/providers/ollama/media.rb +1 -0
 - data/lib/ruby_llm/providers/openai/chat.rb +7 -2
 - data/lib/ruby_llm/providers/openai/media.rb +2 -1
 - data/lib/ruby_llm/providers/openai/streaming.rb +7 -2
 - data/lib/ruby_llm/providers/openai/tools.rb +26 -6
 - data/lib/ruby_llm/providers/openai/transcription.rb +70 -0
 - data/lib/ruby_llm/providers/openai.rb +1 -0
 - data/lib/ruby_llm/providers/vertexai/transcription.rb +16 -0
 - data/lib/ruby_llm/providers/vertexai.rb +3 -0
 - data/lib/ruby_llm/stream_accumulator.rb +10 -4
 - data/lib/ruby_llm/tool.rb +126 -0
 - data/lib/ruby_llm/transcription.rb +35 -0
 - data/lib/ruby_llm/utils.rb +46 -0
 - data/lib/ruby_llm/version.rb +1 -1
 - data/lib/ruby_llm.rb +6 -0
 - metadata +24 -3
 
    
        checksums.yaml
    CHANGED
    
    | 
         @@ -1,7 +1,7 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            ---
         
     | 
| 
       2 
2 
     | 
    
         
             
            SHA256:
         
     | 
| 
       3 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       4 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 3 
     | 
    
         
            +
              metadata.gz: 75dbb53612d3fa2c1089038bcf48fbbc0fe9425d37ffd8fccdfa56337daf97af
         
     | 
| 
      
 4 
     | 
    
         
            +
              data.tar.gz: 316d3ef004a7387a6e723a02f8ab09729b167097127f9cb85ce5a864e6e4ef1e
         
     | 
| 
       5 
5 
     | 
    
         
             
            SHA512:
         
     | 
| 
       6 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       7 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 6 
     | 
    
         
            +
              metadata.gz: bca45bf0d49f6e98e9ea00cf2e760537fbff5f861394080f8dec8baa634784289f23779476eedbda5c9d11e4323e2a67f6eadc84144c18cd4adc7d09e3b9cfe7
         
     | 
| 
      
 7 
     | 
    
         
            +
              data.tar.gz: a12a868701ae4e70f6f397de290d3bc018da91993950366d956fd8d60f0c37cc8ef89f882a680db396336258107e032f881817224668b81d9d65b3f53c1f44d9
         
     | 
    
        data/README.md
    CHANGED
    
    | 
         @@ -18,7 +18,7 @@ Battle tested at [<picture><source media="(prefers-color-scheme: dark)" srcset=" 
     | 
|
| 
       18 
18 
     | 
    
         
             
            </div>
         
     | 
| 
       19 
19 
     | 
    
         | 
| 
       20 
20 
     | 
    
         
             
            > [!NOTE]
         
     | 
| 
       21 
     | 
    
         
            -
            > Using RubyLLM 
     | 
| 
      
 21 
     | 
    
         
            +
            > Using RubyLLM? [Share your story](https://tally.so/r/3Na02p)! Takes 5 minutes.
         
     | 
| 
       22 
22 
     | 
    
         | 
| 
       23 
23 
     | 
    
         
             
            ---
         
     | 
| 
       24 
24 
     | 
    
         | 
| 
         @@ -69,6 +69,11 @@ RubyLLM.paint "a sunset over mountains in watercolor style" 
     | 
|
| 
       69 
69 
     | 
    
         
             
            RubyLLM.embed "Ruby is elegant and expressive"
         
     | 
| 
       70 
70 
     | 
    
         
             
            ```
         
     | 
| 
       71 
71 
     | 
    
         | 
| 
      
 72 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 73 
     | 
    
         
            +
            # Transcribe audio to text
         
     | 
| 
      
 74 
     | 
    
         
            +
            RubyLLM.transcribe "meeting.wav"
         
     | 
| 
      
 75 
     | 
    
         
            +
            ```
         
     | 
| 
      
 76 
     | 
    
         
            +
             
     | 
| 
       72 
77 
     | 
    
         
             
            ```ruby
         
     | 
| 
       73 
78 
     | 
    
         
             
            # Moderate content for safety
         
     | 
| 
       74 
79 
     | 
    
         
             
            RubyLLM.moderate "Check if this text is safe"
         
     | 
| 
         @@ -107,10 +112,10 @@ response = chat.with_schema(ProductSchema).ask "Analyze this product", with: "pr 
     | 
|
| 
       107 
112 
     | 
    
         | 
| 
       108 
113 
     | 
    
         
             
            * **Chat:** Conversational AI with `RubyLLM.chat`
         
     | 
| 
       109 
114 
     | 
    
         
             
            * **Vision:** Analyze images and videos
         
     | 
| 
       110 
     | 
    
         
            -
            * **Audio:** Transcribe and understand speech
         
     | 
| 
      
 115 
     | 
    
         
            +
            * **Audio:** Transcribe and understand speech with `RubyLLM.transcribe`
         
     | 
| 
       111 
116 
     | 
    
         
             
            * **Documents:** Extract from PDFs, CSVs, JSON, any file type
         
     | 
| 
       112 
117 
     | 
    
         
             
            * **Image generation:** Create images with `RubyLLM.paint`
         
     | 
| 
       113 
     | 
    
         
            -
            * **Embeddings:**  
     | 
| 
      
 118 
     | 
    
         
            +
            * **Embeddings:** Generate embeddings with `RubyLLM.embed`
         
     | 
| 
       114 
119 
     | 
    
         
             
            * **Moderation:** Content safety with `RubyLLM.moderate`
         
     | 
| 
       115 
120 
     | 
    
         
             
            * **Tools:** Let AI call your Ruby methods
         
     | 
| 
       116 
121 
     | 
    
         
             
            * **Structured output:** JSON schemas that just work
         
     | 
| 
         @@ -3,8 +3,11 @@ class Create<%= message_model_name.gsub('::', '').pluralize %> < ActiveRecord::M 
     | 
|
| 
       3 
3 
     | 
    
         
             
                create_table :<%= message_table_name %> do |t|
         
     | 
| 
       4 
4 
     | 
    
         
             
                  t.string :role, null: false
         
     | 
| 
       5 
5 
     | 
    
         
             
                  t.text :content
         
     | 
| 
      
 6 
     | 
    
         
            +
                  t.json :content_raw
         
     | 
| 
       6 
7 
     | 
    
         
             
                  t.integer :input_tokens
         
     | 
| 
       7 
8 
     | 
    
         
             
                  t.integer :output_tokens
         
     | 
| 
      
 9 
     | 
    
         
            +
                  t.integer :cached_tokens
         
     | 
| 
      
 10 
     | 
    
         
            +
                  t.integer :cache_creation_tokens
         
     | 
| 
       8 
11 
     | 
    
         
             
                  t.timestamps
         
     | 
| 
       9 
12 
     | 
    
         
             
                end
         
     | 
| 
       10 
13 
     | 
    
         | 
| 
         @@ -9,7 +9,7 @@ module RubyLLM 
     | 
|
| 
       9 
9 
     | 
    
         
             
                # Generator to upgrade existing RubyLLM apps to v1.7 with new Rails-like API
         
     | 
| 
       10 
10 
     | 
    
         
             
                class UpgradeToV17Generator < Rails::Generators::Base
         
     | 
| 
       11 
11 
     | 
    
         
             
                  include Rails::Generators::Migration
         
     | 
| 
       12 
     | 
    
         
            -
                  include RubyLLM::GeneratorHelpers
         
     | 
| 
      
 12 
     | 
    
         
            +
                  include RubyLLM::Generators::GeneratorHelpers
         
     | 
| 
       13 
13 
     | 
    
         | 
| 
       14 
14 
     | 
    
         
             
                  namespace 'ruby_llm:upgrade_to_v1_7'
         
     | 
| 
       15 
15 
     | 
    
         
             
                  source_root File.expand_path('templates', __dir__)
         
     | 
| 
         @@ -0,0 +1,15 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            class AddRubyLlmV19Columns < ActiveRecord::Migration<%= migration_version %>
         
     | 
| 
      
 2 
     | 
    
         
            +
              def change
         
     | 
| 
      
 3 
     | 
    
         
            +
                unless column_exists?(:<%= message_table_name %>, :cached_tokens)
         
     | 
| 
      
 4 
     | 
    
         
            +
                  add_column :<%= message_table_name %>, :cached_tokens, :integer
         
     | 
| 
      
 5 
     | 
    
         
            +
                end
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
                unless column_exists?(:<%= message_table_name %>, :cache_creation_tokens)
         
     | 
| 
      
 8 
     | 
    
         
            +
                  add_column :<%= message_table_name %>, :cache_creation_tokens, :integer
         
     | 
| 
      
 9 
     | 
    
         
            +
                end
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
                unless column_exists?(:<%= message_table_name %>, :content_raw)
         
     | 
| 
      
 12 
     | 
    
         
            +
                  add_column :<%= message_table_name %>, :content_raw, :json
         
     | 
| 
      
 13 
     | 
    
         
            +
                end
         
     | 
| 
      
 14 
     | 
    
         
            +
              end
         
     | 
| 
      
 15 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,49 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            require 'rails/generators'
         
     | 
| 
      
 4 
     | 
    
         
            +
            require 'rails/generators/active_record'
         
     | 
| 
      
 5 
     | 
    
         
            +
            require_relative '../generator_helpers'
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
            module RubyLLM
         
     | 
| 
      
 8 
     | 
    
         
            +
              module Generators
         
     | 
| 
      
 9 
     | 
    
         
            +
                # Generator to add v1.9 columns (cached tokens + raw content support) to existing apps.
         
     | 
| 
      
 10 
     | 
    
         
            +
                class UpgradeToV19Generator < Rails::Generators::Base
         
     | 
| 
      
 11 
     | 
    
         
            +
                  include Rails::Generators::Migration
         
     | 
| 
      
 12 
     | 
    
         
            +
                  include RubyLLM::Generators::GeneratorHelpers
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
                  namespace 'ruby_llm:upgrade_to_v1_9'
         
     | 
| 
      
 15 
     | 
    
         
            +
                  source_root File.expand_path('templates', __dir__)
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                  argument :model_mappings, type: :array, default: [], banner: 'message:MessageName'
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                  desc 'Adds cached token columns and raw content storage fields introduced in v1.9.0'
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                  def self.next_migration_number(dirname)
         
     | 
| 
      
 22 
     | 
    
         
            +
                    ::ActiveRecord::Generators::Base.next_migration_number(dirname)
         
     | 
| 
      
 23 
     | 
    
         
            +
                  end
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
                  def create_migration_file
         
     | 
| 
      
 26 
     | 
    
         
            +
                    parse_model_mappings
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                    migration_template 'add_v1_9_message_columns.rb.tt',
         
     | 
| 
      
 29 
     | 
    
         
            +
                                       'db/migrate/add_ruby_llm_v1_9_columns.rb',
         
     | 
| 
      
 30 
     | 
    
         
            +
                                       migration_version: migration_version,
         
     | 
| 
      
 31 
     | 
    
         
            +
                                       message_table_name: message_table_name
         
     | 
| 
      
 32 
     | 
    
         
            +
                  end
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
                  def show_next_steps
         
     | 
| 
      
 35 
     | 
    
         
            +
                    say_status :success, 'Upgrade prepared!', :green
         
     | 
| 
      
 36 
     | 
    
         
            +
                    say <<~INSTRUCTIONS
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
                      Next steps:
         
     | 
| 
      
 39 
     | 
    
         
            +
                      1. Review the generated migration
         
     | 
| 
      
 40 
     | 
    
         
            +
                      2. Run: rails db:migrate
         
     | 
| 
      
 41 
     | 
    
         
            +
                      3. Restart your application server
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
                      📚 See the v1.9.0 release notes for details on cached token tracking and raw content support.
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
                    INSTRUCTIONS
         
     | 
| 
      
 46 
     | 
    
         
            +
                  end
         
     | 
| 
      
 47 
     | 
    
         
            +
                end
         
     | 
| 
      
 48 
     | 
    
         
            +
              end
         
     | 
| 
      
 49 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -11,22 +11,22 @@ module RubyLLM 
     | 
|
| 
       11 
11 
     | 
    
         
             
                    super
         
     | 
| 
       12 
12 
     | 
    
         
             
                    # Monkey-patch Models to use database when ActsAs is active
         
     | 
| 
       13 
13 
     | 
    
         
             
                    RubyLLM::Models.class_eval do
         
     | 
| 
       14 
     | 
    
         
            -
                      def load_models
         
     | 
| 
      
 14 
     | 
    
         
            +
                      def self.load_models
         
     | 
| 
       15 
15 
     | 
    
         
             
                        read_from_database
         
     | 
| 
       16 
16 
     | 
    
         
             
                      rescue StandardError => e
         
     | 
| 
       17 
17 
     | 
    
         
             
                        RubyLLM.logger.debug "Failed to load models from database: #{e.message}, falling back to JSON"
         
     | 
| 
       18 
18 
     | 
    
         
             
                        read_from_json
         
     | 
| 
       19 
19 
     | 
    
         
             
                      end
         
     | 
| 
       20 
20 
     | 
    
         | 
| 
       21 
     | 
    
         
            -
                      def  
     | 
| 
       22 
     | 
    
         
            -
                        @models = read_from_database
         
     | 
| 
       23 
     | 
    
         
            -
                      end
         
     | 
| 
       24 
     | 
    
         
            -
             
     | 
| 
       25 
     | 
    
         
            -
                      def read_from_database
         
     | 
| 
      
 21 
     | 
    
         
            +
                      def self.read_from_database
         
     | 
| 
       26 
22 
     | 
    
         
             
                        model_class = RubyLLM.config.model_registry_class
         
     | 
| 
       27 
23 
     | 
    
         
             
                        model_class = model_class.constantize if model_class.is_a?(String)
         
     | 
| 
       28 
24 
     | 
    
         
             
                        model_class.all.map(&:to_llm)
         
     | 
| 
       29 
25 
     | 
    
         
             
                      end
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
                      def load_from_database!
         
     | 
| 
      
 28 
     | 
    
         
            +
                        @models = self.class.read_from_database
         
     | 
| 
      
 29 
     | 
    
         
            +
                      end
         
     | 
| 
       30 
30 
     | 
    
         
             
                    end
         
     | 
| 
       31 
31 
     | 
    
         
             
                  end
         
     | 
| 
       32 
32 
     | 
    
         | 
| 
         @@ -174,8 +174,16 @@ module RubyLLM 
     | 
|
| 
       174 
174 
     | 
    
         
             
                  end
         
     | 
| 
       175 
175 
     | 
    
         | 
| 
       176 
176 
     | 
    
         
             
                  def create_user_message(content, with: nil)
         
     | 
| 
       177 
     | 
    
         
            -
                     
     | 
| 
      
 177 
     | 
    
         
            +
                    content_text, attachments, content_raw = prepare_content_for_storage(content)
         
     | 
| 
      
 178 
     | 
    
         
            +
             
     | 
| 
      
 179 
     | 
    
         
            +
                    message_record = messages_association.build(role: :user)
         
     | 
| 
      
 180 
     | 
    
         
            +
                    message_record.content = content_text
         
     | 
| 
      
 181 
     | 
    
         
            +
                    message_record.content_raw = content_raw if message_record.respond_to?(:content_raw=)
         
     | 
| 
      
 182 
     | 
    
         
            +
                    message_record.save!
         
     | 
| 
      
 183 
     | 
    
         
            +
             
     | 
| 
       178 
184 
     | 
    
         
             
                    persist_content(message_record, with) if with.present?
         
     | 
| 
      
 185 
     | 
    
         
            +
                    persist_content(message_record, attachments) if attachments.present?
         
     | 
| 
      
 186 
     | 
    
         
            +
             
     | 
| 
       179 
187 
     | 
    
         
             
                    message_record
         
     | 
| 
       180 
188 
     | 
    
         
             
                  end
         
     | 
| 
       181 
189 
     | 
    
         | 
| 
         @@ -235,28 +243,25 @@ module RubyLLM 
     | 
|
| 
       235 
243 
     | 
    
         
             
                    @message = messages_association.create!(role: :assistant, content: '')
         
     | 
| 
       236 
244 
     | 
    
         
             
                  end
         
     | 
| 
       237 
245 
     | 
    
         | 
| 
       238 
     | 
    
         
            -
                   
     | 
| 
      
 246 
     | 
    
         
            +
                  # rubocop:disable Metrics/PerceivedComplexity
         
     | 
| 
      
 247 
     | 
    
         
            +
                  def persist_message_completion(message)
         
     | 
| 
       239 
248 
     | 
    
         
             
                    return unless message
         
     | 
| 
       240 
249 
     | 
    
         | 
| 
       241 
250 
     | 
    
         
             
                    tool_call_id = find_tool_call_id(message.tool_call_id) if message.tool_call_id
         
     | 
| 
       242 
251 
     | 
    
         | 
| 
       243 
252 
     | 
    
         
             
                    transaction do
         
     | 
| 
       244 
     | 
    
         
            -
                       
     | 
| 
       245 
     | 
    
         
            -
                      attachments_to_persist = nil
         
     | 
| 
       246 
     | 
    
         
            -
             
     | 
| 
       247 
     | 
    
         
            -
                      if content.is_a?(RubyLLM::Content)
         
     | 
| 
       248 
     | 
    
         
            -
                        attachments_to_persist = content.attachments if content.attachments.any?
         
     | 
| 
       249 
     | 
    
         
            -
                        content = content.text
         
     | 
| 
       250 
     | 
    
         
            -
                      elsif content.is_a?(Hash) || content.is_a?(Array)
         
     | 
| 
       251 
     | 
    
         
            -
                        content = content.to_json
         
     | 
| 
       252 
     | 
    
         
            -
                      end
         
     | 
| 
      
 253 
     | 
    
         
            +
                      content_text, attachments_to_persist, content_raw = prepare_content_for_storage(message.content)
         
     | 
| 
       253 
254 
     | 
    
         | 
| 
       254 
255 
     | 
    
         
             
                      attrs = {
         
     | 
| 
       255 
256 
     | 
    
         
             
                        role: message.role,
         
     | 
| 
       256 
     | 
    
         
            -
                        content:  
     | 
| 
      
 257 
     | 
    
         
            +
                        content: content_text,
         
     | 
| 
       257 
258 
     | 
    
         
             
                        input_tokens: message.input_tokens,
         
     | 
| 
       258 
259 
     | 
    
         
             
                        output_tokens: message.output_tokens
         
     | 
| 
       259 
260 
     | 
    
         
             
                      }
         
     | 
| 
      
 261 
     | 
    
         
            +
                      attrs[:cached_tokens] = message.cached_tokens if @message.has_attribute?(:cached_tokens)
         
     | 
| 
      
 262 
     | 
    
         
            +
                      if @message.has_attribute?(:cache_creation_tokens)
         
     | 
| 
      
 263 
     | 
    
         
            +
                        attrs[:cache_creation_tokens] = message.cache_creation_tokens
         
     | 
| 
      
 264 
     | 
    
         
            +
                      end
         
     | 
| 
       260 
265 
     | 
    
         | 
| 
       261 
266 
     | 
    
         
             
                      # Add model association dynamically
         
     | 
| 
       262 
267 
     | 
    
         
             
                      attrs[self.class.model_association_name] = model_association
         
     | 
| 
         @@ -266,12 +271,15 @@ module RubyLLM 
     | 
|
| 
       266 
271 
     | 
    
         
             
                        attrs[parent_tool_call_assoc.foreign_key] = tool_call_id
         
     | 
| 
       267 
272 
     | 
    
         
             
                      end
         
     | 
| 
       268 
273 
     | 
    
         | 
| 
       269 
     | 
    
         
            -
                      @message. 
     | 
| 
      
 274 
     | 
    
         
            +
                      @message.assign_attributes(attrs)
         
     | 
| 
      
 275 
     | 
    
         
            +
                      @message.content_raw = content_raw if @message.respond_to?(:content_raw=)
         
     | 
| 
      
 276 
     | 
    
         
            +
                      @message.save!
         
     | 
| 
       270 
277 
     | 
    
         | 
| 
       271 
278 
     | 
    
         
             
                      persist_content(@message, attachments_to_persist) if attachments_to_persist
         
     | 
| 
       272 
279 
     | 
    
         
             
                      persist_tool_calls(message.tool_calls) if message.tool_calls.present?
         
     | 
| 
       273 
280 
     | 
    
         
             
                    end
         
     | 
| 
       274 
281 
     | 
    
         
             
                  end
         
     | 
| 
      
 282 
     | 
    
         
            +
                  # rubocop:enable Metrics/PerceivedComplexity
         
     | 
| 
       275 
283 
     | 
    
         | 
| 
       276 
284 
     | 
    
         
             
                  def persist_tool_calls(tool_calls)
         
     | 
| 
       277 
285 
     | 
    
         
             
                    tool_calls.each_value do |tool_call|
         
     | 
| 
         @@ -331,6 +339,26 @@ module RubyLLM 
     | 
|
| 
       331 
339 
     | 
    
         
             
                    RubyLLM.logger.warn "Failed to process attachment #{source}: #{e.message}"
         
     | 
| 
       332 
340 
     | 
    
         
             
                    nil
         
     | 
| 
       333 
341 
     | 
    
         
             
                  end
         
     | 
| 
      
 342 
     | 
    
         
            +
             
     | 
| 
      
 343 
     | 
    
         
            +
                  def prepare_content_for_storage(content)
         
     | 
| 
      
 344 
     | 
    
         
            +
                    attachments = nil
         
     | 
| 
      
 345 
     | 
    
         
            +
                    content_raw = nil
         
     | 
| 
      
 346 
     | 
    
         
            +
                    content_text = content
         
     | 
| 
      
 347 
     | 
    
         
            +
             
     | 
| 
      
 348 
     | 
    
         
            +
                    case content
         
     | 
| 
      
 349 
     | 
    
         
            +
                    when RubyLLM::Content::Raw
         
     | 
| 
      
 350 
     | 
    
         
            +
                      content_raw = content.value
         
     | 
| 
      
 351 
     | 
    
         
            +
                      content_text = nil
         
     | 
| 
      
 352 
     | 
    
         
            +
                    when RubyLLM::Content
         
     | 
| 
      
 353 
     | 
    
         
            +
                      attachments = content.attachments if content.attachments.any?
         
     | 
| 
      
 354 
     | 
    
         
            +
                      content_text = content.text
         
     | 
| 
      
 355 
     | 
    
         
            +
                    when Hash, Array
         
     | 
| 
      
 356 
     | 
    
         
            +
                      content_raw = content
         
     | 
| 
      
 357 
     | 
    
         
            +
                      content_text = nil
         
     | 
| 
      
 358 
     | 
    
         
            +
                    end
         
     | 
| 
      
 359 
     | 
    
         
            +
             
     | 
| 
      
 360 
     | 
    
         
            +
                    [content_text, attachments, content_raw]
         
     | 
| 
      
 361 
     | 
    
         
            +
                  end
         
     | 
| 
       334 
362 
     | 
    
         
             
                end
         
     | 
| 
       335 
363 
     | 
    
         
             
              end
         
     | 
| 
       336 
364 
     | 
    
         
             
            end
         
     | 
| 
         @@ -11,6 +11,9 @@ module RubyLLM 
     | 
|
| 
       11 
11 
     | 
    
         
             
                  end
         
     | 
| 
       12 
12 
     | 
    
         | 
| 
       13 
13 
     | 
    
         
             
                  def to_llm
         
     | 
| 
      
 14 
     | 
    
         
            +
                    cached = has_attribute?(:cached_tokens) ? self[:cached_tokens] : nil
         
     | 
| 
      
 15 
     | 
    
         
            +
                    cache_creation = has_attribute?(:cache_creation_tokens) ? self[:cache_creation_tokens] : nil
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
       14 
17 
     | 
    
         
             
                    RubyLLM::Message.new(
         
     | 
| 
       15 
18 
     | 
    
         
             
                      role: role.to_sym,
         
     | 
| 
       16 
19 
     | 
    
         
             
                      content: extract_content,
         
     | 
| 
         @@ -18,6 +21,8 @@ module RubyLLM 
     | 
|
| 
       18 
21 
     | 
    
         
             
                      tool_call_id: extract_tool_call_id,
         
     | 
| 
       19 
22 
     | 
    
         
             
                      input_tokens: input_tokens,
         
     | 
| 
       20 
23 
     | 
    
         
             
                      output_tokens: output_tokens,
         
     | 
| 
      
 24 
     | 
    
         
            +
                      cached_tokens: cached,
         
     | 
| 
      
 25 
     | 
    
         
            +
                      cache_creation_tokens: cache_creation,
         
     | 
| 
       21 
26 
     | 
    
         
             
                      model_id: model_association&.model_id
         
     | 
| 
       22 
27 
     | 
    
         
             
                    )
         
     | 
| 
       23 
28 
     | 
    
         
             
                  end
         
     | 
| 
         @@ -42,9 +47,13 @@ module RubyLLM 
     | 
|
| 
       42 
47 
     | 
    
         
             
                  end
         
     | 
| 
       43 
48 
     | 
    
         | 
| 
       44 
49 
     | 
    
         
             
                  def extract_content
         
     | 
| 
       45 
     | 
    
         
            -
                    return  
     | 
| 
      
 50 
     | 
    
         
            +
                    return RubyLLM::Content::Raw.new(content_raw) if has_attribute?(:content_raw) && content_raw.present?
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
                    content_value = self[:content]
         
     | 
| 
      
 53 
     | 
    
         
            +
             
     | 
| 
      
 54 
     | 
    
         
            +
                    return content_value unless respond_to?(:attachments) && attachments.attached?
         
     | 
| 
       46 
55 
     | 
    
         | 
| 
       47 
     | 
    
         
            -
                    RubyLLM::Content.new( 
     | 
| 
      
 56 
     | 
    
         
            +
                    RubyLLM::Content.new(content_value).tap do |content_obj|
         
     | 
| 
       48 
57 
     | 
    
         
             
                      @_tempfiles = []
         
     | 
| 
       49 
58 
     | 
    
         | 
| 
       50 
59 
     | 
    
         
             
                      attachments.each do |attachment|
         
     | 
| 
         @@ -77,7 +77,7 @@ module RubyLLM 
     | 
|
| 
       77 
77 
     | 
    
         
             
                  delegate :supports?, :supports_vision?, :supports_functions?, :type,
         
     | 
| 
       78 
78 
     | 
    
         
             
                           :input_price_per_million, :output_price_per_million,
         
     | 
| 
       79 
79 
     | 
    
         
             
                           :function_calling?, :structured_output?, :batch?,
         
     | 
| 
       80 
     | 
    
         
            -
                           :reasoning?, :citations?, :streaming?,
         
     | 
| 
      
 80 
     | 
    
         
            +
                           :reasoning?, :citations?, :streaming?, :provider_class,
         
     | 
| 
       81 
81 
     | 
    
         
             
                           to: :to_llm
         
     | 
| 
       82 
82 
     | 
    
         
             
                end
         
     | 
| 
       83 
83 
     | 
    
         
             
              end
         
     | 
    
        data/lib/ruby_llm/aliases.json
    CHANGED
    
    | 
         @@ -8,6 +8,9 @@ 
     | 
|
| 
       8 
8 
     | 
    
         
             
                "openrouter": "anthropic/claude-3.5-haiku",
         
     | 
| 
       9 
9 
     | 
    
         
             
                "bedrock": "anthropic.claude-3-5-haiku-20241022-v1:0"
         
     | 
| 
       10 
10 
     | 
    
         
             
              },
         
     | 
| 
      
 11 
     | 
    
         
            +
              "claude-3-5-haiku-latest": {
         
     | 
| 
      
 12 
     | 
    
         
            +
                "anthropic": "claude-3-5-haiku-latest"
         
     | 
| 
      
 13 
     | 
    
         
            +
              },
         
     | 
| 
       11 
14 
     | 
    
         
             
              "claude-3-5-sonnet": {
         
     | 
| 
       12 
15 
     | 
    
         
             
                "anthropic": "claude-3-5-sonnet-20241022",
         
     | 
| 
       13 
16 
     | 
    
         
             
                "openrouter": "anthropic/claude-3.5-sonnet",
         
     | 
| 
         @@ -18,6 +21,9 @@ 
     | 
|
| 
       18 
21 
     | 
    
         
             
                "openrouter": "anthropic/claude-3.7-sonnet",
         
     | 
| 
       19 
22 
     | 
    
         
             
                "bedrock": "us.anthropic.claude-3-7-sonnet-20250219-v1:0"
         
     | 
| 
       20 
23 
     | 
    
         
             
              },
         
     | 
| 
      
 24 
     | 
    
         
            +
              "claude-3-7-sonnet-latest": {
         
     | 
| 
      
 25 
     | 
    
         
            +
                "anthropic": "claude-3-7-sonnet-latest"
         
     | 
| 
      
 26 
     | 
    
         
            +
              },
         
     | 
| 
       21 
27 
     | 
    
         
             
              "claude-3-haiku": {
         
     | 
| 
       22 
28 
     | 
    
         
             
                "anthropic": "claude-3-haiku-20240307",
         
     | 
| 
       23 
29 
     | 
    
         
             
                "openrouter": "anthropic/claude-3-haiku",
         
     | 
| 
         @@ -31,11 +37,19 @@ 
     | 
|
| 
       31 
37 
     | 
    
         
             
              "claude-3-sonnet": {
         
     | 
| 
       32 
38 
     | 
    
         
             
                "bedrock": "anthropic.claude-3-sonnet-20240229-v1:0"
         
     | 
| 
       33 
39 
     | 
    
         
             
              },
         
     | 
| 
      
 40 
     | 
    
         
            +
              "claude-haiku-4-5": {
         
     | 
| 
      
 41 
     | 
    
         
            +
                "anthropic": "claude-haiku-4-5-20251001",
         
     | 
| 
      
 42 
     | 
    
         
            +
                "openrouter": "anthropic/claude-haiku-4.5",
         
     | 
| 
      
 43 
     | 
    
         
            +
                "bedrock": "us.anthropic.claude-haiku-4-5-20251001-v1:0"
         
     | 
| 
      
 44 
     | 
    
         
            +
              },
         
     | 
| 
       34 
45 
     | 
    
         
             
              "claude-opus-4": {
         
     | 
| 
       35 
46 
     | 
    
         
             
                "anthropic": "claude-opus-4-20250514",
         
     | 
| 
       36 
47 
     | 
    
         
             
                "openrouter": "anthropic/claude-opus-4",
         
     | 
| 
       37 
48 
     | 
    
         
             
                "bedrock": "us.anthropic.claude-opus-4-1-20250805-v1:0"
         
     | 
| 
       38 
49 
     | 
    
         
             
              },
         
     | 
| 
      
 50 
     | 
    
         
            +
              "claude-opus-4-0": {
         
     | 
| 
      
 51 
     | 
    
         
            +
                "anthropic": "claude-opus-4-0"
         
     | 
| 
      
 52 
     | 
    
         
            +
              },
         
     | 
| 
       39 
53 
     | 
    
         
             
              "claude-opus-4-1": {
         
     | 
| 
       40 
54 
     | 
    
         
             
                "anthropic": "claude-opus-4-1-20250805",
         
     | 
| 
       41 
55 
     | 
    
         
             
                "openrouter": "anthropic/claude-opus-4.1",
         
     | 
| 
         @@ -46,30 +60,18 @@ 
     | 
|
| 
       46 
60 
     | 
    
         
             
                "openrouter": "anthropic/claude-sonnet-4",
         
     | 
| 
       47 
61 
     | 
    
         
             
                "bedrock": "us.anthropic.claude-sonnet-4-20250514-v1:0"
         
     | 
| 
       48 
62 
     | 
    
         
             
              },
         
     | 
| 
      
 63 
     | 
    
         
            +
              "claude-sonnet-4-0": {
         
     | 
| 
      
 64 
     | 
    
         
            +
                "anthropic": "claude-sonnet-4-0"
         
     | 
| 
      
 65 
     | 
    
         
            +
              },
         
     | 
| 
      
 66 
     | 
    
         
            +
              "claude-sonnet-4-5": {
         
     | 
| 
      
 67 
     | 
    
         
            +
                "anthropic": "claude-sonnet-4-5-20250929",
         
     | 
| 
      
 68 
     | 
    
         
            +
                "openrouter": "anthropic/claude-sonnet-4.5",
         
     | 
| 
      
 69 
     | 
    
         
            +
                "bedrock": "us.anthropic.claude-sonnet-4-5-20250929-v1:0"
         
     | 
| 
      
 70 
     | 
    
         
            +
              },
         
     | 
| 
       49 
71 
     | 
    
         
             
              "deepseek-chat": {
         
     | 
| 
       50 
72 
     | 
    
         
             
                "deepseek": "deepseek-chat",
         
     | 
| 
       51 
73 
     | 
    
         
             
                "openrouter": "deepseek/deepseek-chat"
         
     | 
| 
       52 
74 
     | 
    
         
             
              },
         
     | 
| 
       53 
     | 
    
         
            -
              "gemini-1.5-flash": {
         
     | 
| 
       54 
     | 
    
         
            -
                "gemini": "gemini-1.5-flash",
         
     | 
| 
       55 
     | 
    
         
            -
                "vertexai": "gemini-1.5-flash"
         
     | 
| 
       56 
     | 
    
         
            -
              },
         
     | 
| 
       57 
     | 
    
         
            -
              "gemini-1.5-flash-002": {
         
     | 
| 
       58 
     | 
    
         
            -
                "gemini": "gemini-1.5-flash-002",
         
     | 
| 
       59 
     | 
    
         
            -
                "vertexai": "gemini-1.5-flash-002"
         
     | 
| 
       60 
     | 
    
         
            -
              },
         
     | 
| 
       61 
     | 
    
         
            -
              "gemini-1.5-flash-8b": {
         
     | 
| 
       62 
     | 
    
         
            -
                "gemini": "gemini-1.5-flash-8b",
         
     | 
| 
       63 
     | 
    
         
            -
                "vertexai": "gemini-1.5-flash-8b"
         
     | 
| 
       64 
     | 
    
         
            -
              },
         
     | 
| 
       65 
     | 
    
         
            -
              "gemini-1.5-pro": {
         
     | 
| 
       66 
     | 
    
         
            -
                "gemini": "gemini-1.5-pro",
         
     | 
| 
       67 
     | 
    
         
            -
                "vertexai": "gemini-1.5-pro"
         
     | 
| 
       68 
     | 
    
         
            -
              },
         
     | 
| 
       69 
     | 
    
         
            -
              "gemini-1.5-pro-002": {
         
     | 
| 
       70 
     | 
    
         
            -
                "gemini": "gemini-1.5-pro-002",
         
     | 
| 
       71 
     | 
    
         
            -
                "vertexai": "gemini-1.5-pro-002"
         
     | 
| 
       72 
     | 
    
         
            -
              },
         
     | 
| 
       73 
75 
     | 
    
         
             
              "gemini-2.0-flash": {
         
     | 
| 
       74 
76 
     | 
    
         
             
                "gemini": "gemini-2.0-flash",
         
     | 
| 
       75 
77 
     | 
    
         
             
                "vertexai": "gemini-2.0-flash"
         
     | 
| 
         @@ -93,6 +95,10 @@ 
     | 
|
| 
       93 
95 
     | 
    
         
             
                "openrouter": "google/gemini-2.5-flash",
         
     | 
| 
       94 
96 
     | 
    
         
             
                "vertexai": "gemini-2.5-flash"
         
     | 
| 
       95 
97 
     | 
    
         
             
              },
         
     | 
| 
      
 98 
     | 
    
         
            +
              "gemini-2.5-flash-image": {
         
     | 
| 
      
 99 
     | 
    
         
            +
                "gemini": "gemini-2.5-flash-image",
         
     | 
| 
      
 100 
     | 
    
         
            +
                "openrouter": "google/gemini-2.5-flash-image"
         
     | 
| 
      
 101 
     | 
    
         
            +
              },
         
     | 
| 
       96 
102 
     | 
    
         
             
              "gemini-2.5-flash-image-preview": {
         
     | 
| 
       97 
103 
     | 
    
         
             
                "gemini": "gemini-2.5-flash-image-preview",
         
     | 
| 
       98 
104 
     | 
    
         
             
                "openrouter": "google/gemini-2.5-flash-image-preview"
         
     | 
| 
         @@ -106,6 +112,14 @@ 
     | 
|
| 
       106 
112 
     | 
    
         
             
                "gemini": "gemini-2.5-flash-lite-preview-06-17",
         
     | 
| 
       107 
113 
     | 
    
         
             
                "openrouter": "google/gemini-2.5-flash-lite-preview-06-17"
         
     | 
| 
       108 
114 
     | 
    
         
             
              },
         
     | 
| 
      
 115 
     | 
    
         
            +
              "gemini-2.5-flash-lite-preview-09-2025": {
         
     | 
| 
      
 116 
     | 
    
         
            +
                "gemini": "gemini-2.5-flash-lite-preview-09-2025",
         
     | 
| 
      
 117 
     | 
    
         
            +
                "openrouter": "google/gemini-2.5-flash-lite-preview-09-2025"
         
     | 
| 
      
 118 
     | 
    
         
            +
              },
         
     | 
| 
      
 119 
     | 
    
         
            +
              "gemini-2.5-flash-preview-09-2025": {
         
     | 
| 
      
 120 
     | 
    
         
            +
                "gemini": "gemini-2.5-flash-preview-09-2025",
         
     | 
| 
      
 121 
     | 
    
         
            +
                "openrouter": "google/gemini-2.5-flash-preview-09-2025"
         
     | 
| 
      
 122 
     | 
    
         
            +
              },
         
     | 
| 
       109 
123 
     | 
    
         
             
              "gemini-2.5-pro": {
         
     | 
| 
       110 
124 
     | 
    
         
             
                "gemini": "gemini-2.5-pro",
         
     | 
| 
       111 
125 
     | 
    
         
             
                "openrouter": "google/gemini-2.5-pro",
         
     | 
| 
         @@ -219,6 +233,10 @@ 
     | 
|
| 
       219 
233 
     | 
    
         
             
                "openai": "gpt-5",
         
     | 
| 
       220 
234 
     | 
    
         
             
                "openrouter": "openai/gpt-5"
         
     | 
| 
       221 
235 
     | 
    
         
             
              },
         
     | 
| 
      
 236 
     | 
    
         
            +
              "gpt-5-codex": {
         
     | 
| 
      
 237 
     | 
    
         
            +
                "openai": "gpt-5-codex",
         
     | 
| 
      
 238 
     | 
    
         
            +
                "openrouter": "openai/gpt-5-codex"
         
     | 
| 
      
 239 
     | 
    
         
            +
              },
         
     | 
| 
       222 
240 
     | 
    
         
             
              "gpt-5-mini": {
         
     | 
| 
       223 
241 
     | 
    
         
             
                "openai": "gpt-5-mini",
         
     | 
| 
       224 
242 
     | 
    
         
             
                "openrouter": "openai/gpt-5-mini"
         
     | 
| 
         @@ -227,6 +245,22 @@ 
     | 
|
| 
       227 
245 
     | 
    
         
             
                "openai": "gpt-5-nano",
         
     | 
| 
       228 
246 
     | 
    
         
             
                "openrouter": "openai/gpt-5-nano"
         
     | 
| 
       229 
247 
     | 
    
         
             
              },
         
     | 
| 
      
 248 
     | 
    
         
            +
              "gpt-5-pro": {
         
     | 
| 
      
 249 
     | 
    
         
            +
                "openai": "gpt-5-pro",
         
     | 
| 
      
 250 
     | 
    
         
            +
                "openrouter": "openai/gpt-5-pro"
         
     | 
| 
      
 251 
     | 
    
         
            +
              },
         
     | 
| 
      
 252 
     | 
    
         
            +
              "gpt-oss-120b": {
         
     | 
| 
      
 253 
     | 
    
         
            +
                "openai": "gpt-oss-120b",
         
     | 
| 
      
 254 
     | 
    
         
            +
                "openrouter": "openai/gpt-oss-120b"
         
     | 
| 
      
 255 
     | 
    
         
            +
              },
         
     | 
| 
      
 256 
     | 
    
         
            +
              "gpt-oss-20b": {
         
     | 
| 
      
 257 
     | 
    
         
            +
                "openai": "gpt-oss-20b",
         
     | 
| 
      
 258 
     | 
    
         
            +
                "openrouter": "openai/gpt-oss-20b"
         
     | 
| 
      
 259 
     | 
    
         
            +
              },
         
     | 
| 
      
 260 
     | 
    
         
            +
              "imagen-4.0-generate-001": {
         
     | 
| 
      
 261 
     | 
    
         
            +
                "gemini": "imagen-4.0-generate-001",
         
     | 
| 
      
 262 
     | 
    
         
            +
                "vertexai": "imagen-4.0-generate-001"
         
     | 
| 
      
 263 
     | 
    
         
            +
              },
         
     | 
| 
       230 
264 
     | 
    
         
             
              "o1": {
         
     | 
| 
       231 
265 
     | 
    
         
             
                "openai": "o1",
         
     | 
| 
       232 
266 
     | 
    
         
             
                "openrouter": "openai/o1"
         
     | 
| 
         @@ -247,6 +281,10 @@ 
     | 
|
| 
       247 
281 
     | 
    
         
             
                "openai": "o3",
         
     | 
| 
       248 
282 
     | 
    
         
             
                "openrouter": "openai/o3"
         
     | 
| 
       249 
283 
     | 
    
         
             
              },
         
     | 
| 
      
 284 
     | 
    
         
            +
              "o3-deep-research": {
         
     | 
| 
      
 285 
     | 
    
         
            +
                "openai": "o3-deep-research",
         
     | 
| 
      
 286 
     | 
    
         
            +
                "openrouter": "openai/o3-deep-research"
         
     | 
| 
      
 287 
     | 
    
         
            +
              },
         
     | 
| 
       250 
288 
     | 
    
         
             
              "o3-mini": {
         
     | 
| 
       251 
289 
     | 
    
         
             
                "openai": "o3-mini",
         
     | 
| 
       252 
290 
     | 
    
         
             
                "openrouter": "openai/o3-mini"
         
     | 
| 
         @@ -259,6 +297,10 @@ 
     | 
|
| 
       259 
297 
     | 
    
         
             
                "openai": "o4-mini",
         
     | 
| 
       260 
298 
     | 
    
         
             
                "openrouter": "openai/o4-mini"
         
     | 
| 
       261 
299 
     | 
    
         
             
              },
         
     | 
| 
      
 300 
     | 
    
         
            +
              "o4-mini-deep-research": {
         
     | 
| 
      
 301 
     | 
    
         
            +
                "openai": "o4-mini-deep-research",
         
     | 
| 
      
 302 
     | 
    
         
            +
                "openrouter": "openai/o4-mini-deep-research"
         
     | 
| 
      
 303 
     | 
    
         
            +
              },
         
     | 
| 
       262 
304 
     | 
    
         
             
              "text-embedding-004": {
         
     | 
| 
       263 
305 
     | 
    
         
             
                "gemini": "text-embedding-004",
         
     | 
| 
       264 
306 
     | 
    
         
             
                "vertexai": "text-embedding-004"
         
     | 
    
        data/lib/ruby_llm/attachment.rb
    CHANGED
    
    
    
        data/lib/ruby_llm/chat.rb
    CHANGED
    
    | 
         @@ -31,7 +31,7 @@ module RubyLLM 
     | 
|
| 
       31 
31 
     | 
    
         
             
                end
         
     | 
| 
       32 
32 
     | 
    
         | 
| 
       33 
33 
     | 
    
         
             
                def ask(message = nil, with: nil, &)
         
     | 
| 
       34 
     | 
    
         
            -
                  add_message role: :user, content:  
     | 
| 
      
 34 
     | 
    
         
            +
                  add_message role: :user, content: build_content(message, with)
         
     | 
| 
       35 
35 
     | 
    
         
             
                  complete(&)
         
     | 
| 
       36 
36 
     | 
    
         
             
                end
         
     | 
| 
       37 
37 
     | 
    
         | 
| 
         @@ -193,7 +193,8 @@ module RubyLLM 
     | 
|
| 
       193 
193 
     | 
    
         
             
                    @on[:tool_call]&.call(tool_call)
         
     | 
| 
       194 
194 
     | 
    
         
             
                    result = execute_tool tool_call
         
     | 
| 
       195 
195 
     | 
    
         
             
                    @on[:tool_result]&.call(result)
         
     | 
| 
       196 
     | 
    
         
            -
                     
     | 
| 
      
 196 
     | 
    
         
            +
                    tool_payload = result.is_a?(Tool::Halt) ? result.content : result
         
     | 
| 
      
 197 
     | 
    
         
            +
                    content = content_like?(tool_payload) ? tool_payload : tool_payload.to_s
         
     | 
| 
       197 
198 
     | 
    
         
             
                    message = add_message role: :tool, content:, tool_call_id: tool_call.id
         
     | 
| 
       198 
199 
     | 
    
         
             
                    @on[:end_message]&.call(message)
         
     | 
| 
       199 
200 
     | 
    
         | 
| 
         @@ -208,5 +209,15 @@ module RubyLLM 
     | 
|
| 
       208 
209 
     | 
    
         
             
                  args = tool_call.arguments
         
     | 
| 
       209 
210 
     | 
    
         
             
                  tool.call(args)
         
     | 
| 
       210 
211 
     | 
    
         
             
                end
         
     | 
| 
      
 212 
     | 
    
         
            +
             
     | 
| 
      
 213 
     | 
    
         
            +
                def build_content(message, attachments)
         
     | 
| 
      
 214 
     | 
    
         
            +
                  return message if content_like?(message)
         
     | 
| 
      
 215 
     | 
    
         
            +
             
     | 
| 
      
 216 
     | 
    
         
            +
                  Content.new(message, attachments)
         
     | 
| 
      
 217 
     | 
    
         
            +
                end
         
     | 
| 
      
 218 
     | 
    
         
            +
             
     | 
| 
      
 219 
     | 
    
         
            +
                def content_like?(object)
         
     | 
| 
      
 220 
     | 
    
         
            +
                  object.is_a?(Content) || object.is_a?(Content::Raw)
         
     | 
| 
      
 221 
     | 
    
         
            +
                end
         
     | 
| 
       211 
222 
     | 
    
         
             
              end
         
     | 
| 
       212 
223 
     | 
    
         
             
            end
         
     | 
| 
         @@ -10,6 +10,7 @@ module RubyLLM 
     | 
|
| 
       10 
10 
     | 
    
         
             
                              :openai_use_system_role,
         
     | 
| 
       11 
11 
     | 
    
         
             
                              :anthropic_api_key,
         
     | 
| 
       12 
12 
     | 
    
         
             
                              :gemini_api_key,
         
     | 
| 
      
 13 
     | 
    
         
            +
                              :gemini_api_base,
         
     | 
| 
       13 
14 
     | 
    
         
             
                              :vertexai_project_id,
         
     | 
| 
       14 
15 
     | 
    
         
             
                              :vertexai_location,
         
     | 
| 
       15 
16 
     | 
    
         
             
                              :deepseek_api_key,
         
     | 
| 
         @@ -31,7 +32,9 @@ module RubyLLM 
     | 
|
| 
       31 
32 
     | 
    
         
             
                              :default_embedding_model,
         
     | 
| 
       32 
33 
     | 
    
         
             
                              :default_moderation_model,
         
     | 
| 
       33 
34 
     | 
    
         
             
                              :default_image_model,
         
     | 
| 
      
 35 
     | 
    
         
            +
                              :default_transcription_model,
         
     | 
| 
       34 
36 
     | 
    
         
             
                              # Model registry
         
     | 
| 
      
 37 
     | 
    
         
            +
                              :model_registry_file,
         
     | 
| 
       35 
38 
     | 
    
         
             
                              :model_registry_class,
         
     | 
| 
       36 
39 
     | 
    
         
             
                              # Rails integration
         
     | 
| 
       37 
40 
     | 
    
         
             
                              :use_new_acts_as,
         
     | 
| 
         @@ -49,7 +52,7 @@ module RubyLLM 
     | 
|
| 
       49 
52 
     | 
    
         
             
                              :log_stream_debug
         
     | 
| 
       50 
53 
     | 
    
         | 
| 
       51 
54 
     | 
    
         
             
                def initialize
         
     | 
| 
       52 
     | 
    
         
            -
                  @request_timeout =  
     | 
| 
      
 55 
     | 
    
         
            +
                  @request_timeout = 300
         
     | 
| 
       53 
56 
     | 
    
         
             
                  @max_retries = 3
         
     | 
| 
       54 
57 
     | 
    
         
             
                  @retry_interval = 0.1
         
     | 
| 
       55 
58 
     | 
    
         
             
                  @retry_backoff_factor = 2
         
     | 
| 
         @@ -60,7 +63,9 @@ module RubyLLM 
     | 
|
| 
       60 
63 
     | 
    
         
             
                  @default_embedding_model = 'text-embedding-3-small'
         
     | 
| 
       61 
64 
     | 
    
         
             
                  @default_moderation_model = 'omni-moderation-latest'
         
     | 
| 
       62 
65 
     | 
    
         
             
                  @default_image_model = 'gpt-image-1'
         
     | 
| 
      
 66 
     | 
    
         
            +
                  @default_transcription_model = 'whisper-1'
         
     | 
| 
       63 
67 
     | 
    
         | 
| 
      
 68 
     | 
    
         
            +
                  @model_registry_file = File.expand_path('models.json', __dir__)
         
     | 
| 
       64 
69 
     | 
    
         
             
                  @model_registry_class = 'Model'
         
     | 
| 
       65 
70 
     | 
    
         
             
                  @use_new_acts_as = false
         
     | 
| 
       66 
71 
     | 
    
         | 
    
        data/lib/ruby_llm/connection.rb
    CHANGED
    
    | 
         @@ -34,8 +34,7 @@ module RubyLLM 
     | 
|
| 
       34 
34 
     | 
    
         
             
                end
         
     | 
| 
       35 
35 
     | 
    
         | 
| 
       36 
36 
     | 
    
         
             
                def post(url, payload, &)
         
     | 
| 
       37 
     | 
    
         
            -
                   
     | 
| 
       38 
     | 
    
         
            -
                  @connection.post url, body do |req|
         
     | 
| 
      
 37 
     | 
    
         
            +
                  @connection.post url, payload do |req|
         
     | 
| 
       39 
38 
     | 
    
         
             
                    req.headers.merge! @provider.headers if @provider.respond_to?(:headers)
         
     | 
| 
       40 
39 
     | 
    
         
             
                    yield req if block_given?
         
     | 
| 
       41 
40 
     | 
    
         
             
                  end
         
     | 
| 
         @@ -77,7 +76,7 @@ module RubyLLM 
     | 
|
| 
       77 
76 
     | 
    
         
             
                                   errors: true,
         
     | 
| 
       78 
77 
     | 
    
         
             
                                   headers: false,
         
     | 
| 
       79 
78 
     | 
    
         
             
                                   log_level: :debug do |logger|
         
     | 
| 
       80 
     | 
    
         
            -
                    logger.filter(%r{[A-Za-z0-9+/=]{100,}}, ' 
     | 
| 
      
 79 
     | 
    
         
            +
                    logger.filter(%r{[A-Za-z0-9+/=]{100,}}, '[BASE64 DATA]')
         
     | 
| 
       81 
80 
     | 
    
         
             
                    logger.filter(/[-\d.e,\s]{100,}/, '[EMBEDDINGS ARRAY]')
         
     | 
| 
       82 
81 
     | 
    
         
             
                  end
         
     | 
| 
       83 
82 
     | 
    
         
             
                end
         
     | 
| 
         @@ -94,6 +93,7 @@ module RubyLLM 
     | 
|
| 
       94 
93 
     | 
    
         
             
                end
         
     | 
| 
       95 
94 
     | 
    
         | 
| 
       96 
95 
     | 
    
         
             
                def setup_middleware(faraday)
         
     | 
| 
      
 96 
     | 
    
         
            +
                  faraday.request :multipart
         
     | 
| 
       97 
97 
     | 
    
         
             
                  faraday.request :json
         
     | 
| 
       98 
98 
     | 
    
         
             
                  faraday.response :json
         
     | 
| 
       99 
99 
     | 
    
         
             
                  faraday.adapter :net_http
         
     | 
    
        data/lib/ruby_llm/content.rb
    CHANGED
    
    | 
         @@ -48,3 +48,26 @@ module RubyLLM 
     | 
|
| 
       48 
48 
     | 
    
         
             
                end
         
     | 
| 
       49 
49 
     | 
    
         
             
              end
         
     | 
| 
       50 
50 
     | 
    
         
             
            end
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
            module RubyLLM
         
     | 
| 
      
 53 
     | 
    
         
            +
              class Content
         
     | 
| 
      
 54 
     | 
    
         
            +
                # Represents provider-specific payloads that should bypass RubyLLM formatting.
         
     | 
| 
      
 55 
     | 
    
         
            +
                class Raw
         
     | 
| 
      
 56 
     | 
    
         
            +
                  attr_reader :value
         
     | 
| 
      
 57 
     | 
    
         
            +
             
     | 
| 
      
 58 
     | 
    
         
            +
                  def initialize(value)
         
     | 
| 
      
 59 
     | 
    
         
            +
                    raise ArgumentError, 'Raw content payload cannot be nil' if value.nil?
         
     | 
| 
      
 60 
     | 
    
         
            +
             
     | 
| 
      
 61 
     | 
    
         
            +
                    @value = value
         
     | 
| 
      
 62 
     | 
    
         
            +
                  end
         
     | 
| 
      
 63 
     | 
    
         
            +
             
     | 
| 
      
 64 
     | 
    
         
            +
                  def format
         
     | 
| 
      
 65 
     | 
    
         
            +
                    @value
         
     | 
| 
      
 66 
     | 
    
         
            +
                  end
         
     | 
| 
      
 67 
     | 
    
         
            +
             
     | 
| 
      
 68 
     | 
    
         
            +
                  def to_h
         
     | 
| 
      
 69 
     | 
    
         
            +
                    @value
         
     | 
| 
      
 70 
     | 
    
         
            +
                  end
         
     | 
| 
      
 71 
     | 
    
         
            +
                end
         
     | 
| 
      
 72 
     | 
    
         
            +
              end
         
     | 
| 
      
 73 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/ruby_llm/message.rb
    CHANGED
    
    | 
         @@ -5,18 +5,21 @@ module RubyLLM 
     | 
|
| 
       5 
5 
     | 
    
         
             
              class Message
         
     | 
| 
       6 
6 
     | 
    
         
             
                ROLES = %i[system user assistant tool].freeze
         
     | 
| 
       7 
7 
     | 
    
         | 
| 
       8 
     | 
    
         
            -
                attr_reader :role, :tool_calls, :tool_call_id, :input_tokens, :output_tokens, 
     | 
| 
      
 8 
     | 
    
         
            +
                attr_reader :role, :model_id, :tool_calls, :tool_call_id, :input_tokens, :output_tokens,
         
     | 
| 
      
 9 
     | 
    
         
            +
                            :cached_tokens, :cache_creation_tokens, :raw, :conversation_id
         
     | 
| 
       9 
10 
     | 
    
         
             
                attr_writer :content
         
     | 
| 
       10 
11 
     | 
    
         | 
| 
       11 
12 
     | 
    
         
             
                def initialize(options = {})
         
     | 
| 
       12 
13 
     | 
    
         
             
                  @role = options.fetch(:role).to_sym
         
     | 
| 
       13 
14 
     | 
    
         
             
                  @content = normalize_content(options.fetch(:content))
         
     | 
| 
      
 15 
     | 
    
         
            +
                  @model_id = options[:model_id]
         
     | 
| 
       14 
16 
     | 
    
         
             
                  @tool_calls = options[:tool_calls]
         
     | 
| 
      
 17 
     | 
    
         
            +
                  @tool_call_id = options[:tool_call_id]
         
     | 
| 
      
 18 
     | 
    
         
            +
                  @conversation_id = options[:conversation_id]
         
     | 
| 
       15 
19 
     | 
    
         
             
                  @input_tokens = options[:input_tokens]
         
     | 
| 
       16 
20 
     | 
    
         
             
                  @output_tokens = options[:output_tokens]
         
     | 
| 
       17 
     | 
    
         
            -
                  @ 
     | 
| 
       18 
     | 
    
         
            -
                  @ 
     | 
| 
       19 
     | 
    
         
            -
                  @tool_call_id = options[:tool_call_id]
         
     | 
| 
      
 21 
     | 
    
         
            +
                  @cached_tokens = options[:cached_tokens]
         
     | 
| 
      
 22 
     | 
    
         
            +
                  @cache_creation_tokens = options[:cache_creation_tokens]
         
     | 
| 
       20 
23 
     | 
    
         
             
                  @raw = options[:raw]
         
     | 
| 
       21 
24 
     | 
    
         | 
| 
       22 
25 
     | 
    
         
             
                  ensure_valid_role
         
     | 
| 
         @@ -46,12 +49,14 @@ module RubyLLM 
     | 
|
| 
       46 
49 
     | 
    
         
             
                  {
         
     | 
| 
       47 
50 
     | 
    
         
             
                    role: role,
         
     | 
| 
       48 
51 
     | 
    
         
             
                    content: content,
         
     | 
| 
      
 52 
     | 
    
         
            +
                    model_id: model_id,
         
     | 
| 
       49 
53 
     | 
    
         
             
                    tool_calls: tool_calls,
         
     | 
| 
       50 
54 
     | 
    
         
             
                    tool_call_id: tool_call_id,
         
     | 
| 
      
 55 
     | 
    
         
            +
                    conversation_id: conversation_id,
         
     | 
| 
       51 
56 
     | 
    
         
             
                    input_tokens: input_tokens,
         
     | 
| 
       52 
57 
     | 
    
         
             
                    output_tokens: output_tokens,
         
     | 
| 
       53 
     | 
    
         
            -
                     
     | 
| 
       54 
     | 
    
         
            -
                     
     | 
| 
      
 58 
     | 
    
         
            +
                    cached_tokens: cached_tokens,
         
     | 
| 
      
 59 
     | 
    
         
            +
                    cache_creation_tokens: cache_creation_tokens
         
     | 
| 
       55 
60 
     | 
    
         
             
                  }.compact
         
     | 
| 
       56 
61 
     | 
    
         
             
                end
         
     | 
| 
       57 
62 
     | 
    
         | 
    
        data/lib/ruby_llm/model/info.rb
    CHANGED
    
    | 
         @@ -72,6 +72,10 @@ module RubyLLM 
     | 
|
| 
       72 
72 
     | 
    
         
             
                    pricing.text_tokens.output
         
     | 
| 
       73 
73 
     | 
    
         
             
                  end
         
     | 
| 
       74 
74 
     | 
    
         | 
| 
      
 75 
     | 
    
         
            +
                  def provider_class
         
     | 
| 
      
 76 
     | 
    
         
            +
                    RubyLLM::Provider.resolve provider
         
     | 
| 
      
 77 
     | 
    
         
            +
                  end
         
     | 
| 
      
 78 
     | 
    
         
            +
             
     | 
| 
       75 
79 
     | 
    
         
             
                  def type # rubocop:disable Metrics/PerceivedComplexity
         
     | 
| 
       76 
80 
     | 
    
         
             
                    if modalities.output.include?('embeddings') && !modalities.output.include?('text')
         
     | 
| 
       77 
81 
     | 
    
         
             
                      'embedding'
         
     |