Records reasoning, citations (the FAQs the assistant found), the tool path, and the model for each V1 Captain reply in a new captain_message_generations table. The assistant reports the source IDs it actually used so cited FAQs can be flagged, and handoff messages capture the transfer reason. Agents can expand any Captain message in the conversation (a sparkle toggle on the timestamp line) to see this breakdown, fetched on demand from a dedicated endpoint. Model is shown only in development. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
67 lines
2.2 KiB
Ruby
67 lines
2.2 KiB
Ruby
# == Schema Information
|
|
#
|
|
# Table name: captain_message_generations
|
|
#
|
|
# id :bigint not null, primary key
|
|
# citations :jsonb not null
|
|
# generation_path :jsonb not null
|
|
# model :string
|
|
# reasoning :text
|
|
# created_at :datetime not null
|
|
# updated_at :datetime not null
|
|
# account_id :bigint not null
|
|
# assistant_id :bigint not null
|
|
# conversation_id :bigint not null
|
|
# message_id :bigint not null
|
|
#
|
|
# Indexes
|
|
#
|
|
# index_captain_message_generations_on_account_id (account_id)
|
|
# index_captain_message_generations_on_assistant_id (assistant_id)
|
|
# index_captain_message_generations_on_conversation_id (conversation_id)
|
|
# index_captain_message_generations_on_message_id (message_id) UNIQUE
|
|
#
|
|
class Captain::MessageGeneration < ApplicationRecord
|
|
self.table_name = 'captain_message_generations'
|
|
|
|
belongs_to :account
|
|
# `Captain::Conversation` exists as a job namespace, so the association would
|
|
# resolve to that module instead of the top-level model without this override.
|
|
belongs_to :conversation, class_name: '::Conversation'
|
|
belongs_to :message
|
|
belongs_to :assistant, class_name: 'Captain::Assistant'
|
|
|
|
before_validation :ensure_account_and_conversation
|
|
|
|
def self.record!(message:, assistant:, reasoning:, used_sources:, metadata: nil)
|
|
metadata ||= {}
|
|
create!(
|
|
message: message,
|
|
assistant: assistant,
|
|
reasoning: reasoning,
|
|
model: metadata[:model],
|
|
citations: flag_used_citations(metadata[:citations], used_sources),
|
|
generation_path: metadata[:generation_path] || []
|
|
)
|
|
end
|
|
|
|
# A found FAQ counts as "used" when the assistant lists its Source ID (the
|
|
# AssistantResponse id) in the `used_sources` field of its response.
|
|
def self.flag_used_citations(citations, used_sources)
|
|
used = Array(used_sources).map(&:to_s)
|
|
|
|
Array(citations).map do |citation|
|
|
citation.merge('used' => used.include?(citation['response_id'].to_s))
|
|
end
|
|
end
|
|
|
|
private
|
|
|
|
def ensure_account_and_conversation
|
|
return if message.blank?
|
|
|
|
self.account ||= message.account
|
|
self.conversation ||= message.conversation
|
|
end
|
|
end
|