class RubyLLM::Providers::OpenAI
def api_base
def api_base 'https://api.openai.com' end
def build_payload(messages, tools:, temperature:, model:, stream: false)
def build_payload(messages, tools:, temperature:, model:, stream: false) { model: model, messages: format_messages(messages), temperature: temperature, stream: stream }.tap do |payload| if tools.any? payload[:tools] = tools.map { |_, tool| tool_for(tool) } payload[:tool_choice] = 'auto' end end end
def completion_url
def completion_url '/v1/chat/completions' end
def format_messages(messages)
def format_messages(messages) messages.map do |msg| { role: msg.role.to_s, content: msg.content, tool_calls: format_tool_calls(msg.tool_calls), tool_call_id: msg.tool_call_id }.compact end end
def format_tool_calls(tool_calls)
def format_tool_calls(tool_calls) return nil unless tool_calls&.any? tool_calls.map do |_, tc| { id: tc.id, type: 'function', function: { name: tc.name, arguments: JSON.generate(tc.arguments) } } end end
def handle_stream(&block)
def handle_stream(&block) to_json_stream do |data| block.call( Chunk.new( role: :assistant, model_id: data['model'], content: data.dig('choices', 0, 'delta', 'content'), tool_calls: parse_tool_calls(data.dig('choices', 0, 'delta', 'tool_calls'), parse_arguments: false) ) ) end end
def headers
def headers { 'Authorization' => "Bearer #{RubyLLM.config.openai_api_key}" } end
def models_url
def models_url '/v1/models' end
def param_schema(param)
def param_schema(param) { type: param.type, description: param.description }.compact end
def parse_completion_response(response)
def parse_completion_response(response) data = response.body return if data.empty? message_data = data.dig('choices', 0, 'message') return unless message_data Message.new( role: :assistant, content: message_data['content'], tool_calls: parse_tool_calls(message_data['tool_calls']), input_tokens: data['usage']['prompt_tokens'], output_tokens: data['usage']['completion_tokens'], model_id: data['model'] ) end
def parse_list_models_response(response)
def parse_list_models_response(response) capabilities = ModelCapabilities::OpenAI (response.body['data'] || []).map do |model| ModelInfo.new( id: model['id'], created_at: Time.at(model['created']), display_name: capabilities.format_display_name(model['id']), provider: 'openai', metadata: { object: model['object'], owned_by: model['owned_by'] }, context_window: capabilities.context_window_for(model['id']), max_tokens: capabilities.max_tokens_for(model['id']), supports_vision: capabilities.supports_vision?(model['id']), supports_functions: capabilities.supports_functions?(model['id']), supports_json_mode: capabilities.supports_json_mode?(model['id']), input_price_per_million: capabilities.input_price_for(model['id']), output_price_per_million: capabilities.output_price_for(model['id']) ) end end
def parse_models_response(response)
def parse_models_response(response) (response.body['data'] || []).map do |model| model_info = begin Models.find(model['id']) rescue StandardError nil end next unless model_info model_info.tap do |info| info.metadata.merge!( object: model['object'], owned_by: model['owned_by'] ) end end.compact end
def parse_tool_calls(tool_calls, parse_arguments: true)
def parse_tool_calls(tool_calls, parse_arguments: true) return nil unless tool_calls&.any? tool_calls.to_h do |tc| [ tc['id'], ToolCall.new( id: tc['id'], name: tc.dig('function', 'name'), arguments: parse_arguments ? JSON.parse(tc.dig('function', 'arguments')) : tc.dig('function', 'arguments') ) ] end end
def tool_for(tool)
def tool_for(tool) { type: 'function', function: { name: tool.name, description: tool.description, parameters: { type: 'object', properties: tool.parameters.transform_values { |param| param_schema(param) }, required: tool.parameters.select { |_, p| p.required }.keys } } } end