Skip to content

Conversation

@codxse
Copy link

@codxse codxse commented Dec 16, 2025

Fixes #521

Gemini 3 Pro models require thought_signature to be preserved and returned during multi-turn function calling conversations. Without this, the API returns an error:

"Function call is missing a thought_signature in functionCall parts."

Changes:

  • Add thought_signature attribute to ToolCall class
  • Extract thoughtSignature from Gemini API responses in extract_tool_calls
  • Include thoughtSignature in functionCall parts via format_tool_call
  • Include thoughtSignature in functionResponse parts via format_tool_result
  • Update MessageFormatter to store and pass signature metadata

This is backward compatible - works with Gemini 2.5 (where signatures are optional) and other providers (which don't use thought signatures).

See: https://ai.google.dev/gemini-api/docs/thought-signatures

What this does

Adds support for Gemini 3's thoughtSignature feature in function calling. Thought signatures are encrypted representations of the model's internal thought process that must be preserved across multi-turn conversations when using tools.

The implementation:

  1. Captures thoughtSignature from Gemini API responses when extracting tool calls
  2. Stores the signature in the ToolCall object
  3. Includes it back in functionCall parts when replaying conversation history
  4. Includes it in functionResponse parts when returning tool results

Type of change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation
  • Performance improvement

Scope check

  • I read the Contributing Guide
  • This aligns with RubyLLM's focus on LLM communication
  • This isn't application-specific logic that belongs in user code
  • This benefits most users, not just my specific use case

Quality check

  • I ran overcommit --install and all hooks pass
  • I tested my changes thoroughly
    • For provider changes: Re-recorded VCR cassettes with bundle exec rake vcr:record[provider_name]
    • All tests pass: bundle exec rspec
  • I updated documentation if needed
  • I didn't modify auto-generated files manually (models.json, aliases.json)

API changes

  • Breaking change
  • New public methods/classes
  • Changed method signatures
  • No API changes

New: ToolCall#thought_signature attribute (optional, defaults to nil)

Related issues

Fixes #521

…ling

Fixes crmne#521

Gemini 3 Pro models require thought_signature to be preserved and
returned during multi-turn function calling conversations. Without
this, the API returns an error:

"Function call is missing a thought_signature in functionCall parts."

Changes:
- Add thought_signature attribute to ToolCall class
- Extract thoughtSignature from Gemini API responses in extract_tool_calls
- Include thoughtSignature in functionCall parts via format_tool_call
- Include thoughtSignature in functionResponse parts via format_tool_result
- Update MessageFormatter to store and pass signature metadata

This is backward compatible - works with Gemini 2.5 (where signatures
are optional) and other providers (which don't use thought signatures).

See: https://ai.google.dev/gemini-api/docs/thought-signatures
@codxse codxse marked this pull request as draft December 16, 2025 14:30
@codxse codxse marked this pull request as ready for review December 16, 2025 20:20
@ausangshukla
Copy link

This is really good, works well

Copy link

@marksweston marksweston left a comment

Choose a reason for hiding this comment

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

This branch is throwing an error in an application that's using the ActiveRecord-backed models, for Chat, Message, ToolCall etc:

Example stacktrace:
/Users/markweston/.asdf/installs/ruby/3.4.4/lib/ruby/gems/3.4.0/gems/activemodel-8.0.2.1/lib/active_model/attribute_assignment.rb:57:in 'ActiveModel::AttributeAssignment#attribute_writer_missing': unknown attribute 'thought_signature' for ToolCall. (ActiveModel::UnknownAttributeError)

  raise UnknownAttributeError.new(self, name)
  ^^^^^
from /Users/markweston/.asdf/installs/ruby/3.4.4/lib/ruby/gems/3.4.0/gems/activemodel-8.0.2.1/lib/active_model/attribute_assignment.rb:74:in 'ActiveModel::AttributeAssignment#_assign_attribute'
from /Users/markweston/.asdf/installs/ruby/3.4.4/lib/ruby/gems/3.4.0/gems/activerecord-8.0.2.1/lib/active_record/attribute_assignment.rb:17:in 'block in ActiveRecord::AttributeAssignment#_assign_attributes'
from /Users/markweston/.asdf/installs/ruby/3.4.4/lib/ruby/gems/3.4.0/gems/activerecord-8.0.2.1/lib/active_record/attribute_assignment.rb:9:in 'Hash#each'
from /Users/markweston/.asdf/installs/ruby/3.4.4/lib/ruby/gems/3.4.0/gems/activerecord-8.0.2.1/lib/active_record/attribute_assignment.rb:9:in 'ActiveRecord::AttributeAssignment#_assign_attributes'
from /Users/markweston/.asdf/installs/ruby/3.4.4/lib/ruby/gems/3.4.0/gems/activemodel-8.0.2.1/lib/active_model/attribute_assignment.rb:34:in 'ActiveModel::AttributeAssignment#assign_attributes'
from /Users/markweston/.asdf/installs/ruby/3.4.4/lib/ruby/gems/3.4.0/gems/activemodel-8.0.2.1/lib/active_model/api.rb:81:in 'ActiveModel::API#initialize'
from /Users/markweston/.asdf/installs/ruby/3.4.4/lib/ruby/gems/3.4.0/gems/activerecord-8.0.2.1/lib/active_record/core.rb:478:in 'ActiveRecord::Core#initialize'
from /Users/markweston/.asdf/installs/ruby/3.4.4/lib/ruby/gems/3.4.0/gems/activerecord-8.0.2.1/lib/active_record/inheritance.rb:76:in 'Class#new'
from /Users/markweston/.asdf/installs/ruby/3.4.4/lib/ruby/gems/3.4.0/gems/activerecord-8.0.2.1/lib/active_record/inheritance.rb:76:in 'ActiveRecord::Inheritance::ClassMethods#new'
from /Users/markweston/.asdf/installs/ruby/3.4.4/lib/ruby/gems/3.4.0/gems/activerecord-8.0.2.1/lib/active_record/reflection.rb:183:in 'ActiveRecord::Reflection::AbstractReflection#build_association'
from /Users/markweston/.asdf/installs/ruby/3.4.4/lib/ruby/gems/3.4.0/gems/activerecord-8.0.2.1/lib/active_record/associations/association.rb:384:in 'ActiveRecord::Associations::Association#build_record'
from /Users/markweston/.asdf/installs/ruby/3.4.4/lib/ruby/gems/3.4.0/gems/activerecord-8.0.2.1/lib/active_record/associations/collection_association.rb:362:in 'ActiveRecord::Associations::CollectionAssociation#_create_record'
from /Users/markweston/.asdf/installs/ruby/3.4.4/lib/ruby/gems/3.4.0/gems/activerecord-8.0.2.1/lib/active_record/associations/has_many_association.rb:147:in 'ActiveRecord::Associations::HasManyAssociation#_create_record'
from /Users/markweston/.asdf/installs/ruby/3.4.4/lib/ruby/gems/3.4.0/gems/activerecord-8.0.2.1/lib/active_record/associations/association.rb:232:in 'ActiveRecord::Associations::Association#create!'
from /Users/markweston/.asdf/installs/ruby/3.4.4/lib/ruby/gems/3.4.0/gems/activerecord-8.0.2.1/lib/active_record/associations/collection_proxy.rb:366:in 'ActiveRecord::Associations::CollectionProxy#create!'
from /Users/markweston/Dropbox/working_code/ruby_llm/lib/ruby_llm/active_record/chat_methods.rb:288:in 'block in RubyLLM::ActiveRecord::ChatMethods#persist_tool_calls'
from /Users/markweston/Dropbox/working_code/ruby_llm/lib/ruby_llm/active_record/chat_methods.rb:285:in 'Hash#each_value'
from /Users/markweston/Dropbox/working_code/ruby_llm/lib/ruby_llm/active_record/chat_methods.rb:285:in 'RubyLLM::ActiveRecord::ChatMethods#persist_tool_calls'
from /Users/markweston/Dropbox/working_code/ruby_llm/lib/ruby_llm/active_record/chat_methods.rb:279:in 'block in RubyLLM::ActiveRecord::ChatMethods#persist_message_completion'
from /Users/markweston/.asdf/installs/ruby/3.4.4/lib/ruby/gems/3.4.0/gems/activerecord-8.0.2.1/lib/active_record/connection_adapters/abstract/transaction.rb:626:in 'block in ActiveRecord::ConnectionAdapters::TransactionManager#within_new_transaction'
from /Users/markweston/.asdf/installs/ruby/3.4.4/lib/ruby/gems/3.4.0/gems/activesupport-8.0.2.1/lib/active_support/concurrency/null_lock.rb:9:in 'ActiveSupport::Concurrency::NullLock#synchronize'
from /Users/markweston/.asdf/installs/ruby/3.4.4/lib/ruby/gems/3.4.0/gems/activerecord-8.0.2.1/lib/active_record/connection_adapters/abstract/transaction.rb:623:in 'ActiveRecord::ConnectionAdapters::TransactionManager#within_new_transaction'
from /Users/markweston/.asdf/installs/ruby/3.4.4/lib/ruby/gems/3.4.0/gems/activerecord-8.0.2.1/lib/active_record/connection_adapters/abstract/database_statements.rb:367:in 'ActiveRecord::ConnectionAdapters::DatabaseStatements#within_new_transaction'
from /Users/markweston/.asdf/installs/ruby/3.4.4/lib/ruby/gems/3.4.0/gems/activerecord-8.0.2.1/lib/active_record/connection_adapters/abstract/database_statements.rb:359:in 'ActiveRecord::ConnectionAdapters::DatabaseStatements#transaction'
from /Users/markweston/.asdf/installs/ruby/3.4.4/lib/ruby/gems/3.4.0/gems/activerecord-8.0.2.1/lib/active_record/transactions.rb:233:in 'block in ActiveRecord::Transactions::ClassMethods#transaction'
from /Users/markweston/.asdf/installs/ruby/3.4.4/lib/ruby/gems/3.4.0/gems/activerecord-8.0.2.1/lib/active_record/connection_adapters/abstract/connection_pool.rb:418:in 'ActiveRecord::ConnectionAdapters::ConnectionPool#with_connection'
from /Users/markweston/.asdf/installs/ruby/3.4.4/lib/ruby/gems/3.4.0/gems/activerecord-8.0.2.1/lib/active_record/connection_handling.rb:310:in 'ActiveRecord::ConnectionHandling#with_connection'
from /Users/markweston/.asdf/installs/ruby/3.4.4/lib/ruby/gems/3.4.0/gems/activerecord-8.0.2.1/lib/active_record/transactions.rb:232:in 'ActiveRecord::Transactions::ClassMethods#transaction'
from /Users/markweston/.asdf/installs/ruby/3.4.4/lib/ruby/gems/3.4.0/gems/activerecord-8.0.2.1/lib/active_record/transactions.rb:353:in 'ActiveRecord::Transactions#transaction'
from /Users/markweston/Dropbox/working_code/ruby_llm/lib/ruby_llm/active_record/chat_methods.rb:252:in 'RubyLLM::ActiveRecord::ChatMethods#persist_message_completion'
from /Users/markweston/Dropbox/working_code/ruby_llm/lib/ruby_llm/active_record/chat_methods.rb:236:in 'block in RubyLLM::ActiveRecord::ChatMethods#setup_persistence_callbacks'
from /Users/markweston/Dropbox/working_code/ruby_llm/lib/ruby_llm/chat.rb:147:in 'RubyLLM::Chat#complete'
from /Users/markweston/Dropbox/working_code/ruby_llm/lib/ruby_llm/active_record/chat_methods.rb:198:in 'RubyLLM::ActiveRecord::ChatMethods#complete'
from /Users/markweston/Dropbox/working_code/ruby_llm/lib/ruby_llm/active_record/chat_methods.rb:192:in 'RubyLLM::ActiveRecord::ChatMethods#ask'
from /Users/markweston/Dropbox/working_code/code-o-matic/lib/codeomatic/agent.rb:51:in 'Agent#handle_chat'
from /Users/markweston/Dropbox/working_code/code-o-matic/lib/codeomatic/agent.rb:26:in 'block in Agent#run'
from <internal:kernel>:168:in 'Kernel#loop'
from /Users/markweston/Dropbox/working_code/code-o-matic/lib/codeomatic/agent.rb:20:in 'Agent#run'
from exe/code-o-matic:29:in '<main>'

Edit: the error that causes this stacktrace happens partway through a conversation when the user prompt causes the assistant (Gemini-3-pro) to call a tool defined by the application ('code-o-matic' in the stacktrace)

Reviewing the code I was going to make the comment that none of the changes seemed to address persistence, but I'm still feeling my way into this codebase so wasn't sure until I tested it in my app.

@ausangshukla
Copy link

ausangshukla commented Dec 23, 2025 via email

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] Gemini-3-Pro-Preview: Function call missing thought_signature

3 participants