ai-chat 0.5.8 → 0.6.1
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 +303 -671
- data/ai-chat.gemspec +2 -2
- data/lib/ai/chat.rb +73 -31
- metadata +3 -3
data/README.md
CHANGED
|
@@ -1,927 +1,559 @@
|
|
|
1
1
|
# AI Chat
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A Ruby gem that makes it easy to use OpenAI's generative AI models. Designed for learners: conversations are just arrays of hashes, so you can see exactly what's happening at every step.
|
|
4
4
|
|
|
5
|
-
##
|
|
6
|
-
|
|
7
|
-
This gem includes comprehensive example scripts that showcase all features and serve as both documentation and validation tests. To explore the capabilities:
|
|
5
|
+
## Quick Start
|
|
8
6
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
```bash
|
|
12
|
-
# Run a quick overview of key features (takes ~1 minute)
|
|
13
|
-
bundle exec ruby examples/01_quick.rb
|
|
14
|
-
```
|
|
7
|
+
1. Add to your Gemfile and install:
|
|
15
8
|
|
|
16
|
-
|
|
9
|
+
```ruby
|
|
10
|
+
gem "ai-chat", "< 1.0.0"
|
|
11
|
+
```
|
|
17
12
|
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
```
|
|
13
|
+
```
|
|
14
|
+
bundle install
|
|
15
|
+
```
|
|
22
16
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
The `examples/` directory contains focused examples for specific features:
|
|
26
|
-
|
|
27
|
-
- `01_quick.rb` - Quick overview of key features
|
|
28
|
-
- `02_core.rb` - Core functionality (basic chat, messages, responses)
|
|
29
|
-
- `03_multimodal.rb` - Basic file and image handling
|
|
30
|
-
- `04_file_handling_comprehensive.rb` - Advanced file handling (PDFs, text files, Rails uploads)
|
|
31
|
-
- `05_structured_output.rb` - Basic structured output with schemas
|
|
32
|
-
- `06_structured_output_comprehensive.rb` - All 6 supported schema formats
|
|
33
|
-
- `07_edge_cases.rb` - Error handling and edge cases
|
|
34
|
-
- `08_additional_patterns.rb` - Less common usage patterns (direct add method, web search + schema, etc.)
|
|
35
|
-
- `09_mixed_content.rb` - Combining text and images in messages
|
|
36
|
-
- `10_image_generation.rb` - Using the image generation tool
|
|
37
|
-
- `11_code_interpreter.rb` - Using the code interpreter tool
|
|
38
|
-
- `12_background_mode.rb` - Running responses in background mode
|
|
39
|
-
- `13_conversation_features_comprehensive.rb` - Conversation features (auto-creation, continuity, inspection)
|
|
40
|
-
- `14_schema_generation.rb` - Generate JSON schemas from natural language
|
|
41
|
-
- `15_proxy.rb` - Proxy support for student accounts
|
|
42
|
-
- `16_get_items.rb` - Inspecting conversation items (reasoning, web searches, image generation)
|
|
43
|
-
|
|
44
|
-
Each example is self-contained and can be run individually:
|
|
45
|
-
```bash
|
|
46
|
-
bundle exec ruby examples/[filename]
|
|
47
|
-
```
|
|
17
|
+
2. Set up your API key in a `.env` file at the root of your project:
|
|
48
18
|
|
|
49
|
-
|
|
19
|
+
```
|
|
20
|
+
AICHAT_PROXY=true
|
|
21
|
+
AICHAT_PROXY_KEY=your-key-from-prepend-me
|
|
22
|
+
```
|
|
50
23
|
|
|
51
|
-
|
|
24
|
+
(If you have your own OpenAI account, you can skip proxy mode and set `OPENAI_API_KEY` instead.)
|
|
52
25
|
|
|
53
|
-
|
|
26
|
+
3. Use it:
|
|
54
27
|
|
|
55
|
-
```ruby
|
|
56
|
-
|
|
57
|
-
|
|
28
|
+
```ruby
|
|
29
|
+
require "dotenv/load"
|
|
30
|
+
require "ai-chat"
|
|
58
31
|
|
|
59
|
-
|
|
32
|
+
chat = AI::Chat.new
|
|
33
|
+
chat.user("What is Ruby?")
|
|
34
|
+
response = chat.generate!
|
|
60
35
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
```
|
|
36
|
+
ap response
|
|
37
|
+
```
|
|
64
38
|
|
|
65
|
-
|
|
39
|
+
That's it. `generate!` returns the assistant's reply as a `Hash`, and `chat.messages` holds the full conversation as an `Array` of `Hash`es you can inspect, loop through, or store in a database.
|
|
66
40
|
|
|
67
|
-
|
|
41
|
+
## It's Just an Array of Hashes
|
|
68
42
|
|
|
69
|
-
|
|
70
|
-
gem install ai-chat
|
|
71
|
-
```
|
|
43
|
+
Every conversation with an AI model is an array of hashes. Each hash has two keys:
|
|
72
44
|
|
|
73
|
-
|
|
45
|
+
- `:role` -- who's speaking (`"system"`, `"user"`, or `"assistant"`)
|
|
46
|
+
- `:content` -- what they said
|
|
74
47
|
|
|
75
|
-
|
|
48
|
+
Here's what a conversation looks like:
|
|
76
49
|
|
|
77
50
|
```ruby
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
a = AI::Chat.new
|
|
82
|
-
|
|
83
|
-
# Build up your conversation by adding messages
|
|
84
|
-
a.add("If the Ruby community had an official motto, what might it be?")
|
|
85
|
-
|
|
86
|
-
# See the convo so far - it's just an array of hashes!
|
|
87
|
-
a.messages
|
|
88
|
-
# => [
|
|
89
|
-
# {
|
|
90
|
-
# :role => "user",
|
|
91
|
-
# :content => "If the Ruby community had an official motto, what might it be?"
|
|
92
|
-
# }
|
|
93
|
-
# ]
|
|
51
|
+
chat = AI::Chat.new
|
|
52
|
+
chat.user("If Ruby had an official motto, what might it be?")
|
|
53
|
+
response = chat.generate!
|
|
94
54
|
|
|
95
|
-
|
|
96
|
-
a.generate!
|
|
55
|
+
ap response
|
|
97
56
|
# => {
|
|
98
57
|
# :role => "assistant",
|
|
99
|
-
# :content => "Matz is nice and so we are nice",
|
|
100
|
-
# :response => { ... }
|
|
58
|
+
# :content => "Matz is nice and so we are nice.",
|
|
59
|
+
# :response => { id: "resp_abc...", model: "gpt-5.2", ... }
|
|
101
60
|
# }
|
|
102
61
|
|
|
103
|
-
|
|
104
|
-
a.messages
|
|
62
|
+
ap chat.messages
|
|
105
63
|
# => [
|
|
106
64
|
# {
|
|
107
65
|
# :role => "user",
|
|
108
|
-
# :content => "If
|
|
66
|
+
# :content => "If Ruby had an official motto, what might it be?"
|
|
109
67
|
# },
|
|
110
68
|
# {
|
|
111
69
|
# :role => "assistant",
|
|
112
|
-
# :content => "Matz is nice and so we are nice",
|
|
70
|
+
# :content => "Matz is nice and so we are nice.",
|
|
113
71
|
# :response => { id: "resp_abc...", model: "gpt-5.2", ... }
|
|
114
72
|
# }
|
|
115
73
|
# ]
|
|
116
|
-
|
|
117
|
-
# Continue the conversation
|
|
118
|
-
a.add("What about Rails?")
|
|
119
|
-
a.generate!
|
|
120
|
-
# => {
|
|
121
|
-
# :role => "assistant",
|
|
122
|
-
# :content => "Convention over configuration.",
|
|
123
|
-
# :response => { ... }
|
|
124
|
-
# }
|
|
125
74
|
```
|
|
126
75
|
|
|
127
|
-
|
|
76
|
+
`generate!` returns the assistant's message as a `Hash`. The `:response` key holds metadata from the API (token usage, response ID, model used, etc.). The user and system hashes are just `:role` and `:content`.
|
|
128
77
|
|
|
129
|
-
|
|
130
|
-
- `:role`: who's speaking ("system", "user", or "assistant")
|
|
131
|
-
- `:content`: what they're saying
|
|
78
|
+
This design is intentional:
|
|
132
79
|
|
|
133
|
-
|
|
80
|
+
- **You can see what you're building.** `ap chat.messages` at any point shows the exact data structure.
|
|
81
|
+
- **It reinforces Ruby fundamentals.** Arrays, hashes, symbols -- you already know these.
|
|
82
|
+
- **It's flexible.** The same structure works when loading messages from a database:
|
|
134
83
|
|
|
135
84
|
```ruby
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
},
|
|
141
|
-
{
|
|
142
|
-
:role => "user",
|
|
143
|
-
:content => "Hello!"
|
|
144
|
-
},
|
|
145
|
-
{
|
|
146
|
-
:role => "assistant",
|
|
147
|
-
:content => "Hi there! How can I help you today?",
|
|
148
|
-
:response => { id: "resp_abc...", model: "gpt-5.2", ... }
|
|
149
|
-
}
|
|
150
|
-
]
|
|
85
|
+
chat = AI::Chat.new
|
|
86
|
+
chat.messages = @conversation.messages # Load from your database
|
|
87
|
+
chat.user("What should I do next?")
|
|
88
|
+
chat.generate!
|
|
151
89
|
```
|
|
152
90
|
|
|
153
|
-
|
|
91
|
+
## Adding Messages
|
|
154
92
|
|
|
155
|
-
|
|
93
|
+
The `user` method adds a message with `role: "user"` and `generate!` sends the conversation to the API and returns the assistant's reply:
|
|
156
94
|
|
|
157
95
|
```ruby
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
# Add system instructions
|
|
163
|
-
b.add("You are a helpful assistant that talks like Shakespeare.", role: "system")
|
|
96
|
+
chat = AI::Chat.new
|
|
97
|
+
chat.user("Hello!")
|
|
98
|
+
ap chat.generate!
|
|
164
99
|
|
|
165
|
-
#
|
|
166
|
-
|
|
100
|
+
# Continue the conversation
|
|
101
|
+
chat.user("What about Rails?")
|
|
102
|
+
ap chat.generate!
|
|
103
|
+
```
|
|
167
104
|
|
|
168
|
-
|
|
169
|
-
b.messages
|
|
170
|
-
# => [
|
|
171
|
-
# {
|
|
172
|
-
# :role => "system",
|
|
173
|
-
# :content => "You are a helpful assistant that talks like Shakespeare."
|
|
174
|
-
# },
|
|
175
|
-
# {
|
|
176
|
-
# :role => "user",
|
|
177
|
-
# :content => "If the Ruby community had an official motto, what might it be?"
|
|
178
|
-
# }
|
|
179
|
-
# ]
|
|
105
|
+
You can also add system instructions (to guide the model's behavior) and manually add assistant messages (to reconstruct past conversations):
|
|
180
106
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
# :response => { ... }
|
|
187
|
-
# }
|
|
107
|
+
```ruby
|
|
108
|
+
chat = AI::Chat.new
|
|
109
|
+
chat.system("You are a helpful assistant that talks like Shakespeare.")
|
|
110
|
+
chat.user("What is Ruby?")
|
|
111
|
+
chat.generate!
|
|
188
112
|
```
|
|
189
113
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
Instead of always specifying the role, you can use these shortcuts:
|
|
114
|
+
Under the hood, these are shortcuts for the `add` method:
|
|
193
115
|
|
|
194
116
|
```ruby
|
|
195
|
-
c = AI::Chat.new
|
|
196
|
-
|
|
197
117
|
# These are equivalent:
|
|
198
|
-
|
|
199
|
-
|
|
118
|
+
chat.system("You are helpful")
|
|
119
|
+
chat.add("You are helpful", role: "system")
|
|
200
120
|
|
|
201
121
|
# These are equivalent:
|
|
202
|
-
|
|
203
|
-
|
|
122
|
+
chat.user("Hello!")
|
|
123
|
+
chat.add("Hello!") # role defaults to "user"
|
|
204
124
|
|
|
205
125
|
# These are equivalent:
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
```
|
|
209
|
-
|
|
210
|
-
## Why This Design?
|
|
211
|
-
|
|
212
|
-
We use the `add` method (and its shortcuts) to build up an array because:
|
|
213
|
-
|
|
214
|
-
1. **It's educational**: You can see exactly what data structure you're building
|
|
215
|
-
2. **It's debuggable**: Use `pp a.messages` anytime to inspect your conversation
|
|
216
|
-
3. **It's flexible**: The same pattern works when loading existing conversations:
|
|
217
|
-
|
|
218
|
-
```ruby
|
|
219
|
-
# In a Rails app, you might do:
|
|
220
|
-
d = AI::Chat.new
|
|
221
|
-
d.messages = @conversation.messages # Load existing messages
|
|
222
|
-
d.user("What should I do next?") # Add a new question
|
|
223
|
-
d.generate! # Generate a response
|
|
126
|
+
chat.assistant("Here's what I think...")
|
|
127
|
+
chat.add("Here's what I think...", role: "assistant")
|
|
224
128
|
```
|
|
225
129
|
|
|
226
130
|
## Configuration
|
|
227
131
|
|
|
228
132
|
### Model
|
|
229
133
|
|
|
230
|
-
|
|
134
|
+
The gem defaults to `gpt-5.2`. You can change it:
|
|
231
135
|
|
|
232
136
|
```ruby
|
|
233
|
-
|
|
234
|
-
|
|
137
|
+
chat = AI::Chat.new
|
|
138
|
+
chat.model = "gpt-4o"
|
|
235
139
|
```
|
|
236
140
|
|
|
237
|
-
|
|
141
|
+
### API Key
|
|
238
142
|
|
|
239
|
-
|
|
143
|
+
By default, the gem looks for an environment variable based on whether proxy mode is on or off:
|
|
240
144
|
|
|
241
|
-
|
|
145
|
+
| Mode | Environment variable |
|
|
146
|
+
|---|---|
|
|
147
|
+
| Proxy on (`AICHAT_PROXY=true`) | `AICHAT_PROXY_KEY` |
|
|
148
|
+
| Proxy off (default) | `OPENAI_API_KEY` |
|
|
242
149
|
|
|
243
|
-
You can specify a
|
|
150
|
+
You can also specify a custom environment variable name or pass the key directly:
|
|
244
151
|
|
|
245
152
|
```ruby
|
|
246
|
-
|
|
247
|
-
|
|
153
|
+
# Use a different environment variable
|
|
154
|
+
chat = AI::Chat.new(api_key_env_var: "MY_OPENAI_TOKEN")
|
|
248
155
|
|
|
249
|
-
Or
|
|
250
|
-
|
|
251
|
-
```ruby
|
|
252
|
-
g = AI::Chat.new(api_key: "your-api-key-goes-here")
|
|
156
|
+
# Or pass the key directly
|
|
157
|
+
chat = AI::Chat.new(api_key: "sk-...")
|
|
253
158
|
```
|
|
254
159
|
|
|
255
|
-
##
|
|
160
|
+
## Proxy (Prepend.me)
|
|
256
161
|
|
|
257
|
-
|
|
162
|
+
If you're using a [Prepend.me](https://prepend.me/) proxy key (common in classroom settings), add these to your `.env` file:
|
|
258
163
|
|
|
259
|
-
```
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
h.generate!
|
|
164
|
+
```
|
|
165
|
+
AICHAT_PROXY=true
|
|
166
|
+
AICHAT_PROXY_KEY=your-key-from-prepend-me
|
|
167
|
+
```
|
|
264
168
|
|
|
265
|
-
|
|
266
|
-
h.messages
|
|
267
|
-
# => [
|
|
268
|
-
# {
|
|
269
|
-
# :role => "system",
|
|
270
|
-
# :content => "You are a helpful cooking assistant"
|
|
271
|
-
# },
|
|
272
|
-
# {
|
|
273
|
-
# :role => "user",
|
|
274
|
-
# :content => "How do I boil an egg?"
|
|
275
|
-
# },
|
|
276
|
-
# {
|
|
277
|
-
# :role => "assistant",
|
|
278
|
-
# :content => "Here's how to boil an egg..."
|
|
279
|
-
# }
|
|
280
|
-
# ]
|
|
169
|
+
You can also enable proxy mode in code:
|
|
281
170
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
171
|
+
```ruby
|
|
172
|
+
# At construction time
|
|
173
|
+
chat = AI::Chat.new(proxy: true)
|
|
285
174
|
|
|
286
|
-
# Or
|
|
287
|
-
|
|
288
|
-
|
|
175
|
+
# Or toggle it on an existing instance
|
|
176
|
+
chat = AI::Chat.new
|
|
177
|
+
chat.proxy = true
|
|
289
178
|
```
|
|
290
179
|
|
|
180
|
+
When proxy is enabled, API calls are routed through Prepend.me, and the gem uses `AICHAT_PROXY_KEY` instead of `OPENAI_API_KEY`.
|
|
181
|
+
|
|
291
182
|
## Web Search
|
|
292
183
|
|
|
293
|
-
|
|
184
|
+
Give the model access to current information from the internet:
|
|
294
185
|
|
|
295
186
|
```ruby
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
187
|
+
chat = AI::Chat.new
|
|
188
|
+
chat.web_search = true
|
|
189
|
+
chat.user("What are the latest developments in the Ruby language?")
|
|
190
|
+
chat.generate!
|
|
300
191
|
```
|
|
301
192
|
|
|
302
|
-
##
|
|
193
|
+
## Including Images
|
|
303
194
|
|
|
304
|
-
|
|
195
|
+
Use the `image:` or `images:` parameter to send images along with your message:
|
|
305
196
|
|
|
306
197
|
```ruby
|
|
307
|
-
|
|
198
|
+
chat = AI::Chat.new
|
|
308
199
|
|
|
309
|
-
|
|
200
|
+
# Single image
|
|
201
|
+
chat.user("What's in this image?", image: "photo.jpg")
|
|
202
|
+
chat.generate!
|
|
310
203
|
|
|
311
|
-
#
|
|
312
|
-
|
|
204
|
+
# Multiple images
|
|
205
|
+
chat.user("Compare these", images: ["image1.jpg", "image2.jpg"])
|
|
206
|
+
chat.generate!
|
|
207
|
+
```
|
|
313
208
|
|
|
314
|
-
|
|
209
|
+
You can pass local file paths, URLs (`https://...`), or file-like objects (such as `File.open(...)` or Rails uploaded files).
|
|
315
210
|
|
|
316
|
-
|
|
317
|
-
data = response[:content]
|
|
318
|
-
# => {:fat=>15, :protein=>12, :carbs=>35, :total_calories=>285}
|
|
211
|
+
## Including Files
|
|
319
212
|
|
|
320
|
-
|
|
321
|
-
data[:total_calories] # => 285
|
|
322
|
-
```
|
|
213
|
+
Use the `file:` or `files:` parameter to send documents:
|
|
323
214
|
|
|
324
|
-
|
|
215
|
+
```ruby
|
|
216
|
+
chat = AI::Chat.new
|
|
325
217
|
|
|
326
|
-
|
|
218
|
+
# Single file
|
|
219
|
+
chat.user("Summarize this document", file: "report.pdf")
|
|
220
|
+
chat.generate!
|
|
327
221
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
i.schema = {
|
|
332
|
-
format: {
|
|
333
|
-
type: :json_schema,
|
|
334
|
-
name: "nutrition_values",
|
|
335
|
-
strict: true,
|
|
336
|
-
schema: {
|
|
337
|
-
type: "object",
|
|
338
|
-
properties: {
|
|
339
|
-
fat: { type: "number", description: "The amount of fat in grams." },
|
|
340
|
-
protein: { type: "number", description: "The amount of protein in grams." }
|
|
341
|
-
},
|
|
342
|
-
required: ["fat", "protein"],
|
|
343
|
-
additionalProperties: false
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
}
|
|
222
|
+
# Multiple files
|
|
223
|
+
chat.user("Compare these", files: ["doc1.pdf", "doc2.txt"])
|
|
224
|
+
chat.generate!
|
|
347
225
|
```
|
|
348
226
|
|
|
349
|
-
|
|
227
|
+
PDFs are sent as attachments. Text-based files have their content extracted and sent as text.
|
|
228
|
+
|
|
229
|
+
You can combine images and files in one message:
|
|
230
|
+
|
|
350
231
|
```ruby
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
schema: {
|
|
356
|
-
type: "object",
|
|
357
|
-
properties: {
|
|
358
|
-
fat: { type: "number", description: "The amount of fat in grams." },
|
|
359
|
-
protein: { type: "number", description: "The amount of protein in grams." }
|
|
360
|
-
},
|
|
361
|
-
required: [:fat, :protein],
|
|
362
|
-
additionalProperties: false
|
|
363
|
-
}
|
|
364
|
-
}
|
|
232
|
+
chat.user("Analyze these materials",
|
|
233
|
+
images: ["chart1.png", "chart2.png"],
|
|
234
|
+
files: ["report.pdf", "data.csv"])
|
|
235
|
+
chat.generate!
|
|
365
236
|
```
|
|
366
237
|
|
|
367
|
-
|
|
238
|
+
## Structured Output
|
|
239
|
+
|
|
240
|
+
Instead of getting back a plain text response, you can ask the model to return data in a specific shape by setting a JSON schema:
|
|
241
|
+
|
|
368
242
|
```ruby
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
243
|
+
chat = AI::Chat.new
|
|
244
|
+
chat.system("You are an expert nutritionist. Estimate the nutritional content of the meal the user describes.")
|
|
245
|
+
|
|
246
|
+
chat.schema = {
|
|
372
247
|
type: "object",
|
|
373
248
|
properties: {
|
|
374
|
-
fat:
|
|
375
|
-
protein:
|
|
249
|
+
fat: { type: "number", description: "Fat in grams" },
|
|
250
|
+
protein: { type: "number", description: "Protein in grams" },
|
|
251
|
+
carbs: { type: "number", description: "Carbohydrates in grams" },
|
|
252
|
+
calories: { type: "number", description: "Total calories" }
|
|
376
253
|
},
|
|
377
|
-
required: ["fat", "protein"],
|
|
254
|
+
required: ["fat", "protein", "carbs", "calories"],
|
|
378
255
|
additionalProperties: false
|
|
379
256
|
}
|
|
380
|
-
```
|
|
381
|
-
|
|
382
|
-
#### 4. JSON String Formats
|
|
383
|
-
All the above formats also work as JSON strings:
|
|
384
|
-
|
|
385
|
-
```ruby
|
|
386
|
-
# As a JSON string with full format
|
|
387
|
-
i.schema = '{"format":{"type":"json_schema","name":"nutrition_values","strict":true,"schema":{...}}}'
|
|
388
257
|
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
# As a simple JSON schema string
|
|
393
|
-
i.schema = '{"type":"object","properties":{...}}'
|
|
394
|
-
```
|
|
395
|
-
|
|
396
|
-
### Generating a Schema
|
|
258
|
+
chat.user("1 slice of pizza")
|
|
259
|
+
response = chat.generate!
|
|
397
260
|
|
|
398
|
-
|
|
261
|
+
data = response[:content]
|
|
262
|
+
# => { fat: 15, protein: 12, carbs: 35, calories: 285 }
|
|
399
263
|
|
|
400
|
-
|
|
401
|
-
AI::Chat.generate_schema!("A user profile with name (required), email (required), age (number), and bio (optional text).")
|
|
402
|
-
# => "{ ... }"
|
|
264
|
+
data[:calories] # => 285
|
|
403
265
|
```
|
|
404
266
|
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
Similar to generating messages with `AI::Chat` objects, this class method will look for `AICHAT_API_KEY` first, then fall back to `OPENAI_API_KEY` if needed. You can also pass the API key directly or choose a different environment variable key for it to use.
|
|
267
|
+
When a schema is set, `generate!` returns a parsed Ruby `Hash` with symbolized keys instead of a `String`.
|
|
408
268
|
|
|
409
|
-
|
|
410
|
-
# Passing the API key directly
|
|
411
|
-
AI::Chat.generate_schema!("A user with full name (required), first_name (required), and last_name (required).", api_key: "MY_SECRET_API_KEY")
|
|
269
|
+
The gem accepts several schema formats and automatically wraps them for the API. You can also pass schemas as JSON strings. See the `examples/` directory for all supported formats.
|
|
412
270
|
|
|
413
|
-
|
|
414
|
-
AI::Chat.generate_schema!("A user with full name (required), first_name (required), and last_name (required).", api_key_env_var: "CUSTOM_KEY")
|
|
415
|
-
```
|
|
271
|
+
### Generating a Schema
|
|
416
272
|
|
|
417
|
-
|
|
273
|
+
You can use AI to generate a schema from a plain English description:
|
|
418
274
|
|
|
419
|
-
```
|
|
420
|
-
|
|
275
|
+
```ruby
|
|
276
|
+
AI::Chat.generate_schema!("A user profile with name (required), email (required), age (number), and bio (optional).")
|
|
421
277
|
```
|
|
422
278
|
|
|
423
|
-
|
|
279
|
+
This returns the JSON schema as a `String` and saves it to `schema.json`. Pass `location: false` to skip saving, or `location: "path/to/file.json"` to save elsewhere.
|
|
424
280
|
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
```rb
|
|
428
|
-
AI::Chat.generate_schema!("A user with full name (required), first_name (required), and last_name (required).", location: "my_schemas/user.json")
|
|
429
|
-
```
|
|
281
|
+
## Image Generation
|
|
430
282
|
|
|
431
|
-
|
|
283
|
+
Enable OpenAI's image generation tool to create images from descriptions:
|
|
432
284
|
|
|
433
|
-
```
|
|
434
|
-
AI::Chat.
|
|
435
|
-
|
|
285
|
+
```ruby
|
|
286
|
+
chat = AI::Chat.new
|
|
287
|
+
chat.image_generation = true
|
|
288
|
+
chat.user("Draw a picture of a kitten")
|
|
289
|
+
chat.generate!
|
|
436
290
|
```
|
|
437
291
|
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
- The keys can be `String`s or `Symbol`s.
|
|
441
|
-
- The gem automatically converts your schema to the format expected by the API.
|
|
442
|
-
- When a schema is set, `generate!` returns a parsed Ruby Hash with symbolized keys, not a String.
|
|
443
|
-
|
|
444
|
-
## Including Images
|
|
445
|
-
|
|
446
|
-
You can include images in your chat messages using the `user` method with the `image` or `images` parameter:
|
|
292
|
+
Generated images are saved to `./images` by default (in timestamped subfolders like `./images/20250804T113039_resp_abc123/001.png`). You can change the folder:
|
|
447
293
|
|
|
448
294
|
```ruby
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
# Send a single image
|
|
452
|
-
j.user("What's in this image?", image: "path/to/local/image.jpg")
|
|
453
|
-
j.generate! # => "I can see a sunset over the ocean..."
|
|
454
|
-
|
|
455
|
-
# Send multiple images
|
|
456
|
-
j.user("Compare these images", images: ["image1.jpg", "image2.jpg"])
|
|
457
|
-
j.generate! # => "The first image shows... while the second..."
|
|
458
|
-
|
|
459
|
-
# Mix URLs and local files
|
|
460
|
-
j.user("What's the difference?", images: [
|
|
461
|
-
"local_photo.jpg",
|
|
462
|
-
"https://example.com/remote_photo.jpg"
|
|
463
|
-
])
|
|
464
|
-
j.generate!
|
|
295
|
+
chat.image_folder = "./my_images"
|
|
465
296
|
```
|
|
466
297
|
|
|
467
|
-
The
|
|
468
|
-
|
|
469
|
-
- **URLs**: Pass an image URL starting with `http://` or `https://`
|
|
470
|
-
- **File paths**: Pass a string with a path to a local image file
|
|
471
|
-
- **File-like objects**: Pass an object that responds to `read` (like `File.open("image.jpg")` or Rails uploaded files)
|
|
472
|
-
|
|
473
|
-
## Including Files
|
|
474
|
-
|
|
475
|
-
You can include files (PDFs, text files, etc.) in your messages using the `file` or `files` parameter:
|
|
298
|
+
The assistant's message will include an `:images` key with the saved file paths:
|
|
476
299
|
|
|
477
300
|
```ruby
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
# Send a single file
|
|
481
|
-
k.user("Summarize this document", file: "report.pdf")
|
|
482
|
-
k.generate!
|
|
483
|
-
|
|
484
|
-
# Send multiple files
|
|
485
|
-
k.user("Compare these documents", files: ["doc1.pdf", "doc2.txt", "data.json"])
|
|
486
|
-
k.generate!
|
|
301
|
+
chat.last[:images]
|
|
302
|
+
# => ["./images/20250804T113039_resp_abc123/001.png"]
|
|
487
303
|
```
|
|
488
304
|
|
|
489
|
-
|
|
490
|
-
- **PDFs**: Sent as file attachments for the model to analyze
|
|
491
|
-
- **Text files**: Content is automatically extracted and sent as text
|
|
492
|
-
- **Other formats**: The gem attempts to read them as text if possible
|
|
493
|
-
|
|
494
|
-
## Mixed Content (Images + Files)
|
|
495
|
-
|
|
496
|
-
You can send images and files together in a single message:
|
|
305
|
+
AI-generated images are stored by OpenAI, so you can refine them in follow-up messages without re-sending:
|
|
497
306
|
|
|
498
307
|
```ruby
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
# Mix image and file in one message
|
|
502
|
-
l.user("Compare this photo with the document",
|
|
503
|
-
image: "photo.jpg",
|
|
504
|
-
file: "document.pdf")
|
|
505
|
-
l.generate!
|
|
506
|
-
|
|
507
|
-
# Mix multiple images and files
|
|
508
|
-
l.user("Analyze all these materials",
|
|
509
|
-
images: ["chart1.png", "chart2.png"],
|
|
510
|
-
files: ["report.pdf", "data.csv"])
|
|
511
|
-
l.generate!
|
|
308
|
+
chat.user("Make it even cuter")
|
|
309
|
+
chat.generate!
|
|
512
310
|
```
|
|
513
311
|
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
## Image generation
|
|
312
|
+
### Configuring the Tool
|
|
517
313
|
|
|
518
|
-
|
|
314
|
+
To configure the tool, pass a `Hash` of options instead of `true`:
|
|
519
315
|
|
|
520
316
|
```ruby
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
# :content => "Here is your picture of a kitten:",
|
|
527
|
-
# :response => { ... }
|
|
528
|
-
# }
|
|
317
|
+
chat.image_generation = {
|
|
318
|
+
size: "1536x1024",
|
|
319
|
+
quality: "low",
|
|
320
|
+
model: "gpt-image-2"
|
|
321
|
+
}
|
|
529
322
|
```
|
|
530
323
|
|
|
531
|
-
|
|
324
|
+
Supported keys include `size`, `quality`, `model`, `action`, `background`, `moderation`, `output_format`, `output_compression`, `input_image_mask`, and `input_fidelity`. The `Hash` is passed through to the OpenAI [image generation tool](https://platform.openai.com/docs/guides/image-generation), so refer to those docs for the full list of supported values.
|
|
532
325
|
|
|
533
|
-
|
|
534
|
-
a = AI::Chat.new
|
|
535
|
-
a.image_generation = true
|
|
536
|
-
a.image_folder = "./my_images"
|
|
537
|
-
a.user("Draw a picture of a kitten")
|
|
538
|
-
a.generate!
|
|
539
|
-
# => {
|
|
540
|
-
# :content => "Here is your picture of a kitten:",
|
|
541
|
-
# :response => { ... }
|
|
542
|
-
# }
|
|
543
|
-
```
|
|
326
|
+
The file extension of saved images is chosen automatically from the decoded bytes, so `output_format: "jpeg"` writes a `.jpg` and `output_format: "webp"` writes a `.webp`.
|
|
544
327
|
|
|
545
|
-
|
|
546
|
-
- `./images/20250804T11303912_resp_abc123/001.png`
|
|
547
|
-
- `./images/20250804T11303912_resp_abc123/002.png` (if multiple images)
|
|
328
|
+
> `partial_images` is not listed above because this gem uses a blocking call to the Responses API. Partial images only stream when `stream: true` is set, which this gem doesn't yet support.
|
|
548
329
|
|
|
549
|
-
|
|
330
|
+
## Code Interpreter
|
|
550
331
|
|
|
551
|
-
|
|
332
|
+
Enable the code interpreter to let the model write and execute Python code on OpenAI's servers. This is useful for math, data analysis, and generating charts:
|
|
552
333
|
|
|
553
334
|
```ruby
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
# :content => "Draw a picture of a kitten"
|
|
559
|
-
# },
|
|
560
|
-
# {
|
|
561
|
-
# :role => "assistant",
|
|
562
|
-
# :content => "Here is your picture of a kitten:",
|
|
563
|
-
# :images => [ "./images/20250804T11303912_resp_abc123/001.png" ],
|
|
564
|
-
# :response => { ... }
|
|
565
|
-
# }
|
|
566
|
-
# ]
|
|
335
|
+
chat = AI::Chat.new
|
|
336
|
+
chat.code_interpreter = true
|
|
337
|
+
chat.user("Plot y = 2x^3 for x from -5 to 5")
|
|
338
|
+
chat.generate!
|
|
567
339
|
```
|
|
568
340
|
|
|
569
|
-
|
|
341
|
+
The model will write a Python script, execute it, and return the result (including any generated files like charts).
|
|
570
342
|
|
|
571
|
-
|
|
572
|
-
# From the last message
|
|
573
|
-
images = a.messages.last[:images]
|
|
574
|
-
# => ["./images/20250804T11303912_resp_abc123/001.png"]
|
|
575
|
-
|
|
576
|
-
# From the response object
|
|
577
|
-
images = a.messages.last.dig(:response, :images)
|
|
578
|
-
# => ["./images/20250804T11303912_resp_abc123/001.png"]
|
|
579
|
-
```
|
|
343
|
+
## Inspecting Your Conversation
|
|
580
344
|
|
|
581
|
-
|
|
345
|
+
You can look at the conversation at any point:
|
|
582
346
|
|
|
583
347
|
```ruby
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
a.generate!
|
|
589
|
-
# => { :content => "Here is a picture of a kitten:", ... }
|
|
590
|
-
a.user("Make it even cuter")
|
|
591
|
-
a.generate!
|
|
592
|
-
# => { :content => "Here is the kitten, but even cuter:", ... }
|
|
593
|
-
```
|
|
348
|
+
chat = AI::Chat.new
|
|
349
|
+
chat.system("You are a helpful cooking assistant")
|
|
350
|
+
chat.user("How do I boil an egg?")
|
|
351
|
+
response = chat.generate!
|
|
594
352
|
|
|
595
|
-
|
|
353
|
+
# The return value is the assistant's reply
|
|
354
|
+
response[:content]
|
|
355
|
+
# => "Here's how to boil an egg..."
|
|
596
356
|
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
y.code_interpreter = true
|
|
600
|
-
y.user("Plot y = 2x*3 when x is -5 to 5.")
|
|
601
|
-
y.generate!
|
|
602
|
-
# => { :content => "Here is the graph.", ... }
|
|
357
|
+
# See the whole conversation
|
|
358
|
+
ap chat.messages
|
|
603
359
|
```
|
|
604
360
|
|
|
605
|
-
##
|
|
361
|
+
## Building Conversations Without API Calls
|
|
606
362
|
|
|
607
|
-
|
|
363
|
+
You can manually build up a conversation without calling the API, which is useful for reconstructing a past conversation from your database:
|
|
608
364
|
|
|
609
365
|
```ruby
|
|
610
366
|
chat = AI::Chat.new
|
|
611
|
-
chat.
|
|
612
|
-
chat.user("Write a short description about a sci-fi novel about a rat in space.")
|
|
613
|
-
chat.generate!
|
|
614
|
-
|
|
615
|
-
# Poll until it completes (this updates the existing assistant message)
|
|
616
|
-
message = chat.get_response(wait: true, timeout: 600)
|
|
617
|
-
puts message[:content]
|
|
618
|
-
```
|
|
367
|
+
chat.system("You are a helpful assistant who provides information about planets.")
|
|
619
368
|
|
|
620
|
-
|
|
369
|
+
chat.user("Tell me about Mars.")
|
|
370
|
+
chat.assistant("Mars is the fourth planet from the Sun....")
|
|
621
371
|
|
|
622
|
-
|
|
372
|
+
chat.user("What's the atmosphere like?")
|
|
373
|
+
chat.assistant("Mars has a very thin atmosphere compared to Earth....")
|
|
623
374
|
|
|
624
|
-
|
|
625
|
-
chat
|
|
626
|
-
chat.proxy = true
|
|
627
|
-
chat.user("Tell me a story")
|
|
375
|
+
# Now continue with an API-generated response
|
|
376
|
+
chat.user("Are there any current missions?")
|
|
628
377
|
chat.generate!
|
|
629
|
-
puts chat.last[:content]
|
|
630
|
-
# => "Once upon a time..."
|
|
631
378
|
```
|
|
632
379
|
|
|
633
|
-
You can also
|
|
380
|
+
You can also set all messages at once with an array of hashes:
|
|
634
381
|
|
|
635
|
-
```
|
|
636
|
-
|
|
382
|
+
```ruby
|
|
383
|
+
chat = AI::Chat.new
|
|
384
|
+
chat.messages = [
|
|
385
|
+
{ role: "system", content: "You are a helpful assistant." },
|
|
386
|
+
{ role: "user", content: "Tell me about Mars." },
|
|
387
|
+
{ role: "assistant", content: "Mars is the fourth planet from the Sun...." },
|
|
388
|
+
{ role: "user", content: "What's the atmosphere like?" },
|
|
389
|
+
{ role: "assistant", content: "Mars has a very thin atmosphere...." }
|
|
390
|
+
]
|
|
391
|
+
|
|
392
|
+
chat.user("Could it support human life?")
|
|
393
|
+
chat.generate!
|
|
637
394
|
```
|
|
638
395
|
|
|
639
|
-
|
|
396
|
+
For messages with images or files, use `chat.user(..., image:, file:)` instead so the gem can build the correct multimodal structure.
|
|
640
397
|
|
|
641
|
-
|
|
398
|
+
## Advanced
|
|
642
399
|
|
|
643
|
-
|
|
400
|
+
### Reasoning Effort
|
|
644
401
|
|
|
645
|
-
|
|
402
|
+
Control how much reasoning the model does before responding:
|
|
646
403
|
|
|
647
404
|
```ruby
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
# Add previous messages
|
|
652
|
-
k.system("You are a helpful assistant who provides information about planets.")
|
|
653
|
-
|
|
654
|
-
k.user("Tell me about Mars.")
|
|
655
|
-
k.assistant("Mars is the fourth planet from the Sun....")
|
|
656
|
-
|
|
657
|
-
k.user("What's the atmosphere like?")
|
|
658
|
-
k.assistant("Mars has a very thin atmosphere compared to Earth....")
|
|
659
|
-
|
|
660
|
-
k.user("Could it support human life?")
|
|
661
|
-
k.assistant("Mars currently can't support human life without....")
|
|
405
|
+
chat = AI::Chat.new
|
|
406
|
+
chat.reasoning_effort = "high" # "low", "medium", or "high"
|
|
662
407
|
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
response = k.generate!
|
|
666
|
-
puts response
|
|
408
|
+
chat.user("Explain the tradeoffs between microservices and monoliths.")
|
|
409
|
+
chat.generate!
|
|
667
410
|
```
|
|
668
411
|
|
|
669
|
-
|
|
412
|
+
By default, `reasoning_effort` is `nil` (no reasoning parameter is sent). For `gpt-5.2`, this is equivalent to no reasoning.
|
|
670
413
|
|
|
671
|
-
|
|
414
|
+
### Verbosity
|
|
672
415
|
|
|
673
|
-
|
|
416
|
+
Control how concise or thorough the model's response is:
|
|
674
417
|
|
|
675
418
|
```ruby
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
l.user("What does this error message mean? <insert error message>")
|
|
680
|
-
l.generate!
|
|
419
|
+
chat = AI::Chat.new
|
|
420
|
+
chat.verbosity = :low # :low, :medium, or :high
|
|
681
421
|
```
|
|
682
422
|
|
|
683
|
-
|
|
684
|
-
- `"low"`: Favors speed and economical token usage.
|
|
685
|
-
- `"medium"`: Balances speed and reasoning accuracy.
|
|
686
|
-
- `"high"`: Favors more complete reasoning.
|
|
687
|
-
|
|
688
|
-
By default, `reasoning_effort` is `nil`, which means no reasoning parameter is sent to the API. For `gpt-5.2` (the default model), this is equivalent to `"none"` reasoning.
|
|
689
|
-
|
|
690
|
-
## Verbosity
|
|
691
|
-
|
|
692
|
-
Verbosity determines how many output tokens are generated. Lowering the number of tokens reduces overall latency. While the model's reasoning approach stays mostly the same, the model finds ways to answer more concisely—which can either improve or diminish answer quality, depending on your use case. Here are some scenarios for both ends of the verbosity spectrum:
|
|
423
|
+
Low verbosity is good for short answers and simple code generation. High verbosity is better for thorough explanations and detailed analysis. Defaults to `:medium`.
|
|
693
424
|
|
|
694
|
-
|
|
695
|
-
- Low verbosity: Best for situations where you want concise answers or simple code generation, such as SQL queries.
|
|
425
|
+
### Background Mode
|
|
696
426
|
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
## Advanced: Response Details
|
|
700
|
-
|
|
701
|
-
When you call `generate!` (or later call `get_response` in background mode), the gem stores additional information about the API response:
|
|
427
|
+
Start a response and poll for it later:
|
|
702
428
|
|
|
703
429
|
```ruby
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
# Each assistant message includes a response object
|
|
709
|
-
t.messages.last
|
|
710
|
-
# => {
|
|
711
|
-
# :role => "assistant",
|
|
712
|
-
# :content => "Hello! How can I help you today?",
|
|
713
|
-
# :response => { id: "resp_abc...", model: "gpt-5.2", ... }
|
|
714
|
-
# }
|
|
430
|
+
chat = AI::Chat.new
|
|
431
|
+
chat.background = true
|
|
432
|
+
chat.user("Write a detailed analysis of Ruby's GC implementation.")
|
|
433
|
+
chat.generate!
|
|
715
434
|
|
|
716
|
-
#
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
response[:model] # => "gpt-5.2"
|
|
720
|
-
response[:usage] # => {:input_tokens=>5, :output_tokens=>7, :total_tokens=>12}
|
|
435
|
+
# Poll until it completes
|
|
436
|
+
message = chat.get_response(wait: true, timeout: 600)
|
|
437
|
+
puts message[:content]
|
|
721
438
|
```
|
|
722
439
|
|
|
723
|
-
|
|
440
|
+
### Conversation Management
|
|
724
441
|
|
|
725
|
-
-
|
|
726
|
-
- Understanding which model was actually used.
|
|
727
|
-
- Future features like cost tracking.
|
|
728
|
-
|
|
729
|
-
### Last Response ID
|
|
730
|
-
|
|
731
|
-
In addition to the `response` object inside each message, the `AI::Chat` instance also provides a convenient reader, `last_response_id`, which always holds the ID of the most recent response.
|
|
442
|
+
The gem automatically creates a server-side conversation on your first `generate!` call:
|
|
732
443
|
|
|
733
444
|
```ruby
|
|
734
445
|
chat = AI::Chat.new
|
|
735
446
|
chat.user("Hello")
|
|
736
447
|
chat.generate!
|
|
737
448
|
|
|
738
|
-
|
|
449
|
+
chat.conversation_id # => "conv_abc123..."
|
|
739
450
|
|
|
740
|
-
|
|
451
|
+
# The model remembers context across messages
|
|
452
|
+
chat.user("What did I just say?")
|
|
741
453
|
chat.generate!
|
|
742
|
-
|
|
743
|
-
puts chat.last_response_id # => "resp_xyz789..." (a new ID)
|
|
744
454
|
```
|
|
745
455
|
|
|
746
|
-
|
|
456
|
+
You can load an existing conversation:
|
|
747
457
|
|
|
748
458
|
```ruby
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
api_key = ENV["AICHAT_API_KEY"]
|
|
752
|
-
api_key = ENV.fetch("OPENAI_API_KEY") if api_key.nil? || api_key.empty?
|
|
753
|
-
client = OpenAI::Client.new(api_key: api_key)
|
|
754
|
-
|
|
755
|
-
response_id = "resp_abc123..." # e.g., load from your database
|
|
756
|
-
response = client.responses.retrieve(response_id)
|
|
459
|
+
chat = AI::Chat.new
|
|
460
|
+
chat.conversation_id = @thread.conversation_id # From your database
|
|
757
461
|
|
|
758
|
-
|
|
462
|
+
chat.user("Continue our discussion")
|
|
463
|
+
chat.generate!
|
|
759
464
|
```
|
|
760
465
|
|
|
761
|
-
###
|
|
466
|
+
### Response Details
|
|
762
467
|
|
|
763
|
-
|
|
468
|
+
Each assistant message includes an API response hash with metadata:
|
|
764
469
|
|
|
765
470
|
```ruby
|
|
766
471
|
chat = AI::Chat.new
|
|
767
|
-
chat.user("Hello")
|
|
472
|
+
chat.user("Hello!")
|
|
768
473
|
chat.generate!
|
|
769
474
|
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
#
|
|
774
|
-
chat.user("What did I just say?")
|
|
775
|
-
chat.generate! # Uses the same conversation automatically
|
|
475
|
+
response = chat.last[:response]
|
|
476
|
+
response[:id] # => "resp_abc123..."
|
|
477
|
+
response[:model] # => "gpt-5.2"
|
|
478
|
+
response[:usage] # => { input_tokens: 5, output_tokens: 7, total_tokens: 12 }
|
|
776
479
|
```
|
|
777
480
|
|
|
778
|
-
|
|
481
|
+
The `last_response_id` reader always holds the most recent response ID:
|
|
779
482
|
|
|
780
483
|
```ruby
|
|
781
|
-
#
|
|
782
|
-
chat = AI::Chat.new
|
|
783
|
-
chat.conversation_id = @thread.conversation_id # From your database
|
|
784
|
-
|
|
785
|
-
chat.user("Continue our discussion")
|
|
786
|
-
chat.generate! # Uses the loaded conversation
|
|
484
|
+
chat.last_response_id # => "resp_abc123..."
|
|
787
485
|
```
|
|
788
486
|
|
|
789
|
-
|
|
487
|
+
### Inspecting Conversation Items
|
|
790
488
|
|
|
791
|
-
The `get_items` method fetches all conversation items
|
|
489
|
+
The `get_items` method fetches all conversation items from the API, including messages, tool calls, reasoning steps, and web searches:
|
|
792
490
|
|
|
793
491
|
```ruby
|
|
794
492
|
chat = AI::Chat.new
|
|
795
|
-
chat.reasoning_effort = "high"
|
|
493
|
+
chat.reasoning_effort = "high"
|
|
796
494
|
chat.web_search = true
|
|
797
495
|
chat.user("Search for Ruby tutorials")
|
|
798
496
|
chat.generate!
|
|
799
497
|
|
|
800
|
-
#
|
|
498
|
+
# Pretty-printed in IRB/console
|
|
801
499
|
chat.get_items
|
|
802
500
|
|
|
803
|
-
#
|
|
804
|
-
# ┌────────────────────────────────────────────────────────────────────────────┐
|
|
805
|
-
# │ Conversation: conv_6903c1eea6cc819695af3a1b1ebf9b390c3db5e8ec021c9a │
|
|
806
|
-
# │ Items: 8 │
|
|
807
|
-
# └────────────────────────────────────────────────────────────────────────────┘
|
|
808
|
-
#
|
|
809
|
-
# [detailed colorized output of all items including web searches,
|
|
810
|
-
# reasoning summaries, tool calls, messages, etc.]
|
|
811
|
-
|
|
812
|
-
# Iterate over items programmatically
|
|
501
|
+
# Iterate programmatically
|
|
813
502
|
chat.get_items.data.each do |item|
|
|
814
503
|
case item.type
|
|
815
504
|
when :message
|
|
816
505
|
puts "#{item.role}: #{item.content.first.text}"
|
|
817
506
|
when :web_search_call
|
|
818
|
-
puts "
|
|
507
|
+
puts "Searched: #{item.action.query}" if item.action.respond_to?(:query)
|
|
819
508
|
when :reasoning
|
|
820
|
-
|
|
821
|
-
if item.summary&.first
|
|
822
|
-
puts "Reasoning: #{item.summary.first.text}"
|
|
823
|
-
end
|
|
824
|
-
when :image_generation_call
|
|
825
|
-
puts "Image generated" if item.result
|
|
509
|
+
puts "Reasoning: #{item.summary.first.text}" if item.summary&.first
|
|
826
510
|
end
|
|
827
511
|
end
|
|
828
|
-
|
|
829
|
-
# For long conversations, you can request reverse chronological order
|
|
830
|
-
# (useful for pagination to get most recent items first)
|
|
831
|
-
recent_items = chat.get_items(order: :desc)
|
|
832
512
|
```
|
|
833
513
|
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
This is useful for:
|
|
837
|
-
- **Learning** how the model uses tools (web search, code interpreter, etc.)
|
|
838
|
-
- **Debugging** why the model made certain decisions
|
|
839
|
-
- **Understanding** the full context beyond just the final response
|
|
840
|
-
- **Transparency** into the model's reasoning process
|
|
841
|
-
|
|
842
|
-
### HTML Output for ERB Templates
|
|
514
|
+
### HTML Output
|
|
843
515
|
|
|
844
516
|
All display objects have a `to_html` method for rendering in ERB templates:
|
|
845
517
|
|
|
846
518
|
```erb
|
|
847
|
-
<%# Display a chat object %>
|
|
848
519
|
<%= @chat.to_html %>
|
|
849
|
-
|
|
850
|
-
<%# Display individual messages %>
|
|
851
|
-
<% @chat.messages.each do |msg| %>
|
|
852
|
-
<%= msg.to_html %>
|
|
853
|
-
<% end %>
|
|
854
|
-
|
|
855
|
-
<%# Display conversation items (quick debug view) %>
|
|
856
520
|
<%= @chat.get_items.to_html %>
|
|
857
521
|
```
|
|
858
522
|
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
You can also loop over `get_items.data` to build custom displays showing reasoning steps, tool calls, etc.:
|
|
862
|
-
|
|
863
|
-
```erb
|
|
864
|
-
<% @chat.get_items.data.each do |item| %>
|
|
865
|
-
<% case item.type.to_s %>
|
|
866
|
-
<% when "message" %>
|
|
867
|
-
<div class="message <%= item.role %>">
|
|
868
|
-
<strong><%= item.role.capitalize %>:</strong>
|
|
869
|
-
<% if item.content&.first %>
|
|
870
|
-
<% content = item.content.first %>
|
|
871
|
-
<% if content.type.to_s == "input_text" %>
|
|
872
|
-
<%= content.text %>
|
|
873
|
-
<% elsif content.type.to_s == "output_text" %>
|
|
874
|
-
<%= content.text %>
|
|
875
|
-
<% end %>
|
|
876
|
-
<% end %>
|
|
877
|
-
</div>
|
|
878
|
-
<% when "reasoning" %>
|
|
879
|
-
<% if item.summary&.first %>
|
|
880
|
-
<details class="reasoning">
|
|
881
|
-
<summary>Reasoning</summary>
|
|
882
|
-
<%= item.summary.first.text %>
|
|
883
|
-
</details>
|
|
884
|
-
<% end %>
|
|
885
|
-
<% when "web_search_call" %>
|
|
886
|
-
<% if item.action.respond_to?(:query) && item.action.query %>
|
|
887
|
-
<div class="web-search">
|
|
888
|
-
Searched: "<%= item.action.query %>"
|
|
889
|
-
</div>
|
|
890
|
-
<% end %>
|
|
891
|
-
<% when "image_generation_call" %>
|
|
892
|
-
<div class="image-generation">
|
|
893
|
-
Image generated
|
|
894
|
-
</div>
|
|
895
|
-
<% end %>
|
|
896
|
-
<% end %>
|
|
897
|
-
```
|
|
898
|
-
|
|
899
|
-
## Setting messages directly
|
|
900
|
-
|
|
901
|
-
You can use `.messages=()` to assign an `Array` of `Hashes` (text-only). Each `Hash` must have keys `:role` and `:content`:
|
|
523
|
+
## Examples
|
|
902
524
|
|
|
903
|
-
|
|
904
|
-
# Using the planet example with array of hashes
|
|
905
|
-
p = AI::Chat.new
|
|
525
|
+
The `examples/` directory contains self-contained scripts demonstrating each feature:
|
|
906
526
|
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
{ role: "user", content: "Tell me about Mars." },
|
|
911
|
-
{ role: "assistant", content: "Mars is the fourth planet from the Sun...." },
|
|
912
|
-
{ role: "user", content: "What's the atmosphere like?" },
|
|
913
|
-
{ role: "assistant", content: "Mars has a very thin atmosphere compared to Earth...." },
|
|
914
|
-
{ role: "user", content: "Could it support human life?" },
|
|
915
|
-
{ role: "assistant", content: "Mars currently can't support human life without...." }
|
|
916
|
-
]
|
|
527
|
+
```bash
|
|
528
|
+
# Run a quick overview (~1 minute)
|
|
529
|
+
bundle exec ruby examples/01_quick.rb
|
|
917
530
|
|
|
918
|
-
#
|
|
919
|
-
|
|
920
|
-
response = p.generate!
|
|
921
|
-
puts response
|
|
922
|
-
```
|
|
531
|
+
# Run all examples
|
|
532
|
+
bundle exec ruby examples/all.rb
|
|
923
533
|
|
|
924
|
-
|
|
534
|
+
# Run any individual example
|
|
535
|
+
bundle exec ruby examples/02_core.rb
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
| File | Feature |
|
|
539
|
+
|---|---|
|
|
540
|
+
| `01_quick.rb` | Quick overview of key features |
|
|
541
|
+
| `02_core.rb` | Basic chat, messages, and responses |
|
|
542
|
+
| `03_multimodal.rb` | Images and basic file handling |
|
|
543
|
+
| `04_file_handling_comprehensive.rb` | PDFs, text files, Rails uploads |
|
|
544
|
+
| `05_structured_output.rb` | Basic structured output |
|
|
545
|
+
| `06_structured_output_comprehensive.rb` | All supported schema formats |
|
|
546
|
+
| `07_edge_cases.rb` | Error handling and edge cases |
|
|
547
|
+
| `08_additional_patterns.rb` | Direct `add` method, web search + schema |
|
|
548
|
+
| `09_mixed_content.rb` | Combining text and images |
|
|
549
|
+
| `10_image_generation.rb` | Image generation tool |
|
|
550
|
+
| `11_code_interpreter.rb` | Code interpreter tool |
|
|
551
|
+
| `12_background_mode.rb` | Background mode |
|
|
552
|
+
| `13_conversation_features_comprehensive.rb` | Conversation auto-creation and continuity |
|
|
553
|
+
| `14_schema_generation.rb` | Generate schemas from descriptions |
|
|
554
|
+
| `15_proxy.rb` | Proxy support |
|
|
555
|
+
| `16_get_items.rb` | Inspecting conversation items |
|
|
556
|
+
| `17_verbosity.rb` | Verbosity control |
|
|
925
557
|
|
|
926
558
|
## Contributing
|
|
927
559
|
|