Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion lib/ruby_llm/active_record/acts_as.rb
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,12 @@ def acts_as_message(chat: :chat, chat_class: nil, chat_foreign_key: nil, touch_c
self.tool_call_class = (tool_call_class || tool_calls.to_s.classify).to_s
self.model_class = (model_class || model.to_s.classify).to_s

parent_tool_call_foreign_key = if tool_calls_foreign_key&.end_with?('_message_id')
tool_calls_foreign_key.sub(/_message_id\z/, '_tool_call_id')
else
ActiveSupport::Inflector.foreign_key(self.tool_call_class)
end

belongs_to chat,
class_name: self.chat_class,
foreign_key: chat_foreign_key,
Expand All @@ -118,7 +124,7 @@ def acts_as_message(chat: :chat, chat_class: nil, chat_foreign_key: nil, touch_c

belongs_to :parent_tool_call,
class_name: self.tool_call_class,
foreign_key: ActiveSupport::Inflector.foreign_key(tool_calls.to_s.singularize),
foreign_key: parent_tool_call_foreign_key,
optional: true

has_many :tool_results,
Expand Down
39 changes: 35 additions & 4 deletions spec/ruby_llm/active_record/acts_as_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -512,15 +512,17 @@ class BotToolCall < ActiveRecord::Base # rubocop:disable Lint/ConstantDefinition

after(:all) do # rubocop:disable RSpec/BeforeAfterAll
ActiveRecord::Migration.suppress_messages do
if ActiveRecord::Base.connection.table_exists?(:support_tool_calls)
ActiveRecord::Migration.drop_table :support_tool_calls
end
ActiveRecord::Base.connection.execute('PRAGMA foreign_keys = OFF')
if ActiveRecord::Base.connection.table_exists?(:support_messages)
ActiveRecord::Migration.drop_table :support_messages
end
if ActiveRecord::Base.connection.table_exists?(:support_tool_calls)
ActiveRecord::Migration.drop_table :support_tool_calls
end
if ActiveRecord::Base.connection.table_exists?(:support_conversations)
ActiveRecord::Migration.drop_table :support_conversations
end
ActiveRecord::Base.connection.execute('PRAGMA foreign_keys = ON')
end
end

Expand All @@ -534,7 +536,12 @@ class Conversation < ActiveRecord::Base # rubocop:disable RSpec/LeakyConstantDec
end

class Message < ActiveRecord::Base # rubocop:disable RSpec/LeakyConstantDeclaration
acts_as_message chat: :conversation, chat_class: 'Support::Conversation', tool_call_class: 'Support::ToolCall'
acts_as_message(
chat: :conversation,
chat_class: 'Support::Conversation',
tool_calls: :stored_calls,
tool_call_class: 'Support::ToolCall'
)
end

class ToolCall < ActiveRecord::Base # rubocop:disable RSpec/LeakyConstantDeclaration
Expand All @@ -548,6 +555,30 @@ class ToolCall < ActiveRecord::Base # rubocop:disable RSpec/LeakyConstantDeclara
expect { conversation.messages.create!(role: 'user', content: 'Test') }.not_to raise_error
expect(conversation.messages.count).to eq(1)
end

it 'uses the tool_call_class-derived foreign key for parent_tool_call' do
association = Support::Message.reflect_on_association(:parent_tool_call)

expect(association.foreign_key).to eq('tool_call_id')
end

it 'persists parent_tool_call with a custom tool_calls association name' do
conversation = Support::Conversation.create!(model: model)
message = conversation.messages.create!(role: 'assistant', content: 'Tool call message')
tool_call = message.stored_calls.create!(
tool_call_id: 'call_123',
name: 'calculator',
arguments: { expression: '2 + 2' }
)

expect do
conversation.messages.create!(
role: 'tool',
content: '4',
parent_tool_call: tool_call
)
end.not_to raise_error
end
end

describe 'to_llm conversion' do
Expand Down