Skip to content

Commit e42417e

Browse files
committed
Refactor to address remaining rubocop offenses
1 parent 73f38fb commit e42417e

File tree

3 files changed

+124
-83
lines changed

3 files changed

+124
-83
lines changed

lib/ruby_llm/providers/openai/response.rb

Lines changed: 61 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -40,52 +40,70 @@ def render_response_payload(messages, tools:, temperature:, model:, cache_prompt
4040
payload
4141
end
4242

43-
def format_input(messages) # rubocop:disable Metrics/PerceivedComplexity
43+
def format_input(messages)
4444
all_tool_calls = messages.flat_map do |m|
4545
m.tool_calls&.values || []
4646
end
47-
messages.flat_map do |msg|
48-
if msg.tool_call?
49-
msg.tool_calls.map do |_, tc|
50-
{
51-
type: 'function_call',
52-
call_id: tc.id,
53-
name: tc.name,
54-
arguments: JSON.generate(tc.arguments),
55-
status: 'completed'
56-
}
57-
end
58-
elsif msg.role == :tool
59-
{
60-
type: 'function_call_output',
61-
call_id: all_tool_calls.detect { |tc| tc.id == msg.tool_call_id }&.id,
62-
output: msg.content,
63-
status: 'completed'
64-
}
65-
elsif assistant_message_with_image_attachment?(msg)
66-
items = []
67-
image_attachment = msg.content.attachments.first
68-
if image_attachment.reasoning_id
69-
items << {
70-
type: 'reasoning',
71-
id: image_attachment.reasoning_id,
72-
summary: []
73-
}
74-
end
75-
items << {
76-
type: 'image_generation_call',
77-
id: image_attachment.id
78-
}
79-
items
80-
else
81-
{
82-
type: 'message',
83-
role: format_role(msg.role),
84-
content: ResponseMedia.format_content(msg.content),
85-
status: 'completed'
86-
}.compact
87-
end
88-
end.flatten
47+
messages.flat_map { |msg| format_message_input(msg, all_tool_calls) }.flatten
48+
end
49+
50+
def format_message_input(msg, all_tool_calls)
51+
if msg.tool_call?
52+
format_tool_call_message(msg)
53+
elsif msg.role == :tool
54+
format_tool_response_message(msg, all_tool_calls)
55+
elsif assistant_message_with_image_attachment?(msg)
56+
format_image_generation_message(msg)
57+
else
58+
format_regular_message(msg)
59+
end
60+
end
61+
62+
def format_tool_call_message(msg)
63+
msg.tool_calls.map do |_, tc|
64+
{
65+
type: 'function_call',
66+
call_id: tc.id,
67+
name: tc.name,
68+
arguments: JSON.generate(tc.arguments),
69+
status: 'completed'
70+
}
71+
end
72+
end
73+
74+
def format_tool_response_message(msg, all_tool_calls)
75+
{
76+
type: 'function_call_output',
77+
call_id: all_tool_calls.detect { |tc| tc.id == msg.tool_call_id }&.id,
78+
output: msg.content,
79+
status: 'completed'
80+
}
81+
end
82+
83+
def format_image_generation_message(msg)
84+
items = []
85+
image_attachment = msg.content.attachments.first
86+
if image_attachment.reasoning_id
87+
items << {
88+
type: 'reasoning',
89+
id: image_attachment.reasoning_id,
90+
summary: []
91+
}
92+
end
93+
items << {
94+
type: 'image_generation_call',
95+
id: image_attachment.id
96+
}
97+
items
98+
end
99+
100+
def format_regular_message(msg)
101+
{
102+
type: 'message',
103+
role: format_role(msg.role),
104+
content: ResponseMedia.format_content(msg.content),
105+
status: 'completed'
106+
}.compact
89107
end
90108

91109
def format_role(role)

lib/ruby_llm/providers/openai/streaming.rb

Lines changed: 46 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -27,48 +27,64 @@ def build_chunk(data)
2727
def build_responses_chunk(data)
2828
case data['type']
2929
when 'response.output_text.delta'
30-
Chunk.new(
31-
role: :assistant,
32-
model_id: nil,
33-
content: data['delta'],
34-
tool_calls: nil,
35-
input_tokens: nil,
36-
output_tokens: nil
37-
)
30+
build_text_delta_chunk(data)
3831
when 'response.function_call_arguments.delta'
3932
build_tool_call_delta_chunk(data)
4033
when 'response.image_generation_call.partial_image'
4134
build_partial_image_chunk(data)
4235
when 'response.output_item.added'
43-
if data.dig('item', 'type') == 'function_call'
44-
build_tool_call_start_chunk(data)
45-
elsif data.dig('item', 'type') == 'reasoning'
46-
build_reasoning_chunk(data)
47-
else
48-
build_empty_chunk(data)
49-
end
36+
handle_output_item_added(data)
5037
when 'response.output_item.done'
51-
if data.dig('item', 'type') == 'function_call'
52-
build_tool_call_complete_chunk(data)
53-
elsif data.dig('item', 'type') == 'image_generation_call'
54-
build_completed_image_chunk(data)
55-
else
56-
build_empty_chunk(data)
57-
end
38+
handle_output_item_done(data)
5839
when 'response.completed'
59-
Chunk.new(
60-
role: :assistant,
61-
model_id: data.dig('response', 'model'),
62-
content: nil,
63-
tool_calls: nil,
64-
input_tokens: data.dig('response', 'usage', 'input_tokens'),
65-
output_tokens: data.dig('response', 'usage', 'output_tokens')
66-
)
40+
build_completion_chunk(data)
41+
else
42+
build_empty_chunk(data)
43+
end
44+
end
45+
46+
def build_text_delta_chunk(data)
47+
Chunk.new(
48+
role: :assistant,
49+
model_id: nil,
50+
content: data['delta'],
51+
tool_calls: nil,
52+
input_tokens: nil,
53+
output_tokens: nil
54+
)
55+
end
56+
57+
def handle_output_item_added(data)
58+
if data.dig('item', 'type') == 'function_call'
59+
build_tool_call_start_chunk(data)
60+
elsif data.dig('item', 'type') == 'reasoning'
61+
build_reasoning_chunk(data)
62+
else
63+
build_empty_chunk(data)
64+
end
65+
end
66+
67+
def handle_output_item_done(data)
68+
if data.dig('item', 'type') == 'function_call'
69+
build_tool_call_complete_chunk(data)
70+
elsif data.dig('item', 'type') == 'image_generation_call'
71+
build_completed_image_chunk(data)
6772
else
6873
build_empty_chunk(data)
6974
end
7075
end
7176

77+
def build_completion_chunk(data)
78+
Chunk.new(
79+
role: :assistant,
80+
model_id: data.dig('response', 'model'),
81+
content: nil,
82+
tool_calls: nil,
83+
input_tokens: data.dig('response', 'usage', 'input_tokens'),
84+
output_tokens: data.dig('response', 'usage', 'output_tokens')
85+
)
86+
end
87+
7288
def build_chat_completions_chunk(data)
7389
Chunk.new(
7490
role: :assistant,

lib/ruby_llm/stream_accumulator.rb

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -33,28 +33,35 @@ def add(chunk)
3333

3434
def to_message(response)
3535
content = final_content
36-
# Associate reasoning_id with image attachments if present
37-
if @reasoning_id && content.is_a?(Content) && content.attachments.any?
38-
content.attachments.each do |attachment|
39-
attachment.instance_variable_set(:@reasoning_id, @reasoning_id) if attachment.is_a?(ImageAttachment)
40-
end
41-
end
36+
associate_reasoning_with_images(content)
4237

4338
Message.new(
4439
role: :assistant,
4540
content: content,
4641
model_id: model_id,
4742
tool_calls: tool_calls_from_stream,
48-
input_tokens: @input_tokens.positive? ? @input_tokens : nil,
49-
output_tokens: @output_tokens.positive? ? @output_tokens : nil,
50-
cached_tokens: @cached_tokens.positive? ? @cached_tokens : nil,
51-
cache_creation_tokens: @cache_creation_tokens.positive? ? @cache_creation_tokens : nil,
43+
input_tokens: positive_or_nil(@input_tokens),
44+
output_tokens: positive_or_nil(@output_tokens),
45+
cached_tokens: positive_or_nil(@cached_tokens),
46+
cache_creation_tokens: positive_or_nil(@cache_creation_tokens),
5247
raw: response
5348
)
5449
end
5550

5651
private
5752

53+
def associate_reasoning_with_images(content)
54+
return unless @reasoning_id && content.is_a?(Content) && content.attachments.any?
55+
56+
content.attachments.each do |attachment|
57+
attachment.instance_variable_set(:@reasoning_id, @reasoning_id) if attachment.is_a?(ImageAttachment)
58+
end
59+
end
60+
61+
def positive_or_nil(value)
62+
value.positive? ? value : nil
63+
end
64+
5865
def accumulate_content(new_content)
5966
return unless new_content
6067

0 commit comments

Comments
 (0)