diff --git a/Gemfile.lock b/Gemfile.lock index 020c247..cf3cd19 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - ai-chat (0.5.1) + ai-chat (0.5.4) amazing_print (~> 2.0) base64 (~> 0.1, > 0.1.1) json (~> 2.0) diff --git a/README.md b/README.md index fc99c72..10ca111 100644 --- a/README.md +++ b/README.md @@ -671,6 +671,15 @@ The `reasoning_effort` parameter guides the model on how many reasoning tokens t 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. +## Verbosity + +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: + +- High verbosity: Use when you need the model to provide thorough explanations of documents or perform extensive code refactoring. +- Low verbosity: Best for situations where you want concise answers or simple code generation, such as SQL queries. + +The supported values are `:high`, `:medium`, or `:low`. The default value is `:medium` for `gpt-5.2`. **Older models (like `gpt-4.1-nano`) only support `:medium`**. + ## Advanced: Response Details When you call `generate!` (or later call `get_response` in background mode), the gem stores additional information about the API response: diff --git a/examples/17_verbosity.rb b/examples/17_verbosity.rb new file mode 100644 index 0000000..360ff39 --- /dev/null +++ b/examples/17_verbosity.rb @@ -0,0 +1,102 @@ +#!/usr/bin/env ruby + +require_relative "../lib/ai-chat" +require "dotenv" +Dotenv.load(File.expand_path("../.env", __dir__)) +require "amazing_print" + +puts "\n=== AI::Chat Verbosity Tests ===" +puts + +puts "1. Supports verbosity = :low" +puts "-" * 30 +chat1 = AI::Chat.new +chat1.verbosity = :low +chat1.user("How high do planes typically fly?") +response = chat1.generate![:content] +puts response +puts +puts "Total characters: #{response.length}" +puts + +puts "2. Supports verbosity = :medium" +puts "-" * 30 +chat2 = AI::Chat.new +chat2.verbosity = :medium +chat2.user("How high do planes typically fly?") +response = chat2.generate![:content] +puts response +puts +puts "Total characters: #{response.length}" +puts + +puts "3. Supports verbosity = :high" +puts "-" * 30 +chat3 = AI::Chat.new +chat3.verbosity = :medium +chat3.user("How high do planes typically fly?") +response = chat3.generate![:content] +puts response +puts +puts "Total characters: #{response.length}" +puts + +puts "4. Raises error for unsupported verbosity values" +puts "-" * 30 +chat4 = AI::Chat.new +chat4.user("How high do planes typically fly?") + begin + chat4.verbosity = :extreme + puts "✗ Failed to raise ArgumentError for invalid verbosity: #{chat4.verbosity}" +rescue ArgumentError => e + puts "✓ Raises Argument error correctly: #{e.message}" +end +puts + +puts +puts "5. Supports verbosity with Structured Outputs" +puts "-" * 30 +path = "my_schemas/test.json" +File.delete(path) if File.exist?(path) +AI::Chat.generate_schema!("A user with full name (required), first_name (required), last_name (required), coolness_level (score between 0-10 representing how cool the user sounds), coolness_reasoning (the explanation of the coolness_leve)", location: path, api_key: ENV["OPENAI_API_KEY"]) + +chat5 = AI::Chat.new +chat5.verbosity = :low +chat5.schema_file = path +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 +begin + response = chat5.generate![:content] + puts "✓ Generates structured output response with verbosity" +rescue => e + puts "✗ Failed to generate structured output response with verbosity: #{e.message}" +end +puts + +puts "6. Supports verbosity = :medium for gpt-4.1-nano" +puts "-" * 30 +chat6 = AI::Chat.new +chat6.model = "gpt-4.1-nano" +chat6.verbosity = :medium +chat6.user("How high do planes typically fly?") +response = chat6.generate![:content] +puts response +puts +puts "Total characters: #{response.length}" +puts + +puts "7. Fails verbosity = :low for gpt-4.1-nano" +puts "-" * 30 +chat7 = AI::Chat.new +chat7.model = "gpt-4.1-nano" +chat7.verbosity = :low +chat7.user("How high do planes typically fly?") +begin + response = chat7.generate![:content] + 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}" +end +puts diff --git a/examples/all.rb b/examples/all.rb index 489a0ca..a2d3055 100755 --- a/examples/all.rb +++ b/examples/all.rb @@ -53,3 +53,9 @@ # Proxy require_relative "15_proxy" + +# Get items +require_relative "16_get_items" + +# Verbosity +require_relative "17_verbosity" diff --git a/lib/ai/chat.rb b/lib/ai/chat.rb index ea6332a..b2b7f91 100644 --- a/lib/ai/chat.rb +++ b/lib/ai/chat.rb @@ -19,7 +19,7 @@ module AI class Chat # :reek:Attribute attr_accessor :background, :code_interpreter, :conversation_id, :image_generation, :image_folder, :messages, :model, :reasoning_effort, :web_search - attr_reader :client, :last_response_id, :proxy, :schema, :schema_file + attr_reader :client, :last_response_id, :proxy, :schema, :schema_file, :verbosity BASE_PROXY_URL = "https://prepend.me/api.openai.com/v1" @@ -34,6 +34,7 @@ def initialize(api_key: nil, api_key_env_var: "OPENAI_API_KEY") @image_generation = false @image_folder = "./images" @api_key_validated = false + @verbosity = :medium end def self.generate_schema!(description, location: "schema.json", api_key: nil, api_key_env_var: "OPENAI_API_KEY", proxy: false) @@ -183,6 +184,14 @@ def schema_file=(path) self.schema = content end + def verbosity=(value) + if ["low", "medium", "high"].include?(value.to_s) + @verbosity = value.to_sym + else + raise ArgumentError, "Invalid verbosity value:'#{value}'. Must be one of :low, :medium, :high." + end + end + def last messages.last end @@ -221,6 +230,7 @@ def inspectable_attributes attrs << [:@web_search, @web_search] if @web_search attrs << [:@schema, @schema] if @schema attrs << [:@schema_file, @schema_file] if @schema_file + attrs << [:@verbosity, verbosity] if verbosity attrs end @@ -274,6 +284,7 @@ def create_response create_conversation unless conversation_id parameters[:conversation] = conversation_id + parameters[:text] = (parameters[:text] || {}).merge(verbosity: verbosity) if verbosity messages_to_send = prepare_messages_for_api parameters[:input] = strip_responses(messages_to_send) unless messages_to_send.empty?