ruby_llm 0.1.0.pre38 → 0.1.0.pre39

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.
@@ -1,242 +0,0 @@
1
- ---
2
- layout: default
3
- title: Streaming
4
- parent: Guides
5
- nav_order: 4
6
- permalink: /guides/streaming
7
- ---
8
-
9
- # Streaming Responses
10
-
11
- RubyLLM provides streaming capabilities that allow you to receive AI responses in real-time as they're being generated, rather than waiting for the complete response. This creates a more interactive experience and is especially useful for long responses or applications with real-time UI updates.
12
-
13
- ## Basic Streaming
14
-
15
- To stream responses, simply provide a block to the `ask` method:
16
-
17
- ```ruby
18
- chat = RubyLLM.chat
19
-
20
- chat.ask "Write a short story about a programmer" do |chunk|
21
- # Each chunk contains a portion of the response
22
- print chunk.content
23
- $stdout.flush # Ensure content is displayed immediately
24
- end
25
- ```
26
-
27
- ## Understanding Chunks
28
-
29
- Each streamed chunk is an instance of `RubyLLM::Chunk` (which inherits from `RubyLLM::Message`) and provides:
30
-
31
- ```ruby
32
- chunk.content # The text fragment in this chunk
33
- chunk.role # Always :assistant for streamed chunks
34
- chunk.model_id # The model generating the response
35
- chunk.input_tokens # Input token count (usually only in the final chunk)
36
- chunk.output_tokens # Output token count (usually only in the final chunk)
37
- ```
38
-
39
- ## Accumulated Response
40
-
41
- Even when streaming, RubyLLM still returns the complete final message:
42
-
43
- ```ruby
44
- final_message = chat.ask "Write a poem" do |chunk|
45
- print chunk.content
46
- end
47
-
48
- # You can use the final message as normal
49
- puts "\nFinal message length: #{final_message.content.length}"
50
- puts "Token usage: #{final_message.output_tokens} tokens"
51
- ```
52
-
53
- ## Web Application Integration
54
-
55
- ### Rails with ActionCable
56
-
57
- ```ruby
58
- # In your controller
59
- def ask
60
- @chat = Chat.find(params[:id])
61
-
62
- @chat.ask(params[:message]) do |chunk|
63
- ActionCable.server.broadcast(
64
- "chat_#{@chat.id}",
65
- { content: chunk.content }
66
- )
67
- end
68
-
69
- head :ok
70
- end
71
-
72
- # In your JavaScript
73
- const channel = consumer.subscriptions.create({ channel: "ChatChannel", id: chatId }, {
74
- received(data) {
75
- // Append incoming chunk to the display
76
- document.getElementById('response').innerHTML += data.content;
77
- }
78
- });
79
- ```
80
-
81
- ### Rails with Turbo Streams
82
-
83
- ```ruby
84
- class ChatJob < ApplicationJob
85
- queue_as :default
86
-
87
- def perform(chat_id, message)
88
- chat = Chat.find(chat_id)
89
-
90
- chat.ask(message) do |chunk|
91
- Turbo::StreamsChannel.broadcast_update_to(
92
- "chat_#{chat.id}",
93
- target: "response",
94
- html: chunk.content,
95
- append: true
96
- )
97
- end
98
- end
99
- end
100
- ```
101
-
102
- ### Sinatra with Server-Sent Events (SSE)
103
-
104
- ```ruby
105
- get '/chat/:id/ask' do
106
- content_type 'text/event-stream'
107
-
108
- chat = Chat.find(params[:id])
109
-
110
- chat.ask(params[:message]) do |chunk|
111
- # Send chunk as SSE event
112
- out << "data: #{chunk.content}\n\n"
113
- end
114
-
115
- # Send completion signal
116
- out << "event: complete\ndata: {}\n\n"
117
- end
118
- ```
119
-
120
- ## Error Handling
121
-
122
- Errors that occur during streaming need special handling:
123
-
124
- ```ruby
125
- begin
126
- chat.ask("Tell me a story") do |chunk|
127
- print chunk.content
128
- end
129
- rescue RubyLLM::Error => e
130
- puts "\nError during streaming: #{e.message}"
131
- end
132
- ```
133
-
134
- Common errors during streaming:
135
-
136
- - `ServiceUnavailableError` - The AI service is temporarily unavailable
137
- - `RateLimitError` - You've exceeded your API rate limit
138
- - `BadRequestError` - There was a problem with your request parameters
139
-
140
- ## Provider-Specific Considerations
141
-
142
- ### OpenAI
143
-
144
- OpenAI's streaming implementation provides small, frequent chunks for a smooth experience.
145
-
146
- ### Anthropic
147
-
148
- Claude models may return slightly larger chunks with potentially longer pauses between them.
149
-
150
- ### Google Gemini
151
-
152
- Gemini streaming is highly responsive but may show slightly different chunking behavior.
153
-
154
- ## Streaming with Tools
155
-
156
- When using tools, streaming works a bit differently:
157
-
158
- ```ruby
159
- chat.with_tool(Calculator)
160
- .ask("What's 123 * 456?") do |chunk|
161
- # Tool call execution isn't streamed
162
- # You'll receive chunks after tool execution completes
163
- print chunk.content
164
- end
165
- ```
166
-
167
- The tool call execution introduces a pause in the streaming, as the model waits for the tool response before continuing.
168
-
169
- ## Performance Considerations
170
-
171
- Streaming typically uses the same number of tokens as non-streaming responses but establishes longer-lived connections to the AI provider. Consider these best practices:
172
-
173
- 1. Set appropriate timeouts for streaming connections
174
- 2. Handle network interruptions gracefully
175
- 3. Consider background processing for long-running streams
176
- 4. Implement rate limiting to avoid overwhelming your servers
177
-
178
- ## Tracking Token Usage
179
-
180
- Token usage information is typically only available in the final chunk or completed message:
181
-
182
- ```ruby
183
- total_tokens = 0
184
-
185
- chat.ask("Write a detailed explanation of quantum computing") do |chunk|
186
- print chunk.content
187
-
188
- # Only count tokens in the final chunk
189
- if chunk.output_tokens
190
- total_tokens = chunk.input_tokens + chunk.output_tokens
191
- end
192
- end
193
-
194
- puts "\nTotal tokens: #{total_tokens}"
195
- ```
196
-
197
- ## Custom Processing of Streamed Content
198
-
199
- You can process streamed content in real-time:
200
-
201
- ```ruby
202
- accumulated_text = ""
203
-
204
- chat.ask("Write a list of 10 fruits") do |chunk|
205
- new_content = chunk.content
206
- accumulated_text += new_content
207
-
208
- # Count fruits as they come in
209
- if new_content.include?("\n")
210
- fruit_count = accumulated_text.scan(/\d+\./).count
211
- print "\rFruits listed: #{fruit_count}/10"
212
- end
213
- end
214
- ```
215
-
216
- ## Rails Integration
217
-
218
- When using RubyLLM's Rails integration with `acts_as_chat`, streaming still works seamlessly:
219
-
220
- ```ruby
221
- class Chat < ApplicationRecord
222
- acts_as_chat
223
- end
224
-
225
- chat = Chat.create!(model_id: 'gpt-4o-mini')
226
-
227
- # Stream responses while persisting the final result
228
- chat.ask("Tell me about Ruby") do |chunk|
229
- ActionCable.server.broadcast("chat_#{chat.id}", { content: chunk.content })
230
- end
231
-
232
- # The complete message is saved in the database
233
- puts chat.messages.last.content
234
- ```
235
-
236
- ## Next Steps
237
-
238
- Now that you understand streaming, you might want to explore:
239
-
240
- - [Using Tools]({% link guides/tools.md %}) to add capabilities to your AI interactions
241
- - [Rails Integration]({% link guides/rails.md %}) to persist conversations
242
- - [Error Handling]({% link guides/error-handling.md %}) for reliable applications
data/docs/guides/tools.md DELETED
@@ -1,247 +0,0 @@
1
- ---
2
- layout: default
3
- title: Tools
4
- parent: Guides
5
- nav_order: 3
6
- permalink: /guides/tools
7
- ---
8
-
9
- # Using Tools with RubyLLM
10
-
11
- Tools allow AI models to call your Ruby code to perform actions or retrieve information. This guide explains how to create and use tools with RubyLLM.
12
-
13
- ## What Are Tools?
14
-
15
- Tools (also known as "functions" or "plugins") let AI models:
16
-
17
- 1. Recognize when external functionality is needed
18
- 2. Call your Ruby code with appropriate parameters
19
- 3. Use the results to enhance their responses
20
-
21
- Common use cases include:
22
- - Retrieving real-time data
23
- - Performing calculations
24
- - Accessing databases
25
- - Controlling external systems
26
-
27
- ## Creating a Tool
28
-
29
- Tools are defined as Ruby classes that inherit from `RubyLLM::Tool`:
30
-
31
- ```ruby
32
- class Calculator < RubyLLM::Tool
33
- description "Performs arithmetic calculations"
34
-
35
- param :expression,
36
- type: :string,
37
- desc: "A mathematical expression to evaluate (e.g. '2 + 2')"
38
-
39
- def execute(expression:)
40
- eval(expression).to_s
41
- rescue StandardError => e
42
- "Error: #{e.message}"
43
- end
44
- end
45
- ```
46
-
47
- ### Tool Components
48
-
49
- Each tool has these key elements:
50
-
51
- 1. **Description** - Explains what the tool does, helping the AI decide when to use it
52
- 2. **Parameters** - Define the inputs the tool expects
53
- 3. **Execute Method** - The code that runs when the tool is called
54
-
55
- ### Parameter Definition
56
-
57
- Parameters accept several options:
58
-
59
- ```ruby
60
- param :parameter_name,
61
- type: :string, # Data type (:string, :integer, :boolean, :array, :object)
62
- desc: "Description", # Description of what the parameter does
63
- required: true # Whether the parameter is required (default: true)
64
- ```
65
-
66
- ## Using Tools in Chat
67
-
68
- To use a tool, attach it to a chat:
69
-
70
- ```ruby
71
- # Create the chat
72
- chat = RubyLLM.chat
73
-
74
- # Add a tool
75
- chat.with_tool(Calculator)
76
-
77
- # Now you can ask questions that might require calculation
78
- response = chat.ask "What's 123 * 456?"
79
- # => "Let me calculate that for you. 123 * 456 = 56088."
80
- ```
81
-
82
- ### Multiple Tools
83
-
84
- You can provide multiple tools to a single chat:
85
-
86
- ```ruby
87
- class Weather < RubyLLM::Tool
88
- description "Gets current weather for a location"
89
-
90
- param :location,
91
- desc: "City name or zip code"
92
-
93
- def execute(location:)
94
- # Simulate weather lookup
95
- "72°F and sunny in #{location}"
96
- end
97
- end
98
-
99
- # Add multiple tools
100
- chat = RubyLLM.chat
101
- .with_tools(Calculator, Weather)
102
-
103
- # Ask questions that might use either tool
104
- chat.ask "What's the temperature in New York City?"
105
- chat.ask "If it's 72°F in NYC and 54°F in Boston, what's the average?"
106
- ```
107
-
108
- ## Custom Initialization
109
-
110
- Tools can have custom initialization:
111
-
112
- ```ruby
113
- class DocumentSearch < RubyLLM::Tool
114
- description "Searches documents by relevance"
115
-
116
- param :query,
117
- desc: "The search query"
118
-
119
- param :limit,
120
- type: :integer,
121
- desc: "Maximum number of results",
122
- required: false
123
-
124
- def initialize(database)
125
- @database = database
126
- end
127
-
128
- def execute(query:, limit: 5)
129
- # Search in @database
130
- @database.search(query, limit: limit)
131
- end
132
- end
133
-
134
- # Initialize with dependencies
135
- search_tool = DocumentSearch.new(MyDatabase)
136
- chat.with_tool(search_tool)
137
- ```
138
-
139
- ## The Tool Execution Flow
140
-
141
- Here's what happens when a tool is used:
142
-
143
- 1. You ask a question
144
- 2. The model decides a tool is needed
145
- 3. The model selects the tool and provides arguments
146
- 4. RubyLLM calls your tool's `execute` method
147
- 5. The result is sent back to the model
148
- 6. The model incorporates the result into its response
149
-
150
- For example:
151
-
152
- ```ruby
153
- response = chat.ask "What's 123 squared plus 456?"
154
-
155
- # Behind the scenes:
156
- # 1. Model decides it needs to calculate
157
- # 2. Model calls Calculator with expression: "123 * 123 + 456"
158
- # 3. Tool returns "15,585"
159
- # 4. Model incorporates this in its response
160
- ```
161
-
162
- ## Debugging Tools
163
-
164
- Enable debugging to see tool calls in action:
165
-
166
- ```ruby
167
- # Enable debug logging
168
- ENV['RUBY_LLM_DEBUG'] = 'true'
169
-
170
- # Make a request
171
- chat.ask "What's 15329 divided by 437?"
172
-
173
- # Console output:
174
- # D, -- RubyLLM: Tool calculator called with: {"expression"=>"15329 / 437"}
175
- # D, -- RubyLLM: Tool calculator returned: "35.078719"
176
- ```
177
-
178
- ## Error Handling
179
-
180
- Tools can handle errors gracefully:
181
-
182
- ```ruby
183
- class Calculator < RubyLLM::Tool
184
- description "Performs arithmetic calculations"
185
-
186
- param :expression,
187
- type: :string,
188
- desc: "Math expression to evaluate"
189
-
190
- def execute(expression:)
191
- eval(expression).to_s
192
- rescue StandardError => e
193
- # Return error as a result
194
- { error: "Error calculating #{expression}: #{e.message}" }
195
- end
196
- end
197
-
198
- # When there's an error, the model will receive and explain it
199
- chat.ask "What's 1/0?"
200
- # => "I tried to calculate 1/0, but there was an error: divided by 0"
201
- ```
202
-
203
- ## Advanced Tool Parameters
204
-
205
- Tools can have complex parameter types:
206
-
207
- ```ruby
208
- class DataAnalysis < RubyLLM::Tool
209
- description "Analyzes numerical data"
210
-
211
- param :data,
212
- type: :array,
213
- desc: "Array of numbers to analyze"
214
-
215
- param :operations,
216
- type: :object,
217
- desc: "Analysis operations to perform",
218
- required: false
219
-
220
- def execute(data:, operations: {mean: true, median: false})
221
- result = {}
222
-
223
- result[:mean] = data.sum.to_f / data.size if operations[:mean]
224
- result[:median] = calculate_median(data) if operations[:median]
225
-
226
- result
227
- end
228
-
229
- private
230
-
231
- def calculate_median(data)
232
- sorted = data.sort
233
- mid = sorted.size / 2
234
- sorted.size.odd? ? sorted[mid] : (sorted[mid-1] + sorted[mid]) / 2.0
235
- end
236
- end
237
- ```
238
-
239
- ## When to Use Tools
240
-
241
- Tools are best for:
242
-
243
- 1. **External data retrieval** - Getting real-time information like weather, prices, or database records
244
- 2. **Computation** - When calculations are complex or involve large numbers
245
- 3. **System integration** - Connecting to external APIs or services
246
- 4. **Data processing** - Working with files, formatting data, or analyzing information
247
- 5. **Stateful operations** - When you need to maintain state between calls
data/docs/index.md DELETED
@@ -1,73 +0,0 @@
1
- ---
2
- layout: default
3
- title: Home
4
- nav_order: 1
5
- description: "RubyLLM is a delightful Ruby way to work with AI."
6
- permalink: /
7
- ---
8
-
9
- # RubyLLM
10
- {: .fs-9 }
11
-
12
- A delightful Ruby way to work with AI through a unified interface to OpenAI, Anthropic, Google, and DeepSeek.
13
- {: .fs-6 .fw-300 }
14
-
15
- [Get started now]({% link installation.md %}){: .btn .btn-primary .fs-5 .mb-4 .mb-md-0 .mr-2 }
16
- [View on GitHub](https://github.com/crmne/ruby_llm){: .btn .fs-5 .mb-4 .mb-md-0 }
17
-
18
- ---
19
- <p align="center">
20
- <img src="https://upload.wikimedia.org/wikipedia/commons/4/4d/OpenAI_Logo.svg" alt="OpenAI" height="40" width="120">
21
- &nbsp;&nbsp;&nbsp;&nbsp;
22
- <img src="https://upload.wikimedia.org/wikipedia/commons/7/78/Anthropic_logo.svg" alt="Anthropic" height="40" width="120">
23
- &nbsp;&nbsp;&nbsp;&nbsp;
24
- <img src="https://upload.wikimedia.org/wikipedia/commons/8/8a/Google_Gemini_logo.svg" alt="Google" height="40" width="120">
25
- &nbsp;&nbsp;&nbsp;&nbsp;
26
- <img src="https://upload.wikimedia.org/wikipedia/commons/e/ec/DeepSeek_logo.svg" alt="DeepSeek" height="40" width="120">
27
- </p>
28
-
29
- <p align="center">
30
- <a href="https://badge.fury.io/rb/ruby_llm"><img src="https://badge.fury.io/rb/ruby_llm.svg" alt="Gem Version" /></a>
31
- <a href="https://github.com/testdouble/standard"><img src="https://img.shields.io/badge/code_style-standard-brightgreen.svg" alt="Ruby Style Guide" /></a>
32
- <a href="https://rubygems.org/gems/ruby_llm"><img alt="Gem Total Downloads" src="https://img.shields.io/gem/dt/ruby_llm"></a>
33
- <a href="https://github.com/crmne/ruby_llm/actions/workflows/cicd.yml"><img src="https://github.com/crmne/ruby_llm/actions/workflows/cicd.yml/badge.svg" alt="CI" /></a>
34
- <a href="https://codecov.io/gh/crmne/ruby_llm"><img src="https://codecov.io/gh/crmne/ruby_llm/branch/main/graph/badge.svg" alt="codecov" /></a>
35
- </p>
36
-
37
- ---
38
-
39
- ## Overview
40
-
41
- RubyLLM provides a beautiful, unified interface to modern AI services, including:
42
-
43
- - 💬 **Chat** with OpenAI GPT, Anthropic Claude, Google Gemini, and DeepSeek models
44
- - 🎵 **Vision and Audio** understanding
45
- - 🖼️ **Image generation** with DALL-E and other providers
46
- - 📊 **Embeddings** for vector search and semantic analysis
47
- - 🔧 **Tools** that let AI use your Ruby code
48
- - 🚂 **Rails integration** to persist chats and messages with ActiveRecord
49
- - 🌊 **Streaming** responses with proper Ruby patterns
50
-
51
- ## Quick start
52
-
53
- ```ruby
54
- require 'ruby_llm'
55
-
56
- # Configure your API keys
57
- RubyLLM.configure do |config|
58
- config.openai_api_key = ENV['OPENAI_API_KEY']
59
- end
60
-
61
- # Start chatting
62
- chat = RubyLLM.chat
63
- response = chat.ask "What's the best way to learn Ruby?"
64
-
65
- # Generate images
66
- image = RubyLLM.paint "a sunset over mountains"
67
- puts image.url
68
- ```
69
-
70
- ## Learn more
71
-
72
- - [Installation]({% link installation.md %})
73
- - [Guides]({% link guides/index.md %})
data/docs/installation.md DELETED
@@ -1,98 +0,0 @@
1
- ---
2
- layout: default
3
- title: Installation
4
- nav_order: 2
5
- permalink: /installation
6
- ---
7
-
8
- # Installation
9
-
10
- RubyLLM is packaged as a Ruby gem, making it easy to install in your projects.
11
-
12
- ## Requirements
13
-
14
- * Ruby 3.1 or later
15
- * An API key from at least one of the supported providers:
16
- * OpenAI
17
- * Anthropic
18
- * Google (Gemini)
19
- * DeepSeek
20
-
21
- ## Installation Methods
22
-
23
- ### Using Bundler (recommended)
24
-
25
- Add RubyLLM to your project's Gemfile:
26
-
27
- ```ruby
28
- gem 'ruby_llm'
29
- ```
30
-
31
- Then install the dependencies:
32
-
33
- ```bash
34
- bundle install
35
- ```
36
-
37
- ### Manual Installation
38
-
39
- If you're not using Bundler, you can install RubyLLM directly:
40
-
41
- ```bash
42
- gem install ruby_llm
43
- ```
44
-
45
- ## Configuration
46
-
47
- After installing RubyLLM, you'll need to configure it with your API keys:
48
-
49
- ```ruby
50
- require 'ruby_llm'
51
-
52
- RubyLLM.configure do |config|
53
- # Required: At least one API key
54
- config.openai_api_key = ENV['OPENAI_API_KEY']
55
- config.anthropic_api_key = ENV['ANTHROPIC_API_KEY']
56
- config.gemini_api_key = ENV['GEMINI_API_KEY']
57
- config.deepseek_api_key = ENV['DEEPSEEK_API_KEY']
58
-
59
- # Optional: Set default models
60
- config.default_model = 'gpt-4o-mini' # Default chat model
61
- config.default_embedding_model = 'text-embedding-3-small' # Default embedding model
62
- config.default_image_model = 'dall-e-3' # Default image generation model
63
-
64
- # Optional: Configure request settings
65
- config.request_timeout = 120 # Request timeout in seconds
66
- config.max_retries = 3 # Number of retries on failures
67
- end
68
- ```
69
-
70
- We recommend storing your API keys as environment variables rather than hardcoding them in your application.
71
-
72
- ## Verifying Installation
73
-
74
- You can verify that RubyLLM is correctly installed and configured by running a simple test:
75
-
76
- ```ruby
77
- require 'ruby_llm'
78
-
79
- # Configure with at least one API key
80
- RubyLLM.configure do |config|
81
- config.openai_api_key = ENV['OPENAI_API_KEY']
82
- end
83
-
84
- # Try a simple query
85
- chat = RubyLLM.chat
86
- response = chat.ask "Hello, world!"
87
- puts response.content
88
-
89
- # Check available models
90
- puts "Available models:"
91
- RubyLLM.models.chat_models.each do |model|
92
- puts "- #{model.id} (#{model.provider})"
93
- end
94
- ```
95
-
96
- ## Next Steps
97
-
98
- Once you've successfully installed RubyLLM, check out the [Getting Started guide]({% link guides/getting-started.md %}) to learn how to use it in your applications.