Class: AssistantBrainEntry
- Inherits:
-
ApplicationRecord
- Object
- ActiveRecord::Base
- ApplicationRecord
- AssistantBrainEntry
- Includes:
- Models::Embeddable
- Defined in:
- app/models/assistant_brain_entry.rb
Overview
== Schema Information
Table name: assistant_brain_entries
Database name: primary
id :bigint not null, primary key
applies_to_services :string default([]), not null, is an Array
category :string not null
priority :enum default("normal"), not null
rule :text not null
scope :string default("global"), not null
source :string default("manual"), not null
status :string default("active"), not null
title :string not null
created_at :datetime not null
updated_at :datetime not null
approved_by_id :bigint
assistant_conversation_id :bigint
source_message_id :bigint
suggested_by_id :bigint
user_id :bigint
Indexes
idx_brain_entries_unique_source_message (user_id,source_message_id) UNIQUE WHERE (source_message_id IS NOT NULL)
index_assistant_brain_entries_on_category (category)
index_assistant_brain_entries_on_scope (scope)
index_assistant_brain_entries_on_source_message_id (source_message_id)
index_assistant_brain_entries_on_status_and_category (status,category)
index_assistant_brain_entries_on_status_and_priority (status,priority)
index_assistant_brain_entries_on_user_id (user_id)
Constant Summary collapse
- CATEGORIES =
Categories.
%w[url_rules product_data content_rules seo_rules general].freeze
- STATUSES =
Statuses.
%w[active pending inactive].freeze
- SOURCES =
Sources.
%w[manual auto_learned].freeze
- SCOPES =
Scopes.
%w[global user].freeze
- ALWAYS_INJECTED_SOFT_BUDGET =
Soft ceiling (estimated tokens) for the always-injected set — the active,
critical, permanent (untagged) global rules rendered verbatim into every
conversation. Drives the "context weight" indicator on the triage page; the
bar turns amber past 60% and red past 100%. A guide, not a hard limit. 2_000- CATEGORY_LABELS =
Category labels.
{ 'url_rules' => 'URL Rules', 'product_data' => 'Product Data', 'content_rules' => 'Content Rules', 'seo_rules' => 'SEO Rules', 'general' => 'General' }.freeze
Constants included from Models::Embeddable
Models::Embeddable::MAX_CONTENT_LENGTH
Constants included from Models::Schedulable
Models::Schedulable::SIMPLE_FORM_OPTIONS
Instance Attribute Summary collapse
- #category ⇒ Object readonly
- #rule ⇒ Object readonly
- #scope ⇒ Object readonly
- #source ⇒ Object readonly
- #status ⇒ Object readonly
- #title ⇒ Object readonly
- #user_id ⇒ Object readonly
Belongs to collapse
- #approved_by ⇒ Party
- #assistant_conversation ⇒ AssistantConversation
-
#source_message ⇒ AssistantMessage
Set when a rep saves an assistant answer from a chat via the in-chat "Save to brain" button — links the personal entry back to the message it came from.
- #suggested_by ⇒ Party
Class Method Summary collapse
-
.active ⇒ ActiveRecord::Relation<AssistantBrainEntry>
A relation of AssistantBrainEntries that are active.
-
.auto_learned ⇒ ActiveRecord::Relation<AssistantBrainEntry>
A relation of AssistantBrainEntries that are auto learned.
-
.by_category ⇒ ActiveRecord::Relation<AssistantBrainEntry>
A relation of AssistantBrainEntries that are by category.
-
.for_context(user_id:, active_services: []) ⇒ ActiveRecord::Relation
Fetch all entries applicable to a given user and set of active tool services.
-
.for_user ⇒ ActiveRecord::Relation<AssistantBrainEntry>
A relation of AssistantBrainEntries that are for user.
-
.global_scope ⇒ ActiveRecord::Relation<AssistantBrainEntry>
A relation of AssistantBrainEntries that are global scope.
-
.inactive ⇒ ActiveRecord::Relation<AssistantBrainEntry>
A relation of AssistantBrainEntries that are inactive.
-
.manual ⇒ ActiveRecord::Relation<AssistantBrainEntry>
A relation of AssistantBrainEntries that are manual.
-
.pending ⇒ ActiveRecord::Relation<AssistantBrainEntry>
A relation of AssistantBrainEntries that are pending.
- .ransackable_attributes(_auth_object = nil) ⇒ Object
-
.saved_from_message ⇒ ActiveRecord::Relation<AssistantBrainEntry>
A relation of AssistantBrainEntries that are saved from message.
-
.semantic_search_for(query, within_ids:, limit: 20) ⇒ Array<AssistantBrainEntry>
Find the most semantically relevant active brain entries for a user query.
-
.user_scope ⇒ ActiveRecord::Relation<AssistantBrainEntry>
A relation of AssistantBrainEntries that are user scope.
Instance Method Summary collapse
- #active? ⇒ Boolean
- #applies_everywhere? ⇒ Boolean
- #approve!(approver) ⇒ Object
- #auto_learned? ⇒ Boolean
- #category_label ⇒ Object
-
#content_for_embedding(_content_type = :primary) ⇒ Object
Embed the full title + rule text so retrieval matches on both what the rule is about (title) and what it says (rule body).
-
#directive? ⇒ Boolean
Kind (the delivery axis), backed by
priority: a Directive is always-on — injected verbatim into every in-scope system prompt; a Skill is on-demand — retrieved by search_brain only when relevant. -
#estimated_tokens ⇒ Object
Estimated token footprint of this rule's text (chars/4 heuristic, matching Assistant::ChatService.estimate_tokens).
- #global? ⇒ Boolean
- #inactive? ⇒ Boolean
- #pending? ⇒ Boolean
- #personal? ⇒ Boolean
- #reject! ⇒ Object
- #skill? ⇒ Boolean
Methods included from Models::Embeddable
#content_embeddings, embeddable_content_types, #embeddable_locales, #embedding_content_hash, #embedding_current?, #embedding_eligible?, embedding_partition_class, #embedding_stale?, #embedding_type_name, #embedding_vector, #find_content_embedding, #find_similar, #generate_all_embeddings!, #generate_chunked_embeddings!, #generate_embedding!, #has_embedding?, #locale_for_embedding, #needs_chunking?, regenerate_all_embeddings, semantic_search, with_shape_lock
Methods inherited from ApplicationRecord
ransackable_associations, ransackable_scopes, ransortable_attributes, #to_relation
Methods included from Models::Schedulable
Methods included from Models::AfterCommittable
Methods included from Models::EventPublishable
Instance Attribute Details
#category ⇒ Object (readonly)
82 |
# File 'app/models/assistant_brain_entry.rb', line 82 validates :category, presence: true, inclusion: { in: CATEGORIES } |
#rule ⇒ Object (readonly)
84 |
# File 'app/models/assistant_brain_entry.rb', line 84 validates :rule, presence: true |
#scope ⇒ Object (readonly)
87 |
# File 'app/models/assistant_brain_entry.rb', line 87 validates :scope, presence: true, inclusion: { in: SCOPES } |
#source ⇒ Object (readonly)
86 |
# File 'app/models/assistant_brain_entry.rb', line 86 validates :source, presence: true, inclusion: { in: SOURCES } |
#status ⇒ Object (readonly)
85 |
# File 'app/models/assistant_brain_entry.rb', line 85 validates :status, presence: true, inclusion: { in: STATUSES } |
#title ⇒ Object (readonly)
83 |
# File 'app/models/assistant_brain_entry.rb', line 83 validates :title, presence: true |
#user_id ⇒ Object (readonly)
88 |
# File 'app/models/assistant_brain_entry.rb', line 88 validates :user_id, presence: true, if: -> { scope == 'user' } |
Class Method Details
.active ⇒ ActiveRecord::Relation<AssistantBrainEntry>
A relation of AssistantBrainEntries that are active. Active Record Scope
90 |
# File 'app/models/assistant_brain_entry.rb', line 90 scope :active, -> { where(status: 'active') } |
.auto_learned ⇒ ActiveRecord::Relation<AssistantBrainEntry>
A relation of AssistantBrainEntries that are auto learned. Active Record Scope
98 |
# File 'app/models/assistant_brain_entry.rb', line 98 scope :auto_learned, -> { where(source: 'auto_learned') } |
.by_category ⇒ ActiveRecord::Relation<AssistantBrainEntry>
A relation of AssistantBrainEntries that are by category. Active Record Scope
93 |
# File 'app/models/assistant_brain_entry.rb', line 93 scope :by_category, -> { order(:category, :title) } |
.for_context(user_id:, active_services: []) ⇒ ActiveRecord::Relation
Fetch all entries applicable to a given user and set of active tool services.
Returns global entries + the user's personal entries, filtered by service applicability.
108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 |
# File 'app/models/assistant_brain_entry.rb', line 108 def self.for_context(user_id:, active_services: []) base = active.where('scope = ? OR (scope = ? AND user_id = ?)', 'global', 'user', user_id) if active_services.present? # Inject if: applies_to_services is empty (universal) OR overlaps with active services. # Cast both sides to varchar[] so PostgreSQL can use the && (overlap) operator. base.where( 'cardinality(applies_to_services) = 0 OR applies_to_services && ARRAY[?]::varchar[]', active_services ) else # No tools active — only inject universal entries (no service filter) base.where('cardinality(applies_to_services) = 0') end end |
.for_user ⇒ ActiveRecord::Relation<AssistantBrainEntry>
A relation of AssistantBrainEntries that are for user. Active Record Scope
96 |
# File 'app/models/assistant_brain_entry.rb', line 96 scope :for_user, ->(user_id) { where(scope: 'user', user_id: user_id) } |
.global_scope ⇒ ActiveRecord::Relation<AssistantBrainEntry>
A relation of AssistantBrainEntries that are global scope. Active Record Scope
94 |
# File 'app/models/assistant_brain_entry.rb', line 94 scope :global_scope, -> { where(scope: 'global') } |
.inactive ⇒ ActiveRecord::Relation<AssistantBrainEntry>
A relation of AssistantBrainEntries that are inactive. Active Record Scope
92 |
# File 'app/models/assistant_brain_entry.rb', line 92 scope :inactive, -> { where(status: 'inactive') } |
.manual ⇒ ActiveRecord::Relation<AssistantBrainEntry>
A relation of AssistantBrainEntries that are manual. Active Record Scope
97 |
# File 'app/models/assistant_brain_entry.rb', line 97 scope :manual, -> { where(source: 'manual') } |
.pending ⇒ ActiveRecord::Relation<AssistantBrainEntry>
A relation of AssistantBrainEntries that are pending. Active Record Scope
91 |
# File 'app/models/assistant_brain_entry.rb', line 91 scope :pending, -> { where(status: 'pending') } |
.ransackable_attributes(_auth_object = nil) ⇒ Object
193 194 195 |
# File 'app/models/assistant_brain_entry.rb', line 193 def self.ransackable_attributes(_auth_object = nil) %w[title rule category status source scope priority created_at updated_at] end |
.saved_from_message ⇒ ActiveRecord::Relation<AssistantBrainEntry>
A relation of AssistantBrainEntries that are saved from message. Active Record Scope
100 |
# File 'app/models/assistant_brain_entry.rb', line 100 scope :saved_from_message, ->(user_id, ) { where(scope: 'user', user_id: user_id, source_message_id: ) } |
.semantic_search_for(query, within_ids:, limit: 20) ⇒ Array<AssistantBrainEntry>
Find the most semantically relevant active brain entries for a user query.
Used by ChatService when the total entry count exceeds the injection threshold
and full verbatim injection would bloat the system prompt.
161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 |
# File 'app/models/assistant_brain_entry.rb', line 161 def self.semantic_search_for(query, within_ids:, limit: 20) return [] if query.blank? || within_ids.empty? query_vector = ContentEmbedding.(query) return [] unless query_vector ContentEmbedding .where(embeddable_type: 'AssistantBrainEntry', embeddable_id: within_ids) .by_model(ContentEmbedding::UNIFIED_MODELS) . .nearest_neighbors(:unified_embedding, query_vector, distance: :cosine) .limit(limit) .includes(:embeddable) .filter_map(&:embeddable) end |
.user_scope ⇒ ActiveRecord::Relation<AssistantBrainEntry>
A relation of AssistantBrainEntries that are user scope. Active Record Scope
95 |
# File 'app/models/assistant_brain_entry.rb', line 95 scope :user_scope, -> { where(scope: 'user') } |
Instance Method Details
#active? ⇒ Boolean
128 |
# File 'app/models/assistant_brain_entry.rb', line 128 def active? = status == 'active' |
#applies_everywhere? ⇒ Boolean
141 142 143 |
# File 'app/models/assistant_brain_entry.rb', line 141 def applies_everywhere? applies_to_services.blank? end |
#approve!(approver) ⇒ Object
185 186 187 |
# File 'app/models/assistant_brain_entry.rb', line 185 def approve!(approver) update!(status: 'active', approved_by: approver) end |
#approved_by ⇒ Party
76 |
# File 'app/models/assistant_brain_entry.rb', line 76 belongs_to :approved_by, class_name: 'Party', optional: true |
#assistant_conversation ⇒ AssistantConversation
77 |
# File 'app/models/assistant_brain_entry.rb', line 77 belongs_to :assistant_conversation, class_name: 'AssistantConversation', optional: true |
#auto_learned? ⇒ Boolean
131 |
# File 'app/models/assistant_brain_entry.rb', line 131 def auto_learned? = source == 'auto_learned' |
#category_label ⇒ Object
124 125 126 |
# File 'app/models/assistant_brain_entry.rb', line 124 def category_label CATEGORY_LABELS.fetch(category, category.humanize) end |
#content_for_embedding(_content_type = :primary) ⇒ Object
Embed the full title + rule text so retrieval matches on both what the rule
is about (title) and what it says (rule body).
181 182 183 |
# File 'app/models/assistant_brain_entry.rb', line 181 def (_content_type = :primary) "#{title}\n\n#{rule}" end |
#directive? ⇒ Boolean
Kind (the delivery axis), backed by priority: a Directive is always-on —
injected verbatim into every in-scope system prompt; a Skill is on-demand —
retrieved by search_brain only when relevant.
138 |
# File 'app/models/assistant_brain_entry.rb', line 138 def directive? = priority_critical? |
#estimated_tokens ⇒ Object
Estimated token footprint of this rule's text (chars/4 heuristic, matching
Assistant::ChatService.estimate_tokens). For critical rules this is the weight
added to every in-scope system prompt; normal rules cost nothing until searched.
148 149 150 |
# File 'app/models/assistant_brain_entry.rb', line 148 def estimated_tokens Assistant::ChatService.estimate_tokens(rule) end |
#global? ⇒ Boolean
132 |
# File 'app/models/assistant_brain_entry.rb', line 132 def global? = scope == 'global' |
#inactive? ⇒ Boolean
130 |
# File 'app/models/assistant_brain_entry.rb', line 130 def inactive? = status == 'inactive' |
#pending? ⇒ Boolean
129 |
# File 'app/models/assistant_brain_entry.rb', line 129 def pending? = status == 'pending' |
#personal? ⇒ Boolean
133 |
# File 'app/models/assistant_brain_entry.rb', line 133 def personal? = scope == 'user' |
#reject! ⇒ Object
189 190 191 |
# File 'app/models/assistant_brain_entry.rb', line 189 def reject! update!(status: 'inactive') end |
#skill? ⇒ Boolean
139 |
# File 'app/models/assistant_brain_entry.rb', line 139 def skill? = priority_normal? |
#source_message ⇒ AssistantMessage
Set when a rep saves an assistant answer from a chat via the in-chat "Save to
brain" button — links the personal entry back to the message it came from.
80 |
# File 'app/models/assistant_brain_entry.rb', line 80 belongs_to :source_message, class_name: 'AssistantMessage', optional: true |
#suggested_by ⇒ Party
75 |
# File 'app/models/assistant_brain_entry.rb', line 75 belongs_to :suggested_by, class_name: 'Party', optional: true |