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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4dd73c65bb0fa8183801233a76038c8e9a32f86268ab113311317a47f72504db
4
- data.tar.gz: 8a6541039268eee87a035f7547d767919962b8f20573e06618700e05ea72ffe6
3
+ metadata.gz: ece58b865d212ce788931ee3e7322b457bbe6ed8495e4374b55ecf697b44e55c
4
+ data.tar.gz: dd4bf5e24a5190eba63c662d93dcaaad24886b7f16cfbec5aebc37a9f3530cd9
5
5
  SHA512:
6
- metadata.gz: b2564b32f5f1c69e8749eb079339d120cbfbf71ce9c5870cfb7b47ea81891ec11dbc9290b59f2b49b35830f59d7f0817d3dcaa558bc5ef1d97309a0a26da3733
7
- data.tar.gz: ae9ebf5fd4b0a1001e6fb047585c852055c43cfc2b7af7fc3385ed8df355aec260e2ba6e80a2443b9104648a49356124974353317d3818449413fe923ab27a0a
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
- - `03_configuration.rb` - Configuration options (API keys, models, reasoning effort)
30
- - `04_multimodal.rb` - Basic file and image handling
31
- - `05_file_handling_comprehensive.rb` - Advanced file handling (PDFs, text files, Rails uploads)
32
- - `06_structured_output.rb` - Basic structured output with schemas
33
- - `07_structured_output_comprehensive.rb` - All 6 supported schema formats
34
- - `08_advanced_usage.rb` - Advanced patterns (chaining, web search)
35
- - `09_edge_cases.rb` - Error handling and edge cases
36
- - `10_additional_patterns.rb` - Less common usage patterns (direct add method, web search + schema, etc.)
37
- - `11_mixed_content.rb` - Combining text and images in messages
38
- - `12_image_generation.rb` - Using the image generation tool
39
- - `13_code_interpreter.rb` - Using the code interpreter tool
40
- - `14_background_mode.rb` - Running responses in background mode
41
- - `15_conversation_features_comprehensive.rb` - All conversation features (auto-creation, inspection, loading, forking)
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
- pp a.messages
87
- # => [{:role=>"user", :content=>"If the Ruby community had an official motto, what might it be?"}]
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! # => { :role => "assistant", :content => "Matz is nice and so we are nice" (or similar) }
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
- pp a.messages
104
+ a.messages
94
105
  # => [
95
- # {:role=>"user", :content=>"If the Ruby community had an official motto, what might it be?"},
96
- # {:role=>"assistant", :content=>"Matz is nice and so we are nice", :response => { id=resp_abc... model=gpt-4.1-nano tokens=12 } }
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! # => { :role => "assistant", :content => "Convention over configuration."}
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
- {:role => "system", :content => "You are a helpful assistant"},
115
- {:role => "user", :content => "Hello!"},
116
- {:role => "assistant", :content => "Hi there! How can I help you today?", :response => { id=resp_abc... model=gpt-4.1-nano tokens=12 } }
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
- pp b.messages
169
+ b.messages
137
170
  # => [
138
- # {:role=>"system", :content=>"You are a helpful assistant that talks like Shakespeare."},
139
- # {:role=>"user", :content=>"If the Ruby community had an official motto, what might it be?"}
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! # => { :role => "assistant", :content => "Methinks 'tis 'Ruby doth bring joy to all who craft with care'" }
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-4.1-nano` model. If you want to use a different model, you can set it:
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 = "o4-mini"
234
+ e.model = "gpt-4o"
191
235
  ```
192
236
 
193
- As of 2025-07-29, the list of chat models that you probably want to choose from are:
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
- pp h.messages
266
+ h.messages
234
267
  # => [
235
- # {:role=>"system", :content=>"You are a helpful cooking assistant"},
236
- # {:role=>"user", :content=>"How do I boil an egg?"},
237
- # {:role=>"assistant", :content=>"Here's how to boil an egg..."}
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 `web_search_preview` tool.
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! # => { :content => "Here is your picture of a kitten:", ... }
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! # => { :content => "Here is your picture of a kitten:", ... }
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
- pp a.messages
546
+ a.messages
529
547
  # => [
530
- # {:role=>"user", :content=>"Draw a picture of a kitten"},
531
- # {:role=>"assistant", :content=>"Here is your picture of a kitten:", :images => ["./images/20250804T11303912_resp_abc123/001.png"], :response => #<Response ...>}
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[:response].images
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! # => { :content => "Here is a picture of a kitten:", ... }
580
+ a.generate!
581
+ # => { :content => "Here is a picture of a kitten:", ... }
555
582
  a.user("Make it even cuter")
556
- a.generate! # => { :content => "Here is the kitten, but even cuter:", ... }
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! # => {:content => "Here is the graph.", ... }
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 Models
655
+ ## Reasoning Effort
612
656
 
613
- When using reasoning models like `o3` or `o4-mini`, you can specify a reasoning effort level to control how much reasoning the model does before producing its final response:
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.model = "o3-mini"
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 before creating a response to the prompt. Options are:
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"`: (Default) Balances speed and reasoning accuracy.
669
+ - `"medium"`: Balances speed and reasoning accuracy.
627
670
  - `"high"`: Favors more complete reasoning.
628
671
 
629
- Setting to `nil` disables the reasoning parameter.
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 `generate!`, the gem stores additional information about the API response:
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
- pp t.messages.last
684
+ t.messages.last
642
685
  # => {
643
- # :role => "assistant",
644
- # :content => "Hello! How can I help you today?",
645
- # :response => { id=resp_abc... model=gpt-4.1-nano tokens=12 }
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-4.1-nano"
652
- response[:usage] # => {:prompt_tokens=>5, :completion_tokens=>7, :total_tokens=>12}
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
- You can also, if you know a response ID, continue an old conversation by setting the `previous_response_id`:
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
- t = AI::Chat.new
665
- t.user("Hello!")
666
- t.generate!
667
- old_id = t.last[:response][:id] # => "resp_abc123..."
709
+ chat = AI::Chat.new
710
+ chat.user("Hello")
711
+ chat.generate!
668
712
 
669
- # Some time in the future...
713
+ puts chat.last_response_id # => "resp_abc123..."
670
714
 
671
- u = AI::Chat.new
672
- u.previous_response_id = "resp_abc123..."
673
- u.user("What did I just say?")
674
- u.generate! # Will have context from the previous conversation}
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
- Unless you've stored the previous messages somewhere yourself, this technique won't bring them back. But OpenAI remembers what they were, so that you can at least continue the conversation. (If you're using a reasoning model, this technique also preserves all of the model's reasoning.)
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 gem provides two methods to inspect what happened during a conversation:
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
- page = chat.items
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
- # Access item data
730
- page.data.each do |item|
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
- puts "Reasoning: #{item.summary.first.text}"
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.items(order: :desc)
804
+ recent_items = chat.get_items(order: :desc)
745
805
  ```
746
806
 
747
- ### `verbose` - Terminal Output
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`. Each `Hash` must have keys `:role` and `:content`, and optionally `:image` or `:images`:
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
- You can still include images:
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
- When contributing to this project:
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).