feat: scenario agents & runner (#11944)

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>
This commit is contained in:
Shivam Mishra
2025-08-14 12:39:21 +05:30
committed by GitHub
parent 14471cc20c
commit c6be04cdc1
21 changed files with 1437 additions and 16 deletions

View File

@@ -19,6 +19,7 @@
class Captain::Assistant < ApplicationRecord
include Avatarable
include Concerns::CaptainToolsHelpers
include Concerns::Agentable
self.table_name = 'captain_assistants'
@@ -35,6 +36,8 @@ class Captain::Assistant < ApplicationRecord
has_many :copilot_threads, dependent: :destroy_async
has_many :scenarios, class_name: 'Captain::Scenario', dependent: :destroy_async
store_accessor :config, :temperature, :feature_faq, :feature_memory, :product_name
validates :name, presence: true
validates :description, presence: true
validates :account_id, presence: true
@@ -71,6 +74,33 @@ class Captain::Assistant < ApplicationRecord
private
def agent_name
name
end
def agent_tools
[
self.class.resolve_tool_class('faq_lookup').new(self),
self.class.resolve_tool_class('handoff').new(self)
]
end
def prompt_context
{
name: name,
description: description,
product_name: config['product_name'] || 'this product',
scenarios: scenarios.enabled.map do |scenario|
{
key: scenario.title.parameterize.underscore,
description: scenario.description
}
end,
response_guidelines: response_guidelines || [],
guardrails: guardrails || []
}
end
def default_avatar_url
"#{ENV.fetch('FRONTEND_URL', nil)}/assets/images/dashboard/captain/logo.svg"
end

View File

@@ -22,6 +22,7 @@
#
class Captain::Scenario < ApplicationRecord
include Concerns::CaptainToolsHelpers
include Concerns::Agentable
self.table_name = 'captain_scenarios'
@@ -37,10 +38,43 @@ class Captain::Scenario < ApplicationRecord
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.