Module: Models::AssistantConversationMessageReplayable
- Extended by:
- ActiveSupport::Concern
- Included in:
- AssistantConversation
- Defined in:
- app/concerns/models/assistant_conversation_message_replayable.rb
Overview
Rebuilds an LLM-ready message history from persisted AssistantMessage
rows for AssistantConversation: compaction cutoff/summary injection,
orphaned tool-call/result filtering (providers reject unpaired blocks),
consecutive-user-message superseding, and provider-specific thinking /
thought-signature handling for Anthropic and Gemini replays.
Instance Method Summary collapse
-
#drop_unpaired_tool_results! ⇒ Integer
Delete tool-result messages whose tool_use no longer exists, so the next replay cannot rebuild a payload the provider will refuse.
-
#has_thinking_history? ⇒ Boolean
True when any assistant message in this conversation contains thinking data.
Instance Method Details
#drop_unpaired_tool_results! ⇒ Integer
Delete tool-result messages whose tool_use no longer exists, so the next
replay cannot rebuild a payload the provider will refuse.
The per-request guard in #replay_messages_onto_chat already refuses to emit
these, but only for the request it is building. This is the durable version:
once the provider has rejected a turn there is no value in keeping a result
nothing can answer for, and leaving it means every future turn depends on the
guard holding (AppSignal #3808, conversation 4640).
A tool result whose parent tool-call record is missing is unanswerable by
definition — the row that named it is gone — and the message body is a tool
payload the model cannot interpret without the call that produced it.
36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
# File 'app/concerns/models/assistant_conversation_message_replayable.rb', line 36 def drop_unpaired_tool_results! results = .where(role: 'tool').to_a return 0 if results.empty? answered = locally_answered_ids(results) unpaired = results.reject { answered.include?(it.id) } return 0 if unpaired.empty? Rails.logger.warn( "[AssistantConversation##{id}] dropping #{unpaired.size} tool result(s) with no surviving tool_use: " \ "#{unpaired.map(&:id).join(', ')}" ) unpaired.each(&:destroy) unpaired.size end |
#has_thinking_history? ⇒ Boolean
True when any assistant message in this conversation contains thinking data.
Used by ChatService to force thinking mode on subsequent turns so Anthropic
doesn't reject the request for missing thinking blocks.
16 17 18 19 20 |
# File 'app/concerns/models/assistant_conversation_message_replayable.rb', line 16 def has_thinking_history? .where(role: 'assistant') .where.not(thinking_text: nil) .exists? end |