refactor: Otimiza a inicialização de atributos em MessageBuilder e simplifica a declaração de módulos em diversos controladores Captain, além de atualizar a configuração de exceções para testes e o Bundler.

This commit is contained in:
Rodrigo Borba 2026-01-25 08:39:23 -03:00
parent c76c8d5c50
commit b80d35a307
14 changed files with 332 additions and 363 deletions

View File

@ -1183,4 +1183,4 @@ RUBY VERSION
ruby 3.4.4p34
BUNDLED WITH
4.0.4
2.5.5

View File

@ -6,19 +6,13 @@ class Messages::MessageBuilder
attr_reader :message
def initialize(user, conversation, params)
@params = params
@private = params[:private] || false
@conversation = conversation
@user = user
@conversation = conversation
@account = conversation.account
@message_type = params[:message_type] || 'outgoing'
@attachments = params[:attachments]
@automation_rule = content_attributes&.dig(:automation_rule_id)
@params = params
return unless params.instance_of?(ActionController::Parameters)
# Try to find in_reply_to in params (top level) or content_attributes
@in_reply_to = params[:in_reply_to_id] || params[:in_reply_to] || content_attributes&.dig(:in_reply_to)
@items = content_attributes&.dig(:items)
init_message_attributes
end
def perform
@ -223,6 +217,16 @@ class Messages::MessageBuilder
'agent' => UserDrop.new(sender)
})
end
def init_message_attributes
@private = @params[:private] || false
@message_type = @params[:message_type] || 'outgoing'
@attachments = @params[:attachments]
@automation_rule = content_attributes&.dig(:automation_rule_id)
# Try to find in_reply_to in params (top level) or content_attributes
@in_reply_to = @params[:in_reply_to_id] || @params[:in_reply_to] || content_attributes&.dig(:in_reply_to)
@items = content_attributes&.dig(:items)
end
end
Messages::MessageBuilder.prepend_mod_with('Messages::MessageBuilder')

View File

@ -1,72 +1,64 @@
module Api
module V1
module Accounts
module Captain
class AssistantsController < Api::V1::Accounts::BaseController
before_action :fetch_assistant, only: [:show, :update, :destroy, :playground, :test_webhook]
class Api::V1::Accounts::Captain::AssistantsController < Api::V1::Accounts::BaseController
before_action :fetch_assistant, only: [:show, :update, :destroy, :playground, :test_webhook]
def index
@assistants = current_account.captain_assistants.order(created_at: :desc)
render json: @assistants
end
def index
@assistants = current_account.captain_assistants.order(created_at: :desc)
render json: @assistants
end
def show
render json: @assistant
end
def show
render json: @assistant
end
def create
@assistant = current_account.captain_assistants.new(assistant_params)
if @assistant.save
render json: @assistant
else
render_error_response(@assistant)
end
end
def update
if @assistant.update(assistant_params)
render json: @assistant
else
render_error_response(@assistant)
end
end
def destroy
@assistant.destroy
head :ok
end
def playground
# TODO: Implement playground logic
render json: { message: 'Playground not implemented yet' }, status: :ok
end
def test_webhook
# TODO: Implement webhook test logic
render json: { message: 'Webhook test not implemented yet' }, status: :ok
end
private
def fetch_assistant
@assistant = current_account.captain_assistants.find(params[:id])
end
def assistant_params
params.require(:assistant).permit(
:name,
:description,
:llm_provider,
:llm_model,
:api_key,
config: {},
response_guidelines: [],
guardrails: [],
handoff_webhook_config: {}
)
end
end
end
def create
@assistant = current_account.captain_assistants.new(assistant_params)
if @assistant.save
render json: @assistant
else
render_error_response(@assistant)
end
end
def update
if @assistant.update(assistant_params)
render json: @assistant
else
render_error_response(@assistant)
end
end
def destroy
@assistant.destroy
head :ok
end
def playground
# TODO: Implement playground logic
render json: { message: 'Playground not implemented yet' }, status: :ok
end
def test_webhook
# TODO: Implement webhook test logic
render json: { message: 'Webhook test not implemented yet' }, status: :ok
end
private
def fetch_assistant
@assistant = current_account.captain_assistants.find(params[:id])
end
def assistant_params
params.require(:assistant).permit(
:name,
:description,
:llm_provider,
:llm_model,
:api_key,
config: {},
response_guidelines: [],
guardrails: [],
handoff_webhook_config: {}
)
end
end

