mirror of
https://github.com/lingble/chatwoot.git
synced 2025-11-11 01:26:14 +00:00
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Sojan Jose <sojan@pepalo.com> Co-authored-by: Pranav <pranav@chatwoot.com> Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
127 lines
3.7 KiB
Ruby
127 lines
3.7 KiB
Ruby
# == Schema Information
|
|
#
|
|
# Table name: captain_scenarios
|
|
#
|
|
# id :bigint not null, primary key
|
|
# description :text
|
|
# enabled :boolean default(TRUE), not null
|
|
# instruction :text
|
|
# title :string
|
|
# tools :jsonb
|
|
# created_at :datetime not null
|
|
# updated_at :datetime not null
|
|
# account_id :bigint not null
|
|
# assistant_id :bigint not null
|
|
#
|
|
# Indexes
|
|
#
|
|
# index_captain_scenarios_on_account_id (account_id)
|
|
# index_captain_scenarios_on_assistant_id (assistant_id)
|
|
# index_captain_scenarios_on_assistant_id_and_enabled (assistant_id,enabled)
|
|
# index_captain_scenarios_on_enabled (enabled)
|
|
#
|
|
class Captain::Scenario < ApplicationRecord
|
|
include Concerns::CaptainToolsHelpers
|
|
include Concerns::Agentable
|
|
|
|
self.table_name = 'captain_scenarios'
|
|
|
|
belongs_to :assistant, class_name: 'Captain::Assistant'
|
|
belongs_to :account
|
|
|
|
validates :title, presence: true
|
|
validates :description, presence: true
|
|
validates :instruction, presence: true
|
|
validates :assistant_id, presence: true
|
|
validates :account_id, presence: true
|
|
validate :validate_instruction_tools
|
|
|
|
scope :enabled, -> { where(enabled: true) }
|
|
|
|
delegate :temperature, :feature_faq, :feature_memory, :product_name, to: :assistant
|
|
|
|
before_save :resolve_tool_references
|
|
|
|
def prompt_context
|
|
{
|
|
title: title,
|
|
instructions: resolved_instructions,
|
|
tools: resolved_tools
|
|
}
|
|
end
|
|
|
|
private
|
|
|
|
def agent_name
|
|
"#{title} Agent".titleize
|
|
end
|
|
|
|
def agent_tools
|
|
resolved_tools.map { |tool| self.class.resolve_tool_class(tool[:id]) }.map { |tool| tool.new(assistant) }
|
|
end
|
|
|
|
def resolved_instructions
|
|
instruction.gsub(TOOL_REFERENCE_REGEX) do |match|
|
|
"#{match} tool "
|
|
end
|
|
end
|
|
|
|
def resolved_tools
|
|
return [] if tools.blank?
|
|
|
|
available_tools = self.class.available_agent_tools
|
|
tools.filter_map do |tool_id|
|
|
available_tools.find { |tool| tool[:id] == tool_id }
|
|
end
|
|
end
|
|
|
|
# Validates that all tool references in the instruction are valid.
|
|
# Parses the instruction for tool references and checks if they exist
|
|
# in the available tools configuration.
|
|
#
|
|
# @return [void]
|
|
# @api private
|
|
# @example Valid instruction
|
|
# scenario.instruction = "Use [Add Contact Note](tool://add_contact_note) to document"
|
|
# scenario.valid? # => true
|
|
#
|
|
# @example Invalid instruction
|
|
# scenario.instruction = "Use [Invalid Tool](tool://invalid_tool) to process"
|
|
# scenario.valid? # => false
|
|
# scenario.errors[:instruction] # => ["contains invalid tools: invalid_tool"]
|
|
def validate_instruction_tools
|
|
return if instruction.blank?
|
|
|
|
tool_ids = extract_tool_ids_from_text(instruction)
|
|
return if tool_ids.empty?
|
|
|
|
available_tool_ids = self.class.available_tool_ids
|
|
invalid_tools = tool_ids - available_tool_ids
|
|
|
|
return unless invalid_tools.any?
|
|
|
|
errors.add(:instruction, "contains invalid tools: #{invalid_tools.join(', ')}")
|
|
end
|
|
|
|
# Resolves tool references from the instruction text into the tools field.
|
|
# Parses the instruction for tool references and materializes them as
|
|
# tool IDs stored in the tools JSONB field.
|
|
#
|
|
# @return [void]
|
|
# @api private
|
|
# @example
|
|
# scenario.instruction = "First [@Add Private Note](tool://add_private_note) then [@Update Priority](tool://update_priority)"
|
|
# scenario.save!
|
|
# scenario.tools # => ["add_private_note", "update_priority"]
|
|
#
|
|
# scenario.instruction = "No tools mentioned here"
|
|
# scenario.save!
|
|
# scenario.tools # => nil
|
|
def resolve_tool_references
|
|
return if instruction.blank?
|
|
|
|
tool_ids = extract_tool_ids_from_text(instruction)
|
|
self.tools = tool_ids.presence
|
|
end
|
|
end
|