ai-chat 0.3.2 → 0.5.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 +252 -194
- data/ai-chat.gemspec +4 -4
- data/lib/ai/amazing_print.rb +25 -26
- data/lib/ai/chat.rb +127 -169
- data/lib/ai/http.rb +1 -1
- data/lib/ai/items.rb +54 -0
- data/lib/ai/message.rb +23 -0
- data/lib/ai-chat.rb +11 -0
- metadata +6 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ece58b865d212ce788931ee3e7322b457bbe6ed8495e4374b55ecf697b44e55c
|
|
4
|
+
data.tar.gz: dd4bf5e24a5190eba63c662d93dcaaad24886b7f16cfbec5aebc37a9f3530cd9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: dcb3587c00724b1da3d04adb2b9a7413225680b80971bd08db127edab6d7f18e52958b7511f3aa7e13cb95f40961f8889cae3f0cc892913930547d5a7887d452
|
|
7
|
+
data.tar.gz: e831f86fecfa31cf28df66910fc6ca98c66a9ad8290709431a0e5e9b8a6e31965ba7af9d5a00dbcf3f907427abe8901abf23a1cd136b26bc2ebffe260b6334ed
|
data/README.md
CHANGED
|
@@ -26,19 +26,20 @@ The `examples/` directory contains focused examples for specific features:
|
|
|
26
26
|
|
|
27
27
|
- `01_quick.rb` - Quick overview of key features
|
|
28
28
|
- `02_core.rb` - Core functionality (basic chat, messages, responses)
|
|
29
|
-
- `
|
|
30
|
-
- `
|
|
31
|
-
- `
|
|
32
|
-
- `
|
|
33
|
-
- `
|
|
34
|
-
- `
|
|
35
|
-
- `
|
|
36
|
-
- `
|
|
37
|
-
- `
|
|
38
|
-
- `
|
|
39
|
-
- `
|
|
40
|
-
- `
|
|
41
|
-
- `
|
|
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)
|
|
42
43
|
|
|
43
44
|
Each example is self-contained and can be run individually:
|
|
44
45
|
```bash
|
|
@@ -83,22 +84,44 @@ a = AI::Chat.new
|
|
|
83
84
|
a.add("If the Ruby community had an official motto, what might it be?")
|
|
84
85
|
|
|
85
86
|
# See the convo so far - it's just an array of hashes!
|
|
86
|
-
|
|
87
|
-
# => [
|
|
87
|
+
a.messages
|
|
88
|
+
# => [
|
|
89
|
+
# {
|
|
90
|
+
# :role => "user",
|
|
91
|
+
# :content => "If the Ruby community had an official motto, what might it be?"
|
|
92
|
+
# }
|
|
93
|
+
# ]
|
|
88
94
|
|
|
89
95
|
# Generate the next message using AI
|
|
90
|
-
a.generate!
|
|
96
|
+
a.generate!
|
|
97
|
+
# => {
|
|
98
|
+
# :role => "assistant",
|
|
99
|
+
# :content => "Matz is nice and so we are nice",
|
|
100
|
+
# :response => { ... }
|
|
101
|
+
# }
|
|
91
102
|
|
|
92
103
|
# Your array now includes the assistant's response
|
|
93
|
-
|
|
104
|
+
a.messages
|
|
94
105
|
# => [
|
|
95
|
-
#
|
|
96
|
-
#
|
|
106
|
+
# {
|
|
107
|
+
# :role => "user",
|
|
108
|
+
# :content => "If the Ruby community had an official motto, what might it be?"
|
|
109
|
+
# },
|
|
110
|
+
# {
|
|
111
|
+
# :role => "assistant",
|
|
112
|
+
# :content => "Matz is nice and so we are nice",
|
|
113
|
+
# :response => { id: "resp_abc...", model: "gpt-5.2", ... }
|
|
114
|
+
# }
|
|
97
115
|
# ]
|
|
98
116
|
|
|
99
117
|
# Continue the conversation
|
|
100
118
|
a.add("What about Rails?")
|
|
101
|
-
a.generate!
|
|
119
|
+
a.generate!
|
|
120
|
+
# => {
|
|
121
|
+
# :role => "assistant",
|
|
122
|
+
# :content => "Convention over configuration.",
|
|
123
|
+
# :response => { ... }
|
|
124
|
+
# }
|
|
102
125
|
```
|
|
103
126
|
|
|
104
127
|
## Understanding the Data Structure
|
|
@@ -111,9 +134,19 @@ That's it! You're building something like this:
|
|
|
111
134
|
|
|
112
135
|
```ruby
|
|
113
136
|
[
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
137
|
+
{
|
|
138
|
+
:role => "system",
|
|
139
|
+
:content => "You are a helpful assistant"
|
|
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
|
+
}
|
|
117
150
|
]
|
|
118
151
|
```
|
|
119
152
|
|
|
@@ -133,14 +166,25 @@ b.add("You are a helpful assistant that talks like Shakespeare.", role: "system"
|
|
|
133
166
|
b.add("If the Ruby community had an official motto, what might it be?")
|
|
134
167
|
|
|
135
168
|
# Check what we've built
|
|
136
|
-
|
|
169
|
+
b.messages
|
|
137
170
|
# => [
|
|
138
|
-
#
|
|
139
|
-
#
|
|
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
|
+
# }
|
|
140
179
|
# ]
|
|
141
180
|
|
|
142
181
|
# Generate a response
|
|
143
|
-
b.generate!
|
|
182
|
+
b.generate!
|
|
183
|
+
# => {
|
|
184
|
+
# :role => "assistant",
|
|
185
|
+
# :content => "Methinks 'tis 'Ruby doth bring joy to all who craft with care'",
|
|
186
|
+
# :response => { ... }
|
|
187
|
+
# }
|
|
144
188
|
```
|
|
145
189
|
|
|
146
190
|
### Convenience Methods
|
|
@@ -183,25 +227,14 @@ d.generate! # Generate a response
|
|
|
183
227
|
|
|
184
228
|
### Model
|
|
185
229
|
|
|
186
|
-
By default, the gem uses OpenAI's `gpt-
|
|
230
|
+
By default, the gem uses OpenAI's `gpt-5.2` model. If you want to use a different model, you can set it:
|
|
187
231
|
|
|
188
232
|
```ruby
|
|
189
233
|
e = AI::Chat.new
|
|
190
|
-
e.model = "
|
|
234
|
+
e.model = "gpt-4o"
|
|
191
235
|
```
|
|
192
236
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
#### Foundation models
|
|
196
|
-
|
|
197
|
-
- gpt-4.1-nano
|
|
198
|
-
- gpt-4.1-mini
|
|
199
|
-
- gpt-4.1
|
|
200
|
-
|
|
201
|
-
#### Reasoning models
|
|
202
|
-
|
|
203
|
-
- o4-mini
|
|
204
|
-
- o3
|
|
237
|
+
See [OpenAI's model documentation](https://platform.openai.com/docs/models) for available models.
|
|
205
238
|
|
|
206
239
|
### API key
|
|
207
240
|
|
|
@@ -230,11 +263,20 @@ h.user("How do I boil an egg?")
|
|
|
230
263
|
h.generate!
|
|
231
264
|
|
|
232
265
|
# See the whole conversation
|
|
233
|
-
|
|
266
|
+
h.messages
|
|
234
267
|
# => [
|
|
235
|
-
#
|
|
236
|
-
#
|
|
237
|
-
#
|
|
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
|
+
# }
|
|
238
280
|
# ]
|
|
239
281
|
|
|
240
282
|
# Get just the last response
|
|
@@ -248,7 +290,7 @@ h.last[:content]
|
|
|
248
290
|
|
|
249
291
|
## Web Search
|
|
250
292
|
|
|
251
|
-
To give the model access to real-time information from the internet, you can enable web searching. This uses OpenAI's built-in `
|
|
293
|
+
To give the model access to real-time information from the internet, you can enable web searching. This uses OpenAI's built-in `web_search` tool.
|
|
252
294
|
|
|
253
295
|
```ruby
|
|
254
296
|
m = AI::Chat.new
|
|
@@ -257,17 +299,6 @@ m.user("What are the latest developments in the Ruby language?")
|
|
|
257
299
|
m.generate! # This may use web search to find current information
|
|
258
300
|
```
|
|
259
301
|
|
|
260
|
-
**Note:** This feature requires a model that supports the `web_search_preview` tool, such as `gpt-4o` or `gpt-4o-mini`. The gem will attempt to use a compatible model if you have `web_search` enabled.
|
|
261
|
-
|
|
262
|
-
If you don't want the model to use web search, set `web_search` to `false` (this is the default):
|
|
263
|
-
|
|
264
|
-
```ruby
|
|
265
|
-
m = AI::Chat.new
|
|
266
|
-
m.web_search = false
|
|
267
|
-
m.user("What are the latest developments in the Ruby language?")
|
|
268
|
-
m.generate! # This definitely won't use web search to find current information
|
|
269
|
-
```
|
|
270
|
-
|
|
271
302
|
## Structured Output
|
|
272
303
|
|
|
273
304
|
Get back Structured Output by setting the `schema` attribute (I suggest using [OpenAI's handy tool for generating the JSON Schema](https://platform.openai.com/docs/guides/structured-outputs)):
|
|
@@ -474,27 +505,6 @@ l.generate!
|
|
|
474
505
|
|
|
475
506
|
**Note**: Images should use `image:`/`images:` parameters, while documents should use `file:`/`files:` parameters.
|
|
476
507
|
|
|
477
|
-
## Re-sending old images and files
|
|
478
|
-
|
|
479
|
-
Note: if you generate another API request using the same chat, old images and files in the conversation history will not be re-sent by default. If you really want to re-send old images and files, then you must set `previous_response_id` to `nil`:
|
|
480
|
-
|
|
481
|
-
```ruby
|
|
482
|
-
a = AI::Chat.new
|
|
483
|
-
a.user("What color is the object in this photo?", image: "thing.png")
|
|
484
|
-
a.generate! # => "Red"
|
|
485
|
-
a.user("What is the object in the photo?")
|
|
486
|
-
a.generate! # => { :content => "I don't see a photo", ... }
|
|
487
|
-
|
|
488
|
-
b = AI::Chat.new
|
|
489
|
-
b.user("What color is the object in this photo?", image: "thing.png")
|
|
490
|
-
b.generate! # => "Red"
|
|
491
|
-
b.user("What is the object in the photo?")
|
|
492
|
-
b.previous_response_id = nil
|
|
493
|
-
b.generate! # => { :content => "An apple", ... }
|
|
494
|
-
```
|
|
495
|
-
|
|
496
|
-
If you don't set `previous_response_id` to `nil`, the model won't have the old image(s) to work with.
|
|
497
|
-
|
|
498
508
|
## Image generation
|
|
499
509
|
|
|
500
510
|
You can enable OpenAI's image generation tool:
|
|
@@ -503,7 +513,11 @@ You can enable OpenAI's image generation tool:
|
|
|
503
513
|
a = AI::Chat.new
|
|
504
514
|
a.image_generation = true
|
|
505
515
|
a.user("Draw a picture of a kitten")
|
|
506
|
-
a.generate!
|
|
516
|
+
a.generate!
|
|
517
|
+
# => {
|
|
518
|
+
# :content => "Here is your picture of a kitten:",
|
|
519
|
+
# :response => { ... }
|
|
520
|
+
# }
|
|
507
521
|
```
|
|
508
522
|
|
|
509
523
|
By default, images are saved to `./images`. You can configure a different location:
|
|
@@ -513,7 +527,11 @@ a = AI::Chat.new
|
|
|
513
527
|
a.image_generation = true
|
|
514
528
|
a.image_folder = "./my_images"
|
|
515
529
|
a.user("Draw a picture of a kitten")
|
|
516
|
-
a.generate!
|
|
530
|
+
a.generate!
|
|
531
|
+
# => {
|
|
532
|
+
# :content => "Here is your picture of a kitten:",
|
|
533
|
+
# :response => { ... }
|
|
534
|
+
# }
|
|
517
535
|
```
|
|
518
536
|
|
|
519
537
|
Images are saved in timestamped subfolders using ISO 8601 basic format. For example:
|
|
@@ -525,11 +543,19 @@ The folder structure ensures images are organized chronologically and by respons
|
|
|
525
543
|
The messages array will now look like this:
|
|
526
544
|
|
|
527
545
|
```ruby
|
|
528
|
-
|
|
546
|
+
a.messages
|
|
529
547
|
# => [
|
|
530
|
-
#
|
|
531
|
-
#
|
|
532
|
-
#
|
|
548
|
+
# {
|
|
549
|
+
# :role => "user",
|
|
550
|
+
# :content => "Draw a picture of a kitten"
|
|
551
|
+
# },
|
|
552
|
+
# {
|
|
553
|
+
# :role => "assistant",
|
|
554
|
+
# :content => "Here is your picture of a kitten:",
|
|
555
|
+
# :images => [ "./images/20250804T11303912_resp_abc123/001.png" ],
|
|
556
|
+
# :response => { ... }
|
|
557
|
+
# }
|
|
558
|
+
# ]
|
|
533
559
|
```
|
|
534
560
|
|
|
535
561
|
You can access the image filenames in several ways:
|
|
@@ -540,7 +566,7 @@ images = a.messages.last[:images]
|
|
|
540
566
|
# => ["./images/20250804T11303912_resp_abc123/001.png"]
|
|
541
567
|
|
|
542
568
|
# From the response object
|
|
543
|
-
images = a.messages.last
|
|
569
|
+
images = a.messages.last.dig(:response, :images)
|
|
544
570
|
# => ["./images/20250804T11303912_resp_abc123/001.png"]
|
|
545
571
|
```
|
|
546
572
|
|
|
@@ -551,9 +577,11 @@ a = AI::Chat.new
|
|
|
551
577
|
a.image_generation = true
|
|
552
578
|
a.image_folder = "./images"
|
|
553
579
|
a.user("Draw a picture of a kitten")
|
|
554
|
-
a.generate!
|
|
580
|
+
a.generate!
|
|
581
|
+
# => { :content => "Here is a picture of a kitten:", ... }
|
|
555
582
|
a.user("Make it even cuter")
|
|
556
|
-
a.generate!
|
|
583
|
+
a.generate!
|
|
584
|
+
# => { :content => "Here is the kitten, but even cuter:", ... }
|
|
557
585
|
```
|
|
558
586
|
|
|
559
587
|
## Code Interpreter
|
|
@@ -562,7 +590,23 @@ a.generate! # => { :content => "Here is the kitten, but even cuter:", ... }
|
|
|
562
590
|
y = AI::Chat.new
|
|
563
591
|
y.code_interpreter = true
|
|
564
592
|
y.user("Plot y = 2x*3 when x is -5 to 5.")
|
|
565
|
-
y.generate!
|
|
593
|
+
y.generate!
|
|
594
|
+
# => { :content => "Here is the graph.", ... }
|
|
595
|
+
```
|
|
596
|
+
|
|
597
|
+
## Background mode
|
|
598
|
+
|
|
599
|
+
If you want to start a response and poll for it later, set `background = true` before calling `generate!`:
|
|
600
|
+
|
|
601
|
+
```ruby
|
|
602
|
+
chat = AI::Chat.new
|
|
603
|
+
chat.background = true
|
|
604
|
+
chat.user("Write a short description about a sci-fi novel about a rat in space.")
|
|
605
|
+
chat.generate!
|
|
606
|
+
|
|
607
|
+
# Poll until it completes (this updates the existing assistant message)
|
|
608
|
+
message = chat.get_response(wait: true, timeout: 600)
|
|
609
|
+
puts message[:content]
|
|
566
610
|
```
|
|
567
611
|
|
|
568
612
|
## Proxying Through prepend.me
|
|
@@ -608,29 +652,28 @@ puts response
|
|
|
608
652
|
|
|
609
653
|
With this, you can loop through any conversation's history (perhaps after retrieving it from your database), recreate an `AI::Chat`, and then continue it.
|
|
610
654
|
|
|
611
|
-
## Reasoning
|
|
655
|
+
## Reasoning Effort
|
|
612
656
|
|
|
613
|
-
|
|
657
|
+
You can control how much reasoning the model does before producing its response:
|
|
614
658
|
|
|
615
659
|
```ruby
|
|
616
660
|
l = AI::Chat.new
|
|
617
|
-
l.
|
|
618
|
-
l.reasoning_effort = "medium" # Can be "low", "medium", or "high"
|
|
661
|
+
l.reasoning_effort = "low" # Can be "low", "medium", or "high"
|
|
619
662
|
|
|
620
663
|
l.user("What does this error message mean? <insert error message>")
|
|
621
664
|
l.generate!
|
|
622
665
|
```
|
|
623
666
|
|
|
624
|
-
The `reasoning_effort` parameter guides the model on how many reasoning tokens to generate
|
|
667
|
+
The `reasoning_effort` parameter guides the model on how many reasoning tokens to generate. Options are:
|
|
625
668
|
- `"low"`: Favors speed and economical token usage.
|
|
626
|
-
- `"medium"`:
|
|
669
|
+
- `"medium"`: Balances speed and reasoning accuracy.
|
|
627
670
|
- `"high"`: Favors more complete reasoning.
|
|
628
671
|
|
|
629
|
-
|
|
672
|
+
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.
|
|
630
673
|
|
|
631
674
|
## Advanced: Response Details
|
|
632
675
|
|
|
633
|
-
When you call `generate!` or `
|
|
676
|
+
When you call `generate!` (or later call `get_response` in background mode), the gem stores additional information about the API response:
|
|
634
677
|
|
|
635
678
|
```ruby
|
|
636
679
|
t = AI::Chat.new
|
|
@@ -638,18 +681,18 @@ t.user("Hello!")
|
|
|
638
681
|
t.generate!
|
|
639
682
|
|
|
640
683
|
# Each assistant message includes a response object
|
|
641
|
-
|
|
684
|
+
t.messages.last
|
|
642
685
|
# => {
|
|
643
|
-
#
|
|
644
|
-
#
|
|
645
|
-
#
|
|
686
|
+
# :role => "assistant",
|
|
687
|
+
# :content => "Hello! How can I help you today?",
|
|
688
|
+
# :response => { id: "resp_abc...", model: "gpt-5.2", ... }
|
|
646
689
|
# }
|
|
647
690
|
|
|
648
691
|
# Access detailed information
|
|
649
692
|
response = t.last[:response]
|
|
650
693
|
response[:id] # => "resp_abc123..."
|
|
651
|
-
response[:model] # => "gpt-
|
|
652
|
-
response[:usage] # => {:
|
|
694
|
+
response[:model] # => "gpt-5.2"
|
|
695
|
+
response[:usage] # => {:input_tokens=>5, :output_tokens=>7, :total_tokens=>12}
|
|
653
696
|
```
|
|
654
697
|
|
|
655
698
|
This information is useful for:
|
|
@@ -658,26 +701,35 @@ This information is useful for:
|
|
|
658
701
|
- Understanding which model was actually used.
|
|
659
702
|
- Future features like cost tracking.
|
|
660
703
|
|
|
661
|
-
|
|
704
|
+
### Last Response ID
|
|
705
|
+
|
|
706
|
+
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.
|
|
662
707
|
|
|
663
708
|
```ruby
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
old_id = t.last[:response][:id] # => "resp_abc123..."
|
|
709
|
+
chat = AI::Chat.new
|
|
710
|
+
chat.user("Hello")
|
|
711
|
+
chat.generate!
|
|
668
712
|
|
|
669
|
-
|
|
713
|
+
puts chat.last_response_id # => "resp_abc123..."
|
|
670
714
|
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
# ]
|
|
676
|
-
u.user("What should we do next?")
|
|
677
|
-
u.generate!
|
|
715
|
+
chat.user("Goodbye")
|
|
716
|
+
chat.generate!
|
|
717
|
+
|
|
718
|
+
puts chat.last_response_id # => "resp_xyz789..." (a new ID)
|
|
678
719
|
```
|
|
679
720
|
|
|
680
|
-
|
|
721
|
+
This is particularly useful for background mode workflows. If you want to retrieve or cancel a background response from a different process, use `OpenAI::Client` directly:
|
|
722
|
+
|
|
723
|
+
```ruby
|
|
724
|
+
require "openai"
|
|
725
|
+
|
|
726
|
+
client = OpenAI::Client.new(api_key: ENV.fetch("OPENAI_API_KEY"))
|
|
727
|
+
|
|
728
|
+
response_id = "resp_abc123..." # e.g., load from your database
|
|
729
|
+
response = client.responses.retrieve(response_id)
|
|
730
|
+
|
|
731
|
+
client.responses.cancel(response_id) unless response.status.to_s == "completed"
|
|
732
|
+
```
|
|
681
733
|
|
|
682
734
|
### Automatic Conversation Management
|
|
683
735
|
|
|
@@ -707,68 +759,119 @@ chat.user("Continue our discussion")
|
|
|
707
759
|
chat.generate! # Uses the loaded conversation
|
|
708
760
|
```
|
|
709
761
|
|
|
710
|
-
**Note on forking:** If you want to "fork" a conversation (create a branch), you can still use `previous_response_id`. If both `conversation_id` and `previous_response_id` are set, the gem will use `previous_response_id` and warn you.
|
|
711
|
-
|
|
712
762
|
## Inspecting Conversation Details
|
|
713
763
|
|
|
714
|
-
The
|
|
715
|
-
|
|
716
|
-
### `items` - Programmatic Access
|
|
717
|
-
|
|
718
|
-
Returns the raw conversation items for programmatic use (displaying in views, filtering, etc.):
|
|
764
|
+
The `get_items` method fetches all conversation items (messages, tool calls, reasoning, etc.) from the API for both programmatic use and debugging:
|
|
719
765
|
|
|
720
766
|
```ruby
|
|
721
767
|
chat = AI::Chat.new
|
|
768
|
+
chat.reasoning_effort = "high" # Enable reasoning summaries
|
|
722
769
|
chat.web_search = true
|
|
723
770
|
chat.user("Search for Ruby tutorials")
|
|
724
771
|
chat.generate!
|
|
725
772
|
|
|
726
773
|
# Get all conversation items (chronological order by default)
|
|
727
|
-
|
|
774
|
+
chat.get_items
|
|
775
|
+
|
|
776
|
+
# Output in IRB/Rails console:
|
|
777
|
+
# ┌────────────────────────────────────────────────────────────────────────────┐
|
|
778
|
+
# │ Conversation: conv_6903c1eea6cc819695af3a1b1ebf9b390c3db5e8ec021c9a │
|
|
779
|
+
# │ Items: 8 │
|
|
780
|
+
# └────────────────────────────────────────────────────────────────────────────┘
|
|
781
|
+
#
|
|
782
|
+
# [detailed colorized output of all items including web searches,
|
|
783
|
+
# reasoning summaries, tool calls, messages, etc.]
|
|
728
784
|
|
|
729
|
-
#
|
|
730
|
-
|
|
785
|
+
# Iterate over items programmatically
|
|
786
|
+
chat.get_items.data.each do |item|
|
|
731
787
|
case item.type
|
|
732
788
|
when :message
|
|
733
789
|
puts "#{item.role}: #{item.content.first.text}"
|
|
734
790
|
when :web_search_call
|
|
735
|
-
puts "Web search: #{item.action.query}"
|
|
736
|
-
puts "Results: #{item.results.length}"
|
|
791
|
+
puts "Web search: #{item.action.query}" if item.action.respond_to?(:query) && item.action.query
|
|
737
792
|
when :reasoning
|
|
738
|
-
|
|
793
|
+
# Reasoning summaries show a high-level view of the model's reasoning
|
|
794
|
+
if item.summary&.first
|
|
795
|
+
puts "Reasoning: #{item.summary.first.text}"
|
|
796
|
+
end
|
|
797
|
+
when :image_generation_call
|
|
798
|
+
puts "Image generated" if item.result
|
|
739
799
|
end
|
|
740
800
|
end
|
|
741
801
|
|
|
742
802
|
# For long conversations, you can request reverse chronological order
|
|
743
803
|
# (useful for pagination to get most recent items first)
|
|
744
|
-
recent_items = chat.
|
|
804
|
+
recent_items = chat.get_items(order: :desc)
|
|
745
805
|
```
|
|
746
806
|
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
Pretty-prints the entire conversation with all details for debugging and learning:
|
|
750
|
-
|
|
751
|
-
```ruby
|
|
752
|
-
chat.verbose
|
|
753
|
-
|
|
754
|
-
# Output:
|
|
755
|
-
# ┌────────────────────────────────────────────────────────────────────────────┐
|
|
756
|
-
# │ Conversation: conv_6903c1eea6cc819695af3a1b1ebf9b390c3db5e8ec021c9a │
|
|
757
|
-
# │ Items: 3 │
|
|
758
|
-
# └────────────────────────────────────────────────────────────────────────────┘
|
|
759
|
-
#
|
|
760
|
-
# [detailed colorized output of all items including web searches,
|
|
761
|
-
# reasoning, tool calls, messages, etc.]
|
|
762
|
-
```
|
|
807
|
+
When `reasoning_effort` is set, the API returns reasoning summaries (e.g., "Planning Ruby version search", "Confirming image tool usage"). Note that not all reasoning items have summaries - some intermediate steps may be empty.
|
|
763
808
|
|
|
764
809
|
This is useful for:
|
|
765
810
|
- **Learning** how the model uses tools (web search, code interpreter, etc.)
|
|
766
811
|
- **Debugging** why the model made certain decisions
|
|
767
812
|
- **Understanding** the full context beyond just the final response
|
|
813
|
+
- **Transparency** into the model's reasoning process
|
|
814
|
+
|
|
815
|
+
### HTML Output for ERB Templates
|
|
816
|
+
|
|
817
|
+
All display objects have a `to_html` method for rendering in ERB templates:
|
|
818
|
+
|
|
819
|
+
```erb
|
|
820
|
+
<%# Display a chat object %>
|
|
821
|
+
<%= @chat.to_html %>
|
|
822
|
+
|
|
823
|
+
<%# Display individual messages %>
|
|
824
|
+
<% @chat.messages.each do |msg| %>
|
|
825
|
+
<%= msg.to_html %>
|
|
826
|
+
<% end %>
|
|
827
|
+
|
|
828
|
+
<%# Display conversation items (quick debug view) %>
|
|
829
|
+
<%= @chat.get_items.to_html %>
|
|
830
|
+
```
|
|
831
|
+
|
|
832
|
+
The HTML output includes a dark background to match the terminal aesthetic.
|
|
833
|
+
|
|
834
|
+
You can also loop over `get_items.data` to build custom displays showing reasoning steps, tool calls, etc.:
|
|
835
|
+
|
|
836
|
+
```erb
|
|
837
|
+
<% @chat.get_items.data.each do |item| %>
|
|
838
|
+
<% case item.type.to_s %>
|
|
839
|
+
<% when "message" %>
|
|
840
|
+
<div class="message <%= item.role %>">
|
|
841
|
+
<strong><%= item.role.capitalize %>:</strong>
|
|
842
|
+
<% if item.content&.first %>
|
|
843
|
+
<% content = item.content.first %>
|
|
844
|
+
<% if content.type.to_s == "input_text" %>
|
|
845
|
+
<%= content.text %>
|
|
846
|
+
<% elsif content.type.to_s == "output_text" %>
|
|
847
|
+
<%= content.text %>
|
|
848
|
+
<% end %>
|
|
849
|
+
<% end %>
|
|
850
|
+
</div>
|
|
851
|
+
<% when "reasoning" %>
|
|
852
|
+
<% if item.summary&.first %>
|
|
853
|
+
<details class="reasoning">
|
|
854
|
+
<summary>Reasoning</summary>
|
|
855
|
+
<%= item.summary.first.text %>
|
|
856
|
+
</details>
|
|
857
|
+
<% end %>
|
|
858
|
+
<% when "web_search_call" %>
|
|
859
|
+
<% if item.action.respond_to?(:query) && item.action.query %>
|
|
860
|
+
<div class="web-search">
|
|
861
|
+
Searched: "<%= item.action.query %>"
|
|
862
|
+
</div>
|
|
863
|
+
<% end %>
|
|
864
|
+
<% when "image_generation_call" %>
|
|
865
|
+
<div class="image-generation">
|
|
866
|
+
Image generated
|
|
867
|
+
</div>
|
|
868
|
+
<% end %>
|
|
869
|
+
<% end %>
|
|
870
|
+
```
|
|
768
871
|
|
|
769
872
|
## Setting messages directly
|
|
770
873
|
|
|
771
|
-
You can use `.messages=()` to assign an `Array` of `Hashes
|
|
874
|
+
You can use `.messages=()` to assign an `Array` of `Hashes` (text-only). Each `Hash` must have keys `:role` and `:content`:
|
|
772
875
|
|
|
773
876
|
```ruby
|
|
774
877
|
# Using the planet example with array of hashes
|
|
@@ -791,53 +894,8 @@ response = p.generate!
|
|
|
791
894
|
puts response
|
|
792
895
|
```
|
|
793
896
|
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
```ruby
|
|
797
|
-
# Create a new chat instance
|
|
798
|
-
q = AI::Chat.new
|
|
799
|
-
|
|
800
|
-
# With images
|
|
801
|
-
q.messages = [
|
|
802
|
-
{ role: "system", content: "You are a helpful assistant." },
|
|
803
|
-
{ role: "user", content: "What's in this image?", image: "path/to/image.jpg" },
|
|
804
|
-
]
|
|
805
|
-
|
|
806
|
-
# With multiple images
|
|
807
|
-
q.messages = [
|
|
808
|
-
{ role: "system", content: "You are a helpful assistant." },
|
|
809
|
-
{ role: "user", content: "Compare these images", images: ["image1.jpg", "image2.jpg"] }
|
|
810
|
-
]
|
|
811
|
-
```
|
|
812
|
-
|
|
813
|
-
## Other Features Being Considered
|
|
814
|
-
|
|
815
|
-
- **Session management**: Save and restore conversations by ID
|
|
816
|
-
- **Streaming responses**: Real-time streaming as the AI generates its response
|
|
817
|
-
- **Cost tracking**: Automatic calculation and tracking of API costs
|
|
818
|
-
|
|
819
|
-
## Testing with Real API Calls
|
|
820
|
-
|
|
821
|
-
While this gem includes specs, they use mocked API responses. To test with real API calls:
|
|
822
|
-
|
|
823
|
-
1. Create a `.env` file at the project root with your API credentials:
|
|
824
|
-
```
|
|
825
|
-
# Your OpenAI API key
|
|
826
|
-
OPENAI_API_KEY=your_openai_api_key_here
|
|
827
|
-
```
|
|
828
|
-
2. Install dependencies: `bundle install`
|
|
829
|
-
3. Run the examples: `bundle exec ruby examples/all.rb`
|
|
830
|
-
|
|
831
|
-
This test program runs through all the major features of the gem, making real API calls to OpenAI.
|
|
897
|
+
For images/files, prefer using `chat.user(..., image:/images:/file:/files:)` so the gem can build the correct multimodal structure.
|
|
832
898
|
|
|
833
899
|
## Contributing
|
|
834
900
|
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
1. **Code Style**: This project uses StandardRB for linting. Run `bundle exec standardrb --fix` before committing to automatically fix style issues.
|
|
838
|
-
|
|
839
|
-
2. **Testing**: Ensure all specs pass with `bundle exec rspec`.
|
|
840
|
-
|
|
841
|
-
3. **Examples**: If adding a feature, consider adding an example in the `examples/` directory.
|
|
842
|
-
|
|
843
|
-
4. **Documentation**: Update the README if your changes affect the public API.
|
|
901
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md).
|