Module: RubyLLM::Providers::OpenAIResponses::Tools

Included in:
RubyLLM::Providers::OpenAIResponses
Defined in:
lib/ruby_llm/providers/openai_responses/tools.rb

Overview

Tools/function calling methods for the OpenAI Responses API. Handles both custom function tools and built-in tools.

Constant Summary collapse

EMPTY_PARAMETERS_SCHEMA =
{
  'type' => 'object',
  'properties' => {},
  'required' => [],
  'additionalProperties' => false
}.freeze
BUILT_IN_TOOLS =

Built-in tool type constants

{
  web_search: { type: 'web_search' },
  web_search_preview: { type: 'web_search_preview' },
  file_search: ->(vector_store_ids) { { type: 'file_search', vector_store_ids: vector_store_ids } },
  code_interpreter: { type: 'code_interpreter', container: { type: 'auto' } },
  image_generation: { type: 'image_generation' },
  computer_use: ->(opts) { { type: 'computer_use_preview', **opts } },
  shell: { type: 'shell', environment: { type: 'container_auto' } },
  apply_patch: { type: 'apply_patch' }
}.freeze

Class Method Summary collapse

Class Method Details

.apply_patch_toolObject



216
217
218
# File 'lib/ruby_llm/providers/openai_responses/tools.rb', line 216

def apply_patch_tool
  { type: 'apply_patch' }
end

.build_schema_from_parameters(parameters) ⇒ Object



84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/ruby_llm/providers/openai_responses/tools.rb', line 84

def build_schema_from_parameters(parameters)
  properties = {}
  required = []

  parameters.each do |name, param|
    properties[name.to_s] = {
      type: param.type || 'string',
      description: param.description
    }.compact

    required << name.to_s if param.required
  end

  {
    'type' => 'object',
    'properties' => properties,
    'required' => required,
    'additionalProperties' => false
  }
end

.code_interpreter_tool(container_type: 'auto') ⇒ Object



177
178
179
180
181
182
# File 'lib/ruby_llm/providers/openai_responses/tools.rb', line 177

def code_interpreter_tool(container_type: 'auto')
  {
    type: 'code_interpreter',
    container: { type: container_type }
  }
end

.file_search_tool(vector_store_ids:, max_num_results: nil, ranking_options: nil) ⇒ Object



167
168
169
170
171
172
173
174
175
# File 'lib/ruby_llm/providers/openai_responses/tools.rb', line 167

def file_search_tool(vector_store_ids:, max_num_results: nil, ranking_options: nil)
  tool = {
    type: 'file_search',
    vector_store_ids: Array(vector_store_ids)
  }
  tool[:max_num_results] = max_num_results if max_num_results
  tool[:ranking_options] = ranking_options if ranking_options
  tool
end

.format_tool_calls(tool_calls) ⇒ Object



105
106
107
108
109
110
111
112
113
114
115
116
# File 'lib/ruby_llm/providers/openai_responses/tools.rb', line 105

def format_tool_calls(tool_calls)
  return nil unless tool_calls&.any?

  tool_calls.map do |_, tc|
    {
      type: 'function_call',
      call_id: tc.id,
      name: tc.name,
      arguments: tc.arguments.is_a?(String) ? tc.arguments : JSON.generate(tc.arguments)
    }
  end
end

.image_generation_tool(partial_images: nil) ⇒ Object



184
185
186
187
188
# File 'lib/ruby_llm/providers/openai_responses/tools.rb', line 184

def image_generation_tool(partial_images: nil)
  tool = { type: 'image_generation' }
  tool[:partial_images] = partial_images if partial_images
  tool
end

.mcp_tool(server_label:, server_url:, require_approval: 'never', allowed_tools: nil, headers: nil) ⇒ Object



190
191
192
193
194
195
196
197
198
199
200
# File 'lib/ruby_llm/providers/openai_responses/tools.rb', line 190

def mcp_tool(server_label:, server_url:, require_approval: 'never', allowed_tools: nil, headers: nil)
  tool = {
    type: 'mcp',
    server_label: server_label,
    server_url: server_url,
    require_approval: require_approval
  }
  tool[:allowed_tools] = allowed_tools if allowed_tools
  tool[:headers] = headers if headers
  tool
end

.parameters_schema_for(tool) ⇒ Object



62
63
64
65
66
67
68
69
70
# File 'lib/ruby_llm/providers/openai_responses/tools.rb', line 62

def parameters_schema_for(tool)
  if tool.respond_to?(:params_schema) && tool.params_schema
    tool.params_schema
  elsif tool.respond_to?(:parameters)
    schema_from_parameters(tool.parameters)
  else
    EMPTY_PARAMETERS_SCHEMA
  end
