ruby_llm 0.1.0.pre19 → 0.1.0.pre21
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/LICENSE +21 -0
- data/README.md +31 -17
- data/lib/ruby_llm/error.rb +54 -0
- data/lib/ruby_llm/provider.rb +1 -1
- data/lib/ruby_llm/providers/anthropic.rb +4 -0
- data/lib/ruby_llm/providers/openai.rb +19 -3
- data/lib/ruby_llm/version.rb +1 -1
- metadata +4 -2
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 5e413abd8ebadc4f30b5dc080113d9e40405117128bc8156bb7d9b0086f76956
         | 
| 4 | 
            +
              data.tar.gz: 9a6971f4f5d7f709a4783b9f816589ca2b7e11199d1189bd5c946a8154d2a529
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: bdc7ad9b3e63755d398cfcc514103532d065a72bbf0eb7219496fc4c278e62b80cede1c9cadc75f3dbdb194cb655fb843d04a77d9860fd5665f5af34419f68eb
         | 
| 7 | 
            +
              data.tar.gz: c4056980199b29e41993e1f7b092d760328b21e6327c427ad1a9ef09f4e5521482bb9a3cc9c1d0eba3380b18127541b583ebbd7d4780057df1e838c9bba48793
         | 
    
        data/LICENSE
    ADDED
    
    | @@ -0,0 +1,21 @@ | |
| 1 | 
            +
            MIT License
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            Copyright (c) 2025 Carmine Paolino
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            Permission is hereby granted, free of charge, to any person obtaining a copy
         | 
| 6 | 
            +
            of this software and associated documentation files (the "Software"), to deal
         | 
| 7 | 
            +
            in the Software without restriction, including without limitation the rights
         | 
| 8 | 
            +
            to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
         | 
| 9 | 
            +
            copies of the Software, and to permit persons to whom the Software is
         | 
| 10 | 
            +
            furnished to do so, subject to the following conditions:
         | 
| 11 | 
            +
             | 
| 12 | 
            +
            The above copyright notice and this permission notice shall be included in all
         | 
| 13 | 
            +
            copies or substantial portions of the Software.
         | 
| 14 | 
            +
             | 
| 15 | 
            +
            THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
         | 
| 16 | 
            +
            IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
         | 
| 17 | 
            +
            FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
         | 
| 18 | 
            +
            AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
         | 
| 19 | 
            +
            LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
         | 
| 20 | 
            +
            OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
         | 
| 21 | 
            +
            SOFTWARE.
         | 
    
        data/README.md
    CHANGED
    
    | @@ -19,9 +19,7 @@ Or install it yourself: | |
| 19 19 | 
             
            gem install ruby_llm
         | 
| 20 20 | 
             
            ```
         | 
| 21 21 |  | 
| 22 | 
            -
            ##  | 
| 23 | 
            -
             | 
| 24 | 
            -
            RubyLLM makes it dead simple to start chatting with AI models:
         | 
| 22 | 
            +
            ## Configuration
         | 
| 25 23 |  | 
| 26 24 | 
             
            ```ruby
         | 
| 27 25 | 
             
            require 'ruby_llm'
         | 
| @@ -31,7 +29,13 @@ RubyLLM.configure do |config| | |
| 31 29 | 
             
              config.openai_api_key = ENV['OPENAI_API_KEY']
         | 
| 32 30 | 
             
              config.anthropic_api_key = ENV['ANTHROPIC_API_KEY']
         | 
| 33 31 | 
             
            end
         | 
| 32 | 
            +
            ```
         | 
| 33 | 
            +
             | 
| 34 | 
            +
            ## Quick Start
         | 
| 35 | 
            +
             | 
| 36 | 
            +
            RubyLLM makes it dead simple to start chatting with AI models:
         | 
| 34 37 |  | 
| 38 | 
            +
            ```ruby
         | 
| 35 39 | 
             
            # Start a conversation
         | 
