Skip to content

Fix Message#to_h to deep convert tool_calls#611

Open
sevginuroksuz wants to merge 1 commit intocrmne:mainfrom
sevginuroksuz:fix/message-to_h-deep-tool_calls
Open

Fix Message#to_h to deep convert tool_calls#611
sevginuroksuz wants to merge 1 commit intocrmne:mainfrom
sevginuroksuz:fix/message-to_h-deep-tool_calls

Conversation

@sevginuroksuz
Copy link

Closes #610

Summary

This PR improves serialization consistency for RubyLLM::Message.

Changes

  • Message#to_h now deep-converts tool_calls using ToolCall#to_h.
  • Message#initialize now accepts both RubyLLM::ToolCall instances and hashes in the tool_calls array.
  • Hash inputs (with symbol or string keys: id, name, arguments, thought_signature) are automatically converted into ToolCall objects.
  • Invalid types or unexpected keys raise a clear ArgumentError.

Why

Previously:

  • Message#to_h performed shallow serialization.
  • Passing hashes to tool_calls did not normalize them into ToolCall objects.

This ensures:

  • Consistent internal state
  • Predictable serialization
  • Improved compatibility when integrating with external APIs

Tests

  • Added coverage for deep serialization of tool_calls
  • Added coverage for hash input normalization

@thinking = options[:thinking]

ensure_valid_role
def normalize_tool_calls(tool_calls)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sevginuroksuz This seems like a copy/paste issue or an AI hallucination

h[:arguments] = h[:arguments].transform_keys(&:to_sym)
end
filtered = h.select { |k, _| allowed_keys.include?(k) }
puts "ToolCall.new args: #{filtered.inspect}"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't have "puts" statements

if h[:arguments].is_a?(Hash)
h[:arguments] = h[:arguments].transform_keys(&:to_sym)
end
filtered = h.select { |k, _| allowed_keys.include?(k) }
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can simplify this to: filtered = h.slice(*allowed_keys)

https://ruby-doc.org/core-3.1.0/Hash.html#method-i-slice

signature: extract_thought_signature(parts)
),
tool_calls: tool_calls,
tool_calls: tool_calls&.map { |tc| tc.respond_to?(:to_h) ? tc.to_h : tc },
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why was this added? tool_calls comes from a extract_tool_calls which provides ToolCall records, and deals with building Message records, not converting to hashes. This seems like an unneeded change.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] chat.messages.map(&:to_h) doesn't convert ToolCall values

2 participants