end

.parse_tool_call_arguments(tool_call) ⇒ Object



138
139
140
141
142
143
144
145
146
147
148
149
150
# File 'lib/ruby_llm/providers/openai_responses/tools.rb', line 138

def parse_tool_call_arguments(tool_call)
  arguments = tool_call['arguments']

  if arguments.nil? || arguments.empty?
    {}
  elsif arguments.is_a?(Hash)
    arguments
  else
    JSON.parse(arguments)
  end
rescue JSON::ParserError
  { raw: arguments }
end

.parse_tool_calls(tool_calls, parse_arguments: true) ⇒ Object



118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/ruby_llm/providers/openai_responses/tools.rb', line 118

def parse_tool_calls(tool_calls, parse_arguments: true)
  return nil unless tool_calls&.any?

  tool_calls.to_h do |tc|
    call_id = tc['call_id'] || tc['id']
    [
      call_id,
      ToolCall.new(
        id: call_id,
        name: tc['name'],
        arguments: if parse_arguments
                     parse_tool_call_arguments(tc)
                   else
                     tc['arguments']
                   end
      )
    ]
  end
end

.schema_from_parameters(parameters) ⇒ Object



72
73
74
75
76
77
78
79
80
81
82
# File 'lib/ruby_llm/providers/openai_responses/tools.rb', line 72

def schema_from_parameters(parameters)
  return EMPTY_PARAMETERS_SCHEMA if parameters.nil? || parameters.empty?

  if defined?(RubyLLM::Tool::SchemaDefinition)
    schema_definition = RubyLLM::Tool::SchemaDefinition.from_parameters(parameters)
    schema_definition&.json_schema || EMPTY_PARAMETERS_SCHEMA
  else
    # Fallback for older RubyLLM versions
    build_schema_from_parameters(parameters)
  end
end

.shell_tool(environment_type: 'container_auto', container_id: nil, network_policy: nil, memory_limit: nil) ⇒ Object



202
203
204
205
206
207
208
209
210
211
212
213
214
# File 'lib/ruby_llm/providers/openai_responses/tools.rb', line 202

def shell_tool(environment_type: 'container_auto', container_id: nil,
               network_policy: nil, memory_limit: nil)
  env = if container_id
          { type: 'container_reference', container_id: container_id }
        else
          { type: environment_type }
        end

  env[:network_policy] = network_policy if network_policy
  env[:memory_limit] = memory_limit if memory_limit

  { type: 'shell', environment: env }
end

.tool_for(tool) ⇒ Object



30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# File 'lib/ruby_llm/providers/openai_responses/tools.rb', line 30

def tool_for(tool)
  # Check if it's a built-in tool specification
  return tool if tool.is_a?(Hash) && tool[:type]

  # Handle symbol references to built-in tools
  if tool.is_a?(Symbol) && BUILT_IN_TOOLS.key?(tool)
    built_in = BUILT_IN_TOOLS[tool]
    return built_in.is_a?(Proc) ? built_in.call([]) : built_in
  end

  # Standard function tool
  parameters_schema = parameters_schema_for(tool)

  definition = {
    type: 'function',
    name: tool.name,
    description: tool.description,
    parameters: parameters_schema
  }

  # Add strict mode if schema supports it
  definition[:strict] = true if parameters_schema['additionalProperties'] == false

  return definition if tool.respond_to?(:provider_params) && tool.provider_params.empty?

  if tool.respond_to?(:provider_params) && tool.provider_params.any?
    RubyLLM::Utils.deep_merge(definition, tool.provider_params)
  else
    definition
  end
end

.web_search_preview_tool(search_context_size: nil, user_location: nil) ⇒ Object



160
161
162
163
164
165
# File 'lib/ruby_llm/providers/openai_responses/tools.rb', line 160

def web_search_preview_tool(search_context_size: nil, user_location: nil)
  tool = { type: 'web_search_preview' }
  tool[:search_context_size] = search_context_size if search_context_size
  tool[:user_location] = user_location if user_location
  tool
end

.web_search_tool(search_context_size: nil, user_location: nil, preview: false) ⇒ Object

Helper to create built-in tool configurations



153
154
155
156
157
158
# File 'lib/ruby_llm/providers/openai_responses/tools.rb', line 153

def web_search_tool(search_context_size: nil, user_location: nil, preview: false)
  tool = { type: preview ? 'web_search_preview' : 'web_search' }
  tool[:search_context_size] = search_context_size if search_context_size
  tool[:user_location] = user_location if user_location
  tool
end