| 36 40 | 
             
            chat = RubyLLM.chat
         | 
| 37 41 | 
             
            chat.ask "What's the best way to learn Ruby?"
         | 
| @@ -82,16 +86,13 @@ Need vector embeddings for your text? RubyLLM makes it simple: | |
| 82 86 |  | 
| 83 87 | 
             
            ```ruby
         | 
| 84 88 | 
             
            # Get embeddings with the default model
         | 
| 85 | 
            -
             | 
| 89 | 
            +
            RubyLLM.embed "Hello, world!"
         | 
| 86 90 |  | 
| 87 91 | 
             
            # Use a specific model
         | 
| 88 | 
            -
             | 
| 89 | 
            -
              "Ruby is awesome!",
         | 
| 90 | 
            -
              model: "text-embedding-3-large"
         | 
| 91 | 
            -
            )
         | 
| 92 | 
            +
            RubyLLM.embed "Ruby is awesome!", model: "text-embedding-3-large"
         | 
| 92 93 |  | 
| 93 94 | 
             
            # Process multiple texts at once
         | 
| 94 | 
            -
             | 
| 95 | 
            +
            RubyLLM.embed([
         | 
| 95 96 | 
             
              "First document",
         | 
| 96 97 | 
             
              "Second document",
         | 
| 97 98 | 
             
              "Third document"
         | 
| @@ -172,6 +173,21 @@ chat.ask "What's 123 * 456?" | |
| 172 173 | 
             
            # D, -- RubyLLM: Tool calculator returned: "56088"
         | 
| 173 174 | 
             
            ```
         | 
| 174 175 |  | 
| 176 | 
            +
            ## Error Handling
         | 
| 177 | 
            +
             | 
| 178 | 
            +
            RubyLLM wraps provider errors in clear Ruby exceptions:
         | 
| 179 | 
            +
             | 
| 180 | 
            +
            ```ruby
         | 
| 181 | 
            +
            begin
         | 
| 182 | 
            +
              chat = RubyLLM.chat
         | 
| 183 | 
            +
              chat.ask "Hello world!"
         | 
| 184 | 
            +
            rescue RubyLLM::UnauthorizedError
         | 
| 185 | 
            +
              puts "Check your API credentials"
         | 
| 186 | 
            +
            rescue RubyLLM::BadRequestError => e
         | 
| 187 | 
            +
              puts "Something went wrong: #{e.message}"
         | 
| 188 | 
            +
            end
         | 
| 189 | 
            +
            ```
         | 
| 190 | 
            +
             | 
| 175 191 | 
             
            ## Rails Integration
         | 
| 176 192 |  | 
| 177 193 | 
             
            RubyLLM comes with built-in Rails support that makes it dead simple to persist your chats and messages. Just create your tables and hook it up:
         | 
| @@ -242,14 +258,14 @@ That's it! Now you can use chats straight from your models: | |
| 242 258 |  | 
| 243 259 | 
             
            ```ruby
         | 
| 244 260 | 
             
            # Create a new chat
         | 
| 245 | 
            -
            chat = Chat.create! | 
| 261 | 
            +
            chat = Chat.create! model_id: "gpt-4o-mini"
         | 
| 246 262 |  | 
| 247 263 | 
             
            # Ask questions - messages are automatically saved
         | 
| 248 264 | 
             
            chat.ask "What's the weather in Paris?"
         | 
| 249 265 |  | 
| 250 266 | 
             
            # Stream responses in real-time
         | 
| 251 267 | 
             
            chat.ask "Tell me a story" do |chunk|
         | 
| 252 | 
            -
              broadcast_chunk | 
| 268 | 
            +
              broadcast_chunk chunk
         | 
| 253 269 | 
             
            end
         | 
| 254 270 |  | 
| 255 271 | 
             
            # Everything is persisted automatically
         | 
| @@ -307,7 +323,7 @@ The persistence works seamlessly with background jobs: | |
| 307 323 | 
             
            ```ruby
         | 
