Module: RubyLLM::Providers::OpenAI::Chat
- Included in:
- RubyLLM::Providers::OpenAI
- Defined in:
- lib/ruby_llm/providers/openai/chat.rb
Overview
Chat methods of the OpenAI API integration
Class Method Summary collapse
- .cache_read_tokens(usage) ⇒ Object
- .cache_write_tokens(usage) ⇒ Object
- .extract_content_and_thinking(content) ⇒ Object
- .extract_text_from_blocks(blocks) ⇒ Object
- .extract_think_tag_content(text) ⇒ Object
- .extract_thinking_from_blocks(blocks) ⇒ Object
- .extract_thinking_signature(message_data) ⇒ Object
- .extract_thinking_text(message_data) ⇒ Object
- .extract_thinking_text_from_block(block) ⇒ Object
- .format_messages(messages) ⇒ Object
- .format_role(role) ⇒ Object
- .format_thinking(msg) ⇒ Object
- .generated_tokens_from_total(usage) ⇒ Object
- .input_tokens(usage) ⇒ Object
- .output_tokens(usage) ⇒ Object
-
.parse_completion_response(response) ⇒ Object
rubocop:enable Metrics/ParameterLists,Metrics/PerceivedComplexity.
-
.render_payload(messages, tools:, temperature:, model:, stream: false, schema: nil, thinking: nil, tool_prefs: nil) ⇒ Object
rubocop:disable Metrics/ParameterLists,Metrics/PerceivedComplexity.
- .resolve_effort(thinking) ⇒ Object
- .thinking_tokens(usage) ⇒ Object
Instance Method Summary collapse
Class Method Details
.cache_read_tokens(usage) ⇒ Object
112 113 114 |
# 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 |
.cache_write_tokens(usage) ⇒ Object
116 117 118 |
# 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 |
.extract_content_and_thinking(content) ⇒ Object
175 176 177 178 179 180 181 182 183 |
# File 'lib/ruby_llm/providers/openai/chat.rb', line 175 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 |
.extract_text_from_blocks(blocks) ⇒ Object
185 186 187 188 189 |
# File 'lib/ruby_llm/providers/openai/chat.rb', line 185 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 |
.extract_think_tag_content(text) ⇒ Object
210 211 212 213 214 215 216 217 |
# File 'lib/ruby_llm/providers/openai/chat.rb', line 210 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 |
.extract_thinking_from_blocks(blocks) ⇒ Object
191 192 193 194 195 196 197 |
# File 'lib/ruby_llm/providers/openai/chat.rb', line 191 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 |
.extract_thinking_signature(message_data) ⇒ Object
170 171 172 173 |
# File 'lib/ruby_llm/providers/openai/chat.rb', line 170 def extract_thinking_signature() candidate = ['reasoning_signature'] || ['signature'] candidate.is_a?(String) ? candidate : nil end |
.extract_thinking_text(message_data) ⇒ Object
165 166 167 168 |
# File 'lib/ruby_llm/providers/openai/chat.rb', line 165 def extract_thinking_text() candidate = ['reasoning_content'] || ['reasoning'] || ['thinking'] candidate.is_a?(String) ? candidate : nil end |
.extract_thinking_text_from_block(block) ⇒ Object
199 200 201 202 203 204 205 206 207 208 |
# File 'lib/ruby_llm/providers/openai/chat.rb', line 199 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 |
.format_messages(messages) ⇒ Object
124 125 126 127 128 129 130 131 132 133 |
# File 'lib/ruby_llm/providers/openai/chat.rb', line 124 def () .map do |msg| { role: format_role(msg.role), content: Media.format_content(msg.content), tool_calls: format_tool_calls(msg.tool_calls), tool_call_id: msg.tool_call_id }.compact.merge(format_thinking(msg)) end end |
.format_role(role) ⇒ Object
135 136 137 138 139 140 141 142 |
# File 'lib/ruby_llm/providers/openai/chat.rb', line 135 def format_role(role) case role when :system @config.openai_use_system_role ? 'system' : 'developer' else role.to_s end end |
.format_thinking(msg) ⇒ Object
150 151 152 153 154 155 156 157 158 159 160 161 162 163 |
# File 'lib/ruby_llm/providers/openai/chat.rb', line 150 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 |
.generated_tokens_from_total(usage) ⇒ Object
104 105 106 107 108 109 110 |
# 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 |
.input_tokens(usage) ⇒ Object
84 85 86 87 88 89 90 91 |
# 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 |
.output_tokens(usage) ⇒ Object
93 94 95 96 97 98 99 100 101 102 |
# 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 |
.parse_completion_response(response) ⇒ Object
rubocop:enable Metrics/ParameterLists,Metrics/PerceivedComplexity
54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |
# 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') = data.dig('choices', 0, 'message') return unless usage = data['usage'] || {} thinking_tokens = thinking_tokens(usage) content, thinking_from_blocks = extract_content_and_thinking(['content']) thinking_text = thinking_from_blocks || extract_thinking_text() thinking_signature = extract_thinking_signature() Message.new( role: :assistant, content: content, thinking: Thinking.build(text: thinking_text, signature: thinking_signature), tool_calls: parse_tool_calls(['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 |
.render_payload(messages, tools:, temperature:, model:, stream: false, schema: nil, thinking: nil, tool_prefs: nil) ⇒ Object
rubocop:disable Metrics/ParameterLists,Metrics/PerceivedComplexity
15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
# File 'lib/ruby_llm/providers/openai/chat.rb', line 15 def render_payload(, tools:, temperature:, model:, stream: false, schema: nil, thinking: nil, tool_prefs: nil) tool_prefs ||= {} payload = { model: model.id, 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 |
.resolve_effort(thinking) ⇒ Object
144 145 146 147 148 |
# File 'lib/ruby_llm/providers/openai/chat.rb', line 144 def resolve_effort(thinking) return nil unless thinking thinking.respond_to?(:effort) ? thinking.effort : thinking end |
.thinking_tokens(usage) ⇒ Object
120 121 122 |
# 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 |
Instance Method Details
#completion_url ⇒ Object
8 9 10 |
# File 'lib/ruby_llm/providers/openai/chat.rb', line 8 def completion_url 'chat/completions' end |