# Pull Request Template ## Description defaults new accounts to captain v2 ## Type of change - [x] New feature (non-breaking change which adds functionality) ## How Has This Been Tested? Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration. locally and specs ## Checklist: - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my code - [x] I have commented on my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [x] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [x] Any dependent changes have been merged and published in downstream modules
227 lines
6.6 KiB
Ruby
227 lines
6.6 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require 'rails_helper'
|
|
|
|
RSpec.describe Concerns::Agentable do
|
|
let(:dummy_class) do
|
|
Class.new do
|
|
include Concerns::Agentable
|
|
|
|
attr_reader :account
|
|
attr_accessor :temperature
|
|
|
|
def initialize(name: 'Test Agent', temperature: 0.8, account: nil)
|
|
@name = name
|
|
@temperature = temperature
|
|
@account = account
|
|
end
|
|
|
|
def self.name
|
|
'DummyClass'
|
|
end
|
|
|
|
private
|
|
|
|
def agent_name
|
|
@name
|
|
end
|
|
|
|
def prompt_context
|
|
{ base_key: 'base_value' }
|
|
end
|
|
end
|
|
end
|
|
|
|
let(:account) { create(:account) }
|
|
let(:dummy_instance) { dummy_class.new(account: account) }
|
|
let(:mock_agents_agent) { instance_double(Agents::Agent) }
|
|
|
|
before do
|
|
InstallationConfig.where(name: 'CAPTAIN_OPEN_AI_MODEL').destroy_all
|
|
allow(Agents::Agent).to receive(:new).and_return(mock_agents_agent)
|
|
allow(Captain::PromptRenderer).to receive(:render).and_return('rendered_template')
|
|
end
|
|
|
|
describe '#agent' do
|
|
it 'creates an Agents::Agent with correct parameters' do
|
|
expect(Agents::Agent).to receive(:new).with(
|
|
name: 'Test Agent',
|
|
instructions: instance_of(Proc),
|
|
tools: [],
|
|
model: Llm::Models.default_model_for('assistant'),
|
|
temperature: 0.8,
|
|
response_schema: Captain::ResponseSchema
|
|
)
|
|
|
|
dummy_instance.agent
|
|
end
|
|
|
|
it 'uses default temperature when temperature is nil' do
|
|
dummy_instance.temperature = nil
|
|
|
|
expect(Agents::Agent).to receive(:new).with(
|
|
hash_including(temperature: 0.5)
|
|
)
|
|
|
|
dummy_instance.agent
|
|
end
|
|
|
|
it 'converts temperature to float' do
|
|
dummy_instance.temperature = '0.5'
|
|
|
|
expect(Agents::Agent).to receive(:new).with(
|
|
hash_including(temperature: 0.5)
|
|
)
|
|
|
|
dummy_instance.agent
|
|
end
|
|
end
|
|
|
|
describe '#agent_instructions' do
|
|
it 'calls Captain::PromptRenderer with base context' do
|
|
expect(Captain::PromptRenderer).to receive(:render).with(
|
|
'dummy_class',
|
|
hash_including(base_key: 'base_value')
|
|
)
|
|
|
|
dummy_instance.agent_instructions
|
|
end
|
|
|
|
it 'merges context state when provided' do
|
|
context_double = instance_double(Agents::RunContext,
|
|
context: {
|
|
state: {
|
|
assistant_config: { 'feature_contact_attributes' => true },
|
|
conversation: { id: 123 },
|
|
contact: { name: 'John' }
|
|
}
|
|
})
|
|
|
|
expected_context = {
|
|
base_key: 'base_value',
|
|
conversation: { id: 123 },
|
|
contact: { name: 'John' },
|
|
campaign: {}
|
|
}
|
|
|
|
expect(Captain::PromptRenderer).to receive(:render).with(
|
|
'dummy_class',
|
|
hash_including(expected_context)
|
|
)
|
|
|
|
dummy_instance.agent_instructions(context_double)
|
|
end
|
|
|
|
it 'merges campaign data from context state' do
|
|
context_double = instance_double(Agents::RunContext,
|
|
context: {
|
|
state: {
|
|
conversation: { id: 123 },
|
|
contact: { name: 'John' },
|
|
campaign: { id: 10, title: 'Summer Sale', message: 'Check it out' }
|
|
}
|
|
})
|
|
|
|
expect(Captain::PromptRenderer).to receive(:render).with(
|
|
'dummy_class',
|
|
hash_including(
|
|
campaign: { id: 10, title: 'Summer Sale', message: 'Check it out' }
|
|
)
|
|
)
|
|
|
|
dummy_instance.agent_instructions(context_double)
|
|
end
|
|
|
|
it 'handles context without state' do
|
|
context_double = instance_double(Agents::RunContext, context: {})
|
|
|
|
expect(Captain::PromptRenderer).to receive(:render).with(
|
|
'dummy_class',
|
|
hash_including(
|
|
base_key: 'base_value',
|
|
conversation: {},
|
|
contact: nil,
|
|
campaign: {}
|
|
)
|
|
)
|
|
|
|
dummy_instance.agent_instructions(context_double)
|
|
end
|
|
end
|
|
|
|
describe '#template_name' do
|
|
it 'returns underscored class name' do
|
|
expect(dummy_instance.send(:template_name)).to eq('dummy_class')
|
|
end
|
|
end
|
|
|
|
describe '#agent_tools' do
|
|
it 'returns empty array by default' do
|
|
expect(dummy_instance.send(:agent_tools)).to eq([])
|
|
end
|
|
end
|
|
|
|
describe '#agent_model' do
|
|
it 'returns the assistant feature default model' do
|
|
expect(dummy_instance.send(:agent_model)).to eq(Llm::Models.default_model_for('assistant'))
|
|
end
|
|
|
|
it 'returns account override model when present' do
|
|
create(:installation_config, name: 'CAPTAIN_OPEN_AI_MODEL', value: 'gpt-4.1-nano')
|
|
account.update!(captain_models: { 'assistant' => 'gpt-5.2' })
|
|
|
|
expect(dummy_instance.send(:agent_model)).to eq('gpt-5.2')
|
|
end
|
|
|
|
it 'returns the installation model when account override is absent' do
|
|
create(:installation_config, name: 'CAPTAIN_OPEN_AI_MODEL', value: 'gpt-4.1-nano')
|
|
|
|
expect(dummy_instance.send(:agent_model)).to eq('gpt-4.1-nano')
|
|
end
|
|
|
|
it 'returns the Captain V2 default when Captain V2 is enabled' do
|
|
create(:installation_config, name: 'CAPTAIN_OPEN_AI_MODEL', value: 'gpt-4.1-nano')
|
|
account.enable_features!('captain_integration_v2')
|
|
|
|
expect(dummy_instance.send(:agent_model)).to eq('gpt-5.2')
|
|
expect(account.reload.captain_models).to be_nil
|
|
end
|
|
|
|
it 'returns the assistant feature default model when account is nil' do
|
|
agent = dummy_class.new(account: nil)
|
|
|
|
expect(agent.send(:agent_model)).to eq(Llm::Models.default_model_for('assistant'))
|
|
end
|
|
end
|
|
|
|
describe '#agent_response_schema' do
|
|
it 'returns Captain::ResponseSchema' do
|
|
expect(dummy_instance.send(:agent_response_schema)).to eq(Captain::ResponseSchema)
|
|
end
|
|
end
|
|
|
|
describe 'required methods' do
|
|
let(:incomplete_class) do
|
|
Class.new do
|
|
include Concerns::Agentable
|
|
end
|
|
end
|
|
|
|
let(:incomplete_instance) { incomplete_class.new }
|
|
|
|
describe '#agent_name' do
|
|
it 'raises NotImplementedError when not implemented' do
|
|
expect { incomplete_instance.send(:agent_name) }
|
|
.to raise_error(NotImplementedError, /must implement agent_name/)
|
|
end
|
|
end
|
|
|
|
describe '#prompt_context' do
|
|
it 'raises NotImplementedError when not implemented' do
|
|
expect { incomplete_instance.send(:prompt_context) }
|
|
.to raise_error(NotImplementedError, /must implement prompt_context/)
|
|
end
|
|
end
|
|
end
|
|
end
|