diff --git a/docs/_core_features/tools.md b/docs/_core_features/tools.md index 46e0297d7..fcffd4b02 100644 --- a/docs/_core_features/tools.md +++ b/docs/_core_features/tools.md @@ -188,6 +188,38 @@ puts response.content # => "Current weather at 52.52, 13.4: Temperature: 12.5°C, Wind Speed: 8.3 km/h, Conditions: Mainly clear, partly cloudy, and overcast." ``` +### Tool Choice Control + +Control when and how tools are called using `choice` and `parallel` options. + +```ruby +chat = RubyLLM.chat(model: 'gpt-4o') + +# Basic usage with defaults +chat.with_tools(Weather, Calculator) # uses provider defaults + +# Force tool usage, one at a time +chat.with_tools(Weather, Calculator, choice: :required, parallel: false) + +# Force specific tool +chat.with_tool(Weather, choice: :weather, parallel: true) +``` + +**Parameter Values:** +- **`choice`**: Controls tool choice behavior + - `:auto` Model decides whether to use any tools + - `:required` - Model must use one of the provided tools + - `:none` - Disable all tools + - `"tool_name"` - Force a specific tool (e.g., `:weather` for `Weather` tool) +- **`parallel`**: Controls parallel tool calls + - `true` Allow multiple tool calls simultaneously + - `false` - One at a time + +If not provided, RubyLLM will use the provider's default behavior for tool choice and parallel tool calls. + +> With `:required` or specific tool choices, the tool_choice is automatically reset to `nil` after tool execution to prevent infinite loops. +{: .note } + ### Model Compatibility RubyLLM will attempt to use tools with any model. If the model doesn't support function calling, the provider will return an appropriate error when you call `ask`. diff --git a/lib/ruby_llm/chat.rb b/lib/ruby_llm/chat.rb index 6b5b94daa..ad6f0b6b6 100644 --- a/lib/ruby_llm/chat.rb +++ b/lib/ruby_llm/chat.rb @@ -5,7 +5,7 @@ module RubyLLM class Chat include Enumerable - attr_reader :model, :messages, :tools, :params, :headers, :schema + attr_reader :model, :messages, :tools, :tool_prefs, :params, :headers, :schema def initialize(model: nil, provider: nil, assume_model_exists: false, context: nil) if assume_model_exists && !provider @@ -19,6 +19,7 @@ def initialize(model: nil, provider: nil, assume_model_exists: false, context: n @temperature = nil @messages = [] @tools = {} + @tool_prefs = { choice: nil, parallel: nil } @params = {} @headers = {} @schema = nil @@ -44,15 +45,19 @@ def with_instructions(instructions, replace: false) self end - def with_tool(tool) - tool_instance = tool.is_a?(Class) ? tool.new : tool - @tools[tool_instance.name.to_sym] = tool_instance + def with_tool(tool, choice: nil, parallel: nil) + unless tool.nil? + tool_instance = tool.is_a?(Class) ? tool.new : tool + @tools[tool_instance.name.to_sym] = tool_instance + end + update_tool_options(choice:, parallel:) self end - def with_tools(*tools, replace: false) + def with_tools(*tools, replace: false, choice: nil, parallel: nil) @tools.clear if replace tools.compact.each { |tool| with_tool tool } + update_tool_options(choice:, parallel:) self end @@ -125,6 +130,7 @@ def complete(&) # rubocop:disable Metrics/PerceivedComplexity response = @provider.complete( messages, tools: @tools, + tool_prefs: @tool_prefs, temperature: @temperature, model: @model, params: @params, @@ -200,6 +206,7 @@ def handle_tool_calls(response, &) # rubocop:disable Metrics/PerceivedComplexity halt_result = result if result.is_a?(Tool::Halt) end + reset_tool_choice if forced_tool_choice? halt_result || complete(&) end @@ -208,5 +215,27 @@ def execute_tool(tool_call) args = tool_call.arguments tool.call(args) end + + def update_tool_options(choice:, parallel:) + unless choice.nil? + valid_tool_choices = %i[auto none required] + tools.keys + unless valid_tool_choices.include?(choice.to_sym) + raise InvalidToolChoiceError, + "Invalid tool choice: #{choice}. Valid choices are: #{valid_tool_choices.join(', ')}" + end + + @tool_prefs[:choice] = choice.to_sym + end + + @tool_prefs[:parallel] = !!parallel unless parallel.nil? + end + + def forced_tool_choice? + @tool_prefs[:choice] && !%i[auto none].include?(@tool_prefs[:choice]) + end + + def reset_tool_choice + @tool_prefs[:choice] = nil + end end end diff --git a/lib/ruby_llm/error.rb b/lib/ruby_llm/error.rb index 3908bee27..92c96120b 100644 --- a/lib/ruby_llm/error.rb +++ b/lib/ruby_llm/error.rb @@ -15,6 +15,7 @@ def initialize(response = nil, message = nil) # Error classes for non-HTTP errors class ConfigurationError < StandardError; end class InvalidRoleError < StandardError; end + class InvalidToolChoiceError < StandardError; end class ModelNotFoundError < StandardError; end class UnsupportedAttachmentError < StandardError; end diff --git a/lib/ruby_llm/provider.rb b/lib/ruby_llm/provider.rb index f3344e57d..d62a82817 100644 --- a/lib/ruby_llm/provider.rb +++ b/lib/ruby_llm/provider.rb @@ -37,13 +37,16 @@ def configuration_requirements self.class.configuration_requirements end - def complete(messages, tools:, temperature:, model:, params: {}, headers: {}, schema: nil, &) # rubocop:disable Metrics/ParameterLists + # rubocop:disable Metrics/ParameterLists + def complete(messages, tools:, temperature:, model:, params: {}, headers: {}, schema: nil, + tool_prefs: nil, &) normalized_temperature = maybe_normalize_temperature(temperature, model) payload = Utils.deep_merge( render_payload( messages, tools: tools, + tool_prefs: tool_prefs, temperature: normalized_temperature, model: model, stream: block_given?, @@ -58,6 +61,7 @@ def complete(messages, tools:, temperature:, model:, params: {}, headers: {}, sc sync_response @connection, payload, headers end end + # rubocop:enable Metrics/ParameterLists def list_models response = @connection.get models_url diff --git a/lib/ruby_llm/providers/anthropic/capabilities.rb b/lib/ruby_llm/providers/anthropic/capabilities.rb index 710b55386..fdf86924c 100644 --- a/lib/ruby_llm/providers/anthropic/capabilities.rb +++ b/lib/ruby_llm/providers/anthropic/capabilities.rb @@ -34,6 +34,14 @@ def supports_functions?(model_id) model_id.match?(/claude-3/) end + def supports_tool_choice?(_model_id) + true + end + + def supports_tool_parallel_control?(_model_id) + true + end + def supports_json_mode?(model_id) model_id.match?(/claude-3/) end diff --git a/lib/ruby_llm/providers/anthropic/chat.rb b/lib/ruby_llm/providers/anthropic/chat.rb index 85630b0c1..e08e3e7d8 100644 --- a/lib/ruby_llm/providers/anthropic/chat.rb +++ b/lib/ruby_llm/providers/anthropic/chat.rb @@ -11,14 +11,17 @@ def completion_url '/v1/messages' end - def render_payload(messages, tools:, temperature:, model:, stream: false, schema: nil) # rubocop:disable Metrics/ParameterLists,Lint/UnusedMethodArgument + # rubocop:disable Metrics/ParameterLists,Lint/UnusedMethodArgument + def render_payload(messages, tools:, tool_prefs:, + temperature:, model:, stream: false, schema: nil) system_messages, chat_messages = separate_messages(messages) system_content = build_system_content(system_messages) build_base_payload(chat_messages, model, stream).tap do |payload| - add_optional_fields(payload, system_content:, tools:, temperature:) + add_optional_fields(payload, system_content:, tools:, tool_prefs:, temperature:) end end + # rubocop:enable Metrics/ParameterLists,Lint/UnusedMethodArgument def separate_messages(messages) messages.partition { |msg| msg.role == :system } @@ -44,8 +47,12 @@ def build_base_payload(chat_messages, model, stream) } end - def add_optional_fields(payload, system_content:, tools:, temperature:) - payload[:tools] = tools.values.map { |t| Tools.function_for(t) } if tools.any? + def add_optional_fields(payload, system_content:, tools:, tool_prefs:, temperature:) + if tools.any? + payload[:tools] = tools.values.map { |t| Tools.function_for(t) } + payload[:tool_choice] = Tools.build_tool_choice(tool_prefs) unless tool_prefs[:choice].nil? + end + payload[:system] = system_content unless system_content.empty? payload[:temperature] = temperature unless temperature.nil? end diff --git a/lib/ruby_llm/providers/anthropic/tools.rb b/lib/ruby_llm/providers/anthropic/tools.rb index 08b9b1725..c8949c6f4 100644 --- a/lib/ruby_llm/providers/anthropic/tools.rb +++ b/lib/ruby_llm/providers/anthropic/tools.rb @@ -101,6 +101,25 @@ def clean_parameters(parameters) def required_parameters(parameters) parameters.select { |_, param| param.required }.keys end + + def build_tool_choice(tool_prefs) + tool_choice = tool_prefs[:choice] + parallel_tool_calls = tool_prefs[:parallel] + + { + type: case tool_choice + when :auto, :none + tool_choice + when :required + :any + else + :tool + end + }.tap do |tc| + tc[:name] = tool_choice if tc[:type] == :tool + tc[:disable_parallel_tool_use] = !parallel_tool_calls unless tc[:type] == :none || parallel_tool_calls.nil? + end + end end end end diff --git a/lib/ruby_llm/providers/bedrock/capabilities.rb b/lib/ruby_llm/providers/bedrock/capabilities.rb index 94b8e1438..757f02be2 100644 --- a/lib/ruby_llm/providers/bedrock/capabilities.rb +++ b/lib/ruby_llm/providers/bedrock/capabilities.rb @@ -46,6 +46,14 @@ def supports_functions?(model_id) model_id.match?(/anthropic\.claude/) end + def supports_tool_choice?(model_id) + model_id.match?(/anthropic\.claude/) + end + + def supports_tool_parallel_control?(_model_id) + false + end + def supports_audio?(_model_id) false end diff --git a/lib/ruby_llm/providers/bedrock/chat.rb b/lib/ruby_llm/providers/bedrock/chat.rb index 4abdae3f3..c90544e41 100644 --- a/lib/ruby_llm/providers/bedrock/chat.rb +++ b/lib/ruby_llm/providers/bedrock/chat.rb @@ -39,16 +39,20 @@ def completion_url "model/#{@model_id}/invoke" end - def render_payload(messages, tools:, temperature:, model:, stream: false, schema: nil) # rubocop:disable Lint/UnusedMethodArgument,Metrics/ParameterLists + # rubocop:disable Metrics/ParameterLists,Lint/UnusedMethodArgument + def render_payload(messages, tools:, tool_prefs:, temperature:, model:, stream: false, + schema: nil) @model_id = model.id system_messages, chat_messages = Anthropic::Chat.separate_messages(messages) system_content = Anthropic::Chat.build_system_content(system_messages) build_base_payload(chat_messages, model).tap do |payload| - Anthropic::Chat.add_optional_fields(payload, system_content:, tools:, temperature:) + Anthropic::Chat.add_optional_fields(payload, system_content:, tools:, tool_prefs:, + temperature:) end end + # rubocop:enable Metrics/ParameterLists,Lint/UnusedMethodArgument def build_base_payload(chat_messages, model) { diff --git a/lib/ruby_llm/providers/deepseek/capabilities.rb b/lib/ruby_llm/providers/deepseek/capabilities.rb index 975f0df9a..8a3bad737 100644 --- a/lib/ruby_llm/providers/deepseek/capabilities.rb +++ b/lib/ruby_llm/providers/deepseek/capabilities.rb @@ -41,6 +41,14 @@ def supports_functions?(model_id) model_id.match?(/deepseek-chat/) end + def supports_tool_choice?(_model_id) + true + end + + def supports_tool_parallel_control?(_model_id) + false + end + def supports_json_mode?(_model_id) false end diff --git a/lib/ruby_llm/providers/gemini/capabilities.rb b/lib/ruby_llm/providers/gemini/capabilities.rb index 41b046d9e..528ae9e2f 100644 --- a/lib/ruby_llm/providers/gemini/capabilities.rb +++ b/lib/ruby_llm/providers/gemini/capabilities.rb @@ -62,6 +62,14 @@ def supports_functions?(model_id) model_id.match?(/gemini|pro|flash/) end + def supports_tool_choice?(_model_id) + true + end + + def supports_tool_parallel_control?(_model_id) + false + end + def supports_json_mode?(model_id) if model_id.match?(/text-embedding|embedding-001|aqa|imagen|gemini-2\.0-flash-lite|gemini-2\.5-pro-exp-03-25/) return false diff --git a/lib/ruby_llm/providers/gemini/chat.rb b/lib/ruby_llm/providers/gemini/chat.rb index c504b08a2..90685949c 100644 --- a/lib/ruby_llm/providers/gemini/chat.rb +++ b/lib/ruby_llm/providers/gemini/chat.rb @@ -11,7 +11,8 @@ def completion_url "models/#{@model}:generateContent" end - def render_payload(messages, tools:, temperature:, model:, stream: false, schema: nil) # rubocop:disable Metrics/ParameterLists,Lint/UnusedMethodArgument + # rubocop:disable Metrics/ParameterLists,Lint/UnusedMethodArgument + def render_payload(messages, tools:, tool_prefs:, temperature:, model:, stream: false, schema: nil) @model = model.id payload = { contents: format_messages(messages), @@ -25,9 +26,15 @@ def render_payload(messages, tools:, temperature:, model:, stream: false, schema payload[:generationConfig][:responseSchema] = convert_schema_to_gemini(schema) end - payload[:tools] = format_tools(tools) if tools.any? + if tools.any? + payload[:tools] = format_tools(tools) + # Gemini doesn't support controlling parallel tool calls + payload[:toolConfig] = build_tool_config(tool_prefs[:choice]) unless tool_prefs[:choice].nil? + end + payload end + # rubocop:enable Metrics/ParameterLists,Lint/UnusedMethodArgument private diff --git a/lib/ruby_llm/providers/gemini/tools.rb b/lib/ruby_llm/providers/gemini/tools.rb index bef6bfa35..9fbc77029 100644 --- a/lib/ruby_llm/providers/gemini/tools.rb +++ b/lib/ruby_llm/providers/gemini/tools.rb @@ -71,6 +71,25 @@ def param_type_for_gemini(type) else 'STRING' end end + + def build_tool_config(tool_choice) + { + functionCallingConfig: { + mode: forced_tool_choice?(tool_choice) ? 'any' : tool_choice + }.tap do |config| + # Use allowedFunctionNames to simulate specific tool choice + config[:allowedFunctionNames] = [tool_choice] if specific_tool_choice?(tool_choice) + end + } + end + + def forced_tool_choice?(tool_choice) + tool_choice == :required || specific_tool_choice?(tool_choice) + end + + def specific_tool_choice?(tool_choice) + !%i[auto none required].include?(tool_choice) + end end end end diff --git a/lib/ruby_llm/providers/gpustack.rb b/lib/ruby_llm/providers/gpustack.rb index fed40ba99..c91d1a06f 100644 --- a/lib/ruby_llm/providers/gpustack.rb +++ b/lib/ruby_llm/providers/gpustack.rb @@ -28,6 +28,10 @@ def local? def configuration_requirements %i[gpustack_api_base] end + + def capabilities + GPUStack::Capabilities + end end end end diff --git a/lib/ruby_llm/providers/gpustack/capabilities.rb b/lib/ruby_llm/providers/gpustack/capabilities.rb new file mode 100644 index 000000000..ad6cb4a28 --- /dev/null +++ b/lib/ruby_llm/providers/gpustack/capabilities.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +module RubyLLM + module Providers + class GPUStack + # Determines capabilities for GPUStack models + module Capabilities + module_function + + def supports_tool_choice?(_model_id) + false + end + + def supports_tool_parallel_control?(_model_id) + false + end + end + end + end +end diff --git a/lib/ruby_llm/providers/mistral/capabilities.rb b/lib/ruby_llm/providers/mistral/capabilities.rb index 85616a95a..26a21edc7 100644 --- a/lib/ruby_llm/providers/mistral/capabilities.rb +++ b/lib/ruby_llm/providers/mistral/capabilities.rb @@ -15,6 +15,14 @@ def supports_tools?(model_id) !model_id.match?(/embed|moderation|ocr|voxtral|transcriptions|mistral-(tiny|small)-(2312|2402)/) end + def supports_tool_choice?(_model_id) + true + end + + def supports_tool_parallel_control?(_model_id) + true + end + def supports_vision?(model_id) model_id.match?(/pixtral|mistral-small-(2503|2506)|mistral-medium/) end diff --git a/lib/ruby_llm/providers/mistral/chat.rb b/lib/ruby_llm/providers/mistral/chat.rb index 10ec965e4..a48eeaadc 100644 --- a/lib/ruby_llm/providers/mistral/chat.rb +++ b/lib/ruby_llm/providers/mistral/chat.rb @@ -12,7 +12,8 @@ def format_role(role) end # rubocop:disable Metrics/ParameterLists - def render_payload(messages, tools:, temperature:, model:, stream: false, schema: nil) + def render_payload(messages, tools:, tool_prefs:, temperature:, model:, stream: false, + schema: nil) payload = super payload.delete(:stream_options) payload diff --git a/lib/ruby_llm/providers/ollama.rb b/lib/ruby_llm/providers/ollama.rb index ea20c4953..2c4182291 100644 --- a/lib/ruby_llm/providers/ollama.rb +++ b/lib/ruby_llm/providers/ollama.rb @@ -24,6 +24,10 @@ def configuration_requirements def local? true end + + def capabilities + Ollama::Capabilities + end end end end diff --git a/lib/ruby_llm/providers/ollama/capabilities.rb b/lib/ruby_llm/providers/ollama/capabilities.rb new file mode 100644 index 000000000..225181db7 --- /dev/null +++ b/lib/ruby_llm/providers/ollama/capabilities.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +module RubyLLM + module Providers + class Ollama + # Determines capabilities for Ollama models + module Capabilities + module_function + + def supports_tool_choice?(_model_id) + false + end + + def supports_tool_parallel_control?(_model_id) + false + end + end + end + end +end diff --git a/lib/ruby_llm/providers/openai/capabilities.rb b/lib/ruby_llm/providers/openai/capabilities.rb index 37c320da8..cbfd57cca 100644 --- a/lib/ruby_llm/providers/openai/capabilities.rb +++ b/lib/ruby_llm/providers/openai/capabilities.rb @@ -97,6 +97,14 @@ def supports_functions?(model_id) end end + def supports_tool_choice?(_model_id) + true + end + + def supports_tool_parallel_control?(_model_id) + true + end + def supports_structured_output?(model_id) case model_family(model_id) when 'gpt5', 'gpt5_mini', 'gpt5_nano', 'gpt41', 'gpt41_mini', 'gpt41_nano', 'chatgpt4o', 'gpt4o', diff --git a/lib/ruby_llm/providers/openai/chat.rb b/lib/ruby_llm/providers/openai/chat.rb index e6d58ec05..c22e85457 100644 --- a/lib/ruby_llm/providers/openai/chat.rb +++ b/lib/ruby_llm/providers/openai/chat.rb @@ -11,7 +11,8 @@ def completion_url module_function - def render_payload(messages, tools:, temperature:, model:, stream: false, schema: nil) # rubocop:disable Metrics/ParameterLists + # rubocop:disable Metrics/ParameterLists + def render_payload(messages, tools:, tool_prefs:, temperature:, model:, stream: false, schema: nil) payload = { model: model.id, messages: format_messages(messages), @@ -19,7 +20,12 @@ def render_payload(messages, tools:, temperature:, model:, stream: false, schema } payload[:temperature] = temperature unless temperature.nil? - payload[:tools] = tools.map { |_, tool| tool_for(tool) } if tools.any? + + if tools.any? + payload[:tools] = tools.map { |_, tool| tool_for(tool) } + payload[:tool_choice] = build_tool_choice(tool_prefs[:choice]) unless tool_prefs[:choice].nil? + payload[:parallel_tool_calls] = tool_prefs[:parallel] unless tool_prefs[:parallel].nil? + end if schema strict = schema[:strict] != false @@ -37,6 +43,7 @@ def render_payload(messages, tools:, temperature:, model:, stream: false, schema payload[:stream_options] = { include_usage: true } if stream payload end + # rubocop:enable Metrics/ParameterLists def parse_completion_response(response) data = response.body diff --git a/lib/ruby_llm/providers/openai/tools.rb b/lib/ruby_llm/providers/openai/tools.rb index 94bb97429..d13752cc3 100644 --- a/lib/ruby_llm/providers/openai/tools.rb +++ b/lib/ruby_llm/providers/openai/tools.rb @@ -72,6 +72,20 @@ def parse_tool_calls(tool_calls, parse_arguments: true) ] end end + + def build_tool_choice(tool_choice) + case tool_choice + when :auto, :none, :required + tool_choice + else + { + type: 'function', + function: { + name: tool_choice + } + } + end + end end end end diff --git a/spec/fixtures/vcr_cassettes/chat_tool_choice_and_parallel_control_anthropic_claude-3-5-haiku-20241022_respects_choice_none.yml b/spec/fixtures/vcr_cassettes/chat_tool_choice_and_parallel_control_anthropic_claude-3-5-haiku-20241022_respects_choice_none.yml new file mode 100644 index 000000000..ea830aab3 --- /dev/null +++ b/spec/fixtures/vcr_cassettes/chat_tool_choice_and_parallel_control_anthropic_claude-3-5-haiku-20241022_respects_choice_none.yml @@ -0,0 +1,86 @@ +--- +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":[{"type":"text","text":"What''s + the weather in Berlin? (52.5200, 13.4050)"}]}],"stream":false,"max_tokens":8192,"tools":[{"name":"weather","description":"Gets + current weather for a location","input_schema":{"type":"object","properties":{"latitude":{"type":"string","description":"Latitude + (e.g., 52.5200)"},"longitude":{"type":"string","description":"Longitude (e.g., + 13.4050)"}},"required":["latitude","longitude"]}}],"tool_choice":{"type":"none"}}' + headers: + User-Agent: + - Faraday v2.13.4 + 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: + - Sun, 31 Aug 2025 01:12:54 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Anthropic-Ratelimit-Input-Tokens-Limit: + - '400000' + Anthropic-Ratelimit-Input-Tokens-Remaining: + - '400000' + Anthropic-Ratelimit-Input-Tokens-Reset: + - '2025-08-31T01:12:53Z' + Anthropic-Ratelimit-Output-Tokens-Limit: + - '80000' + Anthropic-Ratelimit-Output-Tokens-Remaining: + - '80000' + Anthropic-Ratelimit-Output-Tokens-Reset: + - '2025-08-31T01:12:54Z' + Anthropic-Ratelimit-Requests-Limit: + - '4000' + Anthropic-Ratelimit-Requests-Remaining: + - '3999' + Anthropic-Ratelimit-Requests-Reset: + - '2025-08-31T01:12:53Z' + Anthropic-Ratelimit-Tokens-Limit: + - '480000' + Anthropic-Ratelimit-Tokens-Remaining: + - '480000' + Anthropic-Ratelimit-Tokens-Reset: + - '2025-08-31T01:12:53Z' + Request-Id: + - "" + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + Anthropic-Organization-Id: + - "" + X-Envoy-Upstream-Service-Time: + - '934' + Via: + - 1.1 google + Cf-Cache-Status: + - DYNAMIC + X-Robots-Tag: + - none + Server: + - cloudflare + Cf-Ray: + - "" + body: + encoding: ASCII-8BIT + string: '{"id":"msg_01CeSiqgUDBk58hT5GPRDtZm","type":"message","role":"assistant","model":"claude-3-5-haiku-20241022","content":[{"type":"text","text":"Let + me check the current weather for Berlin using the provided coordinates."}],"stop_reason":"end_turn","stop_sequence":null,"usage":{"input_tokens":389,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":0},"output_tokens":22,"service_tier":"standard"}}' + recorded_at: Sun, 31 Aug 2025 01:12:54 GMT +recorded_with: VCR 6.3.1 diff --git a/spec/fixtures/vcr_cassettes/chat_tool_choice_and_parallel_control_anthropic_claude-3-5-haiku-20241022_respects_choice_required_for_unrelated_queries.yml b/spec/fixtures/vcr_cassettes/chat_tool_choice_and_parallel_control_anthropic_claude-3-5-haiku-20241022_respects_choice_required_for_unrelated_queries.yml new file mode 100644 index 000000000..0dad56001 --- /dev/null +++ b/spec/fixtures/vcr_cassettes/chat_tool_choice_and_parallel_control_anthropic_claude-3-5-haiku-20241022_respects_choice_required_for_unrelated_queries.yml @@ -0,0 +1,183 @@ +--- +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":[{"type":"text","text":"When + was the fall of Rome?"}]}],"stream":false,"max_tokens":8192,"tools":[{"name":"weather","description":"Gets + current weather for a location","input_schema":{"type":"object","properties":{"latitude":{"type":"string","description":"Latitude + (e.g., 52.5200)"},"longitude":{"type":"string","description":"Longitude (e.g., + 13.4050)"}},"required":["latitude","longitude"]}}],"tool_choice":{"type":"any"}}' + headers: + User-Agent: + - Faraday v2.13.4 + 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: + - Sun, 31 Aug 2025 01:12:55 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Anthropic-Ratelimit-Input-Tokens-Limit: + - '400000' + Anthropic-Ratelimit-Input-Tokens-Remaining: + - '400000' + Anthropic-Ratelimit-Input-Tokens-Reset: + - '2025-08-31T01:12:55Z' + Anthropic-Ratelimit-Output-Tokens-Limit: + - '80000' + Anthropic-Ratelimit-Output-Tokens-Remaining: + - '80000' + Anthropic-Ratelimit-Output-Tokens-Reset: + - '2025-08-31T01:12:55Z' + Anthropic-Ratelimit-Requests-Limit: + - '4000' + Anthropic-Ratelimit-Requests-Remaining: + - '3999' + Anthropic-Ratelimit-Requests-Reset: + - '2025-08-31T01:12:54Z' + Anthropic-Ratelimit-Tokens-Limit: + - '480000' + Anthropic-Ratelimit-Tokens-Remaining: + - '480000' + Anthropic-Ratelimit-Tokens-Reset: + - '2025-08-31T01:12:55Z' + Request-Id: + - "" + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + Anthropic-Organization-Id: + - "" + X-Envoy-Upstream-Service-Time: + - '1151' + Via: + - 1.1 google + Cf-Cache-Status: + - DYNAMIC + X-Robots-Tag: + - none + Server: + - cloudflare + Cf-Ray: + - "" + body: + encoding: ASCII-8BIT + string: '{"id":"msg_011ZXkF13Ept7roaZ7uy8S2Z","type":"message","role":"assistant","model":"claude-3-5-haiku-20241022","content":[{"type":"tool_use","id":"toolu_01T7eCvB7yP2hayppNh61uW3","name":"weather","input":{"latitude":"41.9028","longitude":"12.4964"}}],"stop_reason":"tool_use","stop_sequence":null,"usage":{"input_tokens":468,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":0},"output_tokens":59,"service_tier":"standard"}}' + recorded_at: Sun, 31 Aug 2025 01:12:55 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":[{"type":"text","text":"When + was the fall of Rome?"}]},{"role":"assistant","content":[{"type":"tool_use","id":"toolu_01T7eCvB7yP2hayppNh61uW3","name":"weather","input":{"latitude":"41.9028","longitude":"12.4964"}}]},{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_01T7eCvB7yP2hayppNh61uW3","content":[{"type":"text","text":"Current + weather at 41.9028, 12.4964: 15°C, Wind: 10 km/h"}]}]}],"stream":false,"max_tokens":8192,"tools":[{"name":"weather","description":"Gets + current weather for a location","input_schema":{"type":"object","properties":{"latitude":{"type":"string","description":"Latitude + (e.g., 52.5200)"},"longitude":{"type":"string","description":"Longitude (e.g., + 13.4050)"}},"required":["latitude","longitude"]}}]}' + headers: + User-Agent: + - Faraday v2.13.4 + 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: + - Sun, 31 Aug 2025 01:13:00 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Anthropic-Ratelimit-Input-Tokens-Limit: + - '400000' + Anthropic-Ratelimit-Input-Tokens-Remaining: + - '400000' + Anthropic-Ratelimit-Input-Tokens-Reset: + - '2025-08-31T01:12:56Z' + Anthropic-Ratelimit-Output-Tokens-Limit: + - '80000' + Anthropic-Ratelimit-Output-Tokens-Remaining: + - '80000' + Anthropic-Ratelimit-Output-Tokens-Reset: + - '2025-08-31T01:13:01Z' + Anthropic-Ratelimit-Requests-Limit: + - '4000' + Anthropic-Ratelimit-Requests-Remaining: + - '3999' + Anthropic-Ratelimit-Requests-Reset: + - '2025-08-31T01:12:55Z' + Anthropic-Ratelimit-Tokens-Limit: + - '480000' + Anthropic-Ratelimit-Tokens-Remaining: + - '480000' + Anthropic-Ratelimit-Tokens-Reset: + - '2025-08-31T01:12:56Z' + Request-Id: + - "" + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + Anthropic-Organization-Id: + - "" + X-Envoy-Upstream-Service-Time: + - '5158' + Via: + - 1.1 google + Cf-Cache-Status: + - DYNAMIC + X-Robots-Tag: + - none + Server: + - cloudflare + Cf-Ray: + - "" + body: + encoding: ASCII-8BIT + string: '{"id":"msg_01FJYoxLea1LN4z76aqyH2gX","type":"message","role":"assistant","model":"claude-3-5-haiku-20241022","content":[{"type":"text","text":"I + apologize, but the weather tool is not appropriate for answering a historical + question about the fall of Rome. Let me provide a historical answer instead.\n\nThe + fall of Rome is typically dated to 476 CE, when the last Roman Emperor in + the West, Romulus Augustulus, was deposed by the Germanic king Odoacer. This + marked the end of the Western Roman Empire, though the Eastern Roman Empire + (Byzantine Empire) continued to exist for nearly another thousand years until + Constantinople fell to the Ottoman Turks in 1453.\n\nHowever, historians debate + the exact moment of \"fall,\" as the decline of the Roman Empire was a gradual + process that occurred over several centuries. Some key events in this decline + include:\n\n1. The division of the Empire into Western and Eastern halves + in 285 CE\n2. The sack of Rome by the Visigoths in 410 CE\n3. The final deposition + of Romulus Augustulus in 476 CE\n4. The end of the Western Roman Empire''s + political structures\n\nThe fall of Rome marked the beginning of the Medieval + period in European history and had profound implications for the political, + social, and cultural landscape of Europe."}],"stop_reason":"end_turn","stop_sequence":null,"usage":{"input_tokens":491,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":0},"output_tokens":258,"service_tier":"standard"}}' + recorded_at: Sun, 31 Aug 2025 01:13:01 GMT +recorded_with: VCR 6.3.1 diff --git a/spec/fixtures/vcr_cassettes/chat_tool_choice_and_parallel_control_anthropic_claude-3-5-haiku-20241022_respects_parallel_false_for_sequential_execution.yml b/spec/fixtures/vcr_cassettes/chat_tool_choice_and_parallel_control_anthropic_claude-3-5-haiku-20241022_respects_parallel_false_for_sequential_execution.yml new file mode 100644 index 000000000..1e2ade981 --- /dev/null +++ b/spec/fixtures/vcr_cassettes/chat_tool_choice_and_parallel_control_anthropic_claude-3-5-haiku-20241022_respects_parallel_false_for_sequential_execution.yml @@ -0,0 +1,272 @@ +--- +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":[{"type":"text","text":"What''s + the weather in Berlin and what''s the best programming language?"}]}],"stream":false,"max_tokens":8192,"tools":[{"name":"weather","description":"Gets + current weather for a location","input_schema":{"type":"object","properties":{"latitude":{"type":"string","description":"Latitude + (e.g., 52.5200)"},"longitude":{"type":"string","description":"Longitude (e.g., + 13.4050)"}},"required":["latitude","longitude"]}},{"name":"best_language_to_learn","description":"Gets + the best language to learn","input_schema":{"type":"object","properties":{},"required":[]}}],"system":"You + must use both the weather tool for Berlin (52.5200, 13.4050) and the best + language tool."}' + headers: + User-Agent: + - Faraday v2.13.4 + 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: + - Sun, 31 Aug 2025 01:13:03 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Anthropic-Ratelimit-Input-Tokens-Limit: + - '400000' + Anthropic-Ratelimit-Input-Tokens-Remaining: + - '400000' + Anthropic-Ratelimit-Input-Tokens-Reset: + - '2025-08-31T01:13:01Z' + Anthropic-Ratelimit-Output-Tokens-Limit: + - '80000' + Anthropic-Ratelimit-Output-Tokens-Remaining: + - '80000' + Anthropic-Ratelimit-Output-Tokens-Reset: + - '2025-08-31T01:13:03Z' + Anthropic-Ratelimit-Requests-Limit: + - '4000' + Anthropic-Ratelimit-Requests-Remaining: + - '3999' + Anthropic-Ratelimit-Requests-Reset: + - '2025-08-31T01:13:01Z' + Anthropic-Ratelimit-Tokens-Limit: + - '480000' + Anthropic-Ratelimit-Tokens-Remaining: + - '480000' + Anthropic-Ratelimit-Tokens-Reset: + - '2025-08-31T01:13:01Z' + Request-Id: + - "" + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + Anthropic-Organization-Id: + - "" + X-Envoy-Upstream-Service-Time: + - '1923' + Via: + - 1.1 google + Cf-Cache-Status: + - DYNAMIC + X-Robots-Tag: + - none + Server: + - cloudflare + Cf-Ray: + - "" + body: + encoding: ASCII-8BIT + string: '{"id":"msg_01JnHdgM7UnVJrDWEAy51pZs","type":"message","role":"assistant","model":"claude-3-5-haiku-20241022","content":[{"type":"text","text":"I''ll + help you find out the weather in Berlin and the best programming language + to learn. I''ll use the available tools to get this information for you.\n\nFirst, + let''s check the weather in Berlin:"},{"type":"tool_use","id":"toolu_01MgneaQ2SYw3piJ14QaVaNi","name":"weather","input":{"latitude":"52.5200","longitude":"13.4050"}}],"stop_reason":"tool_use","stop_sequence":null,"usage":{"input_tokens":460,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":0},"output_tokens":117,"service_tier":"standard"}}' + recorded_at: Sun, 31 Aug 2025 01:13:03 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":[{"type":"text","text":"What''s + the weather in Berlin and what''s the best programming language?"}]},{"role":"assistant","content":[{"type":"text","text":"I''ll + help you find out the weather in Berlin and the best programming language + to learn. I''ll use the available tools to get this information for you.\n\nFirst, + let''s check the weather in Berlin:"},{"type":"tool_use","id":"toolu_01MgneaQ2SYw3piJ14QaVaNi","name":"weather","input":{"latitude":"52.5200","longitude":"13.4050"}}]},{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_01MgneaQ2SYw3piJ14QaVaNi","content":[{"type":"text","text":"Current + weather at 52.5200, 13.4050: 15°C, Wind: 10 km/h"}]}]}],"stream":false,"max_tokens":8192,"tools":[{"name":"weather","description":"Gets + current weather for a location","input_schema":{"type":"object","properties":{"latitude":{"type":"string","description":"Latitude + (e.g., 52.5200)"},"longitude":{"type":"string","description":"Longitude (e.g., + 13.4050)"}},"required":["latitude","longitude"]}},{"name":"best_language_to_learn","description":"Gets + the best language to learn","input_schema":{"type":"object","properties":{},"required":[]}}],"system":"You + must use both the weather tool for Berlin (52.5200, 13.4050) and the best + language tool."}' + headers: + User-Agent: + - Faraday v2.13.4 + 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: + - Sun, 31 Aug 2025 01:13:04 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Anthropic-Ratelimit-Input-Tokens-Limit: + - '400000' + Anthropic-Ratelimit-Input-Tokens-Remaining: + - '400000' + Anthropic-Ratelimit-Input-Tokens-Reset: + - '2025-08-31T01:13:03Z' + Anthropic-Ratelimit-Output-Tokens-Limit: + - '80000' + Anthropic-Ratelimit-Output-Tokens-Remaining: + - '80000' + Anthropic-Ratelimit-Output-Tokens-Reset: + - '2025-08-31T01:13:04Z' + Anthropic-Ratelimit-Requests-Limit: + - '4000' + Anthropic-Ratelimit-Requests-Remaining: + - '3999' + Anthropic-Ratelimit-Requests-Reset: + - '2025-08-31T01:13:03Z' + Anthropic-Ratelimit-Tokens-Limit: + - '480000' + Anthropic-Ratelimit-Tokens-Remaining: + - '480000' + Anthropic-Ratelimit-Tokens-Reset: + - '2025-08-31T01:13:03Z' + Request-Id: + - "" + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + Anthropic-Organization-Id: + - "" + X-Envoy-Upstream-Service-Time: + - '1081' + Via: + - 1.1 google + Cf-Cache-Status: + - DYNAMIC + X-Robots-Tag: + - none + Server: + - cloudflare + Cf-Ray: + - "" + body: + encoding: ASCII-8BIT + string: '{"id":"msg_01GysMofP1j2yodwkPx3HrCw","type":"message","role":"assistant","model":"claude-3-5-haiku-20241022","content":[{"type":"text","text":"Now, + let''s find out the best programming language to learn:"},{"type":"tool_use","id":"toolu_01A7KoRqUe7Y3BrthnxUGbAo","name":"best_language_to_learn","input":{}}],"stop_reason":"tool_use","stop_sequence":null,"usage":{"input_tokens":616,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":0},"output_tokens":55,"service_tier":"standard"}}' + recorded_at: Sun, 31 Aug 2025 01:13:04 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":[{"type":"text","text":"What''s + the weather in Berlin and what''s the best programming language?"}]},{"role":"assistant","content":[{"type":"text","text":"I''ll + help you find out the weather in Berlin and the best programming language + to learn. I''ll use the available tools to get this information for you.\n\nFirst, + let''s check the weather in Berlin:"},{"type":"tool_use","id":"toolu_01MgneaQ2SYw3piJ14QaVaNi","name":"weather","input":{"latitude":"52.5200","longitude":"13.4050"}}]},{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_01MgneaQ2SYw3piJ14QaVaNi","content":[{"type":"text","text":"Current + weather at 52.5200, 13.4050: 15°C, Wind: 10 km/h"}]}]},{"role":"assistant","content":[{"type":"text","text":"Now, + let''s find out the best programming language to learn:"},{"type":"tool_use","id":"toolu_01A7KoRqUe7Y3BrthnxUGbAo","name":"best_language_to_learn","input":{}}]},{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_01A7KoRqUe7Y3BrthnxUGbAo","content":[{"type":"text","text":"Ruby"}]}]}],"stream":false,"max_tokens":8192,"tools":[{"name":"weather","description":"Gets + current weather for a location","input_schema":{"type":"object","properties":{"latitude":{"type":"string","description":"Latitude + (e.g., 52.5200)"},"longitude":{"type":"string","description":"Longitude (e.g., + 13.4050)"}},"required":["latitude","longitude"]}},{"name":"best_language_to_learn","description":"Gets + the best language to learn","input_schema":{"type":"object","properties":{},"required":[]}}],"system":"You + must use both the weather tool for Berlin (52.5200, 13.4050) and the best + language tool."}' + headers: + User-Agent: + - Faraday v2.13.4 + 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: + - Sun, 31 Aug 2025 01:13:07 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Anthropic-Ratelimit-Input-Tokens-Limit: + - '400000' + Anthropic-Ratelimit-Input-Tokens-Remaining: + - '400000' + Anthropic-Ratelimit-Input-Tokens-Reset: + - '2025-08-31T01:13:05Z' + Anthropic-Ratelimit-Output-Tokens-Limit: + - '80000' + Anthropic-Ratelimit-Output-Tokens-Remaining: + - '80000' + Anthropic-Ratelimit-Output-Tokens-Reset: + - '2025-08-31T01:13:07Z' + Anthropic-Ratelimit-Requests-Limit: + - '4000' + Anthropic-Ratelimit-Requests-Remaining: + - '3999' + Anthropic-Ratelimit-Requests-Reset: + - '2025-08-31T01:13:04Z' + Anthropic-Ratelimit-Tokens-Limit: + - '480000' + Anthropic-Ratelimit-Tokens-Remaining: + - '480000' + Anthropic-Ratelimit-Tokens-Reset: + - '2025-08-31T01:13:05Z' + Request-Id: + - "" + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + Anthropic-Organization-Id: + - "" + X-Envoy-Upstream-Service-Time: + - '2376' + Via: + - 1.1 google + Cf-Cache-Status: + - DYNAMIC + X-Robots-Tag: + - none + Server: + - cloudflare + Cf-Ray: + - "" + body: + encoding: ASCII-8BIT + string: !binary |- + eyJpZCI6Im1zZ18wMVN6OFVHTnhRdHdSRlJqQ01yQXRaZm4iLCJ0eXBlIjoibWVzc2FnZSIsInJvbGUiOiJhc3Npc3RhbnQiLCJtb2RlbCI6ImNsYXVkZS0zLTUtaGFpa3UtMjAyNDEwMjIiLCJjb250ZW50IjpbeyJ0eXBlIjoidGV4dCIsInRleHQiOiJIZXJlJ3MgdGhlIGluZm9ybWF0aW9uIHlvdSBhc2tlZCBmb3I6XG4xLiBXZWF0aGVyIGluIEJlcmxpbjogSXQncyBjdXJyZW50bHkgMTXCsEMgd2l0aCBhIHdpbmQgc3BlZWQgb2YgMTAga20vaC5cbjIuIEJlc3QgUHJvZ3JhbW1pbmcgTGFuZ3VhZ2UgdG8gTGVhcm46IFJ1YnlcblxuVGhlIHdlYXRoZXIgc2VlbXMgbWlsZCBhbmQgcGxlYXNhbnQsIGFuZCBSdWJ5IGlzIHJlY29tbWVuZGVkIGFzIGEgZ3JlYXQgcHJvZ3JhbW1pbmcgbGFuZ3VhZ2UgdG8gbGVhcm4uIFJ1YnkgaXMga25vd24gZm9yIGl0cyBzaW1wbGljaXR5IGFuZCByZWFkYWJpbGl0eSwgbWFraW5nIGl0IGEgZ29vZCBjaG9pY2UgZm9yIGJlZ2lubmVycyBhbmQgZXhwZXJpZW5jZWQgZGV2ZWxvcGVycyBhbGlrZS5cblxuSXMgdGhlcmUgYW55dGhpbmcgZWxzZSBJIGNhbiBoZWxwIHlvdSB3aXRoPyJ9XSwic3RvcF9yZWFzb24iOiJlbmRfdHVybiIsInN0b3Bfc2VxdWVuY2UiOm51bGwsInVzYWdlIjp7ImlucHV0X3Rva2VucyI6NjgzLCJjYWNoZV9jcmVhdGlvbl9pbnB1dF90b2tlbnMiOjAsImNhY2hlX3JlYWRfaW5wdXRfdG9rZW5zIjowLCJjYWNoZV9jcmVhdGlvbiI6eyJlcGhlbWVyYWxfNW1faW5wdXRfdG9rZW5zIjowLCJlcGhlbWVyYWxfMWhfaW5wdXRfdG9rZW5zIjowfSwib3V0cHV0X3Rva2VucyI6MTAzLCJzZXJ2aWNlX3RpZXIiOiJzdGFuZGFyZCJ9fQ== + recorded_at: Sun, 31 Aug 2025 01:13:07 GMT +recorded_with: VCR 6.3.1 diff --git a/spec/fixtures/vcr_cassettes/chat_tool_choice_and_parallel_control_anthropic_claude-3-5-haiku-20241022_respects_specific_tool_choice.yml b/spec/fixtures/vcr_cassettes/chat_tool_choice_and_parallel_control_anthropic_claude-3-5-haiku-20241022_respects_specific_tool_choice.yml new file mode 100644 index 000000000..17116fb8f --- /dev/null +++ b/spec/fixtures/vcr_cassettes/chat_tool_choice_and_parallel_control_anthropic_claude-3-5-haiku-20241022_respects_specific_tool_choice.yml @@ -0,0 +1,186 @@ +--- +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":[{"type":"text","text":"What''s + the fall of Rome?"}]}],"stream":false,"max_tokens":8192,"tools":[{"name":"weather","description":"Gets + current weather for a location","input_schema":{"type":"object","properties":{"latitude":{"type":"string","description":"Latitude + (e.g., 52.5200)"},"longitude":{"type":"string","description":"Longitude (e.g., + 13.4050)"}},"required":["latitude","longitude"]}}],"tool_choice":{"type":"tool","name":"weather"}}' + headers: + User-Agent: + - Faraday v2.13.4 + 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: + - Tue, 23 Sep 2025 22:59:27 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Anthropic-Ratelimit-Input-Tokens-Limit: + - '400000' + Anthropic-Ratelimit-Input-Tokens-Remaining: + - '400000' + Anthropic-Ratelimit-Input-Tokens-Reset: + - '2025-09-23T22:59:26Z' + Anthropic-Ratelimit-Output-Tokens-Limit: + - '80000' + Anthropic-Ratelimit-Output-Tokens-Remaining: + - '80000' + Anthropic-Ratelimit-Output-Tokens-Reset: + - '2025-09-23T22:59:27Z' + Anthropic-Ratelimit-Requests-Limit: + - '4000' + Anthropic-Ratelimit-Requests-Remaining: + - '3999' + Anthropic-Ratelimit-Requests-Reset: + - '2025-09-23T22:59:26Z' + Anthropic-Ratelimit-Tokens-Limit: + - '480000' + Anthropic-Ratelimit-Tokens-Remaining: + - '480000' + Anthropic-Ratelimit-Tokens-Reset: + - '2025-09-23T22:59:26Z' + Request-Id: + - "" + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + Anthropic-Organization-Id: + - "" + X-Envoy-Upstream-Service-Time: + - '1045' + Via: + - 1.1 google + Cf-Cache-Status: + - DYNAMIC + X-Robots-Tag: + - none + Server: + - cloudflare + Cf-Ray: + - "" + body: + encoding: ASCII-8BIT + string: '{"id":"msg_01PnqzzGkuf91SLFFz3EiQSq","type":"message","role":"assistant","model":"claude-3-5-haiku-20241022","content":[{"type":"tool_use","id":"toolu_01GcLgKHyM5kXBsnWAaLbPnb","name":"weather","input":{"latitude":"41.9028","longitude":"12.4964"}}],"stop_reason":"tool_use","stop_sequence":null,"usage":{"input_tokens":471,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":0},"output_tokens":56,"service_tier":"standard"}}' + recorded_at: Tue, 23 Sep 2025 22:59:27 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":[{"type":"text","text":"What''s + the fall of Rome?"}]},{"role":"assistant","content":[{"type":"tool_use","id":"toolu_01GcLgKHyM5kXBsnWAaLbPnb","name":"weather","input":{"latitude":"41.9028","longitude":"12.4964"}}]},{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_01GcLgKHyM5kXBsnWAaLbPnb","content":[{"type":"text","text":"Current + weather at 41.9028, 12.4964: 15°C, Wind: 10 km/h"}]}]}],"stream":false,"max_tokens":8192,"tools":[{"name":"weather","description":"Gets + current weather for a location","input_schema":{"type":"object","properties":{"latitude":{"type":"string","description":"Latitude + (e.g., 52.5200)"},"longitude":{"type":"string","description":"Longitude (e.g., + 13.4050)"}},"required":["latitude","longitude"]}}]}' + headers: + User-Agent: + - Faraday v2.13.4 + 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: + - Tue, 23 Sep 2025 22:59:33 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Anthropic-Ratelimit-Input-Tokens-Limit: + - '400000' + Anthropic-Ratelimit-Input-Tokens-Remaining: + - '400000' + Anthropic-Ratelimit-Input-Tokens-Reset: + - '2025-09-23T22:59:27Z' + Anthropic-Ratelimit-Output-Tokens-Limit: + - '80000' + Anthropic-Ratelimit-Output-Tokens-Remaining: + - '80000' + Anthropic-Ratelimit-Output-Tokens-Reset: + - '2025-09-23T22:59:33Z' + Anthropic-Ratelimit-Requests-Limit: + - '4000' + Anthropic-Ratelimit-Requests-Remaining: + - '3999' + Anthropic-Ratelimit-Requests-Reset: + - '2025-09-23T22:59:27Z' + Anthropic-Ratelimit-Tokens-Limit: + - '480000' + Anthropic-Ratelimit-Tokens-Remaining: + - '480000' + Anthropic-Ratelimit-Tokens-Reset: + - '2025-09-23T22:59:27Z' + Request-Id: + - "" + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + Anthropic-Organization-Id: + - "" + X-Envoy-Upstream-Service-Time: + - '6347' + Via: + - 1.1 google + Cf-Cache-Status: + - DYNAMIC + X-Robots-Tag: + - none + Server: + - cloudflare + Cf-Ray: + - "" + body: + encoding: ASCII-8BIT + string: '{"id":"msg_01ArUN4JDfoAhr2Qk2r51KnG","type":"message","role":"assistant","model":"claude-3-5-haiku-20241022","content":[{"type":"text","text":"I + apologize, but the weather function is not related to historical information + about the Fall of Rome. Let me provide you with a historical explanation:\n\nThe + Fall of Rome typically refers to the collapse of the Western Roman Empire, + which is traditionally dated to 476 CE. This was a complex historical process + that occurred over several centuries, marked by several key events:\n\n1. + Internal Decline: The Roman Empire suffered from political instability, economic + problems, and social decay.\n\n2. Military Challenges: Constant invasions + by \"barbarian\" tribes, particularly Germanic peoples like the Goths and + Vandals, weakened the empire''s borders.\n\n3. Specific Event: In 476 CE, + the Germanic leader Odoacer deposed the last Roman Emperor in the West, Romulus + Augustulus, which is often considered the symbolic end of the Western Roman + Empire.\n\n4. Contributing Factors:\n- Overexpansion of the empire\n- Economic + troubles\n- Increasing military expenses\n- Corruption\n- Division of the + empire into Western and Eastern (Byzantine) halves\n- Invasions by external + tribes\n\nThe Eastern Roman Empire (Byzantine Empire) continued to exist for + nearly another thousand years until Constantinople fell to the Ottoman Turks + in 1453.\n\nThis historical event marked the end of classical antiquity and + the beginning of the Medieval period in European history."}],"stop_reason":"end_turn","stop_sequence":null,"usage":{"input_tokens":491,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":0},"output_tokens":295,"service_tier":"standard"}}' + recorded_at: Tue, 23 Sep 2025 22:59:33 GMT +recorded_with: VCR 6.3.1 diff --git a/spec/fixtures/vcr_cassettes/chat_tool_choice_and_parallel_control_bedrock_anthropic_claude-3-5-haiku-20241022-v1_0_respects_choice_required_for_unrelated_queries.yml b/spec/fixtures/vcr_cassettes/chat_tool_choice_and_parallel_control_bedrock_anthropic_claude-3-5-haiku-20241022-v1_0_respects_choice_required_for_unrelated_queries.yml new file mode 100644 index 000000000..fdfabd305 --- /dev/null +++ b/spec/fixtures/vcr_cassettes/chat_tool_choice_and_parallel_control_bedrock_anthropic_claude-3-5-haiku-20241022-v1_0_respects_choice_required_for_unrelated_queries.yml @@ -0,0 +1,128 @@ +--- +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":[{"type":"text","text":"When + was the fall of Rome?"}]}],"max_tokens":4096,"tools":[{"name":"weather","description":"Gets + current weather for a location","input_schema":{"type":"object","properties":{"latitude":{"type":"string","description":"Latitude + (e.g., 52.5200)"},"longitude":{"type":"string","description":"Longitude (e.g., + 13.4050)"}},"required":["latitude","longitude"]}}],"tool_choice":{"type":"any"}}' + headers: + User-Agent: + - Faraday v2.13.4 + Host: + - bedrock-runtime..amazonaws.com + X-Amz-Date: + - 20250923T214505Z + X-Amz-Content-Sha256: + - 01270c912b14280d36f28c9b89e6746ea0899837a8c27b23348c6f9d486d651a + Authorization: + - AWS4-HMAC-SHA256 Credential=/20250923//bedrock/aws4_request, + SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature=5e5d0727a7ce11c5924d4349b3d701c780cfb354ff4ef9f4abb84e535f1264e8 + 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: + - Tue, 23 Sep 2025 21:45:07 GMT + Content-Type: + - application/json + Content-Length: + - '413' + Connection: + - keep-alive + X-Amzn-Requestid: + - 07ffcaa6-be69-4f9a-98ee-1a82ae34b804 + X-Amzn-Bedrock-Invocation-Latency: + - '1147' + X-Amzn-Bedrock-Output-Token-Count: + - '59' + X-Amzn-Bedrock-Input-Token-Count: + - '468' + body: + encoding: UTF-8 + string: '{"id":"msg_bdrk_015iWMEMJFrPYpXma8Zr8LLP","type":"message","role":"assistant","model":"claude-3-5-haiku-20241022","content":[{"type":"tool_use","id":"toolu_bdrk_01EgMpxXBNmMP6oScK2Mgxfj","name":"weather","input":{"latitude":"41.9028","longitude":"12.4964"}}],"stop_reason":"tool_use","stop_sequence":null,"usage":{"input_tokens":468,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"output_tokens":59}}' + recorded_at: Tue, 23 Sep 2025 21:45:07 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":[{"type":"text","text":"When + was the fall of Rome?"}]},{"role":"assistant","content":[{"type":"tool_use","id":"toolu_bdrk_01EgMpxXBNmMP6oScK2Mgxfj","name":"weather","input":{"latitude":"41.9028","longitude":"12.4964"}}]},{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_bdrk_01EgMpxXBNmMP6oScK2Mgxfj","content":[{"type":"text","text":"Current + weather at 41.9028, 12.4964: 15°C, Wind: 10 km/h"}]}]}],"max_tokens":4096,"tools":[{"name":"weather","description":"Gets + current weather for a location","input_schema":{"type":"object","properties":{"latitude":{"type":"string","description":"Latitude + (e.g., 52.5200)"},"longitude":{"type":"string","description":"Longitude (e.g., + 13.4050)"}},"required":["latitude","longitude"]}}]}' + headers: + User-Agent: + - Faraday v2.13.4 + Host: + - bedrock-runtime..amazonaws.com + X-Amz-Date: + - 20250923T214507Z + X-Amz-Content-Sha256: + - 8cd63939c75288cf785ef205cb307d86470fe7acdaa3aae3d11eaae3a87ed4c4 + Authorization: + - AWS4-HMAC-SHA256 Credential=/20250923//bedrock/aws4_request, + SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature=ee7887ca7d2ae151ef5df6b722b6af4d4e2c6bb89d1e261a365da2db94f7f5db + 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: + - Tue, 23 Sep 2025 21:45:13 GMT + Content-Type: + - application/json + Content-Length: + - '1666' + Connection: + - keep-alive + X-Amzn-Requestid: + - fb0fd7ff-7d4f-4aa0-904e-e8f496bf0b6b + X-Amzn-Bedrock-Invocation-Latency: + - '6226' + X-Amzn-Bedrock-Output-Token-Count: + - '289' + X-Amzn-Bedrock-Input-Token-Count: + - '491' + body: + encoding: UTF-8 + string: '{"id":"msg_bdrk_01UptNJSFjnAieTmt4DoYq2t","type":"message","role":"assistant","model":"claude-3-5-haiku-20241022","content":[{"type":"text","text":"I + apologize, but the weather tool is not relevant to answering your historical + question. Let me provide you with a detailed historical answer.\n\nThe \"Fall + of Rome\" is a complex historical event that can be interpreted in different + ways, but typically refers to two major periods:\n\n1. Fall of the Western + Roman Empire: Traditionally dated to 476 CE, when the last Roman Emperor, + Romulus Augustulus, was deposed by the Germanic king Odoacer. This marked + the end of the Western Roman Empire.\n\n2. Fall of Constantinople (Eastern + Roman Empire/Byzantine Empire): This occurred in 1453 CE, when the Ottoman + Turks led by Sultan Mehmed II conquered Constantinople, effectively ending + the Byzantine Empire.\n\nMost historians consider 476 CE as the primary date + for the \"Fall of Rome,\" which signaled the end of ancient Roman political + power in Western Europe and the beginning of the Medieval period. However, + the decline was a gradual process that occurred over several centuries, involving + economic, military, and political challenges that weakened the Roman Empire + from within.\n\nThe fall was caused by multiple factors, including:\n- Invasions + by Germanic tribes\n- Internal political instability\n- Economic decline\n- + Military overextension\n- Social and cultural transformations\n\nWould you + like me to elaborate on any specific aspect of Rome''s fall?"}],"stop_reason":"end_turn","stop_sequence":null,"usage":{"input_tokens":491,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"output_tokens":289}}' + recorded_at: Tue, 23 Sep 2025 21:45:13 GMT +recorded_with: VCR 6.3.1 diff --git a/spec/fixtures/vcr_cassettes/chat_tool_choice_and_parallel_control_bedrock_anthropic_claude-3-5-haiku-20241022-v1_0_respects_specific_tool_choice.yml b/spec/fixtures/vcr_cassettes/chat_tool_choice_and_parallel_control_bedrock_anthropic_claude-3-5-haiku-20241022-v1_0_respects_specific_tool_choice.yml new file mode 100644 index 000000000..242dd5d28 --- /dev/null +++ b/spec/fixtures/vcr_cassettes/chat_tool_choice_and_parallel_control_bedrock_anthropic_claude-3-5-haiku-20241022-v1_0_respects_specific_tool_choice.yml @@ -0,0 +1,131 @@ +--- +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":[{"type":"text","text":"What''s + the fall of Rome?"}]}],"max_tokens":4096,"tools":[{"name":"weather","description":"Gets + current weather for a location","input_schema":{"type":"object","properties":{"latitude":{"type":"string","description":"Latitude + (e.g., 52.5200)"},"longitude":{"type":"string","description":"Longitude (e.g., + 13.4050)"}},"required":["latitude","longitude"]}}],"tool_choice":{"type":"tool","name":"weather"}}' + headers: + User-Agent: + - Faraday v2.13.4 + Host: + - bedrock-runtime..amazonaws.com + X-Amz-Date: + - 20250923T225933Z + X-Amz-Content-Sha256: + - 661cb72b7b51ee6b81def603d6a47e0f50d39bc17e25595818c2b40c09c83a07 + Authorization: + - AWS4-HMAC-SHA256 Credential=/20250923//bedrock/aws4_request, + SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature=6d41ed04e85e46dd75ed7f7d7550fe83be4fdf0beac1cffa378a11984f1311bd + 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: + - Tue, 23 Sep 2025 22:59:36 GMT + Content-Type: + - application/json + Content-Length: + - '413' + Connection: + - keep-alive + X-Amzn-Requestid: + - 84701b2c-765f-4ab3-8402-2e791fbff465 + X-Amzn-Bedrock-Invocation-Latency: + - '2433' + X-Amzn-Bedrock-Output-Token-Count: + - '56' + X-Amzn-Bedrock-Input-Token-Count: + - '471' + body: + encoding: UTF-8 + string: '{"id":"msg_bdrk_015HrsnnfR2hfHhQas5449PJ","type":"message","role":"assistant","model":"claude-3-5-haiku-20241022","content":[{"type":"tool_use","id":"toolu_bdrk_01BYaKhdCDDawU5USiPsuweT","name":"weather","input":{"latitude":"41.9028","longitude":"12.4964"}}],"stop_reason":"tool_use","stop_sequence":null,"usage":{"input_tokens":471,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"output_tokens":56}}' + recorded_at: Tue, 23 Sep 2025 22:59:36 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":[{"type":"text","text":"What''s + the fall of Rome?"}]},{"role":"assistant","content":[{"type":"tool_use","id":"toolu_bdrk_01BYaKhdCDDawU5USiPsuweT","name":"weather","input":{"latitude":"41.9028","longitude":"12.4964"}}]},{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_bdrk_01BYaKhdCDDawU5USiPsuweT","content":[{"type":"text","text":"Current + weather at 41.9028, 12.4964: 15°C, Wind: 10 km/h"}]}]}],"max_tokens":4096,"tools":[{"name":"weather","description":"Gets + current weather for a location","input_schema":{"type":"object","properties":{"latitude":{"type":"string","description":"Latitude + (e.g., 52.5200)"},"longitude":{"type":"string","description":"Longitude (e.g., + 13.4050)"}},"required":["latitude","longitude"]}}]}' + headers: + User-Agent: + - Faraday v2.13.4 + Host: + - bedrock-runtime..amazonaws.com + X-Amz-Date: + - 20250923T225936Z + X-Amz-Content-Sha256: + - e80fd73036e49336f253e3eb381bbe55d7dee20f6910c34e4cb3ab0689abf2b5 + Authorization: + - AWS4-HMAC-SHA256 Credential=/20250923//bedrock/aws4_request, + SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature=32daf224ca97ce94618bc6c7fab9696e812fb5d016d86eac639f959371cf34b0 + 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: + - Tue, 23 Sep 2025 22:59:42 GMT + Content-Type: + - application/json + Content-Length: + - '1918' + Connection: + - keep-alive + X-Amzn-Requestid: + - 34bd0b58-793a-475e-bddb-45cfd8d6705f + X-Amzn-Bedrock-Invocation-Latency: + - '5963' + X-Amzn-Bedrock-Output-Token-Count: + - '331' + X-Amzn-Bedrock-Input-Token-Count: + - '491' + body: + encoding: UTF-8 + string: '{"id":"msg_bdrk_01XrTxbJ3GiQtaFgK4UFYBLu","type":"message","role":"assistant","model":"claude-3-5-haiku-20241022","content":[{"type":"text","text":"I + apologize, but the weather function is not relevant to your question about + the Fall of Rome. Let me provide a historical explanation:\n\nThe Fall of + Rome refers to the decline and ultimate collapse of the Western Roman Empire, + which occurred gradually over several centuries, traditionally marked by the + deposition of the last Roman Emperor Romulus Augustulus in 476 CE by the Germanic + king Odoacer.\n\nKey points about the Fall of Rome include:\n\n1. Multiple + Factors: The fall was caused by a complex combination of internal and external + challenges:\n- Political instability and frequent civil wars\n- Economic decline + and overextension of the empire\n- Military pressures from Germanic tribes + and other external invaders\n- Social and moral decay\n- Corruption in government\n- + Division of the empire into Western and Eastern (Byzantine) halves\n\n2. Barbarian + Invasions: Germanic tribes like the Goths, Vandals, and Franks continuously + pressured Roman borders and eventually settled within imperial territories.\n\n3. + Economic Challenges: High taxation, decreased agricultural productivity, and + reduced trade weakened the empire''s economic foundation.\n\n4. Military Weakness: + The Roman military became less effective, relying more on mercenaries and + struggling to defend vast territories.\n\n5. Cultural Transformation: Rather + than a sudden collapse, it was more a gradual transformation where Roman institutions + were replaced by Germanic kingdoms.\n\nThe Eastern Roman Empire (Byzantine + Empire) continued to exist for nearly a thousand years more, until Constantinople + fell to the Ottoman Turks in 1453."}],"stop_reason":"end_turn","stop_sequence":null,"usage":{"input_tokens":491,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"output_tokens":331}}' + recorded_at: Tue, 23 Sep 2025 22:59:42 GMT +recorded_with: VCR 6.3.1 diff --git a/spec/fixtures/vcr_cassettes/chat_tool_choice_and_parallel_control_deepseek_deepseek-chat_respects_choice_none.yml b/spec/fixtures/vcr_cassettes/chat_tool_choice_and_parallel_control_deepseek_deepseek-chat_respects_choice_none.yml new file mode 100644 index 000000000..7111e97d7 --- /dev/null +++ b/spec/fixtures/vcr_cassettes/chat_tool_choice_and_parallel_control_deepseek_deepseek-chat_respects_choice_none.yml @@ -0,0 +1,60 @@ +--- +http_interactions: +- request: + method: post + uri: https://api.deepseek.com/chat/completions + body: + encoding: UTF-8 + string: '{"model":"deepseek-chat","messages":[{"role":"user","content":"What''s + the weather in Berlin? (52.5200, 13.4050)"}],"stream":false,"tools":[{"type":"function","function":{"name":"weather","description":"Gets + current weather for a location","parameters":{"type":"object","properties":{"latitude":{"type":"string","description":"Latitude + (e.g., 52.5200)"},"longitude":{"type":"string","description":"Longitude (e.g., + 13.4050)"}},"required":["latitude","longitude"]}}}],"tool_choice":"none"}' + headers: + User-Agent: + - Faraday v2.13.4 + 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: + - Tue, 23 Sep 2025 21:36:50 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: + - b336cd0cc8edc43b2c612390c10b590e + 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: !binary |- + eyJpZCI6ImFlYmMwZjFkLWFiZTEtNGZjZC05YWFiLTU1ZGZjYTZmYjQwYSIsIm9iamVjdCI6ImNoYXQuY29tcGxldGlvbiIsImNyZWF0ZWQiOjE3NTg2NjM0MTAsIm1vZGVsIjoiZGVlcHNlZWstY2hhdCIsImNob2ljZXMiOlt7ImluZGV4IjowLCJtZXNzYWdlIjp7InJvbGUiOiJhc3Npc3RhbnQiLCJjb250ZW50IjoiSSBjYW4ndCBwcm92aWRlIHJlYWwtdGltZSB3ZWF0aGVyIGluZm9ybWF0aW9uLCBidXQgSSBjYW4gc3VnZ2VzdCBhIGZldyB3YXlzIGZvciB5b3UgdG8gY2hlY2sgdGhlIGN1cnJlbnQgd2VhdGhlciBpbiBCZXJsaW46XG5cbioqUXVpY2sgd2F5cyB0byBjaGVjazoqKlxuLSBTZWFyY2ggXCJ3ZWF0aGVyIEJlcmxpblwiIG9uIEdvb2dsZSBvciB5b3VyIHByZWZlcnJlZCBzZWFyY2ggZW5naW5lXG4tIFVzZSBhIHdlYXRoZXIgYXBwIG9uIHlvdXIgcGhvbmUgKFdlYXRoZXIsIEFjY3VXZWF0aGVyLCBldGMuKVxuLSBBc2sgeW91ciBwaG9uZSdzIHZvaWNlIGFzc2lzdGFudCAoXCJIZXkgU2lyaSwgd2hhdCdzIHRoZSB3ZWF0aGVyIGluIEJlcmxpbj9cIiBvciBcIk9rYXkgR29vZ2xlLCB3ZWF0aGVyIGluIEJlcmxpblwiKVxuXG4qKldlYXRoZXIgd2Vic2l0ZXM6Kipcbi0gV2VhdGhlci5jb21cbi0gQWNjdVdlYXRoZXIuY29tXG4tIFdlYXRoZXIuZ292IChpZiB5b3UgcHJlZmVyIGdvdmVybm1lbnQgc291cmNlcylcblxuVGhlIGNvb3JkaW5hdGVzIHlvdSBwcm92aWRlZCAoNTIuNTIwMMKwIE4sIDEzLjQwNTDCsCBFKSBjb25maXJtIHlvdSdyZSBsb29raW5nIGZvciBjZW50cmFsIEJlcmxpbidzIHdlYXRoZXIuIFdvdWxkIHlvdSBsaWtlIG1lIHRvIGhlbHAgeW91IHdpdGggYW55dGhpbmcgZWxzZSBhYm91dCBCZXJsaW4sIGxpa2UgdHlwaWNhbCBzZWFzb25hbCB3ZWF0aGVyIHBhdHRlcm5zIG9yIHRoaW5ncyB0byBkbyBiYXNlZCBvbiB3ZWF0aGVyIGNvbmRpdGlvbnM/In0sImxvZ3Byb2JzIjpudWxsLCJmaW5pc2hfcmVhc29uIjoic3RvcCJ9XSwidXNhZ2UiOnsicHJvbXB0X3Rva2VucyI6MjMsImNvbXBsZXRpb25fdG9rZW5zIjoxNzQsInRvdGFsX3Rva2VucyI6MTk3LCJwcm9tcHRfdG9rZW5zX2RldGFpbHMiOnsiY2FjaGVkX3Rva2VucyI6MH0sInByb21wdF9jYWNoZV9oaXRfdG9rZW5zIjowLCJwcm9tcHRfY2FjaGVfbWlzc190b2tlbnMiOjIzfSwic3lzdGVtX2ZpbmdlcnByaW50IjoiZnBfZjI1M2ZjMTlkMV9wcm9kMDgyMF9mcDhfa3ZjYWNoZSJ9 + recorded_at: Tue, 23 Sep 2025 21:37:00 GMT +recorded_with: VCR 6.3.1 diff --git a/spec/fixtures/vcr_cassettes/chat_tool_choice_and_parallel_control_deepseek_deepseek-chat_respects_choice_required_for_unrelated_queries.yml b/spec/fixtures/vcr_cassettes/chat_tool_choice_and_parallel_control_deepseek_deepseek-chat_respects_choice_required_for_unrelated_queries.yml new file mode 100644 index 000000000..f6ffa2b9d --- /dev/null +++ b/spec/fixtures/vcr_cassettes/chat_tool_choice_and_parallel_control_deepseek_deepseek-chat_respects_choice_required_for_unrelated_queries.yml @@ -0,0 +1,118 @@ +--- +http_interactions: +- request: + method: post + uri: https://api.deepseek.com/chat/completions + body: + encoding: UTF-8 + string: '{"model":"deepseek-chat","messages":[{"role":"user","content":"When + was the fall of Rome?"}],"stream":false,"tools":[{"type":"function","function":{"name":"weather","description":"Gets + current weather for a location","parameters":{"type":"object","properties":{"latitude":{"type":"string","description":"Latitude + (e.g., 52.5200)"},"longitude":{"type":"string","description":"Longitude (e.g., + 13.4050)"}},"required":["latitude","longitude"]}}}],"tool_choice":"required"}' + headers: + User-Agent: + - Faraday v2.13.4 + 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: + - Tue, 23 Sep 2025 21:45:13 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: + - e0a19c7bfd2c3f1c6f3fa8b483e0f44b + 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":"b5f392ce-53f9-4ca7-9375-030a3a7ed319","object":"chat.completion","created":1758663913,"model":"deepseek-chat","choices":[{"index":0,"message":{"role":"assistant","content":"","tool_calls":[{"index":0,"id":"call_00_NgmrCKiPiEh4skLW1XvR7nZ1","type":"function","function":{"name":"weather","arguments":"{\"latitude\": + \"41.9028\", \"longitude\": \"12.4964\"}"}}]},"logprobs":null,"finish_reason":"tool_calls"}],"usage":{"prompt_tokens":199,"completion_tokens":24,"total_tokens":223,"prompt_tokens_details":{"cached_tokens":0},"prompt_cache_hit_tokens":0,"prompt_cache_miss_tokens":199},"system_fingerprint":"fp_f253fc19d1_prod0820_fp8_kvcache"}' + recorded_at: Tue, 23 Sep 2025 21:45:17 GMT +- request: + method: post + uri: https://api.deepseek.com/chat/completions + body: + encoding: UTF-8 + string: '{"model":"deepseek-chat","messages":[{"role":"user","content":"When + was the fall of Rome?"},{"role":"assistant","content":"","tool_calls":[{"id":"call_00_NgmrCKiPiEh4skLW1XvR7nZ1","type":"function","function":{"name":"weather","arguments":"{\"latitude\":\"41.9028\",\"longitude\":\"12.4964\"}"}}]},{"role":"tool","content":"Current + weather at 41.9028, 12.4964: 15°C, Wind: 10 km/h","tool_call_id":"call_00_NgmrCKiPiEh4skLW1XvR7nZ1"}],"stream":false,"tools":[{"type":"function","function":{"name":"weather","description":"Gets + current weather for a location","parameters":{"type":"object","properties":{"latitude":{"type":"string","description":"Latitude + (e.g., 52.5200)"},"longitude":{"type":"string","description":"Longitude (e.g., + 13.4050)"}},"required":["latitude","longitude"]}}}]}' + headers: + User-Agent: + - Faraday v2.13.4 + 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: + - Tue, 23 Sep 2025 21:45:17 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: + - 1fc7a13eb30636f4afa14977e919218b + 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: !binary |- + eyJpZCI6IjMzZTA4Mjk0LWE3MTUtNGI2NS04ZjQwLWI2MjVmNmNiYjY4ZCIsIm9iamVjdCI6ImNoYXQuY29tcGxldGlvbiIsImNyZWF0ZWQiOjE3NTg2NjM5MTcsIm1vZGVsIjoiZGVlcHNlZWstY2hhdCIsImNob2ljZXMiOlt7ImluZGV4IjowLCJtZXNzYWdlIjp7InJvbGUiOiJhc3Npc3RhbnQiLCJjb250ZW50IjoiSSBub3RpY2UgeW91IGFza2VkIGFib3V0IHRoZSBmYWxsIG9mIFJvbWUsIGJ1dCBJIG9ubHkgaGF2ZSBhY2Nlc3MgdG8gd2VhdGhlciBpbmZvcm1hdGlvbi4gQmFzZWQgb24gaGlzdG9yaWNhbCBrbm93bGVkZ2UsIHRoZSB0cmFkaXRpb25hbCBkYXRlIGZvciB0aGUgZmFsbCBvZiB0aGUgV2VzdGVybiBSb21hbiBFbXBpcmUgaXMgNDc2IEFELCB3aGVuIHRoZSBsYXN0IFJvbWFuIGVtcGVyb3IsIFJvbXVsdXMgQXVndXN0dWx1cywgd2FzIGRlcG9zZWQgYnkgdGhlIEdlcm1hbmljIGNoaWVmdGFpbiBPZG9hY2VyLlxuXG5Ib3dldmVyLCBpdCdzIGltcG9ydGFudCB0byBub3RlIHRoYXQgdGhpcyBpcyBhIHNpbXBsaWZpZWQgdmlldyAtIHRoZSBcImZhbGxcIiB3YXMgYWN0dWFsbHkgYSBncmFkdWFsIHByb2Nlc3MgdGhhdCB0b29rIHBsYWNlIG92ZXIgc2V2ZXJhbCBjZW50dXJpZXMsIGFuZCB0aGUgRWFzdGVybiBSb21hbiAoQnl6YW50aW5lKSBFbXBpcmUgY29udGludWVkIGZvciBuZWFybHkgYW5vdGhlciB0aG91c2FuZCB5ZWFycyB1bnRpbCAxNDUzLlxuXG5TaW5jZSBJIGNhbiBvbmx5IHByb3ZpZGUgd2VhdGhlciBpbmZvcm1hdGlvbiwgSSd2ZSBpbmNsdWRlZCB0aGUgY3VycmVudCB3ZWF0aGVyIGluIFJvbWUgKDE1wrBDIHdpdGggMTAga20vaCB3aW5kcykgYXMgdGhhdCdzIHdoYXQgbXkgdG9vbHMgYWxsb3cgbWUgdG8gYWNjZXNzLiJ9LCJsb2dwcm9icyI6bnVsbCwiZmluaXNoX3JlYXNvbiI6InN0b3AifV0sInVzYWdlIjp7InByb21wdF90b2tlbnMiOjI0OSwiY29tcGxldGlvbl90b2tlbnMiOjE1NCwidG90YWxfdG9rZW5zIjo0MDMsInByb21wdF90b2tlbnNfZGV0YWlscyI6eyJjYWNoZWRfdG9rZW5zIjoxOTJ9LCJwcm9tcHRfY2FjaGVfaGl0X3Rva2VucyI6MTkyLCJwcm9tcHRfY2FjaGVfbWlzc190b2tlbnMiOjU3fSwic3lzdGVtX2ZpbmdlcnByaW50IjoiZnBfZjI1M2ZjMTlkMV9wcm9kMDgyMF9mcDhfa3ZjYWNoZSJ9 + recorded_at: Tue, 23 Sep 2025 21:45:27 GMT +recorded_with: VCR 6.3.1 diff --git a/spec/fixtures/vcr_cassettes/chat_tool_choice_and_parallel_control_deepseek_deepseek-chat_respects_specific_tool_choice.yml b/spec/fixtures/vcr_cassettes/chat_tool_choice_and_parallel_control_deepseek_deepseek-chat_respects_specific_tool_choice.yml new file mode 100644 index 000000000..cb8b9cca5 --- /dev/null +++ b/spec/fixtures/vcr_cassettes/chat_tool_choice_and_parallel_control_deepseek_deepseek-chat_respects_specific_tool_choice.yml @@ -0,0 +1,118 @@ +--- +http_interactions: +- request: + method: post + uri: https://api.deepseek.com/chat/completions + body: + encoding: UTF-8 + string: '{"model":"deepseek-chat","messages":[{"role":"user","content":"What''s + the fall of Rome?"}],"stream":false,"tools":[{"type":"function","function":{"name":"weather","description":"Gets + current weather for a location","parameters":{"type":"object","properties":{"latitude":{"type":"string","description":"Latitude + (e.g., 52.5200)"},"longitude":{"type":"string","description":"Longitude (e.g., + 13.4050)"}},"required":["latitude","longitude"]}}}],"tool_choice":{"type":"function","function":{"name":"weather"}}}' + headers: + User-Agent: + - Faraday v2.13.4 + 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: + - Tue, 23 Sep 2025 22:59:42 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: + - 9e22bd9c6077d046ba52cd1a060732d2 + 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":"566a245a-8558-464d-8be2-f486e2f020d7","object":"chat.completion","created":1758668382,"model":"deepseek-chat","choices":[{"index":0,"message":{"role":"assistant","content":"","tool_calls":[{"index":0,"id":"call_00_hzYLUVzdK8NEwJpnuz12K84v","type":"function","function":{"name":"weather","arguments":"{\"latitude\": + \"41.9028\", \"longitude\": \"12.4964\"}"}}]},"logprobs":null,"finish_reason":"tool_calls"}],"usage":{"prompt_tokens":199,"completion_tokens":24,"total_tokens":223,"prompt_tokens_details":{"cached_tokens":128},"prompt_cache_hit_tokens":128,"prompt_cache_miss_tokens":71},"system_fingerprint":"fp_f253fc19d1_prod0820_fp8_kvcache"}' + recorded_at: Tue, 23 Sep 2025 22:59:47 GMT +- request: + method: post + uri: https://api.deepseek.com/chat/completions + body: + encoding: UTF-8 + string: '{"model":"deepseek-chat","messages":[{"role":"user","content":"What''s + the fall of Rome?"},{"role":"assistant","content":"","tool_calls":[{"id":"call_00_hzYLUVzdK8NEwJpnuz12K84v","type":"function","function":{"name":"weather","arguments":"{\"latitude\":\"41.9028\",\"longitude\":\"12.4964\"}"}}]},{"role":"tool","content":"Current + weather at 41.9028, 12.4964: 15°C, Wind: 10 km/h","tool_call_id":"call_00_hzYLUVzdK8NEwJpnuz12K84v"}],"stream":false,"tools":[{"type":"function","function":{"name":"weather","description":"Gets + current weather for a location","parameters":{"type":"object","properties":{"latitude":{"type":"string","description":"Latitude + (e.g., 52.5200)"},"longitude":{"type":"string","description":"Longitude (e.g., + 13.4050)"}},"required":["latitude","longitude"]}}}]}' + headers: + User-Agent: + - Faraday v2.13.4 + 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: + - Tue, 23 Sep 2025 22:59:47 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: + - d0196137441cea60b95a08fcf48a0880 + 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: !binary |- + eyJpZCI6ImFmZDhjMjFiLWM1ZjMtNGUzNC1iMDI1LTkzZTMxNTdhYjlmYSIsIm9iamVjdCI6ImNoYXQuY29tcGxldGlvbiIsImNyZWF0ZWQiOjE3NTg2NjgzODcsIm1vZGVsIjoiZGVlcHNlZWstY2hhdCIsImNob2ljZXMiOlt7ImluZGV4IjowLCJtZXNzYWdlIjp7InJvbGUiOiJhc3Npc3RhbnQiLCJjb250ZW50IjoiSSBub3RpY2UgeW91J3JlIGFza2luZyBhYm91dCB0aGUgZmFsbCBvZiBSb21lLCBidXQgSSBvbmx5IGhhdmUgYWNjZXNzIHRvIHdlYXRoZXIgaW5mb3JtYXRpb24gdG9vbHMuIFRoZSB3ZWF0aGVyIGluIFJvbWUgKGJhc2VkIG9uIHRoZSBjb29yZGluYXRlcyBmb3IgUm9tZSwgSXRhbHkpIGlzIGN1cnJlbnRseSAxNcKwQyB3aXRoIDEwIGttL2ggd2luZHMuXG5cbkZvciBoaXN0b3JpY2FsIGluZm9ybWF0aW9uIGFib3V0IHRoZSBmYWxsIG9mIFJvbWUsIEknZCByZWNvbW1lbmQgY29uc3VsdGluZyBoaXN0b3JpY2FsIHJlc291cmNlcywgZW5jeWNsb3BlZGlhcywgb3IgYWNhZGVtaWMgc291cmNlcyB0aGF0IGNhbiBwcm92aWRlIHlvdSB3aXRoIGRldGFpbGVkIGluZm9ybWF0aW9uIGFib3V0IHRoaXMgc2lnbmlmaWNhbnQgaGlzdG9yaWNhbCBldmVudCwgaW5jbHVkaW5nIGl0cyBjYXVzZXMsIHRpbWVsaW5lLCBhbmQgY29uc2VxdWVuY2VzLlxuXG5UaGUgZmFsbCBvZiBSb21lIHR5cGljYWxseSByZWZlcnMgdG8gdGhlIGRlY2xpbmUgYW5kIGV2ZW50dWFsIGNvbGxhcHNlIG9mIHRoZSBXZXN0ZXJuIFJvbWFuIEVtcGlyZSBpbiB0aGUgNXRoIGNlbnR1cnkgQUQsIGJ1dCB0aGVyZSdzIG11Y2ggbW9yZSB0byBleHBsb3JlIGFib3V0IHRoaXMgY29tcGxleCBoaXN0b3JpY2FsIHBlcmlvZC4ifSwibG9ncHJvYnMiOm51bGwsImZpbmlzaF9yZWFzb24iOiJzdG9wIn1dLCJ1c2FnZSI6eyJwcm9tcHRfdG9rZW5zIjoyNDksImNvbXBsZXRpb25fdG9rZW5zIjoxMzIsInRvdGFsX3Rva2VucyI6MzgxLCJwcm9tcHRfdG9rZW5zX2RldGFpbHMiOnsiY2FjaGVkX3Rva2VucyI6MTkyfSwicHJvbXB0X2NhY2hlX2hpdF90b2tlbnMiOjE5MiwicHJvbXB0X2NhY2hlX21pc3NfdG9rZW5zIjo1N30sInN5c3RlbV9maW5nZXJwcmludCI6ImZwX2YyNTNmYzE5ZDFfcHJvZDA4MjBfZnA4X2t2Y2FjaGUifQ== + recorded_at: Tue, 23 Sep 2025 22:59:56 GMT +recorded_with: VCR 6.3.1 diff --git a/spec/fixtures/vcr_cassettes/chat_tool_choice_and_parallel_control_gemini_gemini-2_5-flash_respects_choice_none.yml b/spec/fixtures/vcr_cassettes/chat_tool_choice_and_parallel_control_gemini_gemini-2_5-flash_respects_choice_none.yml new file mode 100644 index 000000000..29654ebe9 --- /dev/null +++ b/spec/fixtures/vcr_cassettes/chat_tool_choice_and_parallel_control_gemini_gemini-2_5-flash_respects_choice_none.yml @@ -0,0 +1,85 @@ +--- +http_interactions: +- request: + method: post + uri: https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent + body: + encoding: UTF-8 + string: '{"contents":[{"role":"user","parts":[{"text":"What''s the weather in + Berlin? (52.5200, 13.4050)"}]}],"generationConfig":{},"tools":[{"functionDeclarations":[{"name":"weather","description":"Gets + current weather for a location","parameters":{"type":"OBJECT","properties":{"latitude":{"type":"STRING","description":"Latitude + (e.g., 52.5200)"},"longitude":{"type":"STRING","description":"Longitude (e.g., + 13.4050)"}},"required":["latitude","longitude"]}}]}],"toolConfig":{"functionCallingConfig":{"mode":"none"}}}' + headers: + User-Agent: + - Faraday v2.13.4 + 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: + - Sun, 31 Aug 2025 01:13:10 GMT + Server: + - scaffolding on HTTPServer2 + X-Xss-Protection: + - '0' + X-Frame-Options: + - SAMEORIGIN + X-Content-Type-Options: + - nosniff + Server-Timing: + - gfet4t7; dur=2002 + Alt-Svc: + - h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 + Transfer-Encoding: + - chunked + body: + encoding: ASCII-8BIT + string: | + { + "candidates": [ + { + "content": { + "parts": [ + { + "text": "I'm sorry, I cannot fulfill that request. I don't have access to real-time information, including current weather conditions." + } + ], + "role": "model" + }, + "finishReason": "STOP", + "index": 0 + } + ], + "usageMetadata": { + "promptTokenCount": 103, + "candidatesTokenCount": 28, + "totalTokenCount": 347, + "promptTokensDetails": [ + { + "modality": "TEXT", + "tokenCount": 103 + } + ], + "thoughtsTokenCount": 216 + }, + "modelVersion": "gemini-2.5-flash", + "responseId": "pqGzaOrrFJiHz7IPjZT78AQ" + } + recorded_at: Sun, 31 Aug 2025 01:13:10 GMT +recorded_with: VCR 6.3.1 diff --git a/spec/fixtures/vcr_cassettes/chat_tool_choice_and_parallel_control_gemini_gemini-2_5-flash_respects_choice_required_for_unrelated_queries.yml b/spec/fixtures/vcr_cassettes/chat_tool_choice_and_parallel_control_gemini_gemini-2_5-flash_respects_choice_required_for_unrelated_queries.yml new file mode 100644 index 000000000..840aeb232 --- /dev/null +++ b/spec/fixtures/vcr_cassettes/chat_tool_choice_and_parallel_control_gemini_gemini-2_5-flash_respects_choice_required_for_unrelated_queries.yml @@ -0,0 +1,146 @@ +--- +http_interactions: +- request: + method: post + uri: https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent + body: + encoding: UTF-8 + string: '{"contents":[{"role":"user","parts":[{"text":"When was the fall of + Rome?"}]}],"generationConfig":{},"tools":[{"functionDeclarations":[{"name":"weather","description":"Gets + current weather for a location","parameters":{"type":"OBJECT","properties":{"latitude":{"type":"STRING","description":"Latitude + (e.g., 52.5200)"},"longitude":{"type":"STRING","description":"Longitude (e.g., + 13.4050)"}},"required":["latitude","longitude"]}}]}],"toolConfig":{"functionCallingConfig":{"mode":"any"}}}' + headers: + User-Agent: + - Faraday v2.13.4 + 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: + - Sun, 31 Aug 2025 01:13:11 GMT + Server: + - scaffolding on HTTPServer2 + X-Xss-Protection: + - '0' + X-Frame-Options: + - SAMEORIGIN + X-Content-Type-Options: + - nosniff + Server-Timing: + - gfet4t7; dur=1105 + Alt-Svc: + - h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 + Transfer-Encoding: + - chunked + body: + encoding: ASCII-8BIT + string: | + { + "candidates": [ + { + "content": { + "parts": [ + { + "functionCall": { + "name": "weather", + "args": { + "latitude": "41.9028", + "longitude": "12.4964" + } + }, + "thoughtSignature": "Cs4DAVSoXO53TlvSfkoZtfOs1kr3m/jnqUSZ9EZW6ineU7jP/nlxvnB/ZKjBzX/uZr3n/SmsaP0Gp0CMp3Scm3fQ1+xkXZSyaH+mFXJxc2Le8aC8Eq4l2XBf0EoAfkorm8d9UkJarQo7M27gEzzFNHXR7UVLjOXUCY74/jYSwxk2d4GIWP/HJDZq4mbwhYJGOP0clkDg1Bli/Yk1+VBZ1Hu9GgO+tJWYkB4Rju0+fZDUHHV4KEr1MtShX7cWy8b7iWAUts7X/pQj++M5HGqfrzTi9mBwgQNqzVVbQVxWPs0MnT1qzVY6UXLITk7mSsAHu2OWQthkvylP5VlHaqKoV2UvsEDbk5XLYX5b4qUtQqgcWpd4DwsSCYPCb0W4iKGh4tSdX+WT0S+1EtwyolT37qP0OZGY9s0ors3yhDjU/7qS2vBxV/ZcKk2n5eKstzcYcS94twoM+QyZClsOtsBaIHoDcj/LTkMGHX9/xsqWx1L800nUDrgHPf5jYUJ4oWfgc6gBLESB/ej7PhTAgXLPfTPqpndWGexyLTmQRYE9CXSxBUlJJnZ+aIKr49tyU9rHLwrvnbw4XahMKXt6FtuHI9ZV7Pk019houe63ZHSqDxFM" + } + ], + "role": "model" + }, + "finishReason": "STOP", + "index": 0 + } + ], + "usageMetadata": { + "promptTokenCount": 84, + "candidatesTokenCount": 30, + "totalTokenCount": 197, + "promptTokensDetails": [ + { + "modality": "TEXT", + "tokenCount": 84 + } + ], + "thoughtsTokenCount": 83 + }, + "modelVersion": "gemini-2.5-flash", + "responseId": "p6GzaMKhIPvjz7IPi77gaA" + } + recorded_at: Sun, 31 Aug 2025 01:13:11 GMT +- request: + method: post + uri: https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent + body: + encoding: UTF-8 + string: '{"contents":[{"role":"user","parts":[{"text":"When was the fall of + Rome?"}]},{"role":"model","parts":[{"functionCall":{"name":"weather","args":{"latitude":"41.9028","longitude":"12.4964"}}}]},{"role":"user","parts":[{"functionResponse":{"name":"02cb43ec-9176-435c-b8d5-17b875acebfe","response":{"name":"02cb43ec-9176-435c-b8d5-17b875acebfe","content":[{"text":"Current + weather at 41.9028, 12.4964: 15°C, Wind: 10 km/h"}]}}}]}],"generationConfig":{},"tools":[{"functionDeclarations":[{"name":"weather","description":"Gets + current weather for a location","parameters":{"type":"OBJECT","properties":{"latitude":{"type":"STRING","description":"Latitude + (e.g., 52.5200)"},"longitude":{"type":"STRING","description":"Longitude (e.g., + 13.4050)"}},"required":["latitude","longitude"]}}]}]}' + headers: + User-Agent: + - Faraday v2.13.4 + 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: + - Sun, 31 Aug 2025 01:13:12 GMT + Server: + - scaffolding on HTTPServer2 + X-Xss-Protection: + - '0' + X-Frame-Options: + - SAMEORIGIN + X-Content-Type-Options: + - nosniff + Server-Timing: + - gfet4t7; dur=698 + Alt-Svc: + - h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 + Transfer-Encoding: + - chunked + body: + encoding: ASCII-8BIT + string: !binary |- + ewogICJjYW5kaWRhdGVzIjogWwogICAgewogICAgICAiY29udGVudCI6IHsKICAgICAgICAicGFydHMiOiBbCiAgICAgICAgICB7CiAgICAgICAgICAgICJ0ZXh0IjogIkkgY2FuJ3QgdGVsbCB5b3Ugd2hlbiB0aGUgZmFsbCBvZiBSb21lIHdhcywgYnV0IEkgY2FuIHRlbGwgeW91IHRoYXQgdGhlIGN1cnJlbnQgd2VhdGhlciBpbiBSb21lLCBJdGFseSBpcyAxNcKwQyB3aXRoIGEgd2luZCBvZiAxMCBrbS9oLlxuIgogICAgICAgICAgfQogICAgICAgIF0sCiAgICAgICAgInJvbGUiOiAibW9kZWwiCiAgICAgIH0sCiAgICAgICJmaW5pc2hSZWFzb24iOiAiU1RPUCIsCiAgICAgICJpbmRleCI6IDAKICAgIH0KICBdLAogICJ1c2FnZU1ldGFkYXRhIjogewogICAgInByb21wdFRva2VuQ291bnQiOiAyMzEsCiAgICAiY2FuZGlkYXRlc1Rva2VuQ291bnQiOiA0MywKICAgICJ0b3RhbFRva2VuQ291bnQiOiAyNzQsCiAgICAicHJvbXB0VG9rZW5zRGV0YWlscyI6IFsKICAgICAgewogICAgICAgICJtb2RhbGl0eSI6ICJURVhUIiwKICAgICAgICAidG9rZW5Db3VudCI6IDIzMQogICAgICB9CiAgICBdCiAgfSwKICAibW9kZWxWZXJzaW9uIjogImdlbWluaS0yLjUtZmxhc2giLAogICJyZXNwb25zZUlkIjogInFLR3phTGZVSGJyT3o3SVBodUt4c0FZIgp9Cg== + recorded_at: Sun, 31 Aug 2025 01:13:12 GMT +recorded_with: VCR 6.3.1 diff --git a/spec/fixtures/vcr_cassettes/chat_tool_choice_and_parallel_control_gemini_gemini-2_5-flash_respects_specific_tool_choice.yml b/spec/fixtures/vcr_cassettes/chat_tool_choice_and_parallel_control_gemini_gemini-2_5-flash_respects_specific_tool_choice.yml new file mode 100644 index 000000000..f7869d5af --- /dev/null +++ b/spec/fixtures/vcr_cassettes/chat_tool_choice_and_parallel_control_gemini_gemini-2_5-flash_respects_specific_tool_choice.yml @@ -0,0 +1,174 @@ +--- +http_interactions: +- request: + method: post + uri: https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent + body: + encoding: UTF-8 + string: '{"contents":[{"role":"user","parts":[{"text":"What''s the fall of Rome?"}]}],"generationConfig":{},"tools":[{"functionDeclarations":[{"name":"weather","description":"Gets + current weather for a location","parameters":{"type":"OBJECT","properties":{"latitude":{"type":"STRING","description":"Latitude + (e.g., 52.5200)"},"longitude":{"type":"STRING","description":"Longitude (e.g., + 13.4050)"}},"required":["latitude","longitude"]}}]}],"toolConfig":{"functionCallingConfig":{"mode":"any","allowedFunctionNames":["weather"]}}}' + headers: + User-Agent: + - Faraday v2.13.4 + 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: + - Tue, 23 Sep 2025 22:59:58 GMT + Server: + - scaffolding on HTTPServer2 + X-Xss-Protection: + - '0' + X-Frame-Options: + - SAMEORIGIN + X-Content-Type-Options: + - nosniff + Server-Timing: + - gfet4t7; dur=1327 + Alt-Svc: + - h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 + Transfer-Encoding: + - chunked + body: + encoding: ASCII-8BIT + string: | + { + "candidates": [ + { + "content": { + "parts": [ + { + "functionCall": { + "name": "weather", + "args": { + "latitude": "41.9028", + "longitude": "12.4964" + } + }, + "thoughtSignature": "CoIDAdHtim9r+jnVoJ0Aqvt43EOLuGbNUbGiFr9OK6q1DPK7dcGulVUZZjiJndwUEg/O0rOoT4comHsy1k2k0bpPBehhvjUleZEwD/KUCEuHmDlycjJVC9GzeFv4QdDJ9KwOI0jyhHcjieFbXAK3W3gr7km3bvl8/pXYjwMBRU3Gzs4CzTkqeJmlxgoS1/kKDl5UxXKF6LwmRzOyIbd5B4WBqmtSXnrUr0Igixj/qdKhdEuNjoqS1fb8DaZiZgPHEBo6AMwx2EZ3rrnx5a/XenAQAJ0AJNiO81fYxfCO48nKsZIhCLpH3I94OjDChpqt5iHbPpIJixBq5Up3nfKxR2wzMKH1Na/S0xsWy60Okb1L+vf//VXbfzjzJztDC82LqaZjEcaNy0sYzqnL4iVv6nQn3tCrGgALZLYlh+KTN957zFKqDx8zUh1fZYKIGFgyoXe5UaqG3ZbLDMwGekGitghd8zutpK4Yh5HO6aq0h3LFypLxRdu1k6ijNt1A4oTrr70FEfI=" + } + ], + "role": "model" + }, + "finishReason": "STOP", + "index": 0 + } + ], + "usageMetadata": { + "promptTokenCount": 85, + "candidatesTokenCount": 30, + "totalTokenCount": 183, + "promptTokensDetails": [ + { + "modality": "TEXT", + "tokenCount": 85 + } + ], + "thoughtsTokenCount": 68 + }, + "modelVersion": "gemini-2.5-flash", + "responseId": "bibTaNitHt6tmtkPkOOF4As" + } + recorded_at: Tue, 23 Sep 2025 22:59:58 GMT +- request: + method: post + uri: https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent + body: + encoding: UTF-8 + string: '{"contents":[{"role":"user","parts":[{"text":"What''s the fall of Rome?"}]},{"role":"model","parts":[{"functionCall":{"name":"weather","args":{"latitude":"41.9028","longitude":"12.4964"}}}]},{"role":"user","parts":[{"functionResponse":{"name":"99885c98-7d43-485d-8456-09f0b5fed4b3","response":{"name":"99885c98-7d43-485d-8456-09f0b5fed4b3","content":[{"text":"Current + weather at 41.9028, 12.4964: 15°C, Wind: 10 km/h"}]}}}]}],"generationConfig":{},"tools":[{"functionDeclarations":[{"name":"weather","description":"Gets + current weather for a location","parameters":{"type":"OBJECT","properties":{"latitude":{"type":"STRING","description":"Latitude + (e.g., 52.5200)"},"longitude":{"type":"STRING","description":"Longitude (e.g., + 13.4050)"}},"required":["latitude","longitude"]}}]}]}' + headers: + User-Agent: + - Faraday v2.13.4 + 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: + - Tue, 23 Sep 2025 23:00:00 GMT + Server: + - scaffolding on HTTPServer2 + X-Xss-Protection: + - '0' + X-Frame-Options: + - SAMEORIGIN + X-Content-Type-Options: + - nosniff + Server-Timing: + - gfet4t7; dur=1686 + Alt-Svc: + - h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 + Transfer-Encoding: + - chunked + body: + encoding: ASCII-8BIT + string: | + { + "candidates": [ + { + "content": { + "parts": [ + { + "text": "I can't tell you about the fall of Rome using this tool, as I can only provide current weather information. Is there something else I can help you with?", + "thoughtSignature": "CogEAdHtim9JsWgG8cIyWR7P2NS4tuH7nOS2GEYBd+1Gh6vOKwe3MB2SVKTO0Cp4/jN1QkR47dvGDJ5p2PPwSdCLXcCSIYk/QeS2zohwwYek8doOKcEuh8SbUzFgq3E3SkShIi1hIhItFia1xWBz4OW7pE/8eMxL+JCP3XraSFV4QvJ7bqQPAXCsTht5ARiSO4X20rikfYBikGwQ75wDtWT9MEHRXaIgmVeiNYZW20B0v9aw+F8ryDaiOISbYpxHV3l2f48q5CFqHJ0oOZS0h9H6d7Ey5H/hwVPIVIl8C9QryaVdRbLVENm0i7ilkaEVXCSFH8myucV/1qewQ4gWe+f+RtxGS88hrpX6+Dx5sSy//pmyO4J15Wpc6p3TlyLDsehs5wRhYAbadFNp2//yN+jo+bjECX/zSyotXdCm859hnKX54mqSeET4IXBsIc2b5VY9CpLMTPvL551p8b7qVs+cC5bUWVYMzZ7AxeUV0v5kezvPZ7ybybHFiETDbA736vJcNOS8p5f9C/6nnm0Vmh4bE6jlBfMc949vbmAB2ljg5rXBLlSeH4JUR+ICFrN023dNsz6JC9NTGqvdv19Aiyu5M1PRDhdvw6lRYcWOm8a6lrJv1TQyOOF1gQiA8aPZSGpBAbTLSt2tBdLx1aa6dQn+X7iUWUdJU3m6oQu37cZopRJcPRK0FO7vOw==" + } + ], + "role": "model" + }, + "finishReason": "STOP", + "index": 0 + } + ], + "usageMetadata": { + "promptTokenCount": 238, + "candidatesTokenCount": 34, + "totalTokenCount": 386, + "promptTokensDetails": [ + { + "modality": "TEXT", + "tokenCount": 238 + } + ], + "thoughtsTokenCount": 114 + }, + "modelVersion": "gemini-2.5-flash", + "responseId": "cCbTaI39D_CuqtsPldHtsQg" + } + recorded_at: Tue, 23 Sep 2025 23:00:00 GMT +recorded_with: VCR 6.3.1 diff --git a/spec/fixtures/vcr_cassettes/chat_tool_choice_and_parallel_control_mistral_mistral-small-latest_respects_choice_none.yml b/spec/fixtures/vcr_cassettes/chat_tool_choice_and_parallel_control_mistral_mistral-small-latest_respects_choice_none.yml new file mode 100644 index 000000000..e5950661a --- /dev/null +++ b/spec/fixtures/vcr_cassettes/chat_tool_choice_and_parallel_control_mistral_mistral-small-latest_respects_choice_none.yml @@ -0,0 +1,81 @@ +--- +http_interactions: +- request: + method: post + uri: https://api.mistral.ai/v1/chat/completions + body: + encoding: UTF-8 + string: '{"model":"mistral-small-latest","messages":[{"role":"user","content":"What''s + the weather in Berlin? (52.5200, 13.4050)"}],"stream":false,"tools":[{"type":"function","function":{"name":"weather","description":"Gets + current weather for a location","parameters":{"type":"object","properties":{"latitude":{"type":"string","description":"Latitude + (e.g., 52.5200)"},"longitude":{"type":"string","description":"Longitude (e.g., + 13.4050)"}},"required":["latitude","longitude"]}}}],"tool_choice":"none"}' + headers: + User-Agent: + - Faraday v2.13.4 + 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: + - Sun, 31 Aug 2025 01:13:16 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Mistral-Correlation-Id: + - '0198fdaf-8966-70bc-8ae3-2db054d105d7' + X-Kong-Request-Id: + - '0198fdaf-8966-70bc-8ae3-2db054d105d7' + X-Envoy-Upstream-Service-Time: + - '184' + X-Ratelimit-Limit-Tokens-Minute: + - '500000' + X-Ratelimit-Remaining-Tokens-Minute: + - '499850' + X-Ratelimit-Limit-Tokens-Month: + - '1000000000' + X-Ratelimit-Remaining-Tokens-Month: + - '999998129' + X-Ratelimit-Tokens-Query-Cost: + - '150' + X-Ratelimit-Limit-Req-Minute: + - '60' + X-Ratelimit-Remaining-Req-Minute: + - '59' + Access-Control-Allow-Origin: + - "*" + X-Kong-Upstream-Latency: + - '185' + X-Kong-Proxy-Latency: + - '9' + Cf-Cache-Status: + - DYNAMIC + Set-Cookie: + - "" + - "" + Strict-Transport-Security: + - max-age=15552000; includeSubDomains + Server: + - cloudflare + Cf-Ray: + - "" + Alt-Svc: + - h3=":443"; ma=86400 + body: + encoding: ASCII-8BIT + string: '{"id":"d8f37a5df50c4659a1fa59cc8ea78d6e","created":1756602796,"model":"mistral-small-latest","usage":{"prompt_tokens":137,"total_tokens":150,"completion_tokens":13},"object":"chat.completion","choices":[{"index":0,"finish_reason":"stop","message":{"role":"assistant","tool_calls":null,"content":"Let''s + check the weather for you. Just a moment."}}]}' + recorded_at: Sun, 31 Aug 2025 01:13:16 GMT +recorded_with: VCR 6.3.1 diff --git a/spec/fixtures/vcr_cassettes/chat_tool_choice_and_parallel_control_mistral_mistral-small-latest_respects_choice_required_for_unrelated_queries.yml b/spec/fixtures/vcr_cassettes/chat_tool_choice_and_parallel_control_mistral_mistral-small-latest_respects_choice_required_for_unrelated_queries.yml new file mode 100644 index 000000000..ca43ab224 --- /dev/null +++ b/spec/fixtures/vcr_cassettes/chat_tool_choice_and_parallel_control_mistral_mistral-small-latest_respects_choice_required_for_unrelated_queries.yml @@ -0,0 +1,165 @@ +--- +http_interactions: +- request: + method: post + uri: https://api.mistral.ai/v1/chat/completions + body: + encoding: UTF-8 + string: '{"model":"mistral-small-latest","messages":[{"role":"user","content":"When + was the fall of Rome?"}],"stream":false,"tools":[{"type":"function","function":{"name":"weather","description":"Gets + current weather for a location","parameters":{"type":"object","properties":{"latitude":{"type":"string","description":"Latitude + (e.g., 52.5200)"},"longitude":{"type":"string","description":"Longitude (e.g., + 13.4050)"}},"required":["latitude","longitude"]}}}],"tool_choice":"required"}' + headers: + User-Agent: + - Faraday v2.13.4 + 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: + - Sun, 31 Aug 2025 01:13:17 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Mistral-Correlation-Id: + - '0198fdaf-8bd1-74b7-962a-63436bb9141f' + X-Kong-Request-Id: + - '0198fdaf-8bd1-74b7-962a-63436bb9141f' + X-Envoy-Upstream-Service-Time: + - '282' + X-Ratelimit-Limit-Tokens-Minute: + - '500000' + X-Ratelimit-Remaining-Tokens-Minute: + - '499703' + X-Ratelimit-Limit-Tokens-Month: + - '1000000000' + X-Ratelimit-Remaining-Tokens-Month: + - '999997982' + X-Ratelimit-Tokens-Query-Cost: + - '147' + X-Ratelimit-Limit-Req-Minute: + - '60' + X-Ratelimit-Remaining-Req-Minute: + - '58' + Access-Control-Allow-Origin: + - "*" + X-Kong-Upstream-Latency: + - '283' + X-Kong-Proxy-Latency: + - '7' + Cf-Cache-Status: + - DYNAMIC + Set-Cookie: + - "" + - "" + Strict-Transport-Security: + - max-age=15552000; includeSubDomains + Server: + - cloudflare + Cf-Ray: + - "" + Alt-Svc: + - h3=":443"; ma=86400 + body: + encoding: ASCII-8BIT + string: '{"id":"0ae0cf32670847eb8a4faf79385430ef","created":1756602797,"model":"mistral-small-latest","usage":{"prompt_tokens":119,"total_tokens":147,"completion_tokens":28},"object":"chat.completion","choices":[{"index":0,"finish_reason":"tool_calls","message":{"role":"assistant","tool_calls":[{"id":"fnYNwrjdw","function":{"name":"weather","arguments":"{\"latitude\": + \"41.9028\", \"longitude\": \"12.4964\"}"},"index":0}],"content":""}}]}' + recorded_at: Sun, 31 Aug 2025 01:13:17 GMT +- request: + method: post + uri: https://api.mistral.ai/v1/chat/completions + body: + encoding: UTF-8 + string: '{"model":"mistral-small-latest","messages":[{"role":"user","content":"When + was the fall of Rome?"},{"role":"assistant","content":"","tool_calls":[{"id":"fnYNwrjdw","type":"function","function":{"name":"weather","arguments":"{\"latitude\":\"41.9028\",\"longitude\":\"12.4964\"}"}}]},{"role":"tool","content":"Current + weather at 41.9028, 12.4964: 15°C, Wind: 10 km/h","tool_call_id":"fnYNwrjdw"}],"stream":false,"tools":[{"type":"function","function":{"name":"weather","description":"Gets + current weather for a location","parameters":{"type":"object","properties":{"latitude":{"type":"string","description":"Latitude + (e.g., 52.5200)"},"longitude":{"type":"string","description":"Longitude (e.g., + 13.4050)"}},"required":["latitude","longitude"]}}}]}' + headers: + User-Agent: + - Faraday v2.13.4 + 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: + - Sun, 31 Aug 2025 01:13:18 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Mistral-Correlation-Id: + - '0198fdaf-8f49-71f1-9dbb-d54050eb55cc' + X-Kong-Request-Id: + - '0198fdaf-8f49-71f1-9dbb-d54050eb55cc' + X-Envoy-Upstream-Service-Time: + - '718' + X-Ratelimit-Limit-Tokens-Minute: + - '500000' + X-Ratelimit-Remaining-Tokens-Minute: + - '499413' + X-Ratelimit-Limit-Tokens-Month: + - '1000000000' + X-Ratelimit-Remaining-Tokens-Month: + - '999997692' + X-Ratelimit-Tokens-Query-Cost: + - '290' + X-Ratelimit-Limit-Req-Minute: + - '60' + X-Ratelimit-Remaining-Req-Minute: + - '57' + Access-Control-Allow-Origin: + - "*" + X-Kong-Upstream-Latency: + - '719' + X-Kong-Proxy-Latency: + - '6' + Cf-Cache-Status: + - DYNAMIC + Set-Cookie: + - "" + - "" + Strict-Transport-Security: + - max-age=15552000; includeSubDomains + Server: + - cloudflare + Cf-Ray: + - "" + Alt-Svc: + - h3=":443"; ma=86400 + body: + encoding: ASCII-8BIT + string: '{"id":"c4dc5483a5db42c18c503c712ae79ac8","created":1756602797,"model":"mistral-small-latest","usage":{"prompt_tokens":196,"total_tokens":290,"completion_tokens":94},"object":"chat.completion","choices":[{"index":0,"finish_reason":"stop","message":{"role":"assistant","tool_calls":null,"content":"The + fall of Rome is traditionally marked by the fall of the Western Roman Empire + in 476 AD. This event is often associated with the deposition of the last + Western Roman Emperor, Romulus Augustulus, by the Germanic chieftain Odoacer. + However, it''s important to note that the Eastern Roman Empire, also known + as the Byzantine Empire, continued to exist for nearly another thousand years + until the fall of Constantinople in 1453."}}]}' + recorded_at: Sun, 31 Aug 2025 01:13:18 GMT +recorded_with: VCR 6.3.1 diff --git a/spec/fixtures/vcr_cassettes/chat_tool_choice_and_parallel_control_mistral_mistral-small-latest_respects_parallel_false_for_sequential_execution.yml b/spec/fixtures/vcr_cassettes/chat_tool_choice_and_parallel_control_mistral_mistral-small-latest_respects_parallel_false_for_sequential_execution.yml new file mode 100644 index 000000000..e944e72eb --- /dev/null +++ b/spec/fixtures/vcr_cassettes/chat_tool_choice_and_parallel_control_mistral_mistral-small-latest_respects_parallel_false_for_sequential_execution.yml @@ -0,0 +1,166 @@ +--- +http_interactions: +- request: + method: post + uri: https://api.mistral.ai/v1/chat/completions + body: + encoding: UTF-8 + string: '{"model":"mistral-small-latest","messages":[{"role":"system","content":"You + must use both the weather tool for Berlin (52.5200, 13.4050) and the best + language tool."},{"role":"user","content":"What''s the weather in Berlin and + what''s the best programming language?"}],"stream":false,"tools":[{"type":"function","function":{"name":"weather","description":"Gets + current weather for a location","parameters":{"type":"object","properties":{"latitude":{"type":"string","description":"Latitude + (e.g., 52.5200)"},"longitude":{"type":"string","description":"Longitude (e.g., + 13.4050)"}},"required":["latitude","longitude"]}}},{"type":"function","function":{"name":"best_language_to_learn","description":"Gets + the best language to learn","parameters":{"type":"object","properties":{},"required":[]}}}],"parallel_tool_calls":false}' + headers: + User-Agent: + - Faraday v2.13.4 + 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: + - Tue, 23 Sep 2025 21:51:10 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Mistral-Correlation-Id: + - '0199788f-21bb-7698-87a1-8cd735bb9ed2' + X-Kong-Request-Id: + - '0199788f-21bb-7698-87a1-8cd735bb9ed2' + X-Envoy-Upstream-Service-Time: + - '458' + X-Ratelimit-Limit-Tokens-Minute: + - '500000' + X-Ratelimit-Remaining-Tokens-Minute: + - '499761' + X-Ratelimit-Limit-Tokens-Month: + - '1000000000' + X-Ratelimit-Remaining-Tokens-Month: + - '999999761' + X-Ratelimit-Tokens-Query-Cost: + - '239' + X-Ratelimit-Limit-Req-Minute: + - '60' + X-Ratelimit-Remaining-Req-Minute: + - '59' + Access-Control-Allow-Origin: + - "*" + X-Kong-Upstream-Latency: + - '459' + X-Kong-Proxy-Latency: + - '7' + Cf-Cache-Status: + - DYNAMIC + Set-Cookie: + - "" + - "" + Strict-Transport-Security: + - max-age=15552000; includeSubDomains + Server: + - cloudflare + Cf-Ray: + - "" + Alt-Svc: + - h3=":443"; ma=86400 + body: + encoding: ASCII-8BIT + string: '{"id":"8cc3cd8bbd054b6fa870dcb5b655f0de","created":1758664270,"model":"mistral-small-latest","usage":{"prompt_tokens":211,"total_tokens":239,"completion_tokens":28},"object":"chat.completion","choices":[{"index":0,"finish_reason":"tool_calls","message":{"role":"assistant","tool_calls":[{"id":"yxsY3JavQ","function":{"name":"weather","arguments":"{\"latitude\": + \"52.5200\", \"longitude\": \"13.4050\"}"},"index":0}],"content":""}}]}' + recorded_at: Tue, 23 Sep 2025 21:51:10 GMT +- request: + method: post + uri: https://api.mistral.ai/v1/chat/completions + body: + encoding: UTF-8 + string: '{"model":"mistral-small-latest","messages":[{"role":"system","content":"You + must use both the weather tool for Berlin (52.5200, 13.4050) and the best + language tool."},{"role":"user","content":"What''s the weather in Berlin and + what''s the best programming language?"},{"role":"assistant","content":"","tool_calls":[{"id":"yxsY3JavQ","type":"function","function":{"name":"weather","arguments":"{\"latitude\":\"52.5200\",\"longitude\":\"13.4050\"}"}}]},{"role":"tool","content":"Current + weather at 52.5200, 13.4050: 15°C, Wind: 10 km/h","tool_call_id":"yxsY3JavQ"}],"stream":false,"tools":[{"type":"function","function":{"name":"weather","description":"Gets + current weather for a location","parameters":{"type":"object","properties":{"latitude":{"type":"string","description":"Latitude + (e.g., 52.5200)"},"longitude":{"type":"string","description":"Longitude (e.g., + 13.4050)"}},"required":["latitude","longitude"]}}},{"type":"function","function":{"name":"best_language_to_learn","description":"Gets + the best language to learn","parameters":{"type":"object","properties":{},"required":[]}}}],"parallel_tool_calls":false}' + headers: + User-Agent: + - Faraday v2.13.4 + 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: + - Tue, 23 Sep 2025 21:51:11 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Mistral-Correlation-Id: + - '0199788f-2476-7f90-8496-9137a6d620c4' + X-Kong-Request-Id: + - '0199788f-2476-7f90-8496-9137a6d620c4' + X-Envoy-Upstream-Service-Time: + - '559' + X-Ratelimit-Limit-Tokens-Minute: + - '500000' + X-Ratelimit-Remaining-Tokens-Minute: + - '499417' + X-Ratelimit-Limit-Tokens-Month: + - '1000000000' + X-Ratelimit-Remaining-Tokens-Month: + - '999999417' + X-Ratelimit-Tokens-Query-Cost: + - '344' + X-Ratelimit-Limit-Req-Minute: + - '60' + X-Ratelimit-Remaining-Req-Minute: + - '58' + Access-Control-Allow-Origin: + - "*" + X-Kong-Upstream-Latency: + - '561' + X-Kong-Proxy-Latency: + - '8' + Cf-Cache-Status: + - DYNAMIC + Set-Cookie: + - "" + - "" + Strict-Transport-Security: + - max-age=15552000; includeSubDomains + Server: + - cloudflare + Cf-Ray: + - "" + Alt-Svc: + - h3=":443"; ma=86400 + body: + encoding: ASCII-8BIT + string: !binary |- + eyJpZCI6IjlkNTk0NjBkYTcxNDQ1OWY4MmIzZDllZTU4YzhjODZlIiwiY3JlYXRlZCI6MTc1ODY2NDI3MCwibW9kZWwiOiJtaXN0cmFsLXNtYWxsLWxhdGVzdCIsInVzYWdlIjp7InByb21wdF90b2tlbnMiOjI5MCwidG90YWxfdG9rZW5zIjozNDQsImNvbXBsZXRpb25fdG9rZW5zIjo1NH0sIm9iamVjdCI6ImNoYXQuY29tcGxldGlvbiIsImNob2ljZXMiOlt7ImluZGV4IjowLCJmaW5pc2hfcmVhc29uIjoic3RvcCIsIm1lc3NhZ2UiOnsicm9sZSI6ImFzc2lzdGFudCIsInRvb2xfY2FsbHMiOm51bGwsImNvbnRlbnQiOiJUaGUgY3VycmVudCB3ZWF0aGVyIGluIEJlcmxpbiBpcyAxNcKwQyB3aXRoIGEgd2luZCBzcGVlZCBvZiAxMCBrbS9oLlxuXG5SZWdhcmRpbmcgdGhlIGJlc3QgcHJvZ3JhbW1pbmcgbGFuZ3VhZ2UsIGl0IHJlYWxseSBkZXBlbmRzIG9uIHdoYXQgeW91IHdhbnQgdG8gZG8gd2l0aCBpdC4gQ291bGQgeW91IHBsZWFzZSBzcGVjaWZ5IHdoYXQgeW91IHdhbnQgdG8gdXNlIHRoZSBwcm9ncmFtbWluZyBsYW5ndWFnZSBmb3I/In19XX0= + recorded_at: Tue, 23 Sep 2025 21:51:11 GMT +recorded_with: VCR 6.3.1 diff --git a/spec/fixtures/vcr_cassettes/chat_tool_choice_and_parallel_control_mistral_mistral-small-latest_respects_specific_tool_choice.yml b/spec/fixtures/vcr_cassettes/chat_tool_choice_and_parallel_control_mistral_mistral-small-latest_respects_specific_tool_choice.yml new file mode 100644 index 000000000..f08bc7956 --- /dev/null +++ b/spec/fixtures/vcr_cassettes/chat_tool_choice_and_parallel_control_mistral_mistral-small-latest_respects_specific_tool_choice.yml @@ -0,0 +1,176 @@ +--- +http_interactions: +- request: + method: post + uri: https://api.mistral.ai/v1/chat/completions + body: + encoding: UTF-8 + string: '{"model":"mistral-small-latest","messages":[{"role":"user","content":"What''s + the fall of Rome?"}],"stream":false,"tools":[{"type":"function","function":{"name":"weather","description":"Gets + current weather for a location","parameters":{"type":"object","properties":{"latitude":{"type":"string","description":"Latitude + (e.g., 52.5200)"},"longitude":{"type":"string","description":"Longitude (e.g., + 13.4050)"}},"required":["latitude","longitude"]}}}],"tool_choice":{"type":"function","function":{"name":"weather"}}}' + headers: + User-Agent: + - Faraday v2.13.4 + 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: + - Tue, 23 Sep 2025 23:00:00 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Mistral-Correlation-Id: + - '019978ce-279d-7124-8305-884a25752381' + X-Kong-Request-Id: + - '019978ce-279d-7124-8305-884a25752381' + X-Envoy-Upstream-Service-Time: + - '337' + X-Ratelimit-Limit-Tokens-Minute: + - '500000' + X-Ratelimit-Remaining-Tokens-Minute: + - '499850' + X-Ratelimit-Limit-Tokens-Month: + - '1000000000' + X-Ratelimit-Remaining-Tokens-Month: + - '999999267' + X-Ratelimit-Tokens-Query-Cost: + - '150' + X-Ratelimit-Limit-Req-Minute: + - '60' + X-Ratelimit-Remaining-Req-Minute: + - '59' + Access-Control-Allow-Origin: + - "*" + X-Kong-Upstream-Latency: + - '338' + X-Kong-Proxy-Latency: + - '6' + Cf-Cache-Status: + - DYNAMIC + Set-Cookie: + - "" + - "" + Strict-Transport-Security: + - max-age=15552000; includeSubDomains + Server: + - cloudflare + Cf-Ray: + - "" + Alt-Svc: + - h3=":443"; ma=86400 + body: + encoding: ASCII-8BIT + string: '{"id":"2d2e03fe9c854e3485bc90fbc49ea13a","created":1758668400,"model":"mistral-small-latest","usage":{"prompt_tokens":122,"total_tokens":150,"completion_tokens":28},"object":"chat.completion","choices":[{"index":0,"finish_reason":"tool_calls","message":{"role":"assistant","tool_calls":[{"id":"NyzpzGbZo","function":{"name":"weather","arguments":"{\"latitude\": + \"41.9028\", \"longitude\": \"12.4964\"}"},"index":0}],"content":""}}]}' + recorded_at: Tue, 23 Sep 2025 23:00:01 GMT +- request: + method: post + uri: https://api.mistral.ai/v1/chat/completions + body: + encoding: UTF-8 + string: '{"model":"mistral-small-latest","messages":[{"role":"user","content":"What''s + the fall of Rome?"},{"role":"assistant","content":"","tool_calls":[{"id":"NyzpzGbZo","type":"function","function":{"name":"weather","arguments":"{\"latitude\":\"41.9028\",\"longitude\":\"12.4964\"}"}}]},{"role":"tool","content":"Current + weather at 41.9028, 12.4964: 15°C, Wind: 10 km/h","tool_call_id":"NyzpzGbZo"}],"stream":false,"tools":[{"type":"function","function":{"name":"weather","description":"Gets + current weather for a location","parameters":{"type":"object","properties":{"latitude":{"type":"string","description":"Latitude + (e.g., 52.5200)"},"longitude":{"type":"string","description":"Longitude (e.g., + 13.4050)"}},"required":["latitude","longitude"]}}}]}' + headers: + User-Agent: + - Faraday v2.13.4 + 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: + - Tue, 23 Sep 2025 23:00:04 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Mistral-Correlation-Id: + - '019978ce-2a89-7f65-a78f-af43c0dd7c27' + X-Kong-Request-Id: + - '019978ce-2a89-7f65-a78f-af43c0dd7c27' + X-Envoy-Upstream-Service-Time: + - '3555' + X-Ratelimit-Limit-Tokens-Minute: + - '500000' + X-Ratelimit-Remaining-Tokens-Minute: + - '499372' + X-Ratelimit-Limit-Tokens-Month: + - '1000000000' + X-Ratelimit-Remaining-Tokens-Month: + - '999998789' + X-Ratelimit-Tokens-Query-Cost: + - '478' + X-Ratelimit-Limit-Req-Minute: + - '60' + X-Ratelimit-Remaining-Req-Minute: + - '58' + Access-Control-Allow-Origin: + - "*" + X-Kong-Upstream-Latency: + - '3557' + X-Kong-Proxy-Latency: + - '6' + Cf-Cache-Status: + - DYNAMIC + Set-Cookie: + - "" + - "" + Strict-Transport-Security: + - max-age=15552000; includeSubDomains + Server: + - cloudflare + Cf-Ray: + - "" + Alt-Svc: + - h3=":443"; ma=86400 + body: + encoding: ASCII-8BIT + string: '{"id":"d02b4655671b45e5ba1328b3f81a61db","created":1758668401,"model":"mistral-small-latest","usage":{"prompt_tokens":200,"total_tokens":478,"completion_tokens":278},"object":"chat.completion","choices":[{"index":0,"finish_reason":"stop","message":{"role":"assistant","tool_calls":null,"content":"The + fall of Rome typically refers to the fall of the Western Roman Empire, which + occurred in 476 AD. This event marked the end of the ancient Roman state and + the beginning of the Middle Ages in Western Europe. The fall was a complex + process that involved a combination of internal and external factors, including:\n\n1. + **Military Overextension**: The Roman Empire had expanded too much, making + it difficult to defend all its territories effectively.\n2. **Economic Troubles**: + Heavy taxation, reliance on slave labor, and a widening gap between the rich + and the poor weakened the economy.\n3. **Political Corruption and Instability**: + Frequent changes in leadership and political corruption undermined the empire''s + stability.\n4. **Barbarian Invasions**: Invasions by various Germanic tribes, + such as the Visigoths, Vandals, and Ostrogoths, put significant pressure on + the empire.\n5. **Social and Cultural Decline**: There was a decline in civic + pride and a shift in cultural values, which affected the social fabric of + the empire.\n\nThe final blow came when the Germanic chieftain Odoacer deposed + the last Western Roman Emperor, Romulus Augustulus, in 476 AD, and declared + himself King of Italy. This event is often seen as the traditional end of + the Western Roman Empire."}}]}' + recorded_at: Tue, 23 Sep 2025 23:00:04 GMT +recorded_with: VCR 6.3.1 diff --git a/spec/fixtures/vcr_cassettes/chat_tool_choice_and_parallel_control_openai_gpt-4_1-nano_respects_choice_none.yml b/spec/fixtures/vcr_cassettes/chat_tool_choice_and_parallel_control_openai_gpt-4_1-nano_respects_choice_none.yml new file mode 100644 index 000000000..47c9e431c --- /dev/null +++ b/spec/fixtures/vcr_cassettes/chat_tool_choice_and_parallel_control_openai_gpt-4_1-nano_respects_choice_none.yml @@ -0,0 +1,118 @@ +--- +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":"What''s + the weather in Berlin? (52.5200, 13.4050)"}],"stream":false,"tools":[{"type":"function","function":{"name":"weather","description":"Gets + current weather for a location","parameters":{"type":"object","properties":{"latitude":{"type":"string","description":"Latitude + (e.g., 52.5200)"},"longitude":{"type":"string","description":"Longitude (e.g., + 13.4050)"}},"required":["latitude","longitude"]}}}],"tool_choice":"none"}' + headers: + User-Agent: + - Faraday v2.13.4 + 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: + - Sun, 31 Aug 2025 01:13:21 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Access-Control-Expose-Headers: + - X-Request-ID + Openai-Organization: + - "" + Openai-Processing-Ms: + - '183' + Openai-Project: + - proj_CT47WB5LbNTGaasdAh4lTjTl + Openai-Version: + - '2020-10-01' + X-Envoy-Upstream-Service-Time: + - '186' + X-Ratelimit-Limit-Requests: + - '500' + X-Ratelimit-Limit-Tokens: + - '200000' + X-Ratelimit-Remaining-Requests: + - '499' + X-Ratelimit-Remaining-Tokens: + - '199986' + X-Ratelimit-Reset-Requests: + - 120ms + X-Ratelimit-Reset-Tokens: + - 4ms + X-Request-Id: + - "" + Cf-Cache-Status: + - DYNAMIC + Set-Cookie: + - "" + - "" + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + X-Content-Type-Options: + - nosniff + Server: + - cloudflare + Cf-Ray: + - "" + Alt-Svc: + - h3=":443"; ma=86400 + body: + encoding: ASCII-8BIT + string: | + { + "id": "chatcmpl-CAQvBQc491J7zUXS44pNB0ZKfDzsj", + "object": "chat.completion", + "created": 1756602801, + "model": "gpt-4.1-nano-2025-04-14", + "choices": [ + { + "index": 0, + "message": { + "role": "assistant", + "content": "I'll check the current weather in Berlin for you.", + "refusal": null, + "annotations": [] + }, + "logprobs": null, + "finish_reason": "stop" + } + ], + "usage": { + "prompt_tokens": 89, + "completion_tokens": 10, + "total_tokens": 99, + "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_c4c155951e" + } + recorded_at: Sun, 31 Aug 2025 01:13:21 GMT +recorded_with: VCR 6.3.1 diff --git a/spec/fixtures/vcr_cassettes/chat_tool_choice_and_parallel_control_openai_gpt-4_1-nano_respects_choice_required_for_unrelated_queries.yml b/spec/fixtures/vcr_cassettes/chat_tool_choice_and_parallel_control_openai_gpt-4_1-nano_respects_choice_required_for_unrelated_queries.yml new file mode 100644 index 000000000..2c60b0e0c --- /dev/null +++ b/spec/fixtures/vcr_cassettes/chat_tool_choice_and_parallel_control_openai_gpt-4_1-nano_respects_choice_required_for_unrelated_queries.yml @@ -0,0 +1,253 @@ +--- +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":"When + was the fall of Rome?"}],"stream":false,"tools":[{"type":"function","function":{"name":"weather","description":"Gets + current weather for a location","parameters":{"type":"object","properties":{"latitude":{"type":"string","description":"Latitude + (e.g., 52.5200)"},"longitude":{"type":"string","description":"Longitude (e.g., + 13.4050)"}},"required":["latitude","longitude"]}}}],"tool_choice":"required"}' + headers: + User-Agent: + - Faraday v2.13.4 + 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: + - Sun, 31 Aug 2025 01:13:23 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Access-Control-Expose-Headers: + - X-Request-ID + Openai-Organization: + - "" + Openai-Processing-Ms: + - '490' + Openai-Project: + - proj_CT47WB5LbNTGaasdAh4lTjTl + Openai-Version: + - '2020-10-01' + X-Envoy-Upstream-Service-Time: + - '493' + X-Ratelimit-Limit-Requests: + - '500' + X-Ratelimit-Limit-Tokens: + - '200000' + X-Ratelimit-Remaining-Requests: + - '499' + X-Ratelimit-Remaining-Tokens: + - '199991' + X-Ratelimit-Reset-Requests: + - 120ms + X-Ratelimit-Reset-Tokens: + - 2ms + X-Request-Id: + - "" + Cf-Cache-Status: + - DYNAMIC + Set-Cookie: + - "" + - "" + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + X-Content-Type-Options: + - nosniff + Server: + - cloudflare + Cf-Ray: + - "" + Alt-Svc: + - h3=":443"; ma=86400 + body: + encoding: ASCII-8BIT + string: | + { + "id": "chatcmpl-CAQvCBTwD89xBM16o1OvbkHFRf1Px", + "object": "chat.completion", + "created": 1756602802, + "model": "gpt-4.1-nano-2025-04-14", + "choices": [ + { + "index": 0, + "message": { + "role": "assistant", + "content": null, + "tool_calls": [ + { + "id": "call_a75vr5O6KEZ1vRKngigOiIY3", + "type": "function", + "function": { + "name": "weather", + "arguments": "{\"latitude\": \"41.9\", \"longitude\": \"12.5\"}" + } + }, + { + "id": "call_qLBIbaaDzs3oI8zKm9muABvX", + "type": "function", + "function": { + "name": "weather", + "arguments": "{\"latitude\": \"40.7\", \"longitude\": \"-73.9\"}" + } + } + ], + "refusal": null, + "annotations": [] + }, + "logprobs": null, + "finish_reason": "tool_calls" + } + ], + "usage": { + "prompt_tokens": 77, + "completion_tokens": 59, + "total_tokens": 136, + "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_c4c155951e" + } + recorded_at: Sun, 31 Aug 2025 01:13:22 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":"When + was the fall of Rome?"},{"role":"assistant","tool_calls":[{"id":"call_a75vr5O6KEZ1vRKngigOiIY3","type":"function","function":{"name":"weather","arguments":"{\"latitude\":\"41.9\",\"longitude\":\"12.5\"}"}},{"id":"call_qLBIbaaDzs3oI8zKm9muABvX","type":"function","function":{"name":"weather","arguments":"{\"latitude\":\"40.7\",\"longitude\":\"-73.9\"}"}}]},{"role":"tool","content":"Current + weather at 41.9, 12.5: 15°C, Wind: 10 km/h","tool_call_id":"call_a75vr5O6KEZ1vRKngigOiIY3"},{"role":"tool","content":"Current + weather at 40.7, -73.9: 15°C, Wind: 10 km/h","tool_call_id":"call_qLBIbaaDzs3oI8zKm9muABvX"}],"stream":false,"tools":[{"type":"function","function":{"name":"weather","description":"Gets + current weather for a location","parameters":{"type":"object","properties":{"latitude":{"type":"string","description":"Latitude + (e.g., 52.5200)"},"longitude":{"type":"string","description":"Longitude (e.g., + 13.4050)"}},"required":["latitude","longitude"]}}}]}' + headers: + User-Agent: + - Faraday v2.13.4 + 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: + - Sun, 31 Aug 2025 01:13:25 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Access-Control-Expose-Headers: + - X-Request-ID + Openai-Organization: + - "" + Openai-Processing-Ms: + - '561' + Openai-Project: + - proj_CT47WB5LbNTGaasdAh4lTjTl + Openai-Version: + - '2020-10-01' + X-Envoy-Upstream-Service-Time: + - '567' + X-Ratelimit-Limit-Requests: + - '500' + X-Ratelimit-Limit-Tokens: + - '200000' + X-Ratelimit-Remaining-Requests: + - '499' + X-Ratelimit-Remaining-Tokens: + - '199962' + X-Ratelimit-Reset-Requests: + - 120ms + X-Ratelimit-Reset-Tokens: + - 11ms + X-Request-Id: + - "" + Cf-Cache-Status: + - DYNAMIC + Set-Cookie: + - "" + - "" + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + X-Content-Type-Options: + - nosniff + Server: + - cloudflare + Cf-Ray: + - "" + Alt-Svc: + - h3=":443"; ma=86400 + body: + encoding: ASCII-8BIT + string: | + { + "id": "chatcmpl-CAQvE4OIDI2bCF3iLQLQbyl3nkyxC", + "object": "chat.completion", + "created": 1756602804, + "model": "gpt-4.1-nano-2025-04-14", + "choices": [ + { + "index": 0, + "message": { + "role": "assistant", + "content": "The fall of Rome is traditionally marked in AD 476, when the last Roman emperor of the Western Roman Empire, Romulus Augustulus, was deposed. This event signifies the end of ancient Rome and the decline of the Western Roman Empire.", + "refusal": null, + "annotations": [] + }, + "logprobs": null, + "finish_reason": "stop" + } + ], + "usage": { + "prompt_tokens": 204, + "completion_tokens": 50, + "total_tokens": 254, + "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_c4c155951e" + } + recorded_at: Sun, 31 Aug 2025 01:13:25 GMT +recorded_with: VCR 6.3.1 diff --git a/spec/fixtures/vcr_cassettes/chat_tool_choice_and_parallel_control_openai_gpt-4_1-nano_respects_parallel_false_for_sequential_execution.yml b/spec/fixtures/vcr_cassettes/chat_tool_choice_and_parallel_control_openai_gpt-4_1-nano_respects_parallel_false_for_sequential_execution.yml new file mode 100644 index 000000000..99c1f9546 --- /dev/null +++ b/spec/fixtures/vcr_cassettes/chat_tool_choice_and_parallel_control_openai_gpt-4_1-nano_respects_parallel_false_for_sequential_execution.yml @@ -0,0 +1,344 @@ +--- +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":"developer","content":"You + must use both the weather tool for Berlin (52.5200, 13.4050) and the best + language tool."},{"role":"user","content":"What''s the weather in Berlin and + what''s the best programming language?"}],"stream":false,"tools":[{"type":"function","function":{"name":"weather","description":"Gets + current weather for a location","parameters":{"type":"object","properties":{"latitude":{"type":"string","description":"Latitude + (e.g., 52.5200)"},"longitude":{"type":"string","description":"Longitude (e.g., + 13.4050)"}},"required":["latitude","longitude"]}}},{"type":"function","function":{"name":"best_language_to_learn","description":"Gets + the best language to learn","parameters":{"type":"object","properties":{},"required":[]}}}],"parallel_tool_calls":false}' + headers: + User-Agent: + - Faraday v2.13.4 + 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: + - Sun, 31 Aug 2025 01:13:25 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Access-Control-Expose-Headers: + - X-Request-ID + Openai-Organization: + - "" + Openai-Processing-Ms: + - '437' + Openai-Project: + - proj_CT47WB5LbNTGaasdAh4lTjTl + Openai-Version: + - '2020-10-01' + X-Envoy-Upstream-Service-Time: + - '441' + X-Ratelimit-Limit-Requests: + - '500' + X-Ratelimit-Limit-Tokens: + - '200000' + X-Ratelimit-Remaining-Requests: + - '499' + X-Ratelimit-Remaining-Tokens: + - '199956' + X-Ratelimit-Reset-Requests: + - 120ms + X-Ratelimit-Reset-Tokens: + - 13ms + X-Request-Id: + - "" + Cf-Cache-Status: + - DYNAMIC + Set-Cookie: + - "" + - "" + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + X-Content-Type-Options: + - nosniff + Server: + - cloudflare + Cf-Ray: + - "" + Alt-Svc: + - h3=":443"; ma=86400 + body: + encoding: ASCII-8BIT + string: | + { + "id": "chatcmpl-CAQvFx3vsphtCMyQGP9SrKhHWucp6", + "object": "chat.completion", + "created": 1756602805, + "model": "gpt-4.1-nano-2025-04-14", + "choices": [ + { + "index": 0, + "message": { + "role": "assistant", + "content": null, + "tool_calls": [ + { + "id": "call_LAG7dZXozlUvleaEaeH3URwb", + "type": "function", + "function": { + "name": "weather", + "arguments": "{\"latitude\":\"52.5200\",\"longitude\":\"13.4050\"}" + } + } + ], + "refusal": null, + "annotations": [] + }, + "logprobs": null, + "finish_reason": "tool_calls" + } + ], + "usage": { + "prompt_tokens": 127, + "completion_tokens": 23, + "total_tokens": 150, + "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_7c233bf9d1" + } + recorded_at: Sun, 31 Aug 2025 01:13:25 GMT +- request: + method: post + uri: https://api.openai.com/v1/chat/completions + body: + encoding: UTF-8 + string: '{"model":"gpt-4.1-nano","messages":[{"role":"developer","content":"You + must use both the weather tool for Berlin (52.5200, 13.4050) and the best + language tool."},{"role":"user","content":"What''s the weather in Berlin and + what''s the best programming language?"},{"role":"assistant","tool_calls":[{"id":"call_LAG7dZXozlUvleaEaeH3URwb","type":"function","function":{"name":"weather","arguments":"{\"latitude\":\"52.5200\",\"longitude\":\"13.4050\"}"}}]},{"role":"tool","content":"Current + weather at 52.5200, 13.4050: 15°C, Wind: 10 km/h","tool_call_id":"call_LAG7dZXozlUvleaEaeH3URwb"}],"stream":false,"tools":[{"type":"function","function":{"name":"weather","description":"Gets + current weather for a location","parameters":{"type":"object","properties":{"latitude":{"type":"string","description":"Latitude + (e.g., 52.5200)"},"longitude":{"type":"string","description":"Longitude (e.g., + 13.4050)"}},"required":["latitude","longitude"]}}},{"type":"function","function":{"name":"best_language_to_learn","description":"Gets + the best language to learn","parameters":{"type":"object","properties":{},"required":[]}}}],"parallel_tool_calls":false}' + headers: + User-Agent: + - Faraday v2.13.4 + 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: + - Sun, 31 Aug 2025 01:13:27 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Access-Control-Expose-Headers: + - X-Request-ID + Openai-Organization: + - "" + Openai-Processing-Ms: + - '343' + Openai-Project: + - proj_CT47WB5LbNTGaasdAh4lTjTl + Openai-Version: + - '2020-10-01' + X-Envoy-Upstream-Service-Time: + - '345' + X-Ratelimit-Limit-Requests: + - '500' + X-Ratelimit-Limit-Tokens: + - '200000' + X-Ratelimit-Remaining-Requests: + - '499' + X-Ratelimit-Remaining-Tokens: + - '199940' + X-Ratelimit-Reset-Requests: + - 120ms + X-Ratelimit-Reset-Tokens: + - 18ms + X-Request-Id: + - "" + Cf-Cache-Status: + - DYNAMIC + Set-Cookie: + - "" + - "" + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + X-Content-Type-Options: + - nosniff + Server: + - cloudflare + Cf-Ray: + - "" + Alt-Svc: + - h3=":443"; ma=86400 + body: + encoding: ASCII-8BIT + string: | + { + "id": "chatcmpl-CAQvHX72BcSTozOXGl4Lvaa8G9Qn4", + "object": "chat.completion", + "created": 1756602807, + "model": "gpt-4.1-nano-2025-04-14", + "choices": [ + { + "index": 0, + "message": { + "role": "assistant", + "content": null, + "tool_calls": [ + { + "id": "call_BvMMs1DpscvHI7ay9DwRk2aC", + "type": "function", + "function": { + "name": "best_language_to_learn", + "arguments": "{}" + } + } + ], + "refusal": null, + "annotations": [] + }, + "logprobs": null, + "finish_reason": "tool_calls" + } + ], + "usage": { + "prompt_tokens": 182, + "completion_tokens": 13, + "total_tokens": 195, + "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_7c233bf9d1" + } + recorded_at: Sun, 31 Aug 2025 01:13:27 GMT +- request: + method: post + uri: https://api.openai.com/v1/chat/completions + body: + encoding: UTF-8 + string: '{"model":"gpt-4.1-nano","messages":[{"role":"developer","content":"You + must use both the weather tool for Berlin (52.5200, 13.4050) and the best + language tool."},{"role":"user","content":"What''s the weather in Berlin and + what''s the best programming language?"},{"role":"assistant","tool_calls":[{"id":"call_LAG7dZXozlUvleaEaeH3URwb","type":"function","function":{"name":"weather","arguments":"{\"latitude\":\"52.5200\",\"longitude\":\"13.4050\"}"}}]},{"role":"tool","content":"Current + weather at 52.5200, 13.4050: 15°C, Wind: 10 km/h","tool_call_id":"call_LAG7dZXozlUvleaEaeH3URwb"},{"role":"assistant","tool_calls":[{"id":"call_BvMMs1DpscvHI7ay9DwRk2aC","type":"function","function":{"name":"best_language_to_learn","arguments":"{}"}}]},{"role":"tool","content":"Ruby","tool_call_id":"call_BvMMs1DpscvHI7ay9DwRk2aC"}],"stream":false,"tools":[{"type":"function","function":{"name":"weather","description":"Gets + current weather for a location","parameters":{"type":"object","properties":{"latitude":{"type":"string","description":"Latitude + (e.g., 52.5200)"},"longitude":{"type":"string","description":"Longitude (e.g., + 13.4050)"}},"required":["latitude","longitude"]}}},{"type":"function","function":{"name":"best_language_to_learn","description":"Gets + the best language to learn","parameters":{"type":"object","properties":{},"required":[]}}}],"parallel_tool_calls":false}' + headers: + User-Agent: + - Faraday v2.13.4 + 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: + - Sun, 31 Aug 2025 01:13:28 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Access-Control-Expose-Headers: + - X-Request-ID + Openai-Organization: + - "" + Openai-Processing-Ms: + - '316' + Openai-Project: + - proj_CT47WB5LbNTGaasdAh4lTjTl + Openai-Version: + - '2020-10-01' + X-Envoy-Upstream-Service-Time: + - '319' + X-Ratelimit-Limit-Requests: + - '500' + X-Ratelimit-Limit-Tokens: + - '200000' + X-Ratelimit-Remaining-Requests: + - '499' + X-Ratelimit-Remaining-Tokens: + - '199937' + X-Ratelimit-Reset-Requests: + - 120ms + X-Ratelimit-Reset-Tokens: + - 18ms + X-Request-Id: + - "" + Cf-Cache-Status: + - DYNAMIC + Set-Cookie: + - "" + - "" + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + X-Content-Type-Options: + - nosniff + Server: + - cloudflare + Cf-Ray: + - "" + Alt-Svc: + - h3=":443"; ma=86400 + body: + encoding: ASCII-8BIT + string: !binary |- + ewogICJpZCI6ICJjaGF0Y21wbC1DQVF2SWxBNHE5RG1hTnVaVGNpUXZIeGVxdjFuRSIsCiAgIm9iamVjdCI6ICJjaGF0LmNvbXBsZXRpb24iLAogICJjcmVhdGVkIjogMTc1NjYwMjgwOCwKICAibW9kZWwiOiAiZ3B0LTQuMS1uYW5vLTIwMjUtMDQtMTQiLAogICJjaG9pY2VzIjogWwogICAgewogICAgICAiaW5kZXgiOiAwLAogICAgICAibWVzc2FnZSI6IHsKICAgICAgICAicm9sZSI6ICJhc3Npc3RhbnQiLAogICAgICAgICJjb250ZW50IjogIlRoZSBjdXJyZW50IHdlYXRoZXIgaW4gQmVybGluIGlzIDE1wrBDIHdpdGggYSB3aW5kIG9mIDEwIGttL2guIFRoZSBiZXN0IHByb2dyYW1taW5nIGxhbmd1YWdlIHRvIGxlYXJuIHJpZ2h0IG5vdyBpcyBSdWJ5LiIsCiAgICAgICAgInJlZnVzYWwiOiBudWxsLAogICAgICAgICJhbm5vdGF0aW9ucyI6IFtdCiAgICAgIH0sCiAgICAgICJsb2dwcm9icyI6IG51bGwsCiAgICAgICJmaW5pc2hfcmVhc29uIjogInN0b3AiCiAgICB9CiAgXSwKICAidXNhZ2UiOiB7CiAgICAicHJvbXB0X3Rva2VucyI6IDIwNywKICAgICJjb21wbGV0aW9uX3Rva2VucyI6IDMwLAogICAgInRvdGFsX3Rva2VucyI6IDIzNywKICAgICJwcm9tcHRfdG9rZW5zX2RldGFpbHMiOiB7CiAgICAgICJjYWNoZWRfdG9rZW5zIjogMCwKICAgICAgImF1ZGlvX3Rva2VucyI6IDAKICAgIH0sCiAgICAiY29tcGxldGlvbl90b2tlbnNfZGV0YWlscyI6IHsKICAgICAgInJlYXNvbmluZ190b2tlbnMiOiAwLAogICAgICAiYXVkaW9fdG9rZW5zIjogMCwKICAgICAgImFjY2VwdGVkX3ByZWRpY3Rpb25fdG9rZW5zIjogMCwKICAgICAgInJlamVjdGVkX3ByZWRpY3Rpb25fdG9rZW5zIjogMAogICAgfQogIH0sCiAgInNlcnZpY2VfdGllciI6ICJkZWZhdWx0IiwKICAic3lzdGVtX2ZpbmdlcnByaW50IjogImZwX2U5MWE1MThkZGIiCn0K + recorded_at: Sun, 31 Aug 2025 01:13:28 GMT +recorded_with: VCR 6.3.1 diff --git a/spec/fixtures/vcr_cassettes/chat_tool_choice_and_parallel_control_openai_gpt-4_1-nano_respects_specific_tool_choice.yml b/spec/fixtures/vcr_cassettes/chat_tool_choice_and_parallel_control_openai_gpt-4_1-nano_respects_specific_tool_choice.yml new file mode 100644 index 000000000..38cc4728c --- /dev/null +++ b/spec/fixtures/vcr_cassettes/chat_tool_choice_and_parallel_control_openai_gpt-4_1-nano_respects_specific_tool_choice.yml @@ -0,0 +1,248 @@ +--- +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":"What''s + the fall of Rome?"}],"stream":false,"tools":[{"type":"function","function":{"name":"weather","description":"Gets + current weather for a location","parameters":{"type":"object","properties":{"latitude":{"type":"string","description":"Latitude + (e.g., 52.5200)"},"longitude":{"type":"string","description":"Longitude (e.g., + 13.4050)"}},"required":["latitude","longitude"]}}}],"tool_choice":{"type":"function","function":{"name":"weather"}}}' + headers: + User-Agent: + - Faraday v2.13.4 + 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: + - Tue, 23 Sep 2025 23:06:03 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Access-Control-Expose-Headers: + - X-Request-ID + Openai-Organization: + - "" + Openai-Processing-Ms: + - '334' + Openai-Project: + - proj_CT47WB5LbNTGaasdAh4lTjTl + Openai-Version: + - '2020-10-01' + X-Envoy-Upstream-Service-Time: + - '551' + X-Ratelimit-Limit-Requests: + - '500' + X-Ratelimit-Limit-Tokens: + - '200000' + X-Ratelimit-Remaining-Requests: + - '499' + X-Ratelimit-Remaining-Tokens: + - '199992' + X-Ratelimit-Reset-Requests: + - 120ms + X-Ratelimit-Reset-Tokens: + - 2ms + X-Request-Id: + - "" + X-Openai-Proxy-Wasm: + - v0.1 + Cf-Cache-Status: + - DYNAMIC + Set-Cookie: + - "" + - "" + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + X-Content-Type-Options: + - nosniff + Server: + - cloudflare + Cf-Ray: + - "" + Alt-Svc: + - h3=":443"; ma=86400 + body: + encoding: ASCII-8BIT + string: | + { + "id": "chatcmpl-CJ6N9EPejqMpgKLnYv7BhmYbVT0aV", + "object": "chat.completion", + "created": 1758668763, + "model": "gpt-4.1-nano-2025-04-14", + "choices": [ + { + "index": 0, + "message": { + "role": "assistant", + "content": null, + "tool_calls": [ + { + "id": "call_7M77PqB14XibKL6C23aSDm8S", + "type": "function", + "function": { + "name": "weather", + "arguments": "{\"latitude\":\"41.9028\",\"longitude\":\"12.4964\"}" + } + } + ], + "refusal": null, + "annotations": [] + }, + "logprobs": null, + "finish_reason": "stop" + } + ], + "usage": { + "prompt_tokens": 84, + "completion_tokens": 15, + "total_tokens": 99, + "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_7c233bf9d1" + } + recorded_at: Tue, 23 Sep 2025 23:06:03 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":"What''s + the fall of Rome?"},{"role":"assistant","tool_calls":[{"id":"call_7M77PqB14XibKL6C23aSDm8S","type":"function","function":{"name":"weather","arguments":"{\"latitude\":\"41.9028\",\"longitude\":\"12.4964\"}"}}]},{"role":"tool","content":"Current + weather at 41.9028, 12.4964: 15°C, Wind: 10 km/h","tool_call_id":"call_7M77PqB14XibKL6C23aSDm8S"}],"stream":false,"tools":[{"type":"function","function":{"name":"weather","description":"Gets + current weather for a location","parameters":{"type":"object","properties":{"latitude":{"type":"string","description":"Latitude + (e.g., 52.5200)"},"longitude":{"type":"string","description":"Longitude (e.g., + 13.4050)"}},"required":["latitude","longitude"]}}}]}' + headers: + User-Agent: + - Faraday v2.13.4 + 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: + - Tue, 23 Sep 2025 23:06:05 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Access-Control-Expose-Headers: + - X-Request-ID + Openai-Organization: + - "" + Openai-Processing-Ms: + - '1036' + Openai-Project: + - proj_CT47WB5LbNTGaasdAh4lTjTl + Openai-Version: + - '2020-10-01' + X-Envoy-Upstream-Service-Time: + - '1298' + X-Ratelimit-Limit-Requests: + - '500' + X-Ratelimit-Limit-Tokens: + - '200000' + X-Ratelimit-Remaining-Requests: + - '499' + X-Ratelimit-Remaining-Tokens: + - '199975' + X-Ratelimit-Reset-Requests: + - 120ms + X-Ratelimit-Reset-Tokens: + - 7ms + X-Request-Id: + - "" + X-Openai-Proxy-Wasm: + - v0.1 + Cf-Cache-Status: + - DYNAMIC + Set-Cookie: + - "" + - "" + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + X-Content-Type-Options: + - nosniff + Server: + - cloudflare + Cf-Ray: + - "" + Alt-Svc: + - h3=":443"; ma=86400 + body: + encoding: ASCII-8BIT + string: | + { + "id": "chatcmpl-CJ6NAtbBfcPENk5puUC9eabd4j3c2", + "object": "chat.completion", + "created": 1758668764, + "model": "gpt-4.1-nano-2025-04-14", + "choices": [ + { + "index": 0, + "message": { + "role": "assistant", + "content": "The fall of Rome is historically considered to have occurred in September 476 AD, when the last Roman emperor of the West, Romulus Augustulus, was deposed by the Germanic chieftain Odoacer. This event marks the end of the Western Roman Empire and is often used to signify the transition from antiquity to the Middle Ages in Europe.", + "refusal": null, + "annotations": [] + }, + "logprobs": null, + "finish_reason": "stop" + } + ], + "usage": { + "prompt_tokens": 131, + "completion_tokens": 73, + "total_tokens": 204, + "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_7c233bf9d1" + } + recorded_at: Tue, 23 Sep 2025 23:06:05 GMT +recorded_with: VCR 6.3.1 diff --git a/spec/fixtures/vcr_cassettes/chat_tool_choice_and_parallel_control_openrouter_anthropic_claude-3_5-haiku_respects_choice_none.yml b/spec/fixtures/vcr_cassettes/chat_tool_choice_and_parallel_control_openrouter_anthropic_claude-3_5-haiku_respects_choice_none.yml new file mode 100644 index 000000000..30d71055e --- /dev/null +++ b/spec/fixtures/vcr_cassettes/chat_tool_choice_and_parallel_control_openrouter_anthropic_claude-3_5-haiku_respects_choice_none.yml @@ -0,0 +1,57 @@ +--- +http_interactions: +- request: + method: post + uri: https://openrouter.ai/api/v1/chat/completions + body: + encoding: UTF-8 + string: '{"model":"anthropic/claude-3.5-haiku","messages":[{"role":"user","content":"What''s + the weather in Berlin? (52.5200, 13.4050)"}],"stream":false,"tools":[{"type":"function","function":{"name":"weather","description":"Gets + current weather for a location","parameters":{"type":"object","properties":{"latitude":{"type":"string","description":"Latitude + (e.g., 52.5200)"},"longitude":{"type":"string","description":"Longitude (e.g., + 13.4050)"}},"required":["latitude","longitude"]}}}],"tool_choice":"none"}' + headers: + User-Agent: + - Faraday v2.13.4 + 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: + - Sun, 31 Aug 2025 01:13:30 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Access-Control-Allow-Origin: + - "*" + Vary: + - Accept-Encoding + Permissions-Policy: + - payment=(self "https://checkout.stripe.com" "https://connect-js.stripe.com" + "https://js.stripe.com" "https://*.js.stripe.com" "https://hooks.stripe.com") + Referrer-Policy: + - no-referrer, strict-origin-when-cross-origin + X-Content-Type-Options: + - nosniff + Server: + - cloudflare + Cf-Ray: + - "" + body: + encoding: ASCII-8BIT + string: "\n \n\n \n{\"id\":\"gen-1756602809-IBEAh5fts2Ov5bpcIrN2\",\"provider\":\"Google\",\"model\":\"anthropic/claude-3.5-haiku\",\"object\":\"chat.completion\",\"created\":1756602809,\"choices\":[{\"logprobs\":null,\"finish_reason\":\"stop\",\"native_finish_reason\":\"stop\",\"index\":0,\"message\":{\"role\":\"assistant\",\"content\":\"I'll + help you check the current weather in Berlin using the provided coordinates.\",\"refusal\":null,\"reasoning\":null}}],\"usage\":{\"prompt_tokens\":389,\"completion_tokens\":24,\"total_tokens\":413}}" + recorded_at: Sun, 31 Aug 2025 01:13:30 GMT +recorded_with: VCR 6.3.1 diff --git a/spec/fixtures/vcr_cassettes/chat_tool_choice_and_parallel_control_openrouter_anthropic_claude-3_5-haiku_respects_choice_required_for_unrelated_queries.yml b/spec/fixtures/vcr_cassettes/chat_tool_choice_and_parallel_control_openrouter_anthropic_claude-3_5-haiku_respects_choice_required_for_unrelated_queries.yml new file mode 100644 index 000000000..e90ba03e2 --- /dev/null +++ b/spec/fixtures/vcr_cassettes/chat_tool_choice_and_parallel_control_openrouter_anthropic_claude-3_5-haiku_respects_choice_required_for_unrelated_queries.yml @@ -0,0 +1,126 @@ +--- +http_interactions: +- request: + method: post + uri: https://openrouter.ai/api/v1/chat/completions + body: + encoding: UTF-8 + string: '{"model":"anthropic/claude-3.5-haiku","messages":[{"role":"user","content":"When + was the fall of Rome?"}],"stream":false,"tools":[{"type":"function","function":{"name":"weather","description":"Gets + current weather for a location","parameters":{"type":"object","properties":{"latitude":{"type":"string","description":"Latitude + (e.g., 52.5200)"},"longitude":{"type":"string","description":"Longitude (e.g., + 13.4050)"}},"required":["latitude","longitude"]}}}],"tool_choice":"required"}' + headers: + User-Agent: + - Faraday v2.13.4 + 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: + - Sun, 31 Aug 2025 01:13:31 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Access-Control-Allow-Origin: + - "*" + Vary: + - Accept-Encoding + Permissions-Policy: + - payment=(self "https://checkout.stripe.com" "https://connect-js.stripe.com" + "https://js.stripe.com" "https://*.js.stripe.com" "https://hooks.stripe.com") + Referrer-Policy: + - no-referrer, strict-origin-when-cross-origin + X-Content-Type-Options: + - nosniff + Server: + - cloudflare + Cf-Ray: + - "" + body: + encoding: ASCII-8BIT + string: "\n \n\n \n{\"id\":\"gen-1756602810-hpQDX95J40A6Cal5jhu6\",\"provider\":\"Google\",\"model\":\"anthropic/claude-3.5-haiku\",\"object\":\"chat.completion\",\"created\":1756602810,\"choices\":[{\"logprobs\":null,\"finish_reason\":\"tool_calls\",\"native_finish_reason\":\"tool_calls\",\"index\":0,\"message\":{\"role\":\"assistant\",\"content\":\"\",\"refusal\":null,\"reasoning\":null,\"tool_calls\":[{\"id\":\"toolu_vrtx_01Fw5bHBFooudxXQxsUpSpnS\",\"index\":0,\"type\":\"function\",\"function\":{\"name\":\"weather\",\"arguments\":\"{\\\"latitude\\\": + \\\"41.9028\\\", \\\"longitude\\\": \\\"12.4964\\\"}\"}}]}}],\"usage\":{\"prompt_tokens\":468,\"completion_tokens\":59,\"total_tokens\":527}}" + recorded_at: Sun, 31 Aug 2025 01:13:31 GMT +- request: + method: post + uri: https://openrouter.ai/api/v1/chat/completions + body: + encoding: UTF-8 + string: '{"model":"anthropic/claude-3.5-haiku","messages":[{"role":"user","content":"When + was the fall of Rome?"},{"role":"assistant","content":"","tool_calls":[{"id":"toolu_vrtx_01Fw5bHBFooudxXQxsUpSpnS","type":"function","function":{"name":"weather","arguments":"{\"latitude\":\"41.9028\",\"longitude\":\"12.4964\"}"}}]},{"role":"tool","content":"Current + weather at 41.9028, 12.4964: 15°C, Wind: 10 km/h","tool_call_id":"toolu_vrtx_01Fw5bHBFooudxXQxsUpSpnS"}],"stream":false,"tools":[{"type":"function","function":{"name":"weather","description":"Gets + current weather for a location","parameters":{"type":"object","properties":{"latitude":{"type":"string","description":"Latitude + (e.g., 52.5200)"},"longitude":{"type":"string","description":"Longitude (e.g., + 13.4050)"}},"required":["latitude","longitude"]}}}]}' + headers: + User-Agent: + - Faraday v2.13.4 + 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: + - Sun, 31 Aug 2025 01:13:33 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Access-Control-Allow-Origin: + - "*" + Vary: + - Accept-Encoding + Permissions-Policy: + - payment=(self "https://checkout.stripe.com" "https://connect-js.stripe.com" + "https://js.stripe.com" "https://*.js.stripe.com" "https://hooks.stripe.com") + Referrer-Policy: + - no-referrer, strict-origin-when-cross-origin + X-Content-Type-Options: + - nosniff + Server: + - cloudflare + Cf-Ray: + - "" + body: + encoding: ASCII-8BIT + string: "\n \n\n \n\n \n\n \n\n \n\n + \ \n\n \n\n \n\n \n\n \n\n \n{\"id\":\"gen-1756602812-ZrGP9hBw9qoCq6tGrpnU\",\"provider\":\"Google\",\"model\":\"anthropic/claude-3.5-haiku\",\"object\":\"chat.completion\",\"created\":1756602812,\"choices\":[{\"logprobs\":null,\"finish_reason\":\"stop\",\"native_finish_reason\":\"stop\",\"index\":0,\"message\":{\"role\":\"assistant\",\"content\":\"I + apologize, but the weather function is not designed to provide historical + information. Let me answer your question about the fall of Rome directly.\\n\\nThe + fall of Rome is typically considered to have occurred in 476 CE, when the + last Roman Emperor in the West, Romulus Augustulus, was deposed by the Germanic + king Odoacer. This marked the end of the Western Roman Empire, though the + Eastern Roman Empire (Byzantine Empire) continued to exist for nearly another + thousand years until Constantinople fell to the Ottoman Turks in 1453.\\n\\nHowever, + historians debate the exact moment of \\\"fall,\\\" as the decline of the + Roman Empire was a gradual process that occurred over several centuries. Some + key events include:\\n\\n1. The division of the Empire into Western and Eastern + halves in 285 CE\\n2. The sack of Rome by the Visigoths in 410 CE\\n3. The + final deposition of Romulus Augustulus in 476 CE\\n\\nThe fall of Rome had + profound consequences, marking the end of classical antiquity and the beginning + of the medieval period in European history.\",\"refusal\":null,\"reasoning\":null}}],\"usage\":{\"prompt_tokens\":491,\"completion_tokens\":238,\"total_tokens\":729}}" + recorded_at: Sun, 31 Aug 2025 01:13:37 GMT +recorded_with: VCR 6.3.1 diff --git a/spec/fixtures/vcr_cassettes/chat_tool_choice_and_parallel_control_openrouter_anthropic_claude-3_5-haiku_respects_parallel_false_for_sequential_execution.yml b/spec/fixtures/vcr_cassettes/chat_tool_choice_and_parallel_control_openrouter_anthropic_claude-3_5-haiku_respects_parallel_false_for_sequential_execution.yml new file mode 100644 index 000000000..0ca002bb7 --- /dev/null +++ b/spec/fixtures/vcr_cassettes/chat_tool_choice_and_parallel_control_openrouter_anthropic_claude-3_5-haiku_respects_parallel_false_for_sequential_execution.yml @@ -0,0 +1,186 @@ +--- +http_interactions: +- request: + method: post + uri: https://openrouter.ai/api/v1/chat/completions + body: + encoding: UTF-8 + string: '{"model":"anthropic/claude-3.5-haiku","messages":[{"role":"developer","content":"You + must use both the weather tool for Berlin (52.5200, 13.4050) and the best + language tool."},{"role":"user","content":"What''s the weather in Berlin and + what''s the best programming language?"}],"stream":false,"tools":[{"type":"function","function":{"name":"weather","description":"Gets + current weather for a location","parameters":{"type":"object","properties":{"latitude":{"type":"string","description":"Latitude + (e.g., 52.5200)"},"longitude":{"type":"string","description":"Longitude (e.g., + 13.4050)"}},"required":["latitude","longitude"]}}},{"type":"function","function":{"name":"best_language_to_learn","description":"Gets + the best language to learn","parameters":{"type":"object","properties":{},"required":[]}}}],"parallel_tool_calls":false}' + headers: + User-Agent: + - Faraday v2.13.4 + 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: + - Sun, 31 Aug 2025 01:13:38 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Access-Control-Allow-Origin: + - "*" + Vary: + - Accept-Encoding + Permissions-Policy: + - payment=(self "https://checkout.stripe.com" "https://connect-js.stripe.com" + "https://js.stripe.com" "https://*.js.stripe.com" "https://hooks.stripe.com") + Referrer-Policy: + - no-referrer, strict-origin-when-cross-origin + X-Content-Type-Options: + - nosniff + Server: + - cloudflare + Cf-Ray: + - "" + body: + encoding: ASCII-8BIT + string: "\n \n\n \n\n \n\n \n\n \n{\"id\":\"gen-1756602817-VMtwmlgbk82l96FTejKC\",\"provider\":\"Google\",\"model\":\"anthropic/claude-3.5-haiku\",\"object\":\"chat.completion\",\"created\":1756602817,\"choices\":[{\"logprobs\":null,\"finish_reason\":\"tool_calls\",\"native_finish_reason\":\"tool_calls\",\"index\":0,\"message\":{\"role\":\"assistant\",\"content\":\"I'll + help you find out the weather in Berlin and the best programming language + to learn. I'll use the available tools to get this information for you.\\n\\nFirst, + let's check the weather in Berlin:\",\"refusal\":null,\"reasoning\":null,\"tool_calls\":[{\"id\":\"toolu_vrtx_018hpaBSASAcq6oSYRRwxTXv\",\"index\":0,\"type\":\"function\",\"function\":{\"name\":\"weather\",\"arguments\":\"{\\\"latitude\\\": + \\\"52.5200\\\", \\\"longitude\\\": \\\"13.4050\\\"}\"}}]}}],\"usage\":{\"prompt_tokens\":462,\"completion_tokens\":108,\"total_tokens\":570}}" + recorded_at: Sun, 31 Aug 2025 01:13:39 GMT +- request: + method: post + uri: https://openrouter.ai/api/v1/chat/completions + body: + encoding: UTF-8 + string: '{"model":"anthropic/claude-3.5-haiku","messages":[{"role":"developer","content":"You + must use both the weather tool for Berlin (52.5200, 13.4050) and the best + language tool."},{"role":"user","content":"What''s the weather in Berlin and + what''s the best programming language?"},{"role":"assistant","content":"I''ll + help you find out the weather in Berlin and the best programming language + to learn. I''ll use the available tools to get this information for you.\n\nFirst, + let''s check the weather in Berlin:","tool_calls":[{"id":"toolu_vrtx_018hpaBSASAcq6oSYRRwxTXv","type":"function","function":{"name":"weather","arguments":"{\"latitude\":\"52.5200\",\"longitude\":\"13.4050\"}"}}]},{"role":"tool","content":"Current + weather at 52.5200, 13.4050: 15°C, Wind: 10 km/h","tool_call_id":"toolu_vrtx_018hpaBSASAcq6oSYRRwxTXv"}],"stream":false,"tools":[{"type":"function","function":{"name":"weather","description":"Gets + current weather for a location","parameters":{"type":"object","properties":{"latitude":{"type":"string","description":"Latitude + (e.g., 52.5200)"},"longitude":{"type":"string","description":"Longitude (e.g., + 13.4050)"}},"required":["latitude","longitude"]}}},{"type":"function","function":{"name":"best_language_to_learn","description":"Gets + the best language to learn","parameters":{"type":"object","properties":{},"required":[]}}}],"parallel_tool_calls":false}' + headers: + User-Agent: + - Faraday v2.13.4 + 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: + - Sun, 31 Aug 2025 01:13:41 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Access-Control-Allow-Origin: + - "*" + Vary: + - Accept-Encoding + Permissions-Policy: + - payment=(self "https://checkout.stripe.com" "https://connect-js.stripe.com" + "https://js.stripe.com" "https://*.js.stripe.com" "https://hooks.stripe.com") + Referrer-Policy: + - no-referrer, strict-origin-when-cross-origin + X-Content-Type-Options: + - nosniff + Server: + - cloudflare + Cf-Ray: + - "" + body: + encoding: ASCII-8BIT + string: "\n \n\n \n{\"id\":\"gen-1756602820-Si0n0YPvus1luifj7TXx\",\"provider\":\"Anthropic\",\"model\":\"anthropic/claude-3.5-haiku\",\"object\":\"chat.completion\",\"created\":1756602820,\"choices\":[{\"logprobs\":null,\"finish_reason\":\"tool_calls\",\"native_finish_reason\":\"tool_calls\",\"index\":0,\"message\":{\"role\":\"assistant\",\"content\":\"Now, + let's find out the best programming language to learn:\",\"refusal\":null,\"reasoning\":null,\"tool_calls\":[{\"id\":\"toolu_01P4w3SrLRhxkbmaWWGoMeGy\",\"index\":0,\"type\":\"function\",\"function\":{\"name\":\"best_language_to_learn\",\"arguments\":\"\"}}]}}],\"usage\":{\"prompt_tokens\":618,\"completion_tokens\":46,\"total_tokens\":664}}" + recorded_at: Sun, 31 Aug 2025 01:13:41 GMT +- request: + method: post + uri: https://openrouter.ai/api/v1/chat/completions + body: + encoding: UTF-8 + string: '{"model":"anthropic/claude-3.5-haiku","messages":[{"role":"developer","content":"You + must use both the weather tool for Berlin (52.5200, 13.4050) and the best + language tool."},{"role":"user","content":"What''s the weather in Berlin and + what''s the best programming language?"},{"role":"assistant","content":"I''ll + help you find out the weather in Berlin and the best programming language + to learn. I''ll use the available tools to get this information for you.\n\nFirst, + let''s check the weather in Berlin:","tool_calls":[{"id":"toolu_vrtx_018hpaBSASAcq6oSYRRwxTXv","type":"function","function":{"name":"weather","arguments":"{\"latitude\":\"52.5200\",\"longitude\":\"13.4050\"}"}}]},{"role":"tool","content":"Current + weather at 52.5200, 13.4050: 15°C, Wind: 10 km/h","tool_call_id":"toolu_vrtx_018hpaBSASAcq6oSYRRwxTXv"},{"role":"assistant","content":"Now, + let''s find out the best programming language to learn:","tool_calls":[{"id":"toolu_01P4w3SrLRhxkbmaWWGoMeGy","type":"function","function":{"name":"best_language_to_learn","arguments":"{}"}}]},{"role":"tool","content":"Ruby","tool_call_id":"toolu_01P4w3SrLRhxkbmaWWGoMeGy"}],"stream":false,"tools":[{"type":"function","function":{"name":"weather","description":"Gets + current weather for a location","parameters":{"type":"object","properties":{"latitude":{"type":"string","description":"Latitude + (e.g., 52.5200)"},"longitude":{"type":"string","description":"Longitude (e.g., + 13.4050)"}},"required":["latitude","longitude"]}}},{"type":"function","function":{"name":"best_language_to_learn","description":"Gets + the best language to learn","parameters":{"type":"object","properties":{},"required":[]}}}],"parallel_tool_calls":false}' + headers: + User-Agent: + - Faraday v2.13.4 + 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: + - Sun, 31 Aug 2025 01:13:42 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Access-Control-Allow-Origin: + - "*" + Vary: + - Accept-Encoding + Permissions-Policy: + - payment=(self "https://checkout.stripe.com" "https://connect-js.stripe.com" + "https://js.stripe.com" "https://*.js.stripe.com" "https://hooks.stripe.com") + Referrer-Policy: + - no-referrer, strict-origin-when-cross-origin + X-Content-Type-Options: + - nosniff + Server: + - cloudflare + Cf-Ray: + - "" + body: + encoding: ASCII-8BIT + string: !binary |- + CiAgICAgICAgIAoKICAgICAgICAgCgogICAgICAgICAKCiAgICAgICAgIAp7ImlkIjoiZ2VuLTE3NTY2MDI4MjItUmE0RjlpenFOMnNKRTdURjdnWlgiLCJwcm92aWRlciI6IkFudGhyb3BpYyIsIm1vZGVsIjoiYW50aHJvcGljL2NsYXVkZS0zLjUtaGFpa3UiLCJvYmplY3QiOiJjaGF0LmNvbXBsZXRpb24iLCJjcmVhdGVkIjoxNzU2NjAyODIyLCJjaG9pY2VzIjpbeyJsb2dwcm9icyI6bnVsbCwiZmluaXNoX3JlYXNvbiI6InN0b3AiLCJuYXRpdmVfZmluaXNoX3JlYXNvbiI6InN0b3AiLCJpbmRleCI6MCwibWVzc2FnZSI6eyJyb2xlIjoiYXNzaXN0YW50IiwiY29udGVudCI6IkxldCBtZSBzdW1tYXJpemUgdGhlIHJlc3VsdHMgZm9yIHlvdTpcbjEuIFdlYXRoZXIgaW4gQmVybGluOiBDdXJyZW50bHkgMTXCsEMgd2l0aCBhIHdpbmQgc3BlZWQgb2YgMTAga20vaC4gSXQgc2VlbXMgbGlrZSBhIG1pbGQgZGF5LlxuMi4gQmVzdCBQcm9ncmFtbWluZyBMYW5ndWFnZTogVGhlIHRvb2wgc3VnZ2VzdHMgUnVieSBhcyB0aGUgYmVzdCBsYW5ndWFnZSB0byBsZWFybi5cblxuSXMgdGhlcmUgYW55dGhpbmcgZWxzZSBJIGNhbiBoZWxwIHlvdSB3aXRoPyIsInJlZnVzYWwiOm51bGwsInJlYXNvbmluZyI6bnVsbH19XSwidXNhZ2UiOnsicHJvbXB0X3Rva2VucyI6Njg1LCJjb21wbGV0aW9uX3Rva2VucyI6NzMsInRvdGFsX3Rva2VucyI6NzU4fX0= + recorded_at: Sun, 31 Aug 2025 01:13:44 GMT +recorded_with: VCR 6.3.1 diff --git a/spec/fixtures/vcr_cassettes/chat_tool_choice_and_parallel_control_openrouter_anthropic_claude-3_5-haiku_respects_specific_tool_choice.yml b/spec/fixtures/vcr_cassettes/chat_tool_choice_and_parallel_control_openrouter_anthropic_claude-3_5-haiku_respects_specific_tool_choice.yml new file mode 100644 index 000000000..d0e485338 --- /dev/null +++ b/spec/fixtures/vcr_cassettes/chat_tool_choice_and_parallel_control_openrouter_anthropic_claude-3_5-haiku_respects_specific_tool_choice.yml @@ -0,0 +1,112 @@ +--- +http_interactions: +- request: + method: post + uri: https://openrouter.ai/api/v1/chat/completions + body: + encoding: UTF-8 + string: '{"model":"anthropic/claude-3.5-haiku","messages":[{"role":"user","content":"What''s + the fall of Rome?"}],"stream":false,"tools":[{"type":"function","function":{"name":"weather","description":"Gets + current weather for a location","parameters":{"type":"object","properties":{"latitude":{"type":"string","description":"Latitude + (e.g., 52.5200)"},"longitude":{"type":"string","description":"Longitude (e.g., + 13.4050)"}},"required":["latitude","longitude"]}}}],"tool_choice":{"type":"function","function":{"name":"weather"}}}' + headers: + User-Agent: + - Faraday v2.13.4 + 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: + - Tue, 23 Sep 2025 23:00:07 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Access-Control-Allow-Origin: + - "*" + Vary: + - Accept-Encoding + Permissions-Policy: + - payment=(self "https://checkout.stripe.com" "https://connect-js.stripe.com" + "https://js.stripe.com" "https://*.js.stripe.com" "https://hooks.stripe.com") + Referrer-Policy: + - no-referrer, strict-origin-when-cross-origin + X-Content-Type-Options: + - nosniff + Server: + - cloudflare + Cf-Ray: + - "" + body: + encoding: ASCII-8BIT + string: "\n \n\n \n\n \n{\"id\":\"gen-1758668406-7YToyLJa9lLnIE72IZO5\",\"provider\":\"Google\",\"model\":\"anthropic/claude-3.5-haiku\",\"object\":\"chat.completion\",\"created\":1758668406,\"choices\":[{\"logprobs\":null,\"finish_reason\":\"tool_calls\",\"native_finish_reason\":\"tool_calls\",\"index\":0,\"message\":{\"role\":\"assistant\",\"content\":\"\",\"refusal\":null,\"reasoning\":null,\"tool_calls\":[{\"id\":\"toolu_vrtx_01Y9uiETpFzuYnVZuav5vj4q\",\"index\":0,\"type\":\"function\",\"function\":{\"name\":\"weather\",\"arguments\":\"{\\\"latitude\\\": + \\\"41.9028\\\", \\\"longitude\\\": \\\"12.4964\\\"}\"}}]}}],\"usage\":{\"prompt_tokens\":471,\"completion_tokens\":56,\"total_tokens\":527}}" + recorded_at: Tue, 23 Sep 2025 23:00:07 GMT +- request: + method: post + uri: https://openrouter.ai/api/v1/chat/completions + body: + encoding: UTF-8 + string: '{"model":"anthropic/claude-3.5-haiku","messages":[{"role":"user","content":"What''s + the fall of Rome?"},{"role":"assistant","content":"","tool_calls":[{"id":"toolu_vrtx_01Y9uiETpFzuYnVZuav5vj4q","type":"function","function":{"name":"weather","arguments":"{\"latitude\":\"41.9028\",\"longitude\":\"12.4964\"}"}}]},{"role":"tool","content":"Current + weather at 41.9028, 12.4964: 15°C, Wind: 10 km/h","tool_call_id":"toolu_vrtx_01Y9uiETpFzuYnVZuav5vj4q"}],"stream":false,"tools":[{"type":"function","function":{"name":"weather","description":"Gets + current weather for a location","parameters":{"type":"object","properties":{"latitude":{"type":"string","description":"Latitude + (e.g., 52.5200)"},"longitude":{"type":"string","description":"Longitude (e.g., + 13.4050)"}},"required":["latitude","longitude"]}}}]}' + headers: + User-Agent: + - Faraday v2.13.4 + 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: + - Tue, 23 Sep 2025 23:00:08 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Access-Control-Allow-Origin: + - "*" + Vary: + - Accept-Encoding + Permissions-Policy: + - payment=(self "https://checkout.stripe.com" "https://connect-js.stripe.com" + "https://js.stripe.com" "https://*.js.stripe.com" "https://hooks.stripe.com") + Referrer-Policy: + - no-referrer, strict-origin-when-cross-origin + X-Content-Type-Options: + - nosniff + Server: + - cloudflare + Cf-Ray: + - "" + body: + encoding: ASCII-8BIT + string: !binary |- + CiAgICAgICAgIAoKICAgICAgICAgCgogICAgICAgICAKCiAgICAgICAgIAoKICAgICAgICAgCgogICAgICAgICAKCiAgICAgICAgIAoKICAgICAgICAgCgogICAgICAgICAKCiAgICAgICAgIAoKICAgICAgICAgCgogICAgICAgICAKCiAgICAgICAgIAoKICAgICAgICAgCgogICAgICAgICAKeyJpZCI6Imdlbi0xNzU4NjY4NDA3LU94bWphaHQ4RmJSSjU4dUswcUJpIiwicHJvdmlkZXIiOiJBbnRocm9waWMiLCJtb2RlbCI6ImFudGhyb3BpYy9jbGF1ZGUtMy41LWhhaWt1Iiwib2JqZWN0IjoiY2hhdC5jb21wbGV0aW9uIiwiY3JlYXRlZCI6MTc1ODY2ODQwNywiY2hvaWNlcyI6W3sibG9ncHJvYnMiOm51bGwsImZpbmlzaF9yZWFzb24iOiJzdG9wIiwibmF0aXZlX2ZpbmlzaF9yZWFzb24iOiJzdG9wIiwiaW5kZXgiOjAsIm1lc3NhZ2UiOnsicm9sZSI6ImFzc2lzdGFudCIsImNvbnRlbnQiOiJJIGFwb2xvZ2l6ZSwgYnV0IHRoZSB3ZWF0aGVyIGZ1bmN0aW9uIGlzIG5vdCByZWxhdGVkIHRvIGhpc3RvcmljYWwgaW5mb3JtYXRpb24uIExldCBtZSBwcm92aWRlIHlvdSB3aXRoIGEgZGV0YWlsZWQgZXhwbGFuYXRpb24gYWJvdXQgdGhlIEZhbGwgb2YgUm9tZTpcblxuVGhlIEZhbGwgb2YgUm9tZSByZWZlcnMgdG8gdGhlIGRlY2xpbmUgYW5kIHVsdGltYXRlIGNvbGxhcHNlIG9mIHRoZSBXZXN0ZXJuIFJvbWFuIEVtcGlyZSwgd2hpY2ggaXMgdHJhZGl0aW9uYWxseSBkYXRlZCB0byA0NzYgQ0UuIFRoaXMgd2FzIGEgY29tcGxleCBoaXN0b3JpY2FsIHByb2Nlc3MgdGhhdCBvY2N1cnJlZCBvdmVyIHNldmVyYWwgY2VudHVyaWVzLCBpbnZvbHZpbmcgbXVsdGlwbGUgZmFjdG9yczpcblxuMS4gSW50ZXJuYWwgQ2hhbGxlbmdlczpcbi0gUG9saXRpY2FsIGluc3RhYmlsaXR5IGFuZCBmcmVxdWVudCBjaXZpbCB3YXJzXG4tIENvcnJ1cHRpb24gaW4gZ292ZXJubWVudFxuLSBFY29ub21pYyBkZWNsaW5lXG4tIE92ZXJleHBhbnNpb24gb2YgdGhlIGVtcGlyZVxuXG4yLiBFeHRlcm5hbCBQcmVzc3VyZXM6XG4tIENvbnN0YW50IGludmFzaW9ucyBieSBHZXJtYW5pYyB0cmliZXMgbGlrZSB0aGUgR290aHMsIFZhbmRhbHMsIGFuZCBIdW5zXG4tIE1pZ3JhdGlvbiBwZXJpb2QgKGtub3duIGFzIHRoZSBcIlbDtmxrZXJ3YW5kZXJ1bmdcIilcbi0gV2Vha2VuaW5nIG9mIFJvbWFuIG1pbGl0YXJ5IGNhcGFiaWxpdGllc1xuXG4zLiBLZXkgRXZlbnRzOlxuLSBJbiA0MTAgQ0UsIHRoZSBWaXNpZ290aHMgdW5kZXIgQWxhcmljIHNhY2tlZCBSb21lXG4tIEluIDQ1NSBDRSwgdGhlIFZhbmRhbHMgYWxzbyBzYWNrZWQgUm9tZVxuLSBJbiA0NzYgQ0UsIHRoZSBHZXJtYW5pYyBsZWFkZXIgT2RvYWNlciBkZXBvc2VkIHRoZSBsYXN0IFJvbWFuIEVtcGVyb3IgUm9tdWx1cyBBdWd1c3R1bHVzLCB3aGljaCBpcyBvZnRlbiBjb25zaWRlcmVkIHRoZSBkZWZpbml0aXZlIGVuZCBvZiB0aGUgV2VzdGVybiBSb21hbiBFbXBpcmVcblxuVGhlIEVhc3Rlcm4gUm9tYW4gRW1waXJlIChCeXphbnRpbmUgRW1waXJlKSBjb250aW51ZWQgdG8gZXhpc3QgZm9yIG5lYXJseSBhbm90aGVyIHRob3VzYW5kIHllYXJzIHVudGlsIENvbnN0YW50aW5vcGxlIGZlbGwgdG8gdGhlIE90dG9tYW4gVHVya3MgaW4gMTQ1My5cblxuVGhlIEZhbGwgb2YgUm9tZSBtYXJrZWQgdGhlIGVuZCBvZiBjbGFzc2ljYWwgYW50aXF1aXR5IGFuZCB0aGUgYmVnaW5uaW5nIG9mIHRoZSBNZWRpZXZhbCBwZXJpb2QgaW4gRXVyb3BlYW4gaGlzdG9yeSwgcHJvZm91bmRseSBjaGFuZ2luZyB0aGUgcG9saXRpY2FsLCBzb2NpYWwsIGFuZCBjdWx0dXJhbCBsYW5kc2NhcGUgb2YgRXVyb3BlLiIsInJlZnVzYWwiOm51bGwsInJlYXNvbmluZyI6bnVsbH19XSwidXNhZ2UiOnsicHJvbXB0X3Rva2VucyI6NDkxLCJjb21wbGV0aW9uX3Rva2VucyI6MzIzLCJ0b3RhbF90b2tlbnMiOjgxNH19 + recorded_at: Tue, 23 Sep 2025 23:00:14 GMT +recorded_with: VCR 6.3.1 diff --git a/spec/fixtures/vcr_cassettes/chat_tool_choice_and_parallel_control_vertexai_gemini-2_5-flash_respects_choice_none.yml b/spec/fixtures/vcr_cassettes/chat_tool_choice_and_parallel_control_vertexai_gemini-2_5-flash_respects_choice_none.yml new file mode 100644 index 000000000..d94df47c0 --- /dev/null +++ b/spec/fixtures/vcr_cassettes/chat_tool_choice_and_parallel_control_vertexai_gemini-2_5-flash_respects_choice_none.yml @@ -0,0 +1,111 @@ +--- +http_interactions: +- request: + method: post + uri: https://oauth2.googleapis.com/token + body: + encoding: ASCII-8BIT + string: grant_type=refresh_token&refresh_token=&client_id=&client_secret= + headers: + User-Agent: + - Faraday v2.13.4 + Content-Type: + - application/x-www-form-urlencoded + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + Cache-Control: + - no-cache, no-store, max-age=0, must-revalidate + Expires: + - Mon, 01 Jan 1990 00:00:00 GMT + Date: + - Tue, 23 Sep 2025 23:40:16 GMT + Pragma: + - no-cache + Content-Type: + - application/json; charset=utf-8 + Vary: + - Origin + - Referer + - X-Origin + Server: + - scaffolding on HTTPServer2 + X-Xss-Protection: + - '0' + X-Frame-Options: + - SAMEORIGIN + X-Content-Type-Options: + - nosniff + Alt-Svc: + - h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 + Transfer-Encoding: + - chunked + body: + encoding: ASCII-8BIT + string: |- + { + "access_token": "", + "expires_in": 3599, + "scope": "https://www.googleapis.com/auth/sqlservice.login openid https://www.googleapis.com/auth/cloud-platform https://www.googleapis.com/auth/userinfo.email", + "token_type": "Bearer", + "id_token": "" + } + recorded_at: Tue, 23 Sep 2025 23:40:16 GMT +- request: + method: post + uri: https://-aiplatform.googleapis.com/v1beta1/projects//locations//publishers/google/models/gemini-2.5-flash:generateContent + body: + encoding: UTF-8 + string: '{"contents":[{"role":"user","parts":[{"text":"What''s the weather in + Berlin? (52.5200, 13.4050)"}]}],"generationConfig":{},"tools":[{"functionDeclarations":[{"name":"weather","description":"Gets + current weather for a location","parameters":{"type":"OBJECT","properties":{"latitude":{"type":"STRING","description":"Latitude + (e.g., 52.5200)"},"longitude":{"type":"STRING","description":"Longitude (e.g., + 13.4050)"}},"required":["latitude","longitude"]}}]}],"toolConfig":{"functionCallingConfig":{"mode":"none"}}}' + headers: + User-Agent: + - Faraday v2.13.4 + 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: + Content-Type: + - application/json; charset=UTF-8 + Vary: + - Origin + - Referer + - X-Origin + Date: + - Tue, 23 Sep 2025 23:40:18 GMT + Server: + - scaffolding on HTTPServer2 + X-Xss-Protection: + - '0' + X-Frame-Options: + - SAMEORIGIN + X-Content-Type-Options: + - nosniff + Alt-Svc: + - h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 + Transfer-Encoding: + - chunked + body: + encoding: ASCII-8BIT + string: !binary |- + ewogICJjYW5kaWRhdGVzIjogWwogICAgewogICAgICAiY29udGVudCI6IHsKICAgICAgICAicm9sZSI6ICJtb2RlbCIsCiAgICAgICAgInBhcnRzIjogWwogICAgICAgICAgewogICAgICAgICAgICAidGV4dCI6ICJUaGUgd2VhdGhlciBpbiBCZXJsaW4sIEdlcm1hbnkgKDUyLjUyMDAsIDEzLjQwNTApIGlzIGN1cnJlbnRseSAqKnBhcnRseSBjbG91ZHkqKiB3aXRoIGEgdGVtcGVyYXR1cmUgb2YgKioxMsKwQyAoNTTCsEYpKiouXG5cblRoZSB3aW5kIGlzIGJsb3dpbmcgZnJvbSB0aGUgd2VzdCBhdCBhYm91dCAxMSBrbS9oICg3IG1waCksIGFuZCB0aGUgaHVtaWRpdHkgaXMgNzYlLiIKICAgICAgICAgIH0KICAgICAgICBdCiAgICAgIH0sCiAgICAgICJmaW5pc2hSZWFzb24iOiAiU1RPUCIsCiAgICAgICJhdmdMb2dwcm9icyI6IC0xLjQ4ODU1ODc5NTAwMTA1NTcKICAgIH0KICBdLAogICJ1c2FnZU1ldGFkYXRhIjogewogICAgInByb21wdFRva2VuQ291bnQiOiA3MCwKICAgICJjYW5kaWRhdGVzVG9rZW5Db3VudCI6IDc0LAogICAgInRvdGFsVG9rZW5Db3VudCI6IDM5NiwKICAgICJ0cmFmZmljVHlwZSI6ICJPTl9ERU1BTkQiLAogICAgInByb21wdFRva2Vuc0RldGFpbHMiOiBbCiAgICAgIHsKICAgICAgICAibW9kYWxpdHkiOiAiVEVYVCIsCiAgICAgICAgInRva2VuQ291bnQiOiA3MAogICAgICB9CiAgICBdLAogICAgImNhbmRpZGF0ZXNUb2tlbnNEZXRhaWxzIjogWwogICAgICB7CiAgICAgICAgIm1vZGFsaXR5IjogIlRFWFQiLAogICAgICAgICJ0b2tlbkNvdW50IjogNzQKICAgICAgfQogICAgXSwKICAgICJ0aG91Z2h0c1Rva2VuQ291bnQiOiAyNTIKICB9LAogICJtb2RlbFZlcnNpb24iOiAiZ2VtaW5pLTIuNS1mbGFzaCIsCiAgImNyZWF0ZVRpbWUiOiAiMjAyNS0wOS0yM1QyMzo0MDoxNi43NDg4NzFaIiwKICAicmVzcG9uc2VJZCI6ICI0Q19UYU1mYUxiejVsZDhQd3ZPaTRRbyIKfQo= + recorded_at: Tue, 23 Sep 2025 23:40:19 GMT +recorded_with: VCR 6.3.1 diff --git a/spec/fixtures/vcr_cassettes/chat_tool_choice_and_parallel_control_vertexai_gemini-2_5-flash_respects_choice_required_for_unrelated_queries.yml b/spec/fixtures/vcr_cassettes/chat_tool_choice_and_parallel_control_vertexai_gemini-2_5-flash_respects_choice_required_for_unrelated_queries.yml new file mode 100644 index 000000000..6a4510b0b --- /dev/null +++ b/spec/fixtures/vcr_cassettes/chat_tool_choice_and_parallel_control_vertexai_gemini-2_5-flash_respects_choice_required_for_unrelated_queries.yml @@ -0,0 +1,302 @@ +--- +http_interactions: +- request: + method: post + uri: https://oauth2.googleapis.com/token + body: + encoding: ASCII-8BIT + string: grant_type=refresh_token&refresh_token=&client_id=&client_secret= + headers: + User-Agent: + - Faraday v2.13.4 + Content-Type: + - application/x-www-form-urlencoded + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + Date: + - Sun, 31 Aug 2025 04:21:18 GMT + Expires: + - Mon, 01 Jan 1990 00:00:00 GMT + Pragma: + - no-cache + Cache-Control: + - no-cache, no-store, max-age=0, must-revalidate + Content-Type: + - application/json; charset=utf-8 + Vary: + - Origin + - Referer + - X-Origin + Server: + - scaffolding on HTTPServer2 + X-Xss-Protection: + - '0' + X-Frame-Options: + - SAMEORIGIN + X-Content-Type-Options: + - nosniff + Alt-Svc: + - h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 + Transfer-Encoding: + - chunked + body: + encoding: ASCII-8BIT + string: |- + { + "access_token": "", + "expires_in": 3599, + "scope": "https://www.googleapis.com/auth/cloud-platform https://www.googleapis.com/auth/sqlservice.login https://www.googleapis.com/auth/userinfo.email openid", + "token_type": "Bearer", + "id_token": "" + } + recorded_at: Sun, 31 Aug 2025 04:21:18 GMT +- request: + method: post + uri: https://-aiplatform.googleapis.com/v1beta1/projects//locations//publishers/google/models/gemini-2.5-flash:generateContent + body: + encoding: UTF-8 + string: '{"contents":[{"role":"user","parts":[{"text":"When was the fall of + Rome?"}]}],"generationConfig":{},"tools":[{"functionDeclarations":[{"name":"weather","description":"Gets + current weather for a location","parameters":{"type":"OBJECT","properties":{"latitude":{"type":"STRING","description":"Latitude + (e.g., 52.5200)"},"longitude":{"type":"STRING","description":"Longitude (e.g., + 13.4050)"}},"required":["latitude","longitude"]}}]}],"toolConfig":{"functionCallingConfig":{"mode":"any"}}}' + headers: + User-Agent: + - Faraday v2.13.4 + 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: + Content-Type: + - application/json; charset=UTF-8 + Vary: + - Origin + - Referer + - X-Origin + Date: + - Sun, 31 Aug 2025 04:21:19 GMT + Server: + - scaffolding on HTTPServer2 + X-Xss-Protection: + - '0' + X-Frame-Options: + - SAMEORIGIN + X-Content-Type-Options: + - nosniff + Alt-Svc: + - h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 + Transfer-Encoding: + - chunked + body: + encoding: ASCII-8BIT + string: | + { + "candidates": [ + { + "content": { + "role": "model", + "parts": [ + { + "functionCall": { + "name": "weather", + "args": { + "latitude": "41.9028", + "longitude": "12.496417" + } + }, + "thoughtSignature": "CvsCAcu98PAb08Q8kb5yHHg7PU0CAjOKLdn8Yxi2SwzpHYUVjGPiedU6mdgPOZdN9tWjBNESIFkhfj2x2vCWqXbVy7/kvVQ8RvC2nCBatPli1p6fGNNyxhFD8kYeE1hPgru/3WQlGcs0tZxYXu0ba62mojAtIt2xI3OSp7egZL0+nsBwfjQNP+0/bbCEFU+yfulkv68tClVXYaQasM7BOewLQJPiTh6/psJEsf7E/GKuQMzKwqyXILBLkp2lk3DjDM5wVmrh4Y4uite5JL6zBD4xcMaEyzIfdMW6UFYGZfTwgwWdeVpCBiQbkjprCXxGdYytGlnY1m5dyrHO0YVh+LcuO22GbZcTMIGIU6ktVXroW178MxaEoT/T99gw6jVfwZF07W1DhzovLjpmeOxGI2yFv3rLaoVdaEXiNKRSjRPam+3bIxcJ8T+4lMlLDceY5FN/LaN6j/meZuUwe08pOv/RXV7++XAARrQgti71qetOrLH0FuFsBVb3OG9tGw==" + } + ] + }, + "finishReason": "STOP", + "avgLogprobs": -1.0042402367842824 + } + ], + "usageMetadata": { + "promptTokenCount": 51, + "candidatesTokenCount": 19, + "totalTokenCount": 137, + "trafficType": "ON_DEMAND", + "promptTokensDetails": [ + { + "modality": "TEXT", + "tokenCount": 51 + } + ], + "candidatesTokensDetails": [ + { + "modality": "TEXT", + "tokenCount": 19 + } + ], + "thoughtsTokenCount": 67 + }, + "modelVersion": "gemini-2.5-flash", + "createTime": "2025-08-31T04:21:18.762574Z", + "responseId": "vs2zaM7FLvKOsLUP1Kyl8A4" + } + recorded_at: Sun, 31 Aug 2025 04:21:19 GMT +- request: + method: post + uri: https://oauth2.googleapis.com/token + body: + encoding: ASCII-8BIT + string: grant_type=refresh_token&refresh_token=&client_id=&client_secret= + headers: + User-Agent: + - Faraday v2.13.4 + Content-Type: + - application/x-www-form-urlencoded + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + Date: + - Sun, 31 Aug 2025 04:21:20 GMT + Expires: + - Mon, 01 Jan 1990 00:00:00 GMT + Cache-Control: + - no-cache, no-store, max-age=0, must-revalidate + Pragma: + - no-cache + Content-Type: + - application/json; charset=utf-8 + Vary: + - Origin + - Referer + - X-Origin + Server: + - scaffolding on HTTPServer2 + X-Xss-Protection: + - '0' + X-Frame-Options: + - SAMEORIGIN + X-Content-Type-Options: + - nosniff + Alt-Svc: + - h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 + Transfer-Encoding: + - chunked + body: + encoding: ASCII-8BIT + string: |- + { + "access_token": "", + "expires_in": 3599, + "scope": "https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/sqlservice.login openid https://www.googleapis.com/auth/cloud-platform", + "token_type": "Bearer", + "id_token": "" + } + recorded_at: Sun, 31 Aug 2025 04:21:20 GMT +- request: + method: post + uri: https://-aiplatform.googleapis.com/v1beta1/projects//locations//publishers/google/models/gemini-2.5-flash:generateContent + body: + encoding: UTF-8 + string: '{"contents":[{"role":"user","parts":[{"text":"When was the fall of + Rome?"}]},{"role":"model","parts":[{"functionCall":{"name":"weather","args":{"latitude":"41.9028","longitude":"12.496417"}}}]},{"role":"user","parts":[{"functionResponse":{"name":"5ae7d84b-2f68-4db0-b691-3ec7e5bb8dc6","response":{"name":"5ae7d84b-2f68-4db0-b691-3ec7e5bb8dc6","content":[{"text":"Current + weather at 41.9028, 12.496417: 15°C, Wind: 10 km/h"}]}}}]}],"generationConfig":{},"tools":[{"functionDeclarations":[{"name":"weather","description":"Gets + current weather for a location","parameters":{"type":"OBJECT","properties":{"latitude":{"type":"STRING","description":"Latitude + (e.g., 52.5200)"},"longitude":{"type":"STRING","description":"Longitude (e.g., + 13.4050)"}},"required":["latitude","longitude"]}}]}]}' + headers: + User-Agent: + - Faraday v2.13.4 + 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: + Content-Type: + - application/json; charset=UTF-8 + Vary: + - Origin + - Referer + - X-Origin + Date: + - Sun, 31 Aug 2025 04:21:21 GMT + Server: + - scaffolding on HTTPServer2 + X-Xss-Protection: + - '0' + X-Frame-Options: + - SAMEORIGIN + X-Content-Type-Options: + - nosniff + Alt-Svc: + - h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 + Transfer-Encoding: + - chunked + body: + encoding: ASCII-8BIT + string: | + { + "candidates": [ + { + "content": { + "role": "model", + "parts": [ + { + "text": "I do not have information on the fall of Rome. My capabilities are limited to providing current weather information given a latitude and longitude.", + "thoughtSignature": "CuwDAcu98PA8YopxQKMu7+a//uFi4cbEWiGyxy6kT/HGyDV5V8CcgKtKpSBtwixRYAuoPd7JRtN/az8OAR2liQXrPsQy+TOX2dyO0kg8c35V3o7DOnevQWnwtlVSS5QoDYZga5s2MUC7Fw4342N7Rs6uO0tJgn+Cz4mNmJ3UexyAyi9+0DNGd9sHpoJknwlSry/PHAahszwYeKJ22HpnLuA85T9Z8Ce4078veM/W/UMYsqQVxE78uuZXrI9TJO6sLiwyC9Nf83VEey+IovdKIVaxg5r3bCvDKhXNn9LOGInyFodJTs1Sa33AvyoIo+kej6kMq035Bij3xF5cbSw0jHxrCBRaDDamgd4zy8B2TFM0XeHJ9bPjmu/xQGjYP7yH7a8ZjWg+Jyxc+1qlmqqi5GFgaYQbXvjRRbbPMBBmmo3eBVX9OIGzL798zzzqTU0HfbMfCFFDpiB2DwwoxvTGFuF+Wsy5iFm3LO+5HXi0fyrrYCcuFkejOuQSdV7eoYJq1A33rUFg7gi6cW31hyxGEjirQRcUOaNbKYTLnXUXN/KfwbEU2cwL7OjZ44A6CDMNdnFzl6zqIMtA6co6DKU0NfFV1wcmZ99LCNfL9wPZaSacN5Z8M7mm8UXbmBvFWk9AeZ+Loc6wGV5bcfuC9tE9" + } + ] + }, + "finishReason": "STOP", + "avgLogprobs": -1.7103475423959584 + } + ], + "usageMetadata": { + "promptTokenCount": 172, + "candidatesTokenCount": 26, + "totalTokenCount": 291, + "trafficType": "ON_DEMAND", + "promptTokensDetails": [ + { + "modality": "TEXT", + "tokenCount": 172 + } + ], + "candidatesTokensDetails": [ + { + "modality": "TEXT", + "tokenCount": 26 + } + ], + "thoughtsTokenCount": 93 + }, + "modelVersion": "gemini-2.5-flash", + "createTime": "2025-08-31T04:21:20.578012Z", + "responseId": "wM2zaNyjI-C82PwP8uWe4A4" + } + recorded_at: Sun, 31 Aug 2025 04:21:21 GMT +recorded_with: VCR 6.3.1 diff --git a/spec/fixtures/vcr_cassettes/chat_tool_choice_and_parallel_control_vertexai_gemini-2_5-flash_respects_specific_tool_choice.yml b/spec/fixtures/vcr_cassettes/chat_tool_choice_and_parallel_control_vertexai_gemini-2_5-flash_respects_specific_tool_choice.yml new file mode 100644 index 000000000..713d7e83b --- /dev/null +++ b/spec/fixtures/vcr_cassettes/chat_tool_choice_and_parallel_control_vertexai_gemini-2_5-flash_respects_specific_tool_choice.yml @@ -0,0 +1,300 @@ +--- +http_interactions: +- request: + method: post + uri: https://oauth2.googleapis.com/token + body: + encoding: ASCII-8BIT + string: grant_type=refresh_token&refresh_token=&client_id=&client_secret= + headers: + User-Agent: + - Faraday v2.13.4 + Content-Type: + - application/x-www-form-urlencoded + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + Cache-Control: + - no-cache, no-store, max-age=0, must-revalidate + Date: + - Tue, 23 Sep 2025 23:40:19 GMT + Expires: + - Mon, 01 Jan 1990 00:00:00 GMT + Pragma: + - no-cache + Content-Type: + - application/json; charset=utf-8 + Vary: + - Origin + - Referer + - X-Origin + Server: + - scaffolding on HTTPServer2 + X-Xss-Protection: + - '0' + X-Frame-Options: + - SAMEORIGIN + X-Content-Type-Options: + - nosniff + Alt-Svc: + - h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 + Transfer-Encoding: + - chunked + body: + encoding: ASCII-8BIT + string: |- + { + "access_token": "", + "expires_in": 3599, + "scope": "https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/sqlservice.login openid https://www.googleapis.com/auth/cloud-platform", + "token_type": "Bearer", + "id_token": "" + } + recorded_at: Tue, 23 Sep 2025 23:40:19 GMT +- request: + method: post + uri: https://-aiplatform.googleapis.com/v1beta1/projects//locations//publishers/google/models/gemini-2.5-flash:generateContent + body: + encoding: UTF-8 + string: '{"contents":[{"role":"user","parts":[{"text":"What''s the fall of Rome?"}]}],"generationConfig":{},"tools":[{"functionDeclarations":[{"name":"weather","description":"Gets + current weather for a location","parameters":{"type":"OBJECT","properties":{"latitude":{"type":"STRING","description":"Latitude + (e.g., 52.5200)"},"longitude":{"type":"STRING","description":"Longitude (e.g., + 13.4050)"}},"required":["latitude","longitude"]}}]}],"toolConfig":{"functionCallingConfig":{"mode":"any","allowedFunctionNames":["weather"]}}}' + headers: + User-Agent: + - Faraday v2.13.4 + 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: + Content-Type: + - application/json; charset=UTF-8 + Vary: + - Origin + - Referer + - X-Origin + Date: + - Tue, 23 Sep 2025 23:40:20 GMT + Server: + - scaffolding on HTTPServer2 + X-Xss-Protection: + - '0' + X-Frame-Options: + - SAMEORIGIN + X-Content-Type-Options: + - nosniff + Alt-Svc: + - h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 + Transfer-Encoding: + - chunked + body: + encoding: ASCII-8BIT + string: | + { + "candidates": [ + { + "content": { + "role": "model", + "parts": [ + { + "functionCall": { + "name": "weather", + "args": { + "longitude": "13.4050", + "latitude": "52.5200" + } + }, + "thoughtSignature": "Ct8CAR/MhbYiyJv/zjhmLZKv3KGwS6Ub8a+VgkWbOiqAiqZni0RcbOZKsBRaWAqO7ZujffAeNN2DlLr25BnU624oQINT98/UFUA9rTnslQAcUqnlMd5psNjzf2nv1SvGrMH4/Naac1L0pAbn2NpecBYfJxVuy1yHRK4W2qe4V77sZMT5ktzY0NC6n9QyNrR/9QhyJKRwfMTK9NAoENRzb5smcBcM/ZTRxGswltsr3BW2tGespKhqhn8CqUxl3+Ll6VZl1GO3C9ARsSqXAJUBI1SXHQ2ayIgHtGy38xDIy578N3hpbr9TqMV5E32HRjxVT03MuoGnK+5zZPQsZLouC6FrZ+KotfEfympgmUjFOKA35bsFUJ/+tP4rr3TR/rmAXdkje/g/rRqfuoiahiwN8/HMpo/CWhhmjCI9TszGjPXByZuwuytP5CxTs+oSO5kT98z8TcDkMQILHR85oyQtzU1x" + } + ] + }, + "finishReason": "STOP", + "avgLogprobs": -0.73671425090116616 + } + ], + "usageMetadata": { + "promptTokenCount": 52, + "candidatesTokenCount": 17, + "totalTokenCount": 133, + "trafficType": "ON_DEMAND", + "promptTokensDetails": [ + { + "modality": "TEXT", + "tokenCount": 52 + } + ], + "candidatesTokensDetails": [ + { + "modality": "TEXT", + "tokenCount": 17 + } + ], + "thoughtsTokenCount": 64 + }, + "modelVersion": "gemini-2.5-flash", + "createTime": "2025-09-23T23:40:19.403263Z", + "responseId": "4y_TaL_OGIL9ld8PqbTR0Qo" + } + recorded_at: Tue, 23 Sep 2025 23:40:20 GMT +- request: + method: post + uri: https://oauth2.googleapis.com/token + body: + encoding: ASCII-8BIT + string: grant_type=refresh_token&refresh_token=&client_id=&client_secret= + headers: + User-Agent: + - Faraday v2.13.4 + Content-Type: + - application/x-www-form-urlencoded + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + Pragma: + - no-cache + Cache-Control: + - no-cache, no-store, max-age=0, must-revalidate + Date: + - Tue, 23 Sep 2025 23:40:20 GMT + Expires: + - Mon, 01 Jan 1990 00:00:00 GMT + Content-Type: + - application/json; charset=utf-8 + Vary: + - Origin + - Referer + - X-Origin + Server: + - scaffolding on HTTPServer2 + X-Xss-Protection: + - '0' + X-Frame-Options: + - SAMEORIGIN + X-Content-Type-Options: + - nosniff + Alt-Svc: + - h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 + Transfer-Encoding: + - chunked + body: + encoding: ASCII-8BIT + string: |- + { + "access_token": "", + "expires_in": 3599, + "scope": "https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/cloud-platform openid https://www.googleapis.com/auth/sqlservice.login", + "token_type": "Bearer", + "id_token": "" + } + recorded_at: Tue, 23 Sep 2025 23:40:20 GMT +- request: + method: post + uri: https://-aiplatform.googleapis.com/v1beta1/projects//locations//publishers/google/models/gemini-2.5-flash:generateContent + body: + encoding: UTF-8 + string: '{"contents":[{"role":"user","parts":[{"text":"What''s the fall of Rome?"}]},{"role":"model","parts":[{"functionCall":{"name":"weather","args":{"longitude":"13.4050","latitude":"52.5200"}}}]},{"role":"user","parts":[{"functionResponse":{"name":"3ec84e41-bb7b-4578-a062-a92cf3e3e4bb","response":{"name":"3ec84e41-bb7b-4578-a062-a92cf3e3e4bb","content":[{"text":"Current + weather at 52.5200, 13.4050: 15°C, Wind: 10 km/h"}]}}}]}],"generationConfig":{},"tools":[{"functionDeclarations":[{"name":"weather","description":"Gets + current weather for a location","parameters":{"type":"OBJECT","properties":{"latitude":{"type":"STRING","description":"Latitude + (e.g., 52.5200)"},"longitude":{"type":"STRING","description":"Longitude (e.g., + 13.4050)"}},"required":["latitude","longitude"]}}]}]}' + headers: + User-Agent: + - Faraday v2.13.4 + 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: + Content-Type: + - application/json; charset=UTF-8 + Vary: + - Origin + - Referer + - X-Origin + Date: + - Tue, 23 Sep 2025 23:40:21 GMT + Server: + - scaffolding on HTTPServer2 + X-Xss-Protection: + - '0' + X-Frame-Options: + - SAMEORIGIN + X-Content-Type-Options: + - nosniff + Alt-Svc: + - h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 + Transfer-Encoding: + - chunked + body: + encoding: ASCII-8BIT + string: | + { + "candidates": [ + { + "content": { + "role": "model", + "parts": [ + { + "text": "I am sorry, I cannot tell you about the fall of Rome with the available tools.", + "thoughtSignature": "CtsCAR/Mhba7U8MzvRalU8+ez57BrQ9yzdjTG/Tohpkadsnk013vgS3b0W3Wu5eY2smCrhDTbOaExj/r7qAXy81icgO29x0NIA96DH3T8MMqH7Xm/WSv8wQnibCAP6P8fc8seZG9ARBYbAw+GstPfHx/thiAyy6ToFZDTyGbi2JevJhsyxaKgomxaFgFm/0NreyjJ46abAYHVdd6zpro4R9KnFKQZiOQ1iWR34d9cst8QUsRll6rub+LyTwuhS9kco4PKbtoP+PHWs62df6v3XLbyBnv2sUu8GiI2bAe7Mzty5jZgotW4NDtWW1oWusv6eL8T4U8/uU8IG9kHKyrbm0QSHwdZREbNy6h6vtodi08W6K8jfJa8A2c6JUSsUzraYpv72jzOttTfNwwSEYgnkafLBZGcywDbG5L2CaFKK8xuk1M/aESDDTGdnp71IqnlOme5xSOaTY9OjATMfk=" + } + ] + }, + "finishReason": "STOP", + "avgLogprobs": -1.2237757576836481 + } + ], + "usageMetadata": { + "promptTokenCount": 171, + "candidatesTokenCount": 18, + "totalTokenCount": 253, + "trafficType": "ON_DEMAND", + "promptTokensDetails": [ + { + "modality": "TEXT", + "tokenCount": 171 + } + ], + "candidatesTokensDetails": [ + { + "modality": "TEXT", + "tokenCount": 18 + } + ], + "thoughtsTokenCount": 64 + }, + "modelVersion": "gemini-2.5-flash", + "createTime": "2025-09-23T23:40:20.524965Z", + "responseId": "5C_TaKWFIIL9ld8PqbTR0Qo" + } + recorded_at: Tue, 23 Sep 2025 23:40:21 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 cff534172..483d5a0e1 100644 --- a/spec/ruby_llm/chat_tools_spec.rb +++ b/spec/ruby_llm/chat_tools_spec.rb @@ -377,6 +377,108 @@ def execute(query:) end end + describe 'tool choice and parallel control' do + CHAT_MODELS.each do |model_info| + model = model_info[:model] + provider = model_info[:provider] + + it "#{provider}/#{model} respects choice: :none" do + unless RubyLLM::Provider.providers[provider]&.local? + model_info = RubyLLM.models.find(model) + skip "#{model} doesn't support function calling" unless model_info&.supports_functions? + end + + provider_class = provider ? RubyLLM::Provider.providers[provider.to_sym] : nil + skip "#{provider} doesn't support tool choice" unless provider_class&.capabilities&.supports_tool_choice?(model) + + skip "Bedrock doesn't support :none tool choice" if provider == :bedrock + + chat = RubyLLM.chat(model: model, provider: provider) + .with_tool(Weather, choice: :none) + + tool_called = false + chat.on_tool_call do |_tool_call| + tool_called = true + end + + response = chat.ask("What's the weather in Berlin? (52.5200, 13.4050)") + + expect(tool_called).to be(false) + expect(response.content).not_to include('15°C') # Should not contain tool result + end + + it "#{provider}/#{model} respects choice: :required for unrelated queries" do + unless RubyLLM::Provider.providers[provider]&.local? + model_info = RubyLLM.models.find(model) + skip "#{model} doesn't support function calling" unless model_info&.supports_functions? + end + + provider_class = provider ? RubyLLM::Provider.providers[provider.to_sym] : nil + skip "#{provider} doesn't support tool choice" unless provider_class&.capabilities&.supports_tool_choice?(model) + + chat = RubyLLM.chat(model: model, provider: provider) + .with_tool(Weather, choice: :required) + + tool_called = false + chat.on_tool_call do |_tool_call| + tool_called = true + end + + # Ask about Roman history - completely unrelated to weather + chat.ask('When was the fall of Rome?') + + expect(tool_called).to be(true) # Tool should be forced to run + end + + it "#{provider}/#{model} respects specific tool choice" do + unless RubyLLM::Provider.providers[provider]&.local? + model_info = RubyLLM.models.find(model) + skip "#{model} doesn't support function calling" unless model_info&.supports_functions? + end + + provider_class = provider ? RubyLLM::Provider.providers[provider.to_sym] : nil + skip "#{provider} doesn't support tool choice" unless provider_class&.capabilities&.supports_tool_choice?(model) + + chat = RubyLLM.chat(model: model, provider: provider) + .with_tool(Weather, choice: :weather) + + tool_called = false + chat.on_tool_call do |_tool_call| + tool_called = true + end + + # Ask about Roman history - completely unrelated to weather + chat.ask("What's the fall of Rome?") + + expect(tool_called).to be(true) + end + + it "#{provider}/#{model} respects parallel: false for sequential execution" do + unless RubyLLM::Provider.providers[provider]&.local? + model_info = RubyLLM.models.find(model) + skip "#{model} doesn't support function calling" unless model_info&.supports_functions? + end + + provider_class = provider ? RubyLLM::Provider.providers[provider.to_sym] : nil + unless provider_class&.capabilities&.supports_tool_parallel_control?(model) + skip "#{provider} doesn't support tool parallel control" + end + + chat = RubyLLM.chat(model: model, provider: provider) + .with_tools(Weather, BestLanguageToLearn, parallel: false) + .with_instructions( + 'You must use both the weather tool for Berlin (52.5200, 13.4050) and the best language tool.' + ) + + chat.on_end_message do |message| + expect(message.tool_calls.length).to eq(1) if message.tool_call? + end + + chat.ask("What's the weather in Berlin and what's the best programming language?") + end + end + end + describe 'error handling' do it 'raises an error when tool execution fails' do chat = RubyLLM.chat.with_tool(BrokenTool)