| 308 324 | 
             
            class ChatJob < ApplicationJob
         | 
| 309 325 | 
             
              def perform(chat_id, message)
         | 
| 310 | 
            -
                chat = Chat.find | 
| 326 | 
            +
                chat = Chat.find chat_id
         | 
| 311 327 |  | 
| 312 328 | 
             
                chat.ask(message) do |chunk|
         | 
| 313 329 | 
             
                  # Optional: Broadcast chunks for real-time updates
         | 
| @@ -341,8 +357,8 @@ class WeatherTool < RubyLLM::Tool | |
| 341 357 | 
             
            end
         | 
| 342 358 |  | 
| 343 359 | 
             
            # Use tools with your persisted chats
         | 
| 344 | 
            -
            chat = Chat.create! | 
| 345 | 
            -
            chat.chat.with_tool | 
| 360 | 
            +
            chat = Chat.create! model_id: "gpt-4"
         | 
| 361 | 
            +
            chat.chat.with_tool WeatherTool.new
         | 
| 346 362 |  | 
| 347 363 | 
             
            # Ask about weather - tool usage is automatically saved
         | 
| 348 364 | 
             
            chat.ask "What's the weather in Paris?"
         | 
| @@ -352,8 +368,6 @@ pp chat.messages.map(&:role) | |
| 352 368 | 
             
            #=> [:user, :assistant, :tool, :assistant]
         | 
