module RubyLLM::Providers::OpenAI::Chat
Public Instance Methods
Source
# File lib/ruby_llm/providers/openai/chat.rb, line 112 def cache_read_tokens(usage) usage.dig('prompt_tokens_details', 'cached_tokens') || usage['prompt_cache_hit_tokens'] end
Source
# File lib/ruby_llm/providers/openai/chat.rb, line 116 def cache_write_tokens(usage) usage.dig('prompt_tokens_details', 'cache_write_tokens') || 0 end
Source
# File lib/ruby_llm/providers/openai/chat.rb, line 8 def completion_url 'chat/completions' end
# File lib/ruby_llm/providers/openai/chat.rb, line 190 def extract_content_and_thinking(content) return extract_think_tag_content(content) if content.is_a?(String) return [content, nil] unless content.is_a?(Array) text = extract_text_from_blocks(content) thinking = extract_thinking_from_blocks(content) [text.empty? ? nil : text, thinking.empty? ? nil : thinking] end
# File lib/ruby_llm/providers/openai/chat.rb, line 200 def extract_text_from_blocks(blocks) blocks.filter_map do |block| block['text'] if block['type'] == 'text' && block['text'].is_a?(String) end.join end
# File lib/ruby_llm/providers/openai/chat.rb, line 225 def extract_think_tag_content(text) return [text, nil] unless text.include?('<think>') thinking = text.scan(%r{<think>(.*?)</think>}m).join content = text.gsub(%r{<think>.*?</think>}m, '').strip [content.empty? ? nil : content, thinking.empty? ? nil : thinking] end
# File lib/ruby_llm/providers/openai/chat.rb, line 206 def extract_thinking_from_blocks(blocks) blocks.filter_map do |block| next unless block['type'] == 'thinking' extract_thinking_text_from_block(block) end.join end
# File lib/ruby_llm/providers/openai/chat.rb, line 185 def extract_thinking_signature(message_data) candidate = message_data['reasoning_signature'] || message_data['signature'] candidate.is_a?(String) ? candidate : nil end
# File lib/ruby_llm/providers/openai/chat.rb, line 180 def extract_thinking_text(message_data) candidate = message_data['reasoning_content'] || message_data['reasoning'] || message_data['thinking'] candidate.is_a?(String) ? candidate : nil end
# File lib/ruby_llm/providers/openai/chat.rb, line 214 def extract_thinking_text_from_block(block) thinking_block = block['thinking'] return thinking_block if thinking_block.is_a?(String) if thinking_block.is_a?(Array) return thinking_block.filter_map { |item| item['text'] if item['type'] == 'text' }.join end block['text'] if block['text'].is_a?(String) end
Source
# File lib/ruby_llm/providers/openai/chat.rb, line 146 def format_content(content) Media.format_content(content) end
Source
# File lib/ruby_llm/providers/openai/chat.rb, line 135 def format_message_content(msg) content = format_content(msg.content) return '' if content.nil? && thinking_only_assistant_message?(msg) content end
Source
# File lib/ruby_llm/providers/openai/chat.rb, line 124 def format_messages(messages) messages.map do |msg| { role: format_role(msg.role), content: format_message_content(msg), tool_calls: format_tool_calls(msg.tool_calls), tool_call_id: msg.tool_call_id }.compact.merge(format_thinking(msg)) end end
Source
# File lib/ruby_llm/providers/openai/chat.rb, line 150 def format_role(role) case role when :system @config.openai_use_system_role ? 'system' : 'developer' else role.to_s end end
Source
# File lib/ruby_llm/providers/openai/chat.rb, line 165 def format_thinking(msg) return {} unless msg.role == :assistant thinking = msg.thinking return {} unless thinking payload = {} if thinking.text payload[:reasoning] = thinking.text payload[:reasoning_content] = thinking.text end payload[:reasoning_signature] = thinking.signature if thinking.signature payload end
# File lib/ruby_llm/providers/openai/chat.rb, line 104 def generated_tokens_from_total(usage) prompt_tokens = usage['prompt_tokens'] total_tokens = usage['total_tokens'] return unless prompt_tokens && total_tokens [total_tokens.to_i - prompt_tokens.to_i, 0].max end
Source
# File lib/ruby_llm/providers/openai/chat.rb, line 84 def input_tokens(usage) return usage['prompt_cache_miss_tokens'] if usage['prompt_cache_miss_tokens'] prompt_tokens = usage['prompt_tokens'] return unless prompt_tokens [prompt_tokens.to_i - cache_read_tokens(usage).to_i - cache_write_tokens(usage).to_i, 0].max end
Source
# File lib/ruby_llm/providers/openai/chat.rb, line 93 def output_tokens(usage) completion_tokens = usage['completion_tokens'] return unless completion_tokens completion_tokens = completion_tokens.to_i generated_tokens = generated_tokens_from_total(usage) return completion_tokens unless generated_tokens && generated_tokens > completion_tokens generated_tokens end
# File lib/ruby_llm/providers/openai/chat.rb, line 54 def parse_completion_response(response) data = response.body return if data.empty? raise Error.new(response, data.dig('error', 'message')) if data.dig('error', 'message') message_data = data.dig('choices', 0, 'message') return unless message_data usage = data['usage'] || {} thinking_tokens = thinking_tokens(usage) content, thinking_from_blocks = extract_content_and_thinking(message_data['content']) thinking_text = thinking_from_blocks || extract_thinking_text(message_data) thinking_signature = extract_thinking_signature(message_data) Message.new( role: :assistant, content: content, thinking: Thinking.build(text: thinking_text, signature: thinking_signature), tool_calls: parse_tool_calls(message_data['tool_calls']), input_tokens: input_tokens(usage), output_tokens: output_tokens(usage), cached_tokens: cache_read_tokens(usage), cache_creation_tokens: cache_write_tokens(usage), thinking_tokens: thinking_tokens, model_id: data['model'], raw: response ) end
rubocop:enable Metrics/ParameterLists,Metrics/PerceivedComplexity
# File lib/ruby_llm/providers/openai/chat.rb, line 15 def render_payload(messages, tools:, temperature:, model:, stream: false, schema: nil, thinking: nil, tool_prefs: nil) tool_prefs ||= {} payload = { model: model.id, messages: format_messages(messages), stream: stream } payload[:temperature] = temperature unless temperature.nil? 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[:calls] == :many unless tool_prefs[:calls].nil? end if schema schema_name = schema[:name] schema_def = schema[:schema] strict = schema[:strict] payload[:response_format] = { type: 'json_schema', json_schema: { name: schema_name, schema: schema_def, strict: strict } } end effort = resolve_effort(thinking) payload[:reasoning_effort] = effort if effort payload[:stream_options] = { include_usage: true } if stream payload end
rubocop:disable Metrics/ParameterLists,Metrics/PerceivedComplexity
Source
# File lib/ruby_llm/providers/openai/chat.rb, line 159 def resolve_effort(thinking) return nil unless thinking thinking.respond_to?(:effort) ? thinking.effort : thinking end
# File lib/ruby_llm/providers/openai/chat.rb, line 142 def thinking_only_assistant_message?(msg) msg.role == :assistant && msg.thinking && !msg.tool_call? end
Source
# File lib/ruby_llm/providers/openai/chat.rb, line 120 def thinking_tokens(usage) usage.dig('completion_tokens_details', 'reasoning_tokens') || usage['reasoning_tokens'] end