From 4e3426a3659731e69e5c8972f5eb846809363c53 Mon Sep 17 00:00:00 2001 From: Raghu Betina Date: Mon, 19 Oct 2026 10:39:52 -0500 Subject: [PATCH 1/3] Default to gpt-6-luna with low reasoning effort The "none" default broke the most common next step for a beginner: switching to a smarter model. gpt-6.1-sol and gpt-6-astra reject "none" with a bare 400, so `chat.model = "gpt-6.1-sol"` failed until you also changed reasoning_effort. Every current flagship model accepts "low", so changing the model alone now works. gpt-6-luna replaces gpt-5.6-terra as the default, including in generate_schema!, because it costs about a twentieth as much per token and answered a sample prompt about twice as fast. Even at "low", it beat terra at "none" (about 5s vs 7.5s). A non-"none" effort also requests a reasoning summary, so one is now requested by default. Set reasoning_effort = "none" for the old behavior on models that support it. --- CHANGELOG.md | 8 ++++++++ README.md | 14 +++++++------- lib/ai/chat.rb | 6 +++--- spec/unit/chat_spec.rb | 21 +++++++++++++++------ 4 files changed, 33 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a5c49a..f603c5b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,14 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +### Changed + +- **Default model**: Changed from `gpt-5.6-terra` to `gpt-6-luna`, the most efficient tier of OpenAI's GPT-6 family. It costs far less ($0.10/$0.50 vs $2/$12 per 1M input/output tokens) and answered a sample prompt about twice as fast in our testing. `generate_schema!` uses the same model. + +- **Default `reasoning_effort` is now `"low"` instead of `"none"`**: `gpt-6.1-sol` and `gpt-6-astra` reject `"none"` with a 400 error, so switching `model` to one of them used to fail unless you also changed `reasoning_effort`. `"low"` is accepted by all of OpenAI's current flagship models. Since the effort is no longer `"none"`, a reasoning summary (`summary: "auto"`) is now requested by default too. Set `reasoning_effort = "none"` to get the previous behavior on models that support it. + ## [0.7.0] - 2026-08-24 ### Changed diff --git a/README.md b/README.md index b3d77fc..9cb8619 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ ap response # => { # :role => "assistant", # :content => "Matz is nice and so we are nice.", -# :response => { id: "resp_abc...", model: "gpt-5.6-terra", ... } +# :response => { id: "resp_abc...", model: "gpt-6-luna", ... } # } ap chat.messages @@ -68,7 +68,7 @@ ap chat.messages # { # :role => "assistant", # :content => "Matz is nice and so we are nice.", -# :response => { id: "resp_abc...", model: "gpt-5.6-terra", ... } +# :response => { id: "resp_abc...", model: "gpt-6-luna", ... } # } # ] ``` @@ -131,11 +131,11 @@ chat.add("Here's what I think...", role: "assistant") ### Model -The gem defaults to `gpt-5.6-terra`. You can change it: +The gem defaults to `gpt-6-luna`. You can change it: ```ruby chat = AI::Chat.new -chat.model = "gpt-5.6-luna" +chat.model = "gpt-6.1-sol" ``` ### API Key @@ -409,9 +409,9 @@ chat.user("Explain the tradeoffs between microservices and monoliths.") chat.generate! ``` -By default, `reasoning_effort` is `"none"`, so the model answers directly without spending reasoning tokens. This matters because GPT-5.5 and newer models (including the default `gpt-5.6-terra`) fall back to `"medium"` reasoning when the parameter is omitted, which is slower and more expensive. +By default, `reasoning_effort` is `"low"`, so the model does a little reasoning before it answers. `"low"` is accepted by all of OpenAI's current flagship models (`gpt-6-luna`, `gpt-6.1-sol`, and `gpt-6-astra`), while some of them reject `"none"`, so you can switch models without also changing the effort. The gem always sends an effort because GPT-5.5 and newer models fall back to `"medium"` when the parameter is omitted, which is slower and more expensive. -Set `reasoning_effort = nil` to omit the reasoning parameter entirely — you will need this if you switch to a model that does not support reasoning (e.g., `gpt-4o`) or one that predates the `"none"` option. +Set `reasoning_effort = "none"` for the fastest replies on models that support it (such as `gpt-6-luna`). Set `reasoning_effort = nil` to omit the reasoning parameter entirely — you will need this if you switch to a model that does not support reasoning (e.g., `gpt-4o`). ### Verbosity @@ -476,7 +476,7 @@ chat.generate! response = chat.last[:response] response[:id] # => "resp_abc123..." -response[:model] # => "gpt-5.6-terra" +response[:model] # => "gpt-6-luna" response[:usage] # => { input_tokens: 5, output_tokens: 7, total_tokens: 12 } ``` diff --git a/lib/ai/chat.rb b/lib/ai/chat.rb index 6c18271..dcad064 100644 --- a/lib/ai/chat.rb +++ b/lib/ai/chat.rb @@ -32,8 +32,8 @@ def initialize(api_key: nil, api_key_env_var: nil, proxy: nil) @proxy = proxy.nil? ? ENV[PROXY_ENV]&.downcase == "true" : !!proxy @api_key = resolve_api_key @messages = [] - @reasoning_effort = "none" - @model = "gpt-5.6-terra" + @reasoning_effort = "low" + @model = "gpt-6-luna" client_options = {api_key: @api_key} client_options[:base_url] = BASE_PROXY_URL if @proxy @client = OpenAI::Client.new(**client_options) @@ -57,7 +57,7 @@ def self.generate_schema!(description, location: "schema.json", api_key: nil, ap client = OpenAI::Client.new(**options) response = client.responses.create( - model: "gpt-5.6-terra", + model: "gpt-6-luna", input: [ {role: :system, content: system_prompt}, {role: :user, content: description} diff --git a/spec/unit/chat_spec.rb b/spec/unit/chat_spec.rb index 2c5af60..2701981 100644 --- a/spec/unit/chat_spec.rb +++ b/spec/unit/chat_spec.rb @@ -769,19 +769,28 @@ def schema_client_double allow(responses).to receive(:create).and_return(double("response")) end - it "defaults to gpt-5.6-terra" do - expect(chat.model).to eq("gpt-5.6-terra") + it "defaults to gpt-6-luna" do + expect(chat.model).to eq("gpt-6-luna") end - it "defaults reasoning_effort to \"none\"" do - expect(chat.reasoning_effort).to eq("none") + it "defaults reasoning_effort to \"low\"" do + expect(chat.reasoning_effort).to eq("low") end - it "sends reasoning effort none without a summary by default" do + it "sends reasoning effort low with a summary by default" do chat.send(:create_response) expect(responses).to have_received(:create).with( - hash_including(model: "gpt-5.6-terra", reasoning: {effort: "none"}) + hash_including(model: "gpt-6-luna", reasoning: {effort: "low", summary: "auto"}) + ) + end + + it "sends reasoning effort none without a summary" do + chat.reasoning_effort = "none" + chat.send(:create_response) + + expect(responses).to have_received(:create).with( + hash_including(reasoning: {effort: "none"}) ) end From 7839ae1ae8d04db531fd7cdb411fd97dfbf2001c Mon Sep 17 00:00:00 2001 From: Raghu Betina Date: Mon, 19 Oct 2026 10:54:34 -0500 Subject: [PATCH 2/3] Fix standardrb offenses Gets `standardrb` back to a clean run so a new offense stands out instead of hiding among old ones. Every fix is behavior-neutral: the proxy= rescue still restores @proxy and re-raises, generate_schema! uses ||= for the same fallback, and the verbosity example drops unused assignments and fixes its indentation. --- examples/17_verbosity.rb | 10 +++++----- lib/ai/chat.rb | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/examples/17_verbosity.rb b/examples/17_verbosity.rb index ff461a8..951d748 100644 --- a/examples/17_verbosity.rb +++ b/examples/17_verbosity.rb @@ -45,8 +45,8 @@ puts "-" * 30 chat4 = AI::Chat.new chat4.user("How high do planes typically fly?") - begin - chat4.verbosity = :extreme +begin + chat4.verbosity = :extreme puts "✗ Failed to raise ArgumentError for invalid verbosity: #{chat4.verbosity}" rescue ArgumentError => e puts "✓ Raises Argument error correctly: #{e.message}" @@ -66,9 +66,9 @@ chat5.system("Extract the profile information from the user message. Describe it like a 90's robot.") chat5.user("My name is Bryan. I like to skateboard and be cool. I'm seventeen and a quarter. You can reach me at bryan@example.com.") puts chat5.last[:content] -puts +puts begin - response = chat5.generate![:content] + chat5.generate! puts "✓ Generates structured output response with verbosity" rescue => e puts "✗ Failed to generate structured output response with verbosity: #{e.message}" @@ -96,7 +96,7 @@ chat7.verbosity = :low chat7.user("How high do planes typically fly?") begin - response = chat7.generate![:content] + chat7.generate! puts "✗ Failed to raise error when setting :low verbosity on unsupported model." rescue OpenAI::Errors::BadRequestError => e puts "✓ Successfully raises error on for unsupported verbosity for older model: #{e.message}" diff --git a/lib/ai/chat.rb b/lib/ai/chat.rb index dcad064..da28c92 100644 --- a/lib/ai/chat.rb +++ b/lib/ai/chat.rb @@ -46,7 +46,7 @@ def initialize(api_key: nil, api_key_env_var: nil, proxy: nil) def self.generate_schema!(description, location: "schema.json", api_key: nil, api_key_env_var: nil, proxy: nil) proxy = proxy.nil? ? ENV[PROXY_ENV]&.downcase == "true" : !!proxy - api_key = api_key || ENV.fetch(api_key_env_var || (proxy ? PROXY_KEY_ENV : OPENAI_KEY_ENV)) + api_key ||= ENV.fetch(api_key_env_var || (proxy ? PROXY_KEY_ENV : OPENAI_KEY_ENV)) prompt_path = File.expand_path("../prompts/schema_generator.md", __dir__) system_prompt = File.read(prompt_path) @@ -175,7 +175,7 @@ def proxy=(value) @api_key = new_key @api_key_validated = false @client = new_client - rescue => error + rescue @proxy = previous_proxy raise end From 79016b878b9a2388d9fff25b311d9a4dae4dcd77 Mon Sep 17 00:00:00 2001 From: Raghu Betina Date: Mon, 19 Oct 2026 10:54:34 -0500 Subject: [PATCH 3/3] Release 0.8.0 --- CHANGELOG.md | 2 +- Gemfile.lock | 2 +- ai-chat.gemspec | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f603c5b..cd8e8d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [Unreleased] +## [0.8.0] - 2026-10-19 ### Changed diff --git a/Gemfile.lock b/Gemfile.lock index 0f9af12..beaad89 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - ai-chat (0.7.0) + ai-chat (0.8.0) amazing_print (~> 2.0) base64 (~> 0.1, > 0.1.1) json (~> 2.0) diff --git a/ai-chat.gemspec b/ai-chat.gemspec index 7304b65..20cd0a1 100644 --- a/ai-chat.gemspec +++ b/ai-chat.gemspec @@ -2,7 +2,7 @@ Gem::Specification.new do |spec| spec.name = "ai-chat" - spec.version = "0.7.0" + spec.version = "0.8.0" spec.authors = ["Raghu Betina", "Jelani Woods"] spec.email = ["raghu@firstdraft.com", "jelani@firstdraft.com"] spec.homepage = "https://github.com/firstdraft/ai-chat"