| 353 369 | 
             
            ```
         | 
| 354 370 |  | 
| 355 | 
            -
            Looking for more examples? Check out the [example Rails app](https://github.com/example/ruby_llm_rails) showing these patterns in action!
         | 
| 356 | 
            -
             | 
| 357 371 | 
             
            ## Development
         | 
| 358 372 |  | 
| 359 373 | 
             
            After checking out the repo, run `bin/setup` to install dependencies. Then, run `bin/console` for an interactive prompt.
         | 
| @@ -364,4 +378,4 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/crmne/ | |
| 364 378 |  | 
| 365 379 | 
             
            ## License
         | 
| 366 380 |  | 
| 367 | 
            -
            Released under the MIT License. See LICENSE | 
| 381 | 
            +
            Released under the MIT License. See [LICENSE](LICENSE) for details.
         | 
| @@ -0,0 +1,54 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module RubyLLM
         | 
| 4 | 
            +
              # Custom error class that wraps API errors from different providers
         | 
| 5 | 
            +
              # into a consistent format with helpful error messages.
         | 
| 6 | 
            +
              #
         | 
| 7 | 
            +
              # Example:
         | 
| 8 | 
            +
              #   begin
         | 
| 9 | 
            +
              #     chat.ask "What's 2+2?"
         | 
| 10 | 
            +
              #   rescue RubyLLM::Error => e
         | 
| 11 | 
            +
              #     puts "Couldn't chat with AI: #{e.message}"
         | 
| 12 | 
            +
              #   end
         | 
| 13 | 
            +
              class Error < StandardError
         | 
| 14 | 
            +
                attr_reader :response
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                def initialize(response = nil, message = nil)
         | 
| 17 | 
            +
                  @response = response
         | 
| 18 | 
            +
                  super(message || response&.body.to_s)
         | 
| 19 | 
            +
                end
         | 
| 20 | 
            +
              end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
              class UnauthorizedError < Error; end
         | 
| 23 | 
            +
              class BadRequestError < Error; end
         | 
| 24 | 
            +
              class RateLimitError < Error; end
         | 
| 25 | 
            +
              class ServerError < Error; end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
              # Faraday middleware that maps provider-specific API errors to RubyLLM errors.
         | 
| 28 | 
            +
              # Uses provider's parse_error method to extract meaningful error messages.
         | 
| 29 | 
            +
              class ErrorMiddleware < Faraday::Middleware
         | 
| 30 | 
            +
                def initialize(app, provider: nil)
         | 
| 31 | 
            +
                  super(app)
         | 
| 32 | 
            +
                  @provider = provider
         | 
| 33 | 
            +
                end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                def call(env) # rubocop:disable Metrics/MethodLength
         | 
| 36 | 
            +
                  @app.call(env).on_complete do |response|
         | 
| 37 | 
            +
                    message = @provider&.parse_error(response)
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                    case response.status
         | 
| 40 | 
            +
                    when 400
         | 
| 41 | 
            +
                      raise BadRequestError.new(response, message)
         | 
| 42 | 
            +
                    when 401
         | 
| 43 | 
            +
                      raise UnauthorizedError.new(response, 'Invalid API key - check your credentials')
         | 
| 44 | 
            +
                    when 429
         | 
| 45 | 
            +
                      raise RateLimitError.new(response, 'Rate limit exceeded - please wait a moment')
         | 
| 46 | 
            +
                    when 500..599
         | 
| 47 | 
            +
                      raise ServerError.new(response, 'API server error - please try again')
         | 
| 48 | 
            +
                    end
         | 
| 49 | 
            +
                  end
         | 
| 50 | 
            +
                end
         | 
| 51 | 
            +
              end
         | 
| 52 | 
            +
            end
         | 
| 53 | 
            +
             | 
| 54 | 
            +
            Faraday::Middleware.register_middleware(llm_errors: RubyLLM::ErrorMiddleware)
         | 
    
        data/lib/ruby_llm/provider.rb
    CHANGED
    
    | @@ -69,7 +69,7 @@ module RubyLLM | |
| 69 69 | 
             
                      f.request :json
         | 
| 70 70 | 
             
                      f.response :json
         | 
| 71 71 | 
             
                      f.adapter Faraday.default_adapter
         | 
| 72 | 
            -
                      f.use  | 
| 72 | 
            +
                      f.use :llm_errors, provider: self
         | 
| 73 73 | 
             
                      f.response :logger, RubyLLM.logger, { headers: false, bodies: true, errors: true, log_level: :debug }
         | 
| 74 74 | 
             
                    end
         | 
| 75 75 | 
             
                  end
         | 
| @@ -8,6 +8,10 @@ module RubyLLM | |
| 8 8 | 
             
                class OpenAI # rubocop:disable Metrics/ClassLength
         | 
| 9 9 | 
             
                  include Provider
         | 
| 10 10 |  | 
| 11 | 
            +
                  def parse_error(response)
         | 
| 12 | 
            +
                    JSON.parse(response.body).dig('error', 'message')
         | 
| 13 | 
            +
                  end
         | 
| 14 | 
            +
             | 
| 11 15 | 
             
                  private
         | 
| 12 16 |  | 
| 13 17 | 
             
                  def api_base
         | 
| @@ -43,13 +47,14 @@ module RubyLLM | |
| 43 47 | 
             
                        payload[:tools] = tools.map { |_, tool| tool_for(tool) }
         | 
| 44 48 | 
             
                        payload[:tool_choice] = 'auto'
         | 
| 45 49 | 
             
                      end
         | 
| 50 | 
            +
                      payload[:stream_options] = { include_usage: true } if stream
         | 
| 46 51 | 
             
                    end
         | 
| 47 52 | 
             
                  end
         | 
| 48 53 |  | 
| 49 54 | 
             
                  def format_messages(messages)
         | 
| 50 55 | 
             
                    messages.map do |msg|
         | 
| 51 56 | 
             
                      {
         | 
| 52 | 
            -
                        role: msg.role | 
| 57 | 
            +
                        role: format_role(msg.role),
         | 
| 53 58 | 
             
                        content: msg.content,
         | 
| 54 59 | 
             
                        tool_calls: format_tool_calls(msg.tool_calls),
         | 
| 55 60 | 
             
                        tool_call_id: msg.tool_call_id
         | 
| @@ -57,6 +62,15 @@ module RubyLLM | |
| 57 62 | 
             
                    end
         | 
| 58 63 | 
             
                  end
         | 
| 59 64 |  | 
| 65 | 
            +
                  def format_role(role)
         | 
| 66 | 
            +
                    case role
         | 
| 67 | 
            +
                    when :system
         | 
| 68 | 
            +
                      'developer'
         | 
| 69 | 
            +
                    else
         | 
| 70 | 
            +
                      role.to_s
         | 
| 71 | 
            +
                    end
         | 
| 72 | 
            +
                  end
         | 
| 73 | 
            +
             | 
| 60 74 | 
             
                  def build_embedding_payload(text, model:)
         | 
| 61 75 | 
             
                    {
         | 
| 62 76 | 
             
                      model: model,
         | 
| @@ -156,14 +170,16 @@ module RubyLLM | |
| 156 170 | 
             
                    end.compact
         | 
| 157 171 | 
             
                  end
         | 
| 158 172 |  | 
| 159 | 
            -
                  def handle_stream(&block)
         | 
| 173 | 
            +
                  def handle_stream(&block) # rubocop:disable Metrics/MethodLength
         | 
| 160 174 | 
             
                    to_json_stream do |data|
         | 
| 161 175 | 
             
                      block.call(
         | 
| 162 176 | 
             
                        Chunk.new(
         | 
| 163 177 | 
             
                          role: :assistant,
         | 
| 164 178 | 
             
                          model_id: data['model'],
         | 
| 165 179 | 
             
                          content: data.dig('choices', 0, 'delta', 'content'),
         | 
| 166 | 
            -
                          tool_calls: parse_tool_calls(data.dig('choices', 0, 'delta', 'tool_calls'), parse_arguments: false)
         | 
| 180 | 
            +
                          tool_calls: parse_tool_calls(data.dig('choices', 0, 'delta', 'tool_calls'), parse_arguments: false),
         | 
| 181 | 
            +
                          input_tokens: data.dig('usage', 'prompt_tokens'),
         | 
| 182 | 
            +
                          output_tokens: data.dig('usage', 'completion_tokens')
         | 
| 167 183 | 
             
                        )
         | 
| 168 184 | 
             
                      )
         | 
| 169 185 | 
             
                    end
         | 
    
        data/lib/ruby_llm/version.rb
    CHANGED
    
    
    
        metadata
    CHANGED
    
    | @@ -1,14 +1,14 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: ruby_llm
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 0.1.0. | 
| 4 | 
            +
              version: 0.1.0.pre21
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Carmine Paolino
         | 
| 8 8 | 
             
            autorequire:
         | 
| 9 9 | 
             
            bindir: exe
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date: 2025-02- | 
| 11 | 
            +
            date: 2025-02-12 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 14 | 
             
              name: event_stream_parser
         | 
| @@ -339,6 +339,7 @@ files: | |
| 339 339 | 
             
            - ".rspec"
         | 
| 340 340 | 
             
            - ".rubocop.yml"
         | 
| 341 341 | 
             
            - Gemfile
         | 
| 342 | 
            +
            - LICENSE
         | 
| 342 343 | 
             
            - README.md
         | 
| 343 344 | 
             
            - Rakefile
         | 
| 344 345 | 
             
            - bin/console
         | 
| @@ -349,6 +350,7 @@ files: | |
| 349 350 | 
             
            - lib/ruby_llm/chunk.rb
         | 
| 350 351 | 
             
            - lib/ruby_llm/configuration.rb
         | 
| 351 352 | 
             
            - lib/ruby_llm/embedding.rb
         | 
| 353 | 
            +
            - lib/ruby_llm/error.rb
         | 
| 352 354 | 
             
            - lib/ruby_llm/message.rb
         | 
| 353 355 | 
             
            - lib/ruby_llm/model_capabilities/anthropic.rb
         | 
| 354 356 | 
             
            - lib/ruby_llm/model_capabilities/openai.rb
         |