Class: Assistant::CommunicationToolBuilder
- Inherits:
-
Object
- Object
- Assistant::CommunicationToolBuilder
- Defined in:
- app/services/assistant/communication_tool_builder.rb
Overview
RubyLLM tools for CRM Communication records — the "send this template to
these people" side of email, as opposed to EmailToolBuilder's templates
(content) and campaign emails (scheduled marketing sends).
Julia asked Sunny to "create a new communication using this template, put
it in draft state, add all active employees except Scott as recipients"
(2026-07-28) and Sunny had no tool for it — it could author the template
but not address it to anyone, and couldn't read employee contact points.
Drafts only, by construction. create_communication_draft always
passes keep_as_draft: true, so the record stays in Communication's
initial draft state and no release worker is ever enqueued. There is
deliberately no tool here that queues, releases, or sends — a human opens
the draft in the CRM, reviews the recipient list, and presses send. That
matters more here than for templates: a template is inert until someone
uses it, whereas a communication already carries its audience.
Usage (via ChatToolBuilder's email-management service):
tools = Assistant::CommunicationToolBuilder.tools(audit_context: { user_id: 42 })
Constant Summary collapse
- CRM_COMMUNICATION_URL =
CRM communication URL.
"#{CRM_URL}/communications".freeze
- MAX_RECIPIENTS =
Refuse absurd recipient lists. A draft can't send itself, but a runaway
list is a sign the model misread the request, and it's cheaper to stop
than to have someone eyeball 10k rows in the CRM. 500
Class Method Summary collapse
-
.employee_email_rows(exclude_names: []) ⇒ Array<Hash>
Active employees with an email contact point, minus any whose name matches
exclude_names(substring, case-insensitive — the user says "everyone except Scott", not an employee id). -
.tools(audit_context: {}) ⇒ Array<RubyLLM::Tool>
Build the communication tools.
Class Method Details
.employee_email_rows(exclude_names: []) ⇒ Array<Hash>
Active employees with an email contact point, minus any whose name
matches exclude_names (substring, case-insensitive — the user says
"everyone except Scott", not an employee id).
Deduplicated by address: shared mailboxes mean one address can hang off
two employee records (epasek@ is on both "Elodie Pasek" and "Warehouse
Canada2"). Communication's own unique index would collapse those
anyway; doing it here keeps the count we report to the user honest.
55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
# File 'app/services/assistant/communication_tool_builder.rb', line 55 def employee_email_rows(exclude_names: []) excluded = Array(exclude_names).filter_map { |n| n.to_s.strip.downcase.presence } ContactPoint.emails.joins(:party) .merge(Employee.active_employees) .includes(:party) .order(Party[:full_name]) .filter_map do |cp| name = cp.party&.full_name.to_s downcased_name = name.downcase # rubocop:disable Style/ArrayIntersect -- substring match ("Scott" must # hit "Scott Rosenbaum"); intersect? compares whole elements. next if excluded.any? { |term| downcased_name.include?(term) } # rubocop:enable Style/ArrayIntersect next if cp.detail.blank? { employee_id: cp.party_id, name: name, email: cp.detail, contact_point_id: cp.id } end.uniq { |row| row[:email].downcase } end |
.tools(audit_context: {}) ⇒ Array<RubyLLM::Tool>
Build the communication tools.
37 38 39 40 41 42 |
# File 'app/services/assistant/communication_tool_builder.rb', line 37 def tools(audit_context: {}) [ build_list_employee_emails_tool, build_create_communication_draft_tool(audit_context) ] end |