View File

@ -11,28 +11,12 @@ class Api::V1::Accounts::Captain::PricingsController < Api::V1::Accounts::BaseCo
end
def create
# Handle multiple inboxes (cloning/distributing the price)
inbox_ids_param = params[:pricing][:inbox_ids]
if inbox_ids_param.present? && inbox_ids_param.is_a?(Array)
last_pricing = nil
ActiveRecord::Base.transaction do
inbox_ids_param.each do |iid|
# Create for each inbox. Merge overwrites default params.
# Convert iid to integer just in case
pricing = current_account.captain_pricings.new(pricing_params)
pricing.inbox_id = iid.to_i
pricing.save!
last_pricing = pricing
end
end
render json: last_pricing
if inbox_ids_param.is_a?(Array) && inbox_ids_param.present?
render json: create_for_multiple_inboxes(inbox_ids_param)
else
# Create Global (inbox_id: nil) if no specific inbox selected
@pricing = current_account.captain_pricings.new(pricing_params)
@pricing.inbox_id = nil
@pricing.save!
render json: @pricing
render json: create_single_pricing
end
rescue StandardError => e
Rails.logger.error "Error creating pricing: #{e.message}"
@ -69,7 +53,7 @@ class Api::V1::Accounts::Captain::PricingsController < Api::V1::Accounts::BaseCo
# Filter by inbox if provided (returns Specific Inbox + Global rules)
@pricings = @pricings.where(inbox_id: [params[:inbox_id], nil]) if params[:inbox_id].present?
return unless params[:query].present?
return if params[:query].blank?
# Fuzzy search using ILIKE for case-insensitive matching
@pricings = @pricings.left_outer_joins(:captain_brand).where(
@ -81,4 +65,24 @@ class Api::V1::Accounts::Captain::PricingsController < Api::V1::Accounts::BaseCo
def pricing_params
params.require(:pricing).permit(:captain_brand_id, :day_range, :suite_category, :duration, :price)
end
def create_for_multiple_inboxes(inbox_ids)
last_pricing = nil
ActiveRecord::Base.transaction do
inbox_ids.each do |iid|
pricing = current_account.captain_pricings.new(pricing_params)
pricing.inbox_id = iid.to_i
pricing.save!
last_pricing = pricing
end
end
last_pricing
end
def create_single_pricing
@pricing = current_account.captain_pricings.new(pricing_params)
@pricing.inbox_id = nil
@pricing.save!
@pricing
end
end

View File

@ -1,71 +1,63 @@
module Api
module V1
module Accounts
module Captain
class ScenariosController < Api::V1::Accounts::BaseController
before_action :fetch_assistant
before_action :fetch_scenario, only: [:show, :update, :destroy]
class Api::V1::Accounts::Captain::ScenariosController < Api::V1::Accounts::BaseController
before_action :fetch_assistant
before_action :fetch_scenario, only: [:show, :update, :destroy]
def index
@scenarios = @assistant.captain_scenarios.order(created_at: :desc)
render json: @scenarios
end
def index
@scenarios = @assistant.captain_scenarios.order(created_at: :desc)
render json: @scenarios
end
def show
render json: @scenario
end
def show
render json: @scenario
end
def create
@scenario = @assistant.captain_scenarios.new(scenario_params)
@scenario.account = current_account
if @scenario.save
render json: @scenario
else
render_error_response(@scenario)
end
end
def update
if @scenario.update(scenario_params)
render json: @scenario
else
render_error_response(@scenario)
end
end
def destroy
@scenario.destroy
head :ok
end
def suggest_triggers
# TODO: Implement AI suggestion logic
# For now, return a dummy list based on title/instruction if possible, or empty
render json: { keywords: 'keyword1, keyword2' }
end
private
def fetch_assistant
@assistant = current_account.captain_assistants.find(params[:assistant_id])
end
def fetch_scenario
@scenario = @assistant.captain_scenarios.find(params[:id])
end
def scenario_params
params.permit(
:title,
:description,
:instruction,
:trigger_keywords,
:enabled,
tools: []
)
end
end
end
def create
@scenario = @assistant.captain_scenarios.new(scenario_params)
@scenario.account = current_account
if @scenario.save
render json: @scenario
else
render_error_response(@scenario)
end
end
def update
if @scenario.update(scenario_params)
render json: @scenario
else
render_error_response(@scenario)
end
end
def destroy
@scenario.destroy
head :ok
end
def suggest_triggers
# TODO: Implement AI suggestion logic
# For now, return a dummy list based on title/instruction if possible, or empty
render json: { keywords: 'keyword1, keyword2' }
end
private
def fetch_assistant
@assistant = current_account.captain_assistants.find(params[:assistant_id])
end
def fetch_scenario
@scenario = @assistant.captain_scenarios.find(params[:id])
end
def scenario_params
params.permit(
:title,
:description,
:instruction,
:trigger_keywords,
:enabled,
tools: []
)
end
end

View File

@ -1,73 +1,65 @@
module Api
module V1
module Accounts
module Captain
class ToolsController < Api::V1::Accounts::BaseController
before_action :fetch_assistant
class Api::V1::Accounts::Captain::ToolsController < Api::V1::Accounts::BaseController
before_action :fetch_assistant
NATIVE_TOOLS = [
{ key: 'react_to_message', name: 'React to Message', description: 'Reage a mensagens do usuário com emojis adequados.' },
{ key: 'check_availability', name: 'Check Availability', description: 'Verifica a disponibilidade de quartos e datas.' },
{ key: 'update_contact', name: 'Update Contact', description: 'Atualiza informações do contato (nome, email, telefone).' },
{ key: 'create_reservation_intent', name: 'Create Reservation Intent', description: 'Cria uma intenção de reserva e calcula valores.' },
{ key: 'generate_pix', name: 'Generate Pix', description: 'Gera código Pix Copy & Paste e QR Code.' },
{ key: 'list_reservations', name: 'List Reservations', description: 'Lista reservas anteriores do cliente.' },
{ key: 'status_suites', name: 'Status Suites', description: 'Verifica o status atual de ocupação das suítes.' },
{ key: 'suite_watchdog', name: 'Suite Watchdog', description: 'Monitoramento automático de status de suítes.' }
]
NATIVE_TOOLS = [
{ key: 'react_to_message', name: 'React to Message', description: 'Reage a mensagens do usuário com emojis adequados.' },
{ key: 'check_availability', name: 'Check Availability', description: 'Verifica a disponibilidade de quartos e datas.' },
{ key: 'update_contact', name: 'Update Contact', description: 'Atualiza informações do contato (nome, email, telefone).' },
{ key: 'create_reservation_intent', name: 'Create Reservation Intent', description: 'Cria uma intenção de reserva e calcula valores.' },
{ key: 'generate_pix', name: 'Generate Pix', description: 'Gera código Pix Copy & Paste e QR Code.' },
{ key: 'list_reservations', name: 'List Reservations', description: 'Lista reservas anteriores do cliente.' },
{ key: 'status_suites', name: 'Status Suites', description: 'Verifica o status atual de ocupação das suítes.' },
{ key: 'suite_watchdog', name: 'Suite Watchdog', description: 'Monitoramento automático de status de suítes.' }
].freeze
def index
tools = NATIVE_TOOLS.map do |tool|
config = @assistant.captain_tool_configs.find_by(tool_key: tool[:key])
tool.merge(
enabled: config&.is_enabled.nil? || config.is_enabled,
webhook_url: config&.webhook_url,
plug_play_id: config&.plug_play_id,
plug_play_token: config&.plug_play_token,
fallback_message: config&.fallback_message
)
end
render json: tools
end
def index
tools = NATIVE_TOOLS.map do |tool|
config = @assistant.captain_tool_configs.find_by(tool_key: tool[:key])
tool.merge(
enabled: config&.is_enabled.nil? || config.is_enabled,
webhook_url: config&.webhook_url,
plug_play_id: config&.plug_play_id,
plug_play_token: config&.plug_play_token,
fallback_message: config&.fallback_message
)
end
render json: tools
end
def update
tool_key = params[:id]
config = @assistant.captain_tool_configs.find_or_initialize_by(tool_key: tool_key)
def update
tool_key = params[:id]
config = @assistant.captain_tool_configs.find_or_initialize_by(tool_key: tool_key)
# Ensure context unique constraint is respected
config.account = current_account
# Ensure context unique constraint is respected
config.account = current_account
# Map 'enabled' from frontend to 'is_enabled' in DB
update_params = tool_params
update_params[:is_enabled] = update_params.delete(:enabled) if update_params.key?(:enabled)
# Map 'enabled' from frontend to 'is_enabled' in DB
update_params = tool_params
update_params[:is_enabled] = update_params.delete(:enabled) if update_params.key?(:enabled)
config.assign_attributes(update_params)
config.assign_attributes(update_params)
if config.save
render json: config
else
render_error_response(config)
end
end
private
def fetch_assistant
@assistant = current_account.captain_assistants.find(params[:assistant_id])
end
def tool_params
params.require(:tool).permit(
:enabled,
:is_enabled,
:webhook_url,
:plug_play_id,
:plug_play_token,
:fallback_message
)
end
end
end
if config.save
render json: config
else
render_error_response(config)
end
end
private
def fetch_assistant
@assistant = current_account.captain_assistants.find(params[:assistant_id])
end
def tool_params
params.require(:tool).permit(
:enabled,
:is_enabled,
:webhook_url,
:plug_play_id,
:plug_play_token,
:fallback_message
)
end
end

View File

@ -1,21 +1,11 @@
module Api
module V1
module Accounts
module Captain
module Units
class ReservationsSyncController < Api::V1::Accounts::BaseController
def create
unit = Current.account.captain_units.find(params[:unit_id])
::Captain::Reservations::SyncService.new(unit).perform
head :ok
rescue ActiveRecord::RecordNotFound
render_not_found_error('Unit not found')
rescue StandardError => e
render_error(e.message)
end
end
end
end
end
class Api::V1::Accounts::Captain::Units::ReservationsSyncController < Api::V1::Accounts::BaseController
def create
unit = Current.account.captain_units.find(params[:unit_id])
::Captain::Reservations::SyncService.new(unit).perform
head :ok
rescue ActiveRecord::RecordNotFound
render_not_found_error('Unit not found')
rescue StandardError => e
render_error(e.message)
end
end

View File

@ -1,65 +1,57 @@
module Api
module V1
module Accounts
module Captain
class UnitsController < Api::V1::Accounts::BaseController
def index
@units = Current.account.captain_units
end
class Api::V1::Accounts::Captain::UnitsController < Api::V1::Accounts::BaseController
def index
@units = Current.account.captain_units
end
def show
@unit = Current.account.captain_units.find(params[:id])
end
def show
@unit = Current.account.captain_units.find(params[:id])
end
def create
@unit = Current.account.captain_units.new(unit_params)
@unit.captain_brand = Current.account.captain_brands.first # Default brand logic for now
def create
@unit = Current.account.captain_units.new(unit_params)
@unit.captain_brand = Current.account.captain_brands.first # Default brand logic for now
if @unit.save
render 'show', status: :created
else
render_error_response(@unit)
end
end
def update
@unit = Current.account.captain_units.find(params[:id])
if @unit.update(unit_params)
render 'show'
else
render_error_response(@unit)
end
end
def destroy
@unit = Current.account.captain_units.find(params[:id])
@unit.destroy
head :ok
end
private
def unit_params
params.require(:unit).permit(
:name,
:status,
:reservations_sync_enabled,
:plug_play_id,
:plug_play_token,
:webhook_url,
:leader_whatsapp,
:reservation_source_tag,
:inter_client_id,
:inter_client_secret,
:inter_pix_key,
:inter_account_number,
visible_suite_categories: [],
suite_category_images: {}
)
end
end
end
if @unit.save
render 'show', status: :created
else
render_error_response(@unit)
end
end
def update
@unit = Current.account.captain_units.find(params[:id])
if @unit.update(unit_params)
render 'show'
else
render_error_response(@unit)
end
end
def destroy
@unit = Current.account.captain_units.find(params[:id])
@unit.destroy
head :ok
end
private
def unit_params
params.require(:unit).permit(
:name,
:status,
:reservations_sync_enabled,
:plug_play_id,
:plug_play_token,
:webhook_url,
:leader_whatsapp,
:reservation_source_tag,
:inter_client_id,
:inter_client_secret,
:inter_pix_key,
:inter_account_number,
visible_suite_categories: [],
suite_category_images: {}
)
end
end

View File

@ -19,25 +19,7 @@ class Api::V1::Accounts::Conversations::CrmInsightsController < Api::V1::Account
def serialize_insight(insight)
return nil if insight.blank?
{
id: insight.id,
conversation_id: insight.conversation_id,
account_id: insight.account_id,
contact_id: insight.contact_id,
summary_text: insight.summary_text,
structured_data: insight.structured_data,
contact_sessions_count: insight.contact_sessions_count,
last_contact_at: insight.last_contact_at,
updated_at: insight.updated_at,
generated_at: insight.generated_at,
range_from_message_id: insight.range_from_message_id,
range_to_message_id: insight.range_to_message_id,
status: insight.status,
error_message: insight.error_message,
schema_version: insight.schema_version,
model: insight.model,
confidence: insight.confidence
}
insight_attributes(insight)
end
def insights_payload
@ -68,4 +50,26 @@ class Api::V1::Accounts::Conversations::CrmInsightsController < Api::V1::Account
meta
end
def insight_attributes(insight)
{
id: insight.id,
conversation_id: insight.conversation_id,
account_id: insight.account_id,
contact_id: insight.contact_id,
summary_text: insight.summary_text,
structured_data: insight.structured_data,
contact_sessions_count: insight.contact_sessions_count,
last_contact_at: insight.last_contact_at,
updated_at: insight.updated_at,
generated_at: insight.generated_at,
range_from_message_id: insight.range_from_message_id,
range_to_message_id: insight.range_to_message_id,
status: insight.status,
error_message: insight.error_message,
schema_version: insight.schema_version,
model: insight.model,
confidence: insight.confidence
}
end
end

View File

@ -88,7 +88,7 @@ class User < ApplicationRecord
accepts_nested_attributes_for :account_users
has_many :assigned_conversations, foreign_key: 'assignee_id', class_name: 'Conversation', dependent: :nullify, inverse_of: :assignee
alias_attribute :conversations, :assigned_conversations
alias conversations assigned_conversations
has_many :csat_survey_responses, foreign_key: 'assigned_agent_id', dependent: :nullify, inverse_of: :assigned_agent
has_many :conversation_participants, dependent: :destroy_async
has_many :participating_conversations, through: :conversation_participants, source: :conversation

View File

@ -29,7 +29,7 @@ Rails.application.configure do
config.cache_store = :null_store
# Raise exceptions instead of rendering exception templates.
config.action_dispatch.show_exceptions = true
config.action_dispatch.show_exceptions = :all
# Disable request forgery protection in test environment.
config.action_controller.allow_forgery_protection = false

View File

@ -1,30 +1,32 @@
require 'rails_helper'
RSpec.describe Jasmine::SemanticSearchService do
subject { described_class.new(inbox) }
let!(:account) { create(:account) }
let!(:inbox) { create(:inbox, account: account) }
let!(:config) { create(:jasmine_inbox_config, inbox: inbox, account: account, is_enabled: true) }
let!(:collection_private) { create(:jasmine_collection, name: "Private", visibility: :private, owner_inbox: inbox, account: account) }
let!(:collection_shared) { create(:jasmine_collection, name: "Shared", visibility: :shared, account: account) }
let!(:collection_private) { create(:jasmine_collection, name: 'Private', visibility: :private, owner_inbox: inbox, account: account) }
let!(:collection_shared) { create(:jasmine_collection, name: 'Shared', visibility: :shared, account: account) }
# Link collections: Private (High Priority), Shared (Low Priority)
let!(:link_private) { create(:jasmine_inbox_collection, inbox: inbox, collection: collection_private, priority: 10, account: account) }
let!(:link_shared) { create(:jasmine_inbox_collection, inbox: inbox, collection: collection_shared, priority: 0, account: account) }
let!(:doc_private) { create(:jasmine_document, collection: collection_private, content: "Private Secret", account: account) }
let!(:doc_shared) { create(:jasmine_document, collection: collection_shared, content: "Shared Knowledge", account: account) }
let!(:doc_private) { create(:jasmine_document, collection: collection_private, content: 'Private Secret', account: account) }
let!(:doc_shared) { create(:jasmine_document, collection: collection_shared, content: 'Shared Knowledge', account: account) }
# Mock Embedding Service behavior by creating chunks directly with known vectors
# Query Vector: [1.0, 0.0, ...]
# Private Match: [0.9, 0.0, ...] -> Distance ~0.1
# Shared Match: [0.8, 0.0, ...] -> Distance ~0.2 (Worse match but still good)
# Irrelevant: [0.0, 1.0, ...] -> Distance ~1.0
before do
# Create chunks manually to bypass job/api dependency
create_chunk(doc_private, [0.9] + [0.0]*1535)
create_chunk(doc_shared, [0.8] + [0.0]*1535)
create_chunk(doc_private, [0.9] + ([0.0]*1535))
create_chunk(doc_shared, [0.8] + ([0.0]*1535))
end
def create_chunk(doc, vec)
@ -37,43 +39,41 @@ RSpec.describe Jasmine::SemanticSearchService do
)
end
subject { described_class.new(inbox) }
describe '#search' do
it 'returns results from enabled collections' do
# Mock the embedding generation for the query
allow(RubyLLM).to receive(:embed).and_return(OpenStruct.new(vectors: [[1.0] + [0.0]*1535]))
allow(RubyLLM).to receive(:embed).and_return(OpenStruct.new(vectors: [[1.0] + ([0.0]*1535)]))
results = subject.search("Query")
results = subject.search('Query')
expect(results.size).to be >= 2
expect(results.first.content).to eq("Private Secret")
expect(results.first.content).to eq('Private Secret')
end
it 'respects priority (waterfall)' do
allow(RubyLLM).to receive(:embed).and_return(OpenStruct.new(vectors: [[1.0] + [0.0]*1535]))
allow(RubyLLM).to receive(:embed).and_return(OpenStruct.new(vectors: [[1.0] + ([0.0]*1535)]))
# Even if shared has a PERFECT match, if Private has a "Good Enough" match (below threshold),
# does the waterfall prioritize Private?
# The algorithm gathers candidates from Groups (Priority 10 first).
# Then it filters/reranks.
# If Priority 10 fills the FINAL_LIMIT, Priority 0 is skipped.
# Let's limit the service to 1 result to test waterfall
# stub_const("Jasmine::SemanticSearchService::FINAL_LIMIT", 1) # Removed as service uses arg
results = subject.search("Query", limit: 1)
results = subject.search('Query', limit: 1)
expect(results.size).to eq(1)
expect(results.first.content).to eq("Private Secret") # Should be from High Priority collection
expect(results.first.content).to eq('Private Secret') # Should be from High Priority collection
end
it 'filters out results above threshold' do
# Create a bad chunk
bad_doc = create(:jasmine_document, collection: collection_private, account: account)
create_chunk(bad_doc, [0.0] + [1.0]*1535) # Orthogonal/Opposite
allow(RubyLLM).to receive(:embed).and_return(OpenStruct.new(vectors: [[1.0] + [0.0]*1535]))
results = subject.search("Query")
create_chunk(bad_doc, [0.0] + ([1.0] * 1535)) # Orthogonal/Opposite
allow(RubyLLM).to receive(:embed).and_return(OpenStruct.new(vectors: [[1.0] + ([0.0] * 1535)]))
results = subject.search('Query')
expect(results.map(&:content)).not_to include(bad_doc.content)
end
end

