From f4e1c47656fbf7e26c259248b41eb2ee26f951c7 Mon Sep 17 00:00:00 2001 From: Jay El-Kaake Date: Sat, 19 Apr 2025 17:03:09 -0400 Subject: [PATCH] Adds the ability to define more complex param schemas, while retaining the existing simple methods + bump version to 1.3 since this is kind of a major feature. --- .gitignore | 5 +- docs/guides/tools.md | 56 +++- lib/ruby_llm/providers/anthropic/tools.rb | 11 +- lib/ruby_llm/providers/gemini/tools.rb | 32 ++- lib/ruby_llm/providers/openai/tools.rb | 9 +- lib/ruby_llm/schema.rb | 78 ++++++ lib/ruby_llm/tool.rb | 53 +++- lib/ruby_llm/version.rb | 2 +- ...les_array_parameters_with_object_items.yml | 172 ++++++++++++ ...les_array_parameters_with_object_items.yml | 122 +++++++++ ...les_array_parameters_with_object_items.yml | 122 +++++++++ ...les_array_parameters_with_object_items.yml | 198 ++++++++++++++ ...les_array_parameters_with_object_items.yml | 249 ++++++++++++++++++ ...41022_handles_nested_object_parameters.yml | 171 ++++++++++++ ...-v1_0_handles_nested_object_parameters.yml | 122 +++++++++ ...-chat_handles_nested_object_parameters.yml | 122 +++++++++ ...flash_handles_nested_object_parameters.yml | 191 ++++++++++++++ ...-nano_handles_nested_object_parameters.yml | 239 +++++++++++++++++ spec/ruby_llm/chat_tools_spec.rb | 109 ++++++++ spec/ruby_llm/schema_spec.rb | 75 ++++++ spec/spec_helper.rb | 5 +- 21 files changed, 2099 insertions(+), 44 deletions(-) create mode 100644 lib/ruby_llm/schema.rb create mode 100644 spec/fixtures/vcr_cassettes/chat_array_parameters_with_anthropic_claude-3-5-haiku-20241022_handles_array_parameters_with_object_items.yml create mode 100644 spec/fixtures/vcr_cassettes/chat_array_parameters_with_bedrock_anthropic_claude-3-5-haiku-20241022-v1_0_handles_array_parameters_with_object_items.yml create mode 100644 spec/fixtures/vcr_cassettes/chat_array_parameters_with_deepseek_deepseek-chat_handles_array_parameters_with_object_items.yml create mode 100644 spec/fixtures/vcr_cassettes/chat_array_parameters_with_gemini_gemini-2_0-flash_handles_array_parameters_with_object_items.yml create mode 100644 spec/fixtures/vcr_cassettes/chat_array_parameters_with_openai_gpt-4_1-nano_handles_array_parameters_with_object_items.yml create mode 100644 spec/fixtures/vcr_cassettes/chat_nested_parameters_with_anthropic_claude-3-5-haiku-20241022_handles_nested_object_parameters.yml create mode 100644 spec/fixtures/vcr_cassettes/chat_nested_parameters_with_bedrock_anthropic_claude-3-5-haiku-20241022-v1_0_handles_nested_object_parameters.yml create mode 100644 spec/fixtures/vcr_cassettes/chat_nested_parameters_with_deepseek_deepseek-chat_handles_nested_object_parameters.yml create mode 100644 spec/fixtures/vcr_cassettes/chat_nested_parameters_with_gemini_gemini-2_0-flash_handles_nested_object_parameters.yml create mode 100644 spec/fixtures/vcr_cassettes/chat_nested_parameters_with_openai_gpt-4_1-nano_handles_nested_object_parameters.yml create mode 100644 spec/ruby_llm/schema_spec.rb diff --git a/.gitignore b/.gitignore index b2ed8ad2..976fafc7 100644 --- a/.gitignore +++ b/.gitignore @@ -47,8 +47,8 @@ build-iPhoneSimulator/ # for a library or gem, you might want to ignore these files since the code is # intended to run in multiple environments; otherwise, check them in: Gemfile.lock -# .ruby-version -# .ruby-gemset +.ruby-version +.ruby-gemset # unless supporting rvm < 1.11.0 or doing something fancy, ignore this: .rvmrc @@ -57,3 +57,4 @@ Gemfile.lock # .rubocop-https?--* repomix-output.* +/.idea/ diff --git a/docs/guides/tools.md b/docs/guides/tools.md index e8c1c3e4..1dc51a46 100644 --- a/docs/guides/tools.md +++ b/docs/guides/tools.md @@ -53,26 +53,60 @@ class Weather < RubyLLM::Tool param :longitude, desc: "Longitude (e.g., 13.4050)" def execute(latitude:, longitude:) + puts "Requested weather for #{location[:city]}, #{location[:country]}" if location + puts "Use unit: #{unit}" if unit + url = "https://api.open-meteo.com/v1/forecast?latitude=#{latitude}&longitude=#{longitude}¤t=temperature_2m,wind_speed_10m" response = Faraday.get(url) - data = JSON.parse(response.body) + JSON.parse(response.body) rescue => e { error: e.message } end end ``` -### Tool Components +### 1. Inherit from `RubyLLM::Tool`. +**Inheritance:** Must inherit from `RubyLLM::Tool`. +### 2. Use the **`description`** to describe the tool. +Use the **`description`** class method defining what the tool does. Crucial for the AI model to understand its purpose. Keep it clear and concise. + +### 3. Define params with **`param`** +Define parameters with **`param`** class method. + +* The first param is always the name of the parameter. +* The remaining arguments are a hash representing the JSON schema of the parameter. + * Note 1: The root `required:` option isn't included in the schema. + * It specifies whether the AI *must* provide this parameter when calling the tool. + * It defaults to `true` (required) + * Set to `false` for optional parameters and provide a default value in your `execute` method signature. + * Note 2: `desc:` is automatically converted to `description:` in the schema. + * Only root level `desc:` is supported. + * If using more complex schemas, use `description:` instead. + +#### Short form example: +Define simple parameters with the shorthand format: +```ruby + param :latitude, desc: "Latitude (e.g., 52.5200)" +``` + +#### More complex examples: +More complex JSON schemas are supported simply by defining them here as well: +```ruby + param :longitude, type: 'number', description: "Longitude (e.g., 13.4050)", required: true + + param :unit, type: :string, enum: %w[f c], description: "Temperature unit (e.g., celsius, fahrenheit)", required: false + + param :location, type: :object, desc: "Country and city where weather is requested.", required: false, properties: { + country: { type: :string, description: "Full name of the country." }, + city: { type: :string, description: "Full name of the city." } + } +``` +Remember, `required:` at the root is not part of the JSON schema and automatically removed before sending to the LLM API. +Also, `desc` at the root level is converted to `description`. -1. **Inheritance:** Must inherit from `RubyLLM::Tool`. -2. **`description`:** A class method defining what the tool does. Crucial for the AI model to understand its purpose. Keep it clear and concise. -3. **`param`:** A class method used to define each input parameter. - * **Name:** The first argument (a symbol) is the parameter name. It will become a keyword argument in the `execute` method. - * **`type:`:** (Optional, defaults to `:string`) The expected data type. Common types include `:string`, `:integer`, `:number` (float), `:boolean`. Provider support for complex types like `:array` or `:object` varies. Stick to simple types for broad compatibility. - * **`desc:`:** (Required) A clear description of the parameter, explaining its purpose and expected format (e.g., "The city and state, e.g., San Francisco, CA"). - * **`required:`:** (Optional, defaults to `true`) Whether the AI *must* provide this parameter when calling the tool. Set to `false` for optional parameters and provide a default value in your `execute` method signature. -4. **`execute` Method:** The instance method containing your Ruby code. It receives the parameters defined by `param` as keyword arguments. Its return value (typically a String or Hash) is sent back to the AI model. +### 3. Define `execute` method +Define the `execute` instance method containing your Ruby code. It receives the parameters defined by `param` as keyword arguments. Its return value (typically a String or Hash) is sent back to the AI model. {: .note } The tool's class name is automatically converted to a snake_case name used in the API call (e.g., `WeatherLookup` becomes `weather_lookup`). @@ -210,4 +244,4 @@ Treat any arguments passed to your `execute` method as potentially untrusted use * [Chatting with AI Models]({% link guides/chat.md %}) * [Streaming Responses]({% link guides/streaming.md %}) (See how tools interact with streaming) * [Rails Integration]({% link guides/rails.md %}) (Persisting tool calls and results) -* [Error Handling]({% link guides/error-handling.md %}) \ No newline at end of file +* [Error Handling]({% link guides/error-handling.md %}) diff --git a/lib/ruby_llm/providers/anthropic/tools.rb b/lib/ruby_llm/providers/anthropic/tools.rb index 3ccfb202..9dcb4048 100644 --- a/lib/ruby_llm/providers/anthropic/tools.rb +++ b/lib/ruby_llm/providers/anthropic/tools.rb @@ -53,7 +53,7 @@ def function_for(tool) description: tool.description, input_schema: { type: 'object', - properties: clean_parameters(tool.parameters), + properties: tool.parameters.transform_values(&:schema), required: required_parameters(tool.parameters) } } @@ -79,15 +79,6 @@ def parse_tool_calls(content_block) } end - def clean_parameters(parameters) - parameters.transform_values do |param| - { - type: param.type, - description: param.description - }.compact - end - end - def required_parameters(parameters) parameters.select { |_, param| param.required }.keys end diff --git a/lib/ruby_llm/providers/gemini/tools.rb b/lib/ruby_llm/providers/gemini/tools.rb index 8cb85b1c..4073cbda 100644 --- a/lib/ruby_llm/providers/gemini/tools.rb +++ b/lib/ruby_llm/providers/gemini/tools.rb @@ -59,19 +59,43 @@ def function_declaration_for(tool) end # Format tool parameters for Gemini API + # @param parameters [Hash{Symbol => RubyLLM::Parameter}] + # @return [Hash{String => String|Array|Hash|Boolean|NilClass}] def format_parameters(parameters) { type: 'OBJECT', properties: parameters.transform_values do |param| - { - type: param_type_for_gemini(param.type), - description: param.description - }.compact + convert_gemini_types(param.schema) end, required: parameters.select { |_, p| p.required }.keys.map(&:to_s) } end + ## + # Convert JSON schema types to Gemini API types + # @param schema [Hash] + def convert_gemini_types(schema) + schema = schema.dup + + schema['type'] = param_type_for_gemini(schema['type']) if schema.key?('type') + schema[:type] = param_type_for_gemini(schema[:type]) if schema.key?(:type) + + schema.transform_values { |schema_value| convert_schema_value(schema_value) } + end + + ## + # Convert schema values to Gemini API types + # @param schema_value [String|Array|Hash|Boolean|NilClass] + def convert_schema_value(schema_value) + if schema_value.is_a?(Hash) + convert_gemini_types(schema_value.to_h) + elsif schema_value.is_a?(Array) + schema_value.map { |v| convert_gemini_types(v) } + else + schema_value + end + end + # Convert RubyLLM param types to Gemini API types def param_type_for_gemini(type) case type.to_s.downcase diff --git a/lib/ruby_llm/providers/openai/tools.rb b/lib/ruby_llm/providers/openai/tools.rb index 4c81bb49..6377933c 100644 --- a/lib/ruby_llm/providers/openai/tools.rb +++ b/lib/ruby_llm/providers/openai/tools.rb @@ -15,20 +15,13 @@ def tool_for(tool) # rubocop:disable Metrics/MethodLength description: tool.description, parameters: { type: 'object', - properties: tool.parameters.transform_values { |param| param_schema(param) }, + properties: tool.parameters.transform_values(&:schema), required: tool.parameters.select { |_, p| p.required }.keys } } } end - def param_schema(param) - { - type: param.type, - description: param.description - }.compact - end - def format_tool_calls(tool_calls) # rubocop:disable Metrics/MethodLength return nil unless tool_calls&.any? diff --git a/lib/ruby_llm/schema.rb b/lib/ruby_llm/schema.rb new file mode 100644 index 00000000..30a69730 --- /dev/null +++ b/lib/ruby_llm/schema.rb @@ -0,0 +1,78 @@ +# frozen_string_literal: true + +module RubyLLM + ## + # Schema class for defining the structure of data objects. + # Wraps the #Hash class + # @see #Hash + class Schema + delegate_missing_to :@schema + + ## + # @param schema [Hash] + def initialize(schema = {}) + @schema = deep_transform_keys_in_object(schema.to_h.dup, &:to_sym) + end + + def [](key) + @schema[key.to_sym] + end + + def []=(key, new_value) + @schema[key.to_sym] = deep_transform_keys_in_object(new_value, &:to_sym) + end + + # Adds the new_value into the new_key key for every sub-schema that is of type: :object + # @param new_key [Symbol] The key to add to each object type. + # @param new_value [Boolean, String] The value to assign to the new key. + def add_to_each_object_type!(new_key, new_value) + add_to_each_object_type(new_key, new_value, @schema) + end + + # @return [Boolean] + def present? + @schema.present? && @schema[:type].present? + end + + private + + def add_to_each_object_type(new_key, new_value, schema) + return schema unless schema.is_a?(Hash) + + if schema[:type].to_s == :object.to_s + add_to_object_type(new_key, new_value, schema) + elsif schema[:type].to_s == :array.to_s && schema[:items] + schema[:items] = add_to_each_object_type(new_key, new_value, schema[:items]) + end + + schema + end + + def add_to_object_type(new_key, new_value, schema) + if schema[new_key.to_sym].nil? + schema[new_key.to_sym] = new_value.is_a?(Proc) ? new_value.call(schema) : new_value + end + + schema[:properties]&.transform_values! { |value| add_to_each_object_type(new_key, new_value, value) } + end + + ## + # Recursively transforms keys in a hash or array to symbols. + # Borrowed from ActiveSupport's Hash#deep_transform_keys + # @param object [Object] The object to transform. + # @param block [Proc] The block to apply to each key. + # @return [Object] The transformed object. + def deep_transform_keys_in_object(object, &block) + case object + when Hash + object.each_with_object({}) do |(key, value), result| + result[yield(key)] = deep_transform_keys_in_object(value, &block) + end + when Array + object.map { |e| deep_transform_keys_in_object(e, &block) } + else + object + end + end + end +end diff --git a/lib/ruby_llm/tool.rb b/lib/ruby_llm/tool.rb index fa28ed55..b5b05d62 100644 --- a/lib/ruby_llm/tool.rb +++ b/lib/ruby_llm/tool.rb @@ -3,15 +3,40 @@ module RubyLLM # Parameter definition for Tool methods. Specifies type constraints, # descriptions, and whether parameters are required. + # + # @!attribute name [r] + # @return [Symbol] + # @!attribute required [r] + # @return [Boolean] + # @!attribute schema [r] + # @return [RubyLLM::Schema] class Parameter - attr_reader :name, :type, :description, :required + attr_reader :name, :required, :schema - def initialize(name, type: 'string', desc: nil, required: true) + # If providing schema directly MAKE SURE TO USE STRING KEYS. + # Also note that under_scored keys are NOT automatically transformed to camelCase. + # @param name [Symbol] + # @param schema [Hash{String|Symbol => String|Array|Hash|Boolean|NilClass}, NilClass] + def initialize(name, required: true, **schema) @name = name - @type = type - @description = desc @required = required + + @schema = Schema.new(schema) + @schema[:description] ||= @schema.delete(:desc) if @schema.key?(:desc) + @schema[:type] ||= :string + end + + # @return [String] + def type + @schema[:type] end + + # @return [String, NilClass] + def description + @schema[:description] + end + + alias required? required end # Base class for creating tools that AI models can use. Provides a simple @@ -39,8 +64,24 @@ def description(text = nil) @description = text end - def param(name, **options) - parameters[name] = Parameter.new(name, **options) + # Define a parameter for the tool. + # Examples: + # ```ruby + # param :latitude, desc: "Latitude (e.g., 52.5200)" # Shorthand format + # + # param :longitude, type: 'number', description: "Longitude (e.g., 13.4050)", required: false # Longer format + # + # param :unit, type: :string, enum: %w[f c], description: "Temperature unit (e.g., celsius, fahrenheit)" + # + # param :location, type: :object, desc: "Country and city where weather is requested.", properties: { + # country: { type: :string, description: "Full name of the country." }, + # city: { type: :string, description: "Full name of the city." } + # } + # ``` + # @param name [Symbol] + # @param schema [Hash{String|Symbol => String|Numeric|Boolean|Hash|Array|NilClass}, NilClass] + def param(name, required: true, **schema) + parameters[name] = Parameter.new(name, required: required, **schema) end def parameters diff --git a/lib/ruby_llm/version.rb b/lib/ruby_llm/version.rb index e80bfb73..73b80c7e 100644 --- a/lib/ruby_llm/version.rb +++ b/lib/ruby_llm/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module RubyLLM - VERSION = '1.2.0' + VERSION = '1.3.0' end diff --git a/spec/fixtures/vcr_cassettes/chat_array_parameters_with_anthropic_claude-3-5-haiku-20241022_handles_array_parameters_with_object_items.yml b/spec/fixtures/vcr_cassettes/chat_array_parameters_with_anthropic_claude-3-5-haiku-20241022_handles_array_parameters_with_object_items.yml new file mode 100644 index 00000000..ee409b5f --- /dev/null +++ b/spec/fixtures/vcr_cassettes/chat_array_parameters_with_anthropic_claude-3-5-haiku-20241022_handles_array_parameters_with_object_items.yml @@ -0,0 +1,172 @@ +--- +http_interactions: +- request: + method: post + uri: https://api.anthropic.com/v1/messages + body: + encoding: UTF-8 + string: '{"model":"claude-3-5-haiku-20241022","messages":[{"role":"user","content":"Add + information about California (capital: Sacramento, pop: 39538223) and Texas + (capital: Austin, pop: 29145505). Make sure to return all the information + in the final output. "}],"temperature":0.7,"stream":false,"max_tokens":8192,"tools":[{"name":"state_manager","description":"Manages + US states information","input_schema":{"type":"object","properties":{"states":{"type":"array","description":"List + of states","items":{"name":{"type":"string","description":"The state name"},"capital":{"type":"string","description":"The + capital city"},"population":{"type":"number","description":"Population count"}}}},"required":["states"]}}]}' + headers: + User-Agent: + - Faraday v2.13.0 + X-Api-Key: + - "" + Anthropic-Version: + - '2023-06-01' + Content-Type: + - application/json + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + Date: + - Sat, 19 Apr 2025 20:16:11 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Anthropic-Ratelimit-Requests-Limit: + - '50' + Anthropic-Ratelimit-Requests-Remaining: + - '49' + Anthropic-Ratelimit-Requests-Reset: + - '2025-04-19T20:16:09Z' + Anthropic-Ratelimit-Input-Tokens-Limit: + - '50000' + Anthropic-Ratelimit-Input-Tokens-Remaining: + - '50000' + Anthropic-Ratelimit-Input-Tokens-Reset: + - '2025-04-19T20:16:09Z' + Anthropic-Ratelimit-Output-Tokens-Limit: + - '10000' + Anthropic-Ratelimit-Output-Tokens-Remaining: + - '10000' + Anthropic-Ratelimit-Output-Tokens-Reset: + - '2025-04-19T20:16:11Z' + Anthropic-Ratelimit-Tokens-Limit: + - '60000' + Anthropic-Ratelimit-Tokens-Remaining: + - '60000' + Anthropic-Ratelimit-Tokens-Reset: + - '2025-04-19T20:16:09Z' + Request-Id: + - "" + Anthropic-Organization-Id: + - 1e1d231c-90fe-4375-9772-cb9f1cf081f0 + Via: + - 1.1 google + Cf-Cache-Status: + - DYNAMIC + X-Robots-Tag: + - none + Server: + - cloudflare + Cf-Ray: + - "" + body: + encoding: ASCII-8BIT + string: '{"id":"msg_01HUHyBJf62tCMPECQyubQLX","type":"message","role":"assistant","model":"claude-3-5-haiku-20241022","content":[{"type":"text","text":"I''ll + help you add information about California and Texas using the state_manager + tool. I''ll input the states with their respective capitals and populations."},{"type":"tool_use","id":"toolu_01TtUtAd1BAtDzy2tXm3iU7M","name":"state_manager","input":{"states":[{"name":"California","capital":"Sacramento","population":39538223},{"name":"Texas","capital":"Austin","population":29145505}]}}],"stop_reason":"tool_use","stop_sequence":null,"usage":{"input_tokens":431,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"output_tokens":147}}' + recorded_at: Sat, 19 Apr 2025 20:16:11 GMT +- request: + method: post + uri: https://api.anthropic.com/v1/messages + body: + encoding: UTF-8 + string: '{"model":"claude-3-5-haiku-20241022","messages":[{"role":"user","content":"Add + information about California (capital: Sacramento, pop: 39538223) and Texas + (capital: Austin, pop: 29145505). Make sure to return all the information + in the final output. "},{"role":"assistant","content":[{"type":"text","text":"I''ll + help you add information about California and Texas using the state_manager + tool. I''ll input the states with their respective capitals and populations."},{"type":"tool_use","id":"toolu_01TtUtAd1BAtDzy2tXm3iU7M","name":"state_manager","input":{"states":[{"name":"California","capital":"Sacramento","population":39538223},{"name":"Texas","capital":"Austin","population":29145505}]}}]},{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_01TtUtAd1BAtDzy2tXm3iU7M","content":"California: + Capital is Sacramento (pop: 39538223)\nTexas: Capital is Austin (pop: 29145505)"}]}],"temperature":0.7,"stream":false,"max_tokens":8192,"tools":[{"name":"state_manager","description":"Manages + US states information","input_schema":{"type":"object","properties":{"states":{"type":"array","description":"List + of states","items":{"name":{"type":"string","description":"The state name"},"capital":{"type":"string","description":"The + capital city"},"population":{"type":"number","description":"Population count"}}}},"required":["states"]}}]}' + headers: + User-Agent: + - Faraday v2.13.0 + X-Api-Key: + - "" + Anthropic-Version: + - '2023-06-01' + Content-Type: + - application/json + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + Date: + - Sat, 19 Apr 2025 20:16:14 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Anthropic-Ratelimit-Requests-Limit: + - '50' + Anthropic-Ratelimit-Requests-Remaining: + - '49' + Anthropic-Ratelimit-Requests-Reset: + - '2025-04-19T20:16:12Z' + Anthropic-Ratelimit-Input-Tokens-Limit: + - '50000' + Anthropic-Ratelimit-Input-Tokens-Remaining: + - '50000' + Anthropic-Ratelimit-Input-Tokens-Reset: + - '2025-04-19T20:16:13Z' + Anthropic-Ratelimit-Output-Tokens-Limit: + - '10000' + Anthropic-Ratelimit-Output-Tokens-Remaining: + - '10000' + Anthropic-Ratelimit-Output-Tokens-Reset: + - '2025-04-19T20:16:14Z' + Anthropic-Ratelimit-Tokens-Limit: + - '60000' + Anthropic-Ratelimit-Tokens-Remaining: + - '60000' + Anthropic-Ratelimit-Tokens-Reset: + - '2025-04-19T20:16:13Z' + Request-Id: + - "" + Anthropic-Organization-Id: + - 1e1d231c-90fe-4375-9772-cb9f1cf081f0 + Via: + - 1.1 google + Cf-Cache-Status: + - DYNAMIC + X-Robots-Tag: + - none + Server: + - cloudflare + Cf-Ray: + - "" + body: + encoding: ASCII-8BIT + string: '{"id":"msg_017tba9nRK7xxyys3XaqEw4t","type":"message","role":"assistant","model":"claude-3-5-haiku-20241022","content":[{"type":"text","text":"I''ve + added the information for California and Texas to the state manager. The output + confirms the details:\n- California: \n - Capital: Sacramento\n - Population: + 39,538,223\n- Texas:\n - Capital: Austin\n - Population: 29,145,505\n\nIs + there anything else you would like me to do with this information?"}],"stop_reason":"end_turn","stop_sequence":null,"usage":{"input_tokens":592,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"output_tokens":82}}' + recorded_at: Sat, 19 Apr 2025 20:16:14 GMT +recorded_with: VCR 6.3.1 diff --git a/spec/fixtures/vcr_cassettes/chat_array_parameters_with_bedrock_anthropic_claude-3-5-haiku-20241022-v1_0_handles_array_parameters_with_object_items.yml b/spec/fixtures/vcr_cassettes/chat_array_parameters_with_bedrock_anthropic_claude-3-5-haiku-20241022-v1_0_handles_array_parameters_with_object_items.yml new file mode 100644 index 00000000..7ebb44a0 --- /dev/null +++ b/spec/fixtures/vcr_cassettes/chat_array_parameters_with_bedrock_anthropic_claude-3-5-haiku-20241022-v1_0_handles_array_parameters_with_object_items.yml @@ -0,0 +1,122 @@ +--- +http_interactions: +- request: + method: post + uri: https://bedrock-runtime..amazonaws.com/model/anthropic.claude-3-5-haiku-20241022-v1:0/invoke + body: + encoding: UTF-8 + string: '{"anthropic_version":"bedrock-2023-05-31","messages":[{"role":"user","content":"Add + information about California (capital: Sacramento, pop: 39538223) and Texas + (capital: Austin, pop: 29145505). Make sure to return all the information + in the final output. "}],"temperature":0.7,"max_tokens":4096,"tools":[{"name":"state_manager","description":"Manages + US states information","input_schema":{"type":"object","properties":{"states":{"type":"array","description":"List + of states","items":{"name":{"type":"string","description":"The state name"},"capital":{"type":"string","description":"The + capital city"},"population":{"type":"number","description":"Population count"}}}},"required":["states"]}}]}' + headers: + User-Agent: + - Faraday v2.13.0 + Host: + - bedrock-runtime..amazonaws.com + X-Amz-Date: + - 20250419T201614Z + X-Amz-Content-Sha256: + - d61790fcba80b917bafe76cbfd4f2406b9d68385801b25b37c545fd5156f6b0c + Authorization: + - AWS4-HMAC-SHA256 Credential=/20250419//bedrock/aws4_request, + SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature=d617544f0a90cac92b8e03d0fd7466f0dac2bd83bd7f1742c7f27e97d5cb022f + Content-Type: + - application/json + Accept: + - application/json + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + response: + status: + code: 200 + message: OK + headers: + Date: + - Sat, 19 Apr 2025 20:16:17 GMT + Content-Type: + - application/json + Content-Length: + - '625' + Connection: + - keep-alive + X-Amzn-Requestid: + - 95202f7f-24e0-4546-a62f-c39c4e375201 + X-Amzn-Bedrock-Invocation-Latency: + - '2419' + X-Amzn-Bedrock-Output-Token-Count: + - '135' + X-Amzn-Bedrock-Input-Token-Count: + - '431' + body: + encoding: UTF-8 + string: '{"id":"msg_bdrk_014gknxdkEYzPHkheSPqYFRr","type":"message","role":"assistant","model":"claude-3-5-haiku-20241022","content":[{"type":"text","text":"I''ll + help you add information about California and Texas using the state_manager + tool."},{"type":"tool_use","id":"toolu_bdrk_01HgKheMkpq6aDgtDDBSFw8q","name":"state_manager","input":{"states":[{"name":"California","capital":"Sacramento","population":39538223},{"name":"Texas","capital":"Austin","population":29145505}]}}],"stop_reason":"tool_use","stop_sequence":null,"usage":{"input_tokens":431,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"output_tokens":135}}' + recorded_at: Sat, 19 Apr 2025 20:16:17 GMT +- request: + method: post + uri: https://bedrock-runtime..amazonaws.com/model/anthropic.claude-3-5-haiku-20241022-v1:0/invoke + body: + encoding: UTF-8 + string: '{"anthropic_version":"bedrock-2023-05-31","messages":[{"role":"user","content":"Add + information about California (capital: Sacramento, pop: 39538223) and Texas + (capital: Austin, pop: 29145505). Make sure to return all the information + in the final output. "},{"role":"assistant","content":[{"type":"text","text":"I''ll + help you add information about California and Texas using the state_manager + tool."},{"type":"tool_use","id":"toolu_bdrk_01HgKheMkpq6aDgtDDBSFw8q","name":"state_manager","input":{"states":[{"name":"California","capital":"Sacramento","population":39538223},{"name":"Texas","capital":"Austin","population":29145505}]}}]},{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_bdrk_01HgKheMkpq6aDgtDDBSFw8q","content":"California: + Capital is Sacramento (pop: 39538223)\nTexas: Capital is Austin (pop: 29145505)"}]}],"temperature":0.7,"max_tokens":4096,"tools":[{"name":"state_manager","description":"Manages + US states information","input_schema":{"type":"object","properties":{"states":{"type":"array","description":"List + of states","items":{"name":{"type":"string","description":"The state name"},"capital":{"type":"string","description":"The + capital city"},"population":{"type":"number","description":"Population count"}}}},"required":["states"]}}]}' + headers: + User-Agent: + - Faraday v2.13.0 + Host: + - bedrock-runtime..amazonaws.com + X-Amz-Date: + - 20250419T201617Z + X-Amz-Content-Sha256: + - 4a218c7dcd646c7fe18e807bc6037736c2433a585b75fbf096b1a2b63bac7d20 + Authorization: + - AWS4-HMAC-SHA256 Credential=/20250419//bedrock/aws4_request, + SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature=1d7f6deced3ac82b56c9e78462e03b77a1e26f7e79e7a855daa71865b66e8ae4 + Content-Type: + - application/json + Accept: + - application/json + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + response: + status: + code: 200 + message: OK + headers: + Date: + - Sat, 19 Apr 2025 20:16:19 GMT + Content-Type: + - application/json + Content-Length: + - '572' + Connection: + - keep-alive + X-Amzn-Requestid: + - f6132a29-d1db-42dd-affb-d712468c42fb + X-Amzn-Bedrock-Invocation-Latency: + - '2311' + X-Amzn-Bedrock-Output-Token-Count: + - '85' + X-Amzn-Bedrock-Input-Token-Count: + - '580' + body: + encoding: UTF-8 + string: '{"id":"msg_bdrk_0166SUrZQSLjYE9dbQsgUpay","type":"message","role":"assistant","model":"claude-3-5-haiku-20241022","content":[{"type":"text","text":"I''ve + added the information for California and Texas to the state manager. The output + confirms the details you provided:\n- California: \n - Capital: Sacramento\n - + Population: 39,538,223\n- Texas:\n - Capital: Austin\n - Population: 29,145,505\n\nIs + there anything else you would like me to do with this state information?"}],"stop_reason":"end_turn","stop_sequence":null,"usage":{"input_tokens":580,"output_tokens":85}}' + recorded_at: Sat, 19 Apr 2025 20:16:19 GMT +recorded_with: VCR 6.3.1 diff --git a/spec/fixtures/vcr_cassettes/chat_array_parameters_with_deepseek_deepseek-chat_handles_array_parameters_with_object_items.yml b/spec/fixtures/vcr_cassettes/chat_array_parameters_with_deepseek_deepseek-chat_handles_array_parameters_with_object_items.yml new file mode 100644 index 00000000..1cdb341b --- /dev/null +++ b/spec/fixtures/vcr_cassettes/chat_array_parameters_with_deepseek_deepseek-chat_handles_array_parameters_with_object_items.yml @@ -0,0 +1,122 @@ +--- +http_interactions: +- request: + method: post + uri: https://api.deepseek.com/chat/completions + body: + encoding: UTF-8 + string: '{"model":"deepseek-chat","messages":[{"role":"user","content":"Add + information about California (capital: Sacramento, pop: 39538223) and Texas + (capital: Austin, pop: 29145505). Make sure to return all the information + in the final output. "}],"temperature":0.7,"stream":false,"tools":[{"type":"function","function":{"name":"state_manager","description":"Manages + US states information","parameters":{"type":"object","properties":{"states":{"type":"array","description":"List + of states","items":{"name":{"type":"string","description":"The state name"},"capital":{"type":"string","description":"The + capital city"},"population":{"type":"number","description":"Population count"}}}},"required":["states"]}}}],"tool_choice":"auto"}' + headers: + User-Agent: + - Faraday v2.13.0 + Authorization: + - Bearer + Content-Type: + - application/json + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + Date: + - Sat, 19 Apr 2025 20:16:20 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Vary: + - origin, access-control-request-method, access-control-request-headers + Access-Control-Allow-Credentials: + - 'true' + X-Ds-Trace-Id: + - c90c5ea31d4f0840e14db076340daac6 + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + X-Content-Type-Options: + - nosniff + Cf-Cache-Status: + - DYNAMIC + Set-Cookie: + - "" + Server: + - cloudflare + Cf-Ray: + - "" + body: + encoding: ASCII-8BIT + string: '{"id":"015c760d-2c9e-4cec-83bf-cd3d0d608807","object":"chat.completion","created":1745093779,"model":"deepseek-chat","choices":[{"index":0,"message":{"role":"assistant","content":"","tool_calls":[{"index":0,"id":"call_0_d9274728-663f-42ad-acc1-78cae2f79d58","type":"function","function":{"name":"state_manager","arguments":"{\"states\":[{\"name\":\"California\",\"capital\":\"Sacramento\",\"population\":39538223},{\"name\":\"Texas\",\"capital\":\"Austin\",\"population\":29145505}]}"}}]},"logprobs":null,"finish_reason":"tool_calls"}],"usage":{"prompt_tokens":226,"completion_tokens":47,"total_tokens":273,"prompt_tokens_details":{"cached_tokens":0},"prompt_cache_hit_tokens":0,"prompt_cache_miss_tokens":226},"system_fingerprint":"fp_3d5141a69a_prod0225"}' + recorded_at: Sat, 19 Apr 2025 20:16:26 GMT +- request: + method: post + uri: https://api.deepseek.com/chat/completions + body: + encoding: UTF-8 + string: '{"model":"deepseek-chat","messages":[{"role":"user","content":"Add + information about California (capital: Sacramento, pop: 39538223) and Texas + (capital: Austin, pop: 29145505). Make sure to return all the information + in the final output. "},{"role":"assistant","tool_calls":[{"id":"call_0_d9274728-663f-42ad-acc1-78cae2f79d58","type":"function","function":{"name":"state_manager","arguments":"{\"states\":[{\"name\":\"California\",\"capital\":\"Sacramento\",\"population\":39538223},{\"name\":\"Texas\",\"capital\":\"Austin\",\"population\":29145505}]}"}}]},{"role":"tool","content":"California: + Capital is Sacramento (pop: 39538223)\nTexas: Capital is Austin (pop: 29145505)","tool_call_id":"call_0_d9274728-663f-42ad-acc1-78cae2f79d58"}],"temperature":0.7,"stream":false,"tools":[{"type":"function","function":{"name":"state_manager","description":"Manages + US states information","parameters":{"type":"object","properties":{"states":{"type":"array","description":"List + of states","items":{"name":{"type":"string","description":"The state name"},"capital":{"type":"string","description":"The + capital city"},"population":{"type":"number","description":"Population count"}}}},"required":["states"]}}}],"tool_choice":"auto"}' + headers: + User-Agent: + - Faraday v2.13.0 + Authorization: + - Bearer + Content-Type: + - application/json + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + Date: + - Sat, 19 Apr 2025 20:16:27 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Vary: + - origin, access-control-request-method, access-control-request-headers + Access-Control-Allow-Credentials: + - 'true' + X-Ds-Trace-Id: + - f90a62b23012402d54df8a947c872d7c + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + X-Content-Type-Options: + - nosniff + Cf-Cache-Status: + - DYNAMIC + Set-Cookie: + - "" + Server: + - cloudflare + Cf-Ray: + - "" + body: + encoding: ASCII-8BIT + string: '{"id":"8d39c1fb-b4eb-4f49-a8b5-2223ca250096","object":"chat.completion","created":1745093786,"model":"deepseek-chat","choices":[{"index":0,"message":{"role":"assistant","content":"Here + is the information you requested:\n\n- **California**: Capital is Sacramento + (population: 39,538,223)\n- **Texas**: Capital is Austin (population: 29,145,505)"},"logprobs":null,"finish_reason":"stop"}],"usage":{"prompt_tokens":304,"completion_tokens":41,"total_tokens":345,"prompt_tokens_details":{"cached_tokens":256},"prompt_cache_hit_tokens":256,"prompt_cache_miss_tokens":48},"system_fingerprint":"fp_3d5141a69a_prod0225"}' + recorded_at: Sat, 19 Apr 2025 20:16:32 GMT +recorded_with: VCR 6.3.1 diff --git a/spec/fixtures/vcr_cassettes/chat_array_parameters_with_gemini_gemini-2_0-flash_handles_array_parameters_with_object_items.yml b/spec/fixtures/vcr_cassettes/chat_array_parameters_with_gemini_gemini-2_0-flash_handles_array_parameters_with_object_items.yml new file mode 100644 index 00000000..fcc70cba --- /dev/null +++ b/spec/fixtures/vcr_cassettes/chat_array_parameters_with_gemini_gemini-2_0-flash_handles_array_parameters_with_object_items.yml @@ -0,0 +1,198 @@ +--- +http_interactions: +- request: + method: post + uri: https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent + body: + encoding: UTF-8 + string: '{"contents":[{"role":"user","parts":[{"text":"Add information about + California (capital: Sacramento, pop: 39538223) and Texas (capital: Austin, + pop: 29145505). Make sure to return all the information in the final output. + "}]}],"generationConfig":{"temperature":0.7},"tools":[{"functionDeclarations":[{"name":"state_manager","description":"Manages + US states information","parameters":{"type":"OBJECT","properties":{"states":{"type":"ARRAY","description":"List + of states","items":{"type":"object","properties":{"name":{"type":"string","description":"The + state name"},"capital":{"type":"string","description":"The capital city"},"population":{"type":"number","description":"Population + count"}}}}},"required":["states"]}}]}]}' + headers: + User-Agent: + - Faraday v2.13.0 + X-Goog-Api-Key: + - "" + Content-Type: + - application/json + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + Content-Type: + - application/json; charset=UTF-8 + Vary: + - Origin + - Referer + - X-Origin + Date: + - Sat, 19 Apr 2025 20:29:42 GMT + Server: + - scaffolding on HTTPServer2 + X-Xss-Protection: + - '0' + X-Frame-Options: + - SAMEORIGIN + X-Content-Type-Options: + - nosniff + Server-Timing: + - gfet4t7; dur=807 + Alt-Svc: + - h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 + Transfer-Encoding: + - chunked + body: + encoding: ASCII-8BIT + string: | + { + "candidates": [ + { + "content": { + "parts": [ + { + "functionCall": { + "name": "state_manager", + "args": { + "states": [ + { + "capital": "Sacramento", + "population": 39538223, + "name": "California" + }, + { + "capital": "Austin", + "name": "Texas", + "population": 29145505 + } + ] + } + } + } + ], + "role": "model" + }, + "finishReason": "STOP", + "avgLogprobs": -0.00050646079970257621 + } + ], + "usageMetadata": { + "promptTokenCount": 77, + "candidatesTokenCount": 14, + "totalTokenCount": 91, + "promptTokensDetails": [ + { + "modality": "TEXT", + "tokenCount": 77 + } + ], + "candidatesTokensDetails": [ + { + "modality": "TEXT", + "tokenCount": 14 + } + ] + }, + "modelVersion": "gemini-2.0-flash" + } + recorded_at: Sat, 19 Apr 2025 20:29:42 GMT +- request: + method: post + uri: https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent + body: + encoding: UTF-8 + string: '{"contents":[{"role":"user","parts":[{"text":"Add information about + California (capital: Sacramento, pop: 39538223) and Texas (capital: Austin, + pop: 29145505). Make sure to return all the information in the final output. + "}]},{"role":"model","parts":[{"functionCall":{"name":"state_manager","args":{"states":[{"capital":"Sacramento","population":39538223,"name":"California"},{"capital":"Austin","name":"Texas","population":29145505}]}}}]},{"role":"user","parts":[{"functionResponse":{"name":"2eb7c64c-828e-4561-bf2b-0caf322f9ebc","response":{"name":"2eb7c64c-828e-4561-bf2b-0caf322f9ebc","content":"California: + Capital is Sacramento (pop: 39538223)\nTexas: Capital is Austin (pop: 29145505)"}}}]}],"generationConfig":{"temperature":0.7},"tools":[{"functionDeclarations":[{"name":"state_manager","description":"Manages + US states information","parameters":{"type":"OBJECT","properties":{"states":{"type":"ARRAY","description":"List + of states","items":{"type":"object","properties":{"name":{"type":"string","description":"The + state name"},"capital":{"type":"string","description":"The capital city"},"population":{"type":"number","description":"Population + count"}}}}},"required":["states"]}}]}]}' + headers: + User-Agent: + - Faraday v2.13.0 + X-Goog-Api-Key: + - "" + Content-Type: + - application/json + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + Content-Type: + - application/json; charset=UTF-8 + Vary: + - Origin + - Referer + - X-Origin + Date: + - Sat, 19 Apr 2025 20:29:42 GMT + Server: + - scaffolding on HTTPServer2 + X-Xss-Protection: + - '0' + X-Frame-Options: + - SAMEORIGIN + X-Content-Type-Options: + - nosniff + Server-Timing: + - gfet4t7; dur=646 + Alt-Svc: + - h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 + Transfer-Encoding: + - chunked + body: + encoding: ASCII-8BIT + string: | + { + "candidates": [ + { + "content": { + "parts": [ + { + "text": "California: Capital is Sacramento (pop: 39538223)\nTexas: Capital is Austin (pop: 29145505)\n" + } + ], + "role": "model" + }, + "finishReason": "STOP", + "avgLogprobs": -0.00013679095641954948 + } + ], + "usageMetadata": { + "promptTokenCount": 192, + "candidatesTokenCount": 38, + "totalTokenCount": 230, + "promptTokensDetails": [ + { + "modality": "TEXT", + "tokenCount": 192 + } + ], + "candidatesTokensDetails": [ + { + "modality": "TEXT", + "tokenCount": 38 + } + ] + }, + "modelVersion": "gemini-2.0-flash" + } + recorded_at: Sat, 19 Apr 2025 20:29:42 GMT +recorded_with: VCR 6.3.1 diff --git a/spec/fixtures/vcr_cassettes/chat_array_parameters_with_openai_gpt-4_1-nano_handles_array_parameters_with_object_items.yml b/spec/fixtures/vcr_cassettes/chat_array_parameters_with_openai_gpt-4_1-nano_handles_array_parameters_with_object_items.yml new file mode 100644 index 00000000..5d1c4e2a --- /dev/null +++ b/spec/fixtures/vcr_cassettes/chat_array_parameters_with_openai_gpt-4_1-nano_handles_array_parameters_with_object_items.yml @@ -0,0 +1,249 @@ +--- +http_interactions: +- request: + method: post + uri: https://api.openai.com/v1/chat/completions + body: + encoding: UTF-8 + string: '{"model":"gpt-4.1-nano","messages":[{"role":"user","content":"Add information + about California (capital: Sacramento, pop: 39538223) and Texas (capital: + Austin, pop: 29145505). Make sure to return all the information in the final + output. "}],"temperature":0.7,"stream":false,"tools":[{"type":"function","function":{"name":"state_manager","description":"Manages + US states information","parameters":{"type":"object","properties":{"states":{"type":"array","description":"List + of states","items":{"name":{"type":"string","description":"The state name"},"capital":{"type":"string","description":"The + capital city"},"population":{"type":"number","description":"Population count"}}}},"required":["states"]}}}],"tool_choice":"auto"}' + headers: + User-Agent: + - Faraday v2.13.0 + Authorization: + - Bearer + Content-Type: + - application/json + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + Date: + - Sat, 19 Apr 2025 20:16:32 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Access-Control-Expose-Headers: + - X-Request-ID + Openai-Organization: + - "" + Openai-Processing-Ms: + - '351' + Openai-Version: + - '2020-10-01' + X-Ratelimit-Limit-Requests: + - '30000' + X-Ratelimit-Limit-Tokens: + - '150000000' + X-Ratelimit-Remaining-Requests: + - '29999' + X-Ratelimit-Remaining-Tokens: + - '149999953' + X-Ratelimit-Reset-Requests: + - 2ms + X-Ratelimit-Reset-Tokens: + - 0s + X-Request-Id: + - "" + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + Cf-Cache-Status: + - DYNAMIC + Set-Cookie: + - "" + - "" + X-Content-Type-Options: + - nosniff + Server: + - cloudflare + Cf-Ray: + - "" + Alt-Svc: + - h3=":443"; ma=86400 + body: + encoding: ASCII-8BIT + string: | + { + "id": "chatcmpl-BO8u0BjKwkacpjBMv3Voo4gkPicU7", + "object": "chat.completion", + "created": 1745093792, + "model": "gpt-4.1-nano-2025-04-14", + "choices": [ + { + "index": 0, + "message": { + "role": "assistant", + "content": null, + "tool_calls": [ + { + "id": "call_qgm7FWk4nAD1088mfK1Y2V6Z", + "type": "function", + "function": { + "name": "state_manager", + "arguments": "{\"states\": [{\"name\": \"California\", \"capital\": \"Sacramento\", \"population\": 39538223}, {\"name\": \"Texas\", \"capital\": \"Austin\", \"population\": 29145505}]}" + } + }, + { + "id": "call_nE7Iusgl605qGA1YhG03HB2h", + "type": "function", + "function": { + "name": "state_manager", + "arguments": "{\"states\": []}" + } + } + ], + "refusal": null, + "annotations": [] + }, + "logprobs": null, + "finish_reason": "tool_calls" + } + ], + "usage": { + "prompt_tokens": 91, + "completion_tokens": 74, + "total_tokens": 165, + "prompt_tokens_details": { + "cached_tokens": 0, + "audio_tokens": 0 + }, + "completion_tokens_details": { + "reasoning_tokens": 0, + "audio_tokens": 0, + "accepted_prediction_tokens": 0, + "rejected_prediction_tokens": 0 + } + }, + "service_tier": "default", + "system_fingerprint": "fp_20621fa8f3" + } + recorded_at: Sat, 19 Apr 2025 20:16:32 GMT +- request: + method: post + uri: https://api.openai.com/v1/chat/completions + body: + encoding: UTF-8 + string: '{"model":"gpt-4.1-nano","messages":[{"role":"user","content":"Add information + about California (capital: Sacramento, pop: 39538223) and Texas (capital: + Austin, pop: 29145505). Make sure to return all the information in the final + output. "},{"role":"assistant","tool_calls":[{"id":"call_qgm7FWk4nAD1088mfK1Y2V6Z","type":"function","function":{"name":"state_manager","arguments":"{\"states\":[{\"name\":\"California\",\"capital\":\"Sacramento\",\"population\":39538223},{\"name\":\"Texas\",\"capital\":\"Austin\",\"population\":29145505}]}"}},{"id":"call_nE7Iusgl605qGA1YhG03HB2h","type":"function","function":{"name":"state_manager","arguments":"{\"states\":[]}"}}]},{"role":"tool","content":"California: + Capital is Sacramento (pop: 39538223)\nTexas: Capital is Austin (pop: 29145505)","tool_call_id":"call_qgm7FWk4nAD1088mfK1Y2V6Z"},{"role":"tool","content":"No + states provided","tool_call_id":"call_nE7Iusgl605qGA1YhG03HB2h"}],"temperature":0.7,"stream":false,"tools":[{"type":"function","function":{"name":"state_manager","description":"Manages + US states information","parameters":{"type":"object","properties":{"states":{"type":"array","description":"List + of states","items":{"name":{"type":"string","description":"The state name"},"capital":{"type":"string","description":"The + capital city"},"population":{"type":"number","description":"Population count"}}}},"required":["states"]}}}],"tool_choice":"auto"}' + headers: + User-Agent: + - Faraday v2.13.0 + Authorization: + - Bearer + Content-Type: + - application/json + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + Date: + - Sat, 19 Apr 2025 20:16:32 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Access-Control-Expose-Headers: + - X-Request-ID + Openai-Organization: + - "" + Openai-Processing-Ms: + - '292' + Openai-Version: + - '2020-10-01' + X-Ratelimit-Limit-Requests: + - '30000' + X-Ratelimit-Limit-Tokens: + - '150000000' + X-Ratelimit-Remaining-Requests: + - '29999' + X-Ratelimit-Remaining-Tokens: + - '149999924' + X-Ratelimit-Reset-Requests: + - 2ms + X-Ratelimit-Reset-Tokens: + - 0s + X-Request-Id: + - "" + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + Cf-Cache-Status: + - DYNAMIC + Set-Cookie: + - "" + - "" + X-Content-Type-Options: + - nosniff + Server: + - cloudflare + Cf-Ray: + - "" + Alt-Svc: + - h3=":443"; ma=86400 + body: + encoding: ASCII-8BIT + string: | + { + "id": "chatcmpl-BO8u0afKOxVRBem7FmHfCXE59Xcam", + "object": "chat.completion", + "created": 1745093792, + "model": "gpt-4.1-nano-2025-04-14", + "choices": [ + { + "index": 0, + "message": { + "role": "assistant", + "content": "California: Capital is Sacramento (pop: 39538223)\nTexas: Capital is Austin (pop: 29145505)", + "refusal": null, + "annotations": [] + }, + "logprobs": null, + "finish_reason": "stop" + } + ], + "usage": { + "prompt_tokens": 206, + "completion_tokens": 28, + "total_tokens": 234, + "prompt_tokens_details": { + "cached_tokens": 0, + "audio_tokens": 0 + }, + "completion_tokens_details": { + "reasoning_tokens": 0, + "audio_tokens": 0, + "accepted_prediction_tokens": 0, + "rejected_prediction_tokens": 0 + } + }, + "service_tier": "default", + "system_fingerprint": "fp_20621fa8f3" + } + recorded_at: Sat, 19 Apr 2025 20:16:32 GMT +recorded_with: VCR 6.3.1 diff --git a/spec/fixtures/vcr_cassettes/chat_nested_parameters_with_anthropic_claude-3-5-haiku-20241022_handles_nested_object_parameters.yml b/spec/fixtures/vcr_cassettes/chat_nested_parameters_with_anthropic_claude-3-5-haiku-20241022_handles_nested_object_parameters.yml new file mode 100644 index 00000000..7704183d --- /dev/null +++ b/spec/fixtures/vcr_cassettes/chat_nested_parameters_with_anthropic_claude-3-5-haiku-20241022_handles_nested_object_parameters.yml @@ -0,0 +1,171 @@ +--- +http_interactions: +- request: + method: post + uri: https://api.anthropic.com/v1/messages + body: + encoding: UTF-8 + string: '{"model":"claude-3-5-haiku-20241022","messages":[{"role":"user","content":"Add + John Doe to the address book at 123 Main St, Springfield 12345"}],"temperature":0.7,"stream":false,"max_tokens":8192,"tools":[{"name":"address_book","description":"Manages + address book entries","input_schema":{"type":"object","properties":{"contact":{"type":"object","description":"Contact + information","properties":{"name":{"type":"string","description":"Full name"},"address":{"type":"object","description":"Address + details","properties":{"street":{"type":"string","description":"Street address"},"city":{"type":"string","description":"City + name"},"zip":{"type":"string","description":"ZIP/Postal code"}}}}}},"required":["contact"]}}]}' + headers: + User-Agent: + - Faraday v2.13.0 + X-Api-Key: + - "" + Anthropic-Version: + - '2023-06-01' + Content-Type: + - application/json + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + Date: + - Sat, 19 Apr 2025 20:12:23 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Anthropic-Ratelimit-Requests-Limit: + - '50' + Anthropic-Ratelimit-Requests-Remaining: + - '49' + Anthropic-Ratelimit-Requests-Reset: + - '2025-04-19T20:12:22Z' + Anthropic-Ratelimit-Input-Tokens-Limit: + - '50000' + Anthropic-Ratelimit-Input-Tokens-Remaining: + - '50000' + Anthropic-Ratelimit-Input-Tokens-Reset: + - '2025-04-19T20:12:22Z' + Anthropic-Ratelimit-Output-Tokens-Limit: + - '10000' + Anthropic-Ratelimit-Output-Tokens-Remaining: + - '10000' + Anthropic-Ratelimit-Output-Tokens-Reset: + - '2025-04-19T20:12:23Z' + Anthropic-Ratelimit-Tokens-Limit: + - '60000' + Anthropic-Ratelimit-Tokens-Remaining: + - '60000' + Anthropic-Ratelimit-Tokens-Reset: + - '2025-04-19T20:12:22Z' + Request-Id: + - "" + Anthropic-Organization-Id: + - 1e1d231c-90fe-4375-9772-cb9f1cf081f0 + Via: + - 1.1 google + Cf-Cache-Status: + - DYNAMIC + X-Robots-Tag: + - none + Server: + - cloudflare + Cf-Ray: + - "" + body: + encoding: ASCII-8BIT + string: '{"id":"msg_01H4bBNC7UHEcP4pTima1KPA","type":"message","role":"assistant","model":"claude-3-5-haiku-20241022","content":[{"type":"text","text":"I''ll + help you add John Doe to the address book using the `address_book` function. + I''ll structure the contact information with the provided details."},{"type":"tool_use","id":"toolu_01UsthiZzNnu7UnyMGSm7Lfi","name":"address_book","input":{"contact":{"name":"John + Doe","address":{"street":"123 Main St","city":"Springfield","zip":"12345"}}}}],"stop_reason":"tool_use","stop_sequence":null,"usage":{"input_tokens":444,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"output_tokens":137}}' + recorded_at: Sat, 19 Apr 2025 20:12:23 GMT +- request: + method: post + uri: https://api.anthropic.com/v1/messages + body: + encoding: UTF-8 + string: '{"model":"claude-3-5-haiku-20241022","messages":[{"role":"user","content":"Add + John Doe to the address book at 123 Main St, Springfield 12345"},{"role":"assistant","content":[{"type":"text","text":"I''ll + help you add John Doe to the address book using the `address_book` function. + I''ll structure the contact information with the provided details."},{"type":"tool_use","id":"toolu_01UsthiZzNnu7UnyMGSm7Lfi","name":"address_book","input":{"contact":{"name":"John + Doe","address":{"street":"123 Main St","city":"Springfield","zip":"12345"}}}}]},{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_01UsthiZzNnu7UnyMGSm7Lfi","content":"Completed + contact: John Doe at 123 Main St, Springfield 12345"}]}],"temperature":0.7,"stream":false,"max_tokens":8192,"tools":[{"name":"address_book","description":"Manages + address book entries","input_schema":{"type":"object","properties":{"contact":{"type":"object","description":"Contact + information","properties":{"name":{"type":"string","description":"Full name"},"address":{"type":"object","description":"Address + details","properties":{"street":{"type":"string","description":"Street address"},"city":{"type":"string","description":"City + name"},"zip":{"type":"string","description":"ZIP/Postal code"}}}}}},"required":["contact"]}}]}' + headers: + User-Agent: + - Faraday v2.13.0 + X-Api-Key: + - "" + Anthropic-Version: + - '2023-06-01' + Content-Type: + - application/json + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + Date: + - Sat, 19 Apr 2025 20:12:27 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Anthropic-Ratelimit-Requests-Limit: + - '50' + Anthropic-Ratelimit-Requests-Remaining: + - '49' + Anthropic-Ratelimit-Requests-Reset: + - '2025-04-19T20:12:25Z' + Anthropic-Ratelimit-Input-Tokens-Limit: + - '50000' + Anthropic-Ratelimit-Input-Tokens-Remaining: + - '50000' + Anthropic-Ratelimit-Input-Tokens-Reset: + - '2025-04-19T20:12:26Z' + Anthropic-Ratelimit-Output-Tokens-Limit: + - '10000' + Anthropic-Ratelimit-Output-Tokens-Remaining: + - '10000' + Anthropic-Ratelimit-Output-Tokens-Reset: + - '2025-04-19T20:12:27Z' + Anthropic-Ratelimit-Tokens-Limit: + - '60000' + Anthropic-Ratelimit-Tokens-Remaining: + - '60000' + Anthropic-Ratelimit-Tokens-Reset: + - '2025-04-19T20:12:26Z' + Request-Id: + - "" + Anthropic-Organization-Id: + - 1e1d231c-90fe-4375-9772-cb9f1cf081f0 + Via: + - 1.1 google + Cf-Cache-Status: + - DYNAMIC + X-Robots-Tag: + - none + Server: + - cloudflare + Cf-Ray: + - "" + body: + encoding: ASCII-8BIT + string: '{"id":"msg_017FjbqneUCMw1nizqpPpjhC","type":"message","role":"assistant","model":"claude-3-5-haiku-20241022","content":[{"type":"text","text":"I''ve + added John Doe to the address book with the following details:\n- Name: John + Doe\n- Street: 123 Main St\n- City: Springfield\n- ZIP: 12345\n\nThe contact + has been successfully saved. Is there anything else I can help you with?"}],"stop_reason":"end_turn","stop_sequence":null,"usage":{"input_tokens":591,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"output_tokens":65}}' + recorded_at: Sat, 19 Apr 2025 20:12:27 GMT +recorded_with: VCR 6.3.1 diff --git a/spec/fixtures/vcr_cassettes/chat_nested_parameters_with_bedrock_anthropic_claude-3-5-haiku-20241022-v1_0_handles_nested_object_parameters.yml b/spec/fixtures/vcr_cassettes/chat_nested_parameters_with_bedrock_anthropic_claude-3-5-haiku-20241022-v1_0_handles_nested_object_parameters.yml new file mode 100644 index 00000000..a3dfa46c --- /dev/null +++ b/spec/fixtures/vcr_cassettes/chat_nested_parameters_with_bedrock_anthropic_claude-3-5-haiku-20241022-v1_0_handles_nested_object_parameters.yml @@ -0,0 +1,122 @@ +--- +http_interactions: +- request: + method: post + uri: https://bedrock-runtime..amazonaws.com/model/anthropic.claude-3-5-haiku-20241022-v1:0/invoke + body: + encoding: UTF-8 + string: '{"anthropic_version":"bedrock-2023-05-31","messages":[{"role":"user","content":"Add + John Doe to the address book at 123 Main St, Springfield 12345"}],"temperature":0.7,"max_tokens":4096,"tools":[{"name":"address_book","description":"Manages + address book entries","input_schema":{"type":"object","properties":{"contact":{"type":"object","description":"Contact + information","properties":{"name":{"type":"string","description":"Full name"},"address":{"type":"object","description":"Address + details","properties":{"street":{"type":"string","description":"Street address"},"city":{"type":"string","description":"City + name"},"zip":{"type":"string","description":"ZIP/Postal code"}}}}}},"required":["contact"]}}]}' + headers: + User-Agent: + - Faraday v2.13.0 + Host: + - bedrock-runtime..amazonaws.com + X-Amz-Date: + - 20250419T201227Z + X-Amz-Content-Sha256: + - 20bc836937097802a104c9ae3c86ad4f5f74750c68d26050182b4720a23eba7b + Authorization: + - AWS4-HMAC-SHA256 Credential=/20250419//bedrock/aws4_request, + SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature=de7f1fce08e5a9754778278dfc2317f45383ae5e9e9cdf877d5b082cdf439ff9 + Content-Type: + - application/json + Accept: + - application/json + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + response: + status: + code: 200 + message: OK + headers: + Date: + - Sat, 19 Apr 2025 20:12:29 GMT + Content-Type: + - application/json + Content-Length: + - '657' + Connection: + - keep-alive + X-Amzn-Requestid: + - 0de0dbdf-b5ca-48ed-81b7-f34bf085d86b + X-Amzn-Bedrock-Invocation-Latency: + - '2068' + X-Amzn-Bedrock-Output-Token-Count: + - '139' + X-Amzn-Bedrock-Input-Token-Count: + - '444' + body: + encoding: UTF-8 + string: '{"id":"msg_bdrk_01HwSrJPBMJYRApsWGbiGUcD","type":"message","role":"assistant","model":"claude-3-5-haiku-20241022","content":[{"type":"text","text":"I''ll + help you add John Doe to the address book using the `address_book` function. + I''ll structure the contact information based on the details you provided."},{"type":"tool_use","id":"toolu_bdrk_016MywFt1RxzLgcrcZCnmENY","name":"address_book","input":{"contact":{"name":"John + Doe","address":{"street":"123 Main St","city":"Springfield","zip":"12345"}}}}],"stop_reason":"tool_use","stop_sequence":null,"usage":{"input_tokens":444,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"output_tokens":139}}' + recorded_at: Sat, 19 Apr 2025 20:12:29 GMT +- request: + method: post + uri: https://bedrock-runtime..amazonaws.com/model/anthropic.claude-3-5-haiku-20241022-v1:0/invoke + body: + encoding: UTF-8 + string: '{"anthropic_version":"bedrock-2023-05-31","messages":[{"role":"user","content":"Add + John Doe to the address book at 123 Main St, Springfield 12345"},{"role":"assistant","content":[{"type":"text","text":"I''ll + help you add John Doe to the address book using the `address_book` function. + I''ll structure the contact information based on the details you provided."},{"type":"tool_use","id":"toolu_bdrk_016MywFt1RxzLgcrcZCnmENY","name":"address_book","input":{"contact":{"name":"John + Doe","address":{"street":"123 Main St","city":"Springfield","zip":"12345"}}}}]},{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_bdrk_016MywFt1RxzLgcrcZCnmENY","content":"Completed + contact: John Doe at 123 Main St, Springfield 12345"}]}],"temperature":0.7,"max_tokens":4096,"tools":[{"name":"address_book","description":"Manages + address book entries","input_schema":{"type":"object","properties":{"contact":{"type":"object","description":"Contact + information","properties":{"name":{"type":"string","description":"Full name"},"address":{"type":"object","description":"Address + details","properties":{"street":{"type":"string","description":"Street address"},"city":{"type":"string","description":"City + name"},"zip":{"type":"string","description":"ZIP/Postal code"}}}}}},"required":["contact"]}}]}' + headers: + User-Agent: + - Faraday v2.13.0 + Host: + - bedrock-runtime..amazonaws.com + X-Amz-Date: + - 20250419T201229Z + X-Amz-Content-Sha256: + - 4f98aedabb6a934b5efe0a1fc33f4653f103ece7b914445b233352af3ad08331 + Authorization: + - AWS4-HMAC-SHA256 Credential=/20250419//bedrock/aws4_request, + SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature=35a41a216ee1568291c33cbd3d7ad9638cca643b69cdc862a09490b1ba83d08d + Content-Type: + - application/json + Accept: + - application/json + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + response: + status: + code: 200 + message: OK + headers: + Date: + - Sat, 19 Apr 2025 20:12:31 GMT + Content-Type: + - application/json + Content-Length: + - '482' + Connection: + - keep-alive + X-Amzn-Requestid: + - ea5241c1-304c-4530-b509-4e3640047f8c + X-Amzn-Bedrock-Invocation-Latency: + - '2015' + X-Amzn-Bedrock-Output-Token-Count: + - '66' + X-Amzn-Bedrock-Input-Token-Count: + - '593' + body: + encoding: UTF-8 + string: '{"id":"msg_bdrk_011ETNUFxkf5C7KZ5pLE8VtM","type":"message","role":"assistant","model":"claude-3-5-haiku-20241022","content":[{"type":"text","text":"I''ve + added John Doe to the address book with the following details:\n- Name: John + Doe\n- Street: 123 Main St\n- City: Springfield\n- ZIP Code: 12345\n\nThe + contact has been successfully saved. Is there anything else I can help you + with?"}],"stop_reason":"end_turn","stop_sequence":null,"usage":{"input_tokens":593,"output_tokens":66}}' + recorded_at: Sat, 19 Apr 2025 20:12:31 GMT +recorded_with: VCR 6.3.1 diff --git a/spec/fixtures/vcr_cassettes/chat_nested_parameters_with_deepseek_deepseek-chat_handles_nested_object_parameters.yml b/spec/fixtures/vcr_cassettes/chat_nested_parameters_with_deepseek_deepseek-chat_handles_nested_object_parameters.yml new file mode 100644 index 00000000..646653ed --- /dev/null +++ b/spec/fixtures/vcr_cassettes/chat_nested_parameters_with_deepseek_deepseek-chat_handles_nested_object_parameters.yml @@ -0,0 +1,122 @@ +--- +http_interactions: +- request: + method: post + uri: https://api.deepseek.com/chat/completions + body: + encoding: UTF-8 + string: '{"model":"deepseek-chat","messages":[{"role":"user","content":"Add + John Doe to the address book at 123 Main St, Springfield 12345"}],"temperature":0.7,"stream":false,"tools":[{"type":"function","function":{"name":"address_book","description":"Manages + address book entries","parameters":{"type":"object","properties":{"contact":{"type":"object","description":"Contact + information","properties":{"name":{"type":"string","description":"Full name"},"address":{"type":"object","description":"Address + details","properties":{"street":{"type":"string","description":"Street address"},"city":{"type":"string","description":"City + name"},"zip":{"type":"string","description":"ZIP/Postal code"}}}}}},"required":["contact"]}}}],"tool_choice":"auto"}' + headers: + User-Agent: + - Faraday v2.13.0 + Authorization: + - Bearer + Content-Type: + - application/json + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + Date: + - Sat, 19 Apr 2025 20:12:33 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Vary: + - origin, access-control-request-method, access-control-request-headers + Access-Control-Allow-Credentials: + - 'true' + X-Ds-Trace-Id: + - 283170d60537d2595a01025fcd7f9e9a + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + X-Content-Type-Options: + - nosniff + Cf-Cache-Status: + - DYNAMIC + Set-Cookie: + - "" + Server: + - cloudflare + Cf-Ray: + - "" + body: + encoding: ASCII-8BIT + string: '{"id":"8cfbb05a-4b59-4d97-965d-79622cf6d903","object":"chat.completion","created":1745093553,"model":"deepseek-chat","choices":[{"index":0,"message":{"role":"assistant","content":"","tool_calls":[{"index":0,"id":"call_0_3238e74f-5d38-4a16-be63-6d62b6854230","type":"function","function":{"name":"address_book","arguments":"{\"contact\":{\"name\":\"John + Doe\",\"address\":{\"street\":\"123 Main St\",\"city\":\"Springfield\",\"zip\":\"12345\"}}}"}}]},"logprobs":null,"finish_reason":"tool_calls"}],"usage":{"prompt_tokens":252,"completion_tokens":43,"total_tokens":295,"prompt_tokens_details":{"cached_tokens":192},"prompt_cache_hit_tokens":192,"prompt_cache_miss_tokens":60},"system_fingerprint":"fp_3d5141a69a_prod0225"}' + recorded_at: Sat, 19 Apr 2025 20:12:39 GMT +- request: + method: post + uri: https://api.deepseek.com/chat/completions + body: + encoding: UTF-8 + string: '{"model":"deepseek-chat","messages":[{"role":"user","content":"Add + John Doe to the address book at 123 Main St, Springfield 12345"},{"role":"assistant","tool_calls":[{"id":"call_0_3238e74f-5d38-4a16-be63-6d62b6854230","type":"function","function":{"name":"address_book","arguments":"{\"contact\":{\"name\":\"John + Doe\",\"address\":{\"street\":\"123 Main St\",\"city\":\"Springfield\",\"zip\":\"12345\"}}}"}}]},{"role":"tool","content":"Completed + contact: John Doe at 123 Main St, Springfield 12345","tool_call_id":"call_0_3238e74f-5d38-4a16-be63-6d62b6854230"}],"temperature":0.7,"stream":false,"tools":[{"type":"function","function":{"name":"address_book","description":"Manages + address book entries","parameters":{"type":"object","properties":{"contact":{"type":"object","description":"Contact + information","properties":{"name":{"type":"string","description":"Full name"},"address":{"type":"object","description":"Address + details","properties":{"street":{"type":"string","description":"Street address"},"city":{"type":"string","description":"City + name"},"zip":{"type":"string","description":"ZIP/Postal code"}}}}}},"required":["contact"]}}}],"tool_choice":"auto"}' + headers: + User-Agent: + - Faraday v2.13.0 + Authorization: + - Bearer + Content-Type: + - application/json + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + Date: + - Sat, 19 Apr 2025 20:12:40 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Vary: + - origin, access-control-request-method, access-control-request-headers + Access-Control-Allow-Credentials: + - 'true' + X-Ds-Trace-Id: + - d8562146b8d18e8233f704249399c64b + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + X-Content-Type-Options: + - nosniff + Cf-Cache-Status: + - DYNAMIC + Set-Cookie: + - "" + Server: + - cloudflare + Cf-Ray: + - "" + body: + encoding: ASCII-8BIT + string: '{"id":"5100a489-c8e1-424c-a3aa-4a6214e854d7","object":"chat.completion","created":1745093559,"model":"deepseek-chat","choices":[{"index":0,"message":{"role":"assistant","content":"John + Doe has been successfully added to the address book with the address: 123 + Main St, Springfield, 12345."},"logprobs":null,"finish_reason":"stop"}],"usage":{"prompt_tokens":313,"completion_tokens":25,"total_tokens":338,"prompt_tokens_details":{"cached_tokens":256},"prompt_cache_hit_tokens":256,"prompt_cache_miss_tokens":57},"system_fingerprint":"fp_3d5141a69a_prod0225"}' + recorded_at: Sat, 19 Apr 2025 20:12:44 GMT +recorded_with: VCR 6.3.1 diff --git a/spec/fixtures/vcr_cassettes/chat_nested_parameters_with_gemini_gemini-2_0-flash_handles_nested_object_parameters.yml b/spec/fixtures/vcr_cassettes/chat_nested_parameters_with_gemini_gemini-2_0-flash_handles_nested_object_parameters.yml new file mode 100644 index 00000000..b28a4121 --- /dev/null +++ b/spec/fixtures/vcr_cassettes/chat_nested_parameters_with_gemini_gemini-2_0-flash_handles_nested_object_parameters.yml @@ -0,0 +1,191 @@ +--- +http_interactions: +- request: + method: post + uri: https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent + body: + encoding: UTF-8 + string: '{"contents":[{"role":"user","parts":[{"text":"Add John Doe to the address + book at 123 Main St, Springfield 12345"}]}],"generationConfig":{"temperature":0.7},"tools":[{"functionDeclarations":[{"name":"address_book","description":"Manages + address book entries","parameters":{"type":"OBJECT","properties":{"contact":{"type":"OBJECT","description":"Contact + information","properties":{"name":{"type":"string","description":"Full name"},"address":{"type":"object","description":"Address + details","properties":{"street":{"type":"string","description":"Street address"},"city":{"type":"string","description":"City + name"},"zip":{"type":"string","description":"ZIP/Postal code"}}}}}},"required":["contact"]}}]}]}' + headers: + User-Agent: + - Faraday v2.13.0 + X-Goog-Api-Key: + - "" + Content-Type: + - application/json + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + Content-Type: + - application/json; charset=UTF-8 + Vary: + - Origin + - Referer + - X-Origin + Date: + - Sat, 19 Apr 2025 20:29:46 GMT + Server: + - scaffolding on HTTPServer2 + X-Xss-Protection: + - '0' + X-Frame-Options: + - SAMEORIGIN + X-Content-Type-Options: + - nosniff + Server-Timing: + - gfet4t7; dur=628 + Alt-Svc: + - h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 + Transfer-Encoding: + - chunked + body: + encoding: ASCII-8BIT + string: | + { + "candidates": [ + { + "content": { + "parts": [ + { + "functionCall": { + "name": "address_book", + "args": { + "contact": { + "address": { + "street": "123 Main St", + "zip": "12345", + "city": "Springfield" + }, + "name": "John Doe" + } + } + } + } + ], + "role": "model" + }, + "finishReason": "STOP", + "avgLogprobs": -2.3544138953597709e-05 + } + ], + "usageMetadata": { + "promptTokenCount": 51, + "candidatesTokenCount": 22, + "totalTokenCount": 73, + "promptTokensDetails": [ + { + "modality": "TEXT", + "tokenCount": 51 + } + ], + "candidatesTokensDetails": [ + { + "modality": "TEXT", + "tokenCount": 22 + } + ] + }, + "modelVersion": "gemini-2.0-flash" + } + recorded_at: Sat, 19 Apr 2025 20:29:46 GMT +- request: + method: post + uri: https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent + body: + encoding: UTF-8 + string: '{"contents":[{"role":"user","parts":[{"text":"Add John Doe to the address + book at 123 Main St, Springfield 12345"}]},{"role":"model","parts":[{"functionCall":{"name":"address_book","args":{"contact":{"address":{"street":"123 + Main St","zip":"12345","city":"Springfield"},"name":"John Doe"}}}}]},{"role":"user","parts":[{"functionResponse":{"name":"23736de4-17ac-4b9c-856a-d84a93283c1a","response":{"name":"23736de4-17ac-4b9c-856a-d84a93283c1a","content":"Completed + contact: John Doe at 123 Main St, Springfield 12345"}}}]}],"generationConfig":{"temperature":0.7},"tools":[{"functionDeclarations":[{"name":"address_book","description":"Manages + address book entries","parameters":{"type":"OBJECT","properties":{"contact":{"type":"OBJECT","description":"Contact + information","properties":{"name":{"type":"string","description":"Full name"},"address":{"type":"object","description":"Address + details","properties":{"street":{"type":"string","description":"Street address"},"city":{"type":"string","description":"City + name"},"zip":{"type":"string","description":"ZIP/Postal code"}}}}}},"required":["contact"]}}]}]}' + headers: + User-Agent: + - Faraday v2.13.0 + X-Goog-Api-Key: + - "" + Content-Type: + - application/json + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + Content-Type: + - application/json; charset=UTF-8 + Vary: + - Origin + - Referer + - X-Origin + Date: + - Sat, 19 Apr 2025 20:29:47 GMT + Server: + - scaffolding on HTTPServer2 + X-Xss-Protection: + - '0' + X-Frame-Options: + - SAMEORIGIN + X-Content-Type-Options: + - nosniff + Server-Timing: + - gfet4t7; dur=511 + Alt-Svc: + - h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 + Transfer-Encoding: + - chunked + body: + encoding: ASCII-8BIT + string: | + { + "candidates": [ + { + "content": { + "parts": [ + { + "text": "OK. I've added John Doe to the address book at 123 Main St, Springfield 12345.\n" + } + ], + "role": "model" + }, + "finishReason": "STOP", + "avgLogprobs": -2.1892696105200669e-05 + } + ], + "usageMetadata": { + "promptTokenCount": 163, + "candidatesTokenCount": 29, + "totalTokenCount": 192, + "promptTokensDetails": [ + { + "modality": "TEXT", + "tokenCount": 163 + } + ], + "candidatesTokensDetails": [ + { + "modality": "TEXT", + "tokenCount": 29 + } + ] + }, + "modelVersion": "gemini-2.0-flash" + } + recorded_at: Sat, 19 Apr 2025 20:29:47 GMT +recorded_with: VCR 6.3.1 diff --git a/spec/fixtures/vcr_cassettes/chat_nested_parameters_with_openai_gpt-4_1-nano_handles_nested_object_parameters.yml b/spec/fixtures/vcr_cassettes/chat_nested_parameters_with_openai_gpt-4_1-nano_handles_nested_object_parameters.yml new file mode 100644 index 00000000..e0d84538 --- /dev/null +++ b/spec/fixtures/vcr_cassettes/chat_nested_parameters_with_openai_gpt-4_1-nano_handles_nested_object_parameters.yml @@ -0,0 +1,239 @@ +--- +http_interactions: +- request: + method: post + uri: https://api.openai.com/v1/chat/completions + body: + encoding: UTF-8 + string: '{"model":"gpt-4.1-nano","messages":[{"role":"user","content":"Add John + Doe to the address book at 123 Main St, Springfield 12345"}],"temperature":0.7,"stream":false,"tools":[{"type":"function","function":{"name":"address_book","description":"Manages + address book entries","parameters":{"type":"object","properties":{"contact":{"type":"object","description":"Contact + information","properties":{"name":{"type":"string","description":"Full name"},"address":{"type":"object","description":"Address + details","properties":{"street":{"type":"string","description":"Street address"},"city":{"type":"string","description":"City + name"},"zip":{"type":"string","description":"ZIP/Postal code"}}}}}},"required":["contact"]}}}],"tool_choice":"auto"}' + headers: + User-Agent: + - Faraday v2.13.0 + Authorization: + - Bearer + Content-Type: + - application/json + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + Date: + - Sat, 19 Apr 2025 20:12:45 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Access-Control-Expose-Headers: + - X-Request-ID + Openai-Organization: + - "" + Openai-Processing-Ms: + - '366' + Openai-Version: + - '2020-10-01' + X-Ratelimit-Limit-Requests: + - '30000' + X-Ratelimit-Limit-Tokens: + - '150000000' + X-Ratelimit-Remaining-Requests: + - '29999' + X-Ratelimit-Remaining-Tokens: + - '149999981' + X-Ratelimit-Reset-Requests: + - 2ms + X-Ratelimit-Reset-Tokens: + - 0s + X-Request-Id: + - "" + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + Cf-Cache-Status: + - DYNAMIC + Set-Cookie: + - "" + - "" + X-Content-Type-Options: + - nosniff + Server: + - cloudflare + Cf-Ray: + - "" + Alt-Svc: + - h3=":443"; ma=86400 + body: + encoding: ASCII-8BIT + string: | + { + "id": "chatcmpl-BO8qLo6pKWul0XYwXKWZB0YoFD2wT", + "object": "chat.completion", + "created": 1745093565, + "model": "gpt-4.1-nano-2025-04-14", + "choices": [ + { + "index": 0, + "message": { + "role": "assistant", + "content": null, + "tool_calls": [ + { + "id": "call_35mkh5qMxFu9PINPOyaM5E7i", + "type": "function", + "function": { + "name": "address_book", + "arguments": "{\"contact\": {\"name\": \"John Doe\", \"address\": {\"street\": \"123 Main St\", \"city\": \"Springfield\", \"zip\": \"12345\"}}}" + } + } + ], + "refusal": null, + "annotations": [] + }, + "logprobs": null, + "finish_reason": "tool_calls" + } + ], + "usage": { + "prompt_tokens": 101, + "completion_tokens": 53, + "total_tokens": 154, + "prompt_tokens_details": { + "cached_tokens": 0, + "audio_tokens": 0 + }, + "completion_tokens_details": { + "reasoning_tokens": 0, + "audio_tokens": 0, + "accepted_prediction_tokens": 0, + "rejected_prediction_tokens": 0 + } + }, + "service_tier": "default", + "system_fingerprint": "fp_c1fb89028d" + } + recorded_at: Sat, 19 Apr 2025 20:12:45 GMT +- request: + method: post + uri: https://api.openai.com/v1/chat/completions + body: + encoding: UTF-8 + string: '{"model":"gpt-4.1-nano","messages":[{"role":"user","content":"Add John + Doe to the address book at 123 Main St, Springfield 12345"},{"role":"assistant","tool_calls":[{"id":"call_35mkh5qMxFu9PINPOyaM5E7i","type":"function","function":{"name":"address_book","arguments":"{\"contact\":{\"name\":\"John + Doe\",\"address\":{\"street\":\"123 Main St\",\"city\":\"Springfield\",\"zip\":\"12345\"}}}"}}]},{"role":"tool","content":"Completed + contact: John Doe at 123 Main St, Springfield 12345","tool_call_id":"call_35mkh5qMxFu9PINPOyaM5E7i"}],"temperature":0.7,"stream":false,"tools":[{"type":"function","function":{"name":"address_book","description":"Manages + address book entries","parameters":{"type":"object","properties":{"contact":{"type":"object","description":"Contact + information","properties":{"name":{"type":"string","description":"Full name"},"address":{"type":"object","description":"Address + details","properties":{"street":{"type":"string","description":"Street address"},"city":{"type":"string","description":"City + name"},"zip":{"type":"string","description":"ZIP/Postal code"}}}}}},"required":["contact"]}}}],"tool_choice":"auto"}' + headers: + User-Agent: + - Faraday v2.13.0 + Authorization: + - Bearer + Content-Type: + - application/json + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + Date: + - Sat, 19 Apr 2025 20:12:46 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Access-Control-Expose-Headers: + - X-Request-ID + Openai-Organization: + - "" + Openai-Processing-Ms: + - '170' + Openai-Version: + - '2020-10-01' + X-Ratelimit-Limit-Requests: + - '30000' + X-Ratelimit-Limit-Tokens: + - '150000000' + X-Ratelimit-Remaining-Requests: + - '29999' + X-Ratelimit-Remaining-Tokens: + - '149999963' + X-Ratelimit-Reset-Requests: + - 2ms + X-Ratelimit-Reset-Tokens: + - 0s + X-Request-Id: + - "" + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + Cf-Cache-Status: + - DYNAMIC + Set-Cookie: + - "" + - "" + X-Content-Type-Options: + - nosniff + Server: + - cloudflare + Cf-Ray: + - "" + Alt-Svc: + - h3=":443"; ma=86400 + body: + encoding: ASCII-8BIT + string: | + { + "id": "chatcmpl-BO8qLybU62pY82ZnWQeXIp18OITYf", + "object": "chat.completion", + "created": 1745093565, + "model": "gpt-4.1-nano-2025-04-14", + "choices": [ + { + "index": 0, + "message": { + "role": "assistant", + "content": "John Doe has been added to the address book at 123 Main St, Springfield 12345.", + "refusal": null, + "annotations": [] + }, + "logprobs": null, + "finish_reason": "stop" + } + ], + "usage": { + "prompt_tokens": 160, + "completion_tokens": 22, + "total_tokens": 182, + "prompt_tokens_details": { + "cached_tokens": 0, + "audio_tokens": 0 + }, + "completion_tokens_details": { + "reasoning_tokens": 0, + "audio_tokens": 0, + "accepted_prediction_tokens": 0, + "rejected_prediction_tokens": 0 + } + }, + "service_tier": "default", + "system_fingerprint": "fp_c1fb89028d" + } + recorded_at: Sat, 19 Apr 2025 20:12:45 GMT +recorded_with: VCR 6.3.1 diff --git a/spec/ruby_llm/chat_tools_spec.rb b/spec/ruby_llm/chat_tools_spec.rb index 1d4c0aed..8d132570 100644 --- a/spec/ruby_llm/chat_tools_spec.rb +++ b/spec/ruby_llm/chat_tools_spec.rb @@ -31,6 +31,75 @@ def execute end end + class AddressBook < RubyLLM::Tool # rubocop:disable Lint/ConstantDefinitionInBlock,RSpec/LeakyConstantDeclaration + description 'Manages address book entries' + + param :contact, + type: 'object', + description: 'Contact information', + properties: { + name: { + type: 'string', + description: 'Full name' + }, + address: { + type: 'object', + description: 'Address details', + properties: { + street: { + type: 'string', + description: 'Street address' + }, + city: { + type: 'string', + description: 'City name' + }, + zip: { + type: 'string', + description: 'ZIP/Postal code' + } + } + } + } + + def execute(contact:) + address = contact['address'] + "Completed contact: #{contact['name']} at #{address['street']}, " \ + "#{address['city']} #{address['zip']}" + end + end + + class StateManager < RubyLLM::Tool # rubocop:disable Lint/ConstantDefinitionInBlock,RSpec/LeakyConstantDeclaration + description 'Manages US states information' + + param :states, + type: 'array', + description: 'List of states', + items: { + type: 'object', + properties: { + name: { + type: 'string', + description: 'The state name' + }, + capital: { + type: 'string', + description: 'The capital city' + }, + population: { + type: 'number', + description: 'Population count' + } + } + } + + def execute(states:) + return 'No states provided' if states.empty? + + states.map { |s| "#{s['name']}: Capital is #{s['capital']} (pop: #{s['population']})" }.join("\n") + end + end + describe 'function calling' do CHAT_MODELS.each do |model_info| model = model_info[:model] @@ -131,6 +200,46 @@ def execute end end + describe 'nested parameters' do + chat_models.each do |model| + provider = RubyLLM::Models.provider_for(model).slug + + context "with #{provider}/#{model}" do + let(:chat) { RubyLLM.chat(model: model).with_tool(AddressBook) } + + it 'handles nested object parameters', :aggregate_failures do + prompt = 'Add John Doe to the address book at 123 Main St, Springfield 12345' + response = chat.ask(prompt) + + expect(response.content).to include('John Doe', '123 Main St', + 'Springfield', '12345') + end + end + end + end + + describe 'array parameters' do + chat_models.each do |model| + provider = RubyLLM::Models.provider_for(model).slug + + context "with #{provider}/#{model}" do + let(:chat) { RubyLLM.chat(model: model).with_tool(StateManager) } + let(:prompt) do + 'Add information about California (capital: Sacramento, ' \ + 'pop: 39538223) and Texas (capital: Austin, pop: 29145505). ' \ + 'Make sure to return all the information in the final output. ' + end + + it 'handles array parameters with object items', :aggregate_failures do + response = chat.ask(prompt) + + expect(response.content).to include('Sacramento', 'Austin') + expect(response.content).to match(/39538223|39,538,223/).and(match(/29145505|29,145,505/)) + end + end + end + end + describe 'error handling' do it 'raises an error when tool execution fails' do # rubocop:disable RSpec/MultipleExpectations chat = RubyLLM.chat.with_tool(BrokenTool) diff --git a/spec/ruby_llm/schema_spec.rb b/spec/ruby_llm/schema_spec.rb new file mode 100644 index 00000000..71f56ce3 --- /dev/null +++ b/spec/ruby_llm/schema_spec.rb @@ -0,0 +1,75 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe RubyLLM::Schema do + it 'deeply stringifies keys in a hash automatically' do + hash = { foo: 'bar', bar: { foo: 'bar' } } + schema = described_class.new(hash) + expect(schema.to_h).to eq(foo: 'bar', bar: { foo: 'bar' }) + end + + describe '#[]' do + let(:schema) { described_class.new('foo' => { 'bar' => 'foo' }, 'arr' => [{ 'some' => :val }]) } + + it 'deeply symbolizes keys in hash' do + expect(schema[:foo][:bar]).to eq('foo') + end + + it 'deeply symbolizes keys in array' do + expect(schema[:arr][0][:some]).to eq(:val) + end + end + + describe '#[]=' do + let(:schema) { described_class.new('foo' => {}) } + + it 'sets schema values with symbol keys in nested objects' do + schema[:foo] = { 'bar' => 123 } + expect(schema[:foo][:bar]).to eq(123) + end + end + + describe '#add_to_each_object_type!' do + before { schema.add_to_each_object_type!(:additionalProperties, true) } + + context 'with root data object' do + let(:schema) { described_class.new(type: :object, properties: { name: { type: :string } }) } + + it 'sets schema values with indifferent key vs symbols' do + expect(schema[:additionalProperties]).to be(true) + end + end + + context 'with nested data object' do + let(:schema) do + described_class.new(type: :object, + properties: { address: { type: :object, + properties: { city: { type: :string } } } }) + end + + it 'sets schema values with indifferent key vs symbols' do + expect(schema[:properties][:address][:additionalProperties]).to be(true) + end + end + + context 'with array item objects' do + let(:schema) do + described_class.new(type: :object, + properties: { + coordinates: { + type: :array, + items: { + type: :object, + properties: { lat: { type: :number }, lon: { type: :number } } + } + } + }) + end + + it 'sets schema values with indifferent key vs symbols' do + expect(schema[:properties][:coordinates][:items][:additionalProperties]).to be(true) + end + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 0fa83e3c..f9bf24fe 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -32,7 +32,8 @@ # Don't record new HTTP interactions when running in CI config.default_cassette_options = { - record: ENV['CI'] ? :none : :new_episodes + record: ENV['CI'] ? :none : :new_episodes, + record_on_error: false } # Create new cassette directory if it doesn't exist @@ -100,7 +101,7 @@ config.bedrock_api_key = ENV.fetch('AWS_ACCESS_KEY_ID', 'test') config.bedrock_secret_key = ENV.fetch('AWS_SECRET_ACCESS_KEY', 'test') - config.bedrock_region = 'us-west-2' + config.bedrock_region = ENV.fetch('AWS_REGION', 'us-west-2') config.bedrock_session_token = ENV.fetch('AWS_SESSION_TOKEN', nil) config.max_retries = 10