View File

@ -11,7 +11,6 @@ def test_msg(assistant, msg)
puts " (Raciocínio: #{res['reasoning']})"
end
test_msg(assistant, "Oi, qual o valor da pernoite na Alexa?")
test_msg(assistant, "Quero reservar para amanhã 22h")
test_msg(assistant, "ESTOU COM MUITA RAIVA!")
test_msg(assistant, 'Oi, qual o valor da pernoite na Alexa?')
test_msg(assistant, 'Quero reservar para amanhã 22h')
test_msg(assistant, 'ESTOU COM MUITA RAIVA!')

View File

@ -3,22 +3,22 @@
assistant = Captain::Assistant.find_by(name: 'Jasmine (Hotel Prime)')
unless assistant
puts "Assistente Jasmine não encontrada. Execute o seed primeiro."
puts 'Assistente Jasmine não encontrada. Execute o seed primeiro.'
exit
end
def simulate_chat(assistant, message)
puts "\n--- CLIENTE: #{message}"
# Usamos o AgentRunnerService (V2) que é o que suporta handoffs/sub-agentes
runner = Captain::Assistant::AgentRunnerService.new(assistant: assistant)
# Simulamos o histórico com apenas a última mensagem do usuário
history = [{ role: 'user', content: message }]
response = runner.generate_response(message_history: history)
puts "--- JASMINE RESPONDE:"
puts '--- JASMINE RESPONDE:'
puts "DEBUG: #{response.inspect}"
puts "Pensamento: #{response['reasoning']}"
puts "Agente Atual: #{response['agent_name'] || 'Orquestrador'}"
@ -27,8 +27,8 @@ def simulate_chat(assistant, message)
end
# Casos de Teste
simulate_chat(assistant, "Oi, quanto custa a pernoite na suite Alexa?")
simulate_chat(assistant, "Quero reservar para sabado que vem às 20h.")
simulate_chat(assistant, "Tem suite livre agora? To chegando em 10 minutos.")
simulate_chat(assistant, "Pode me mandar fotos da suite com hidro?")
simulate_chat(assistant, "ESTOU MUITO IRRITADO COM A DEMORA!") # Teste de sentimento
simulate_chat(assistant, 'Oi, quanto custa a pernoite na suite Alexa?')
simulate_chat(assistant, 'Quero reservar para sabado que vem às 20h.')
simulate_chat(assistant, 'Tem suite livre agora? To chegando em 10 minutos.')
simulate_chat(assistant, 'Pode me mandar fotos da suite com hidro?')
simulate_chat(assistant, 'ESTOU MUITO IRRITADO COM A DEMORA!') # Teste de sentimento