# Pull Request Template ## Description Captain (v1) makes false promises by saying it will handoff but doesn't. This happens due to an exact string match comparison and the prompt gives the model a lot of responsibilities: - identity - what to respond - obey custom instructions - decide on tool calls This PR decouples responsibility, the core prompt responds, and an additional llm call evaluates if handoff was needed or not after that message. ## Type of change - [x] Bug fix (non-breaking change which fixes an issue) ## 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 ## 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
58 lines
2.1 KiB
Ruby
58 lines
2.1 KiB
Ruby
module Captain::Conversation::V1ActionClassifier
|
|
private
|
|
|
|
def v1_action_classifier_enabled?
|
|
account.feature_enabled?('captain_v1_action_classifier')
|
|
end
|
|
|
|
def classify_v1_response_action(message_history)
|
|
return unless v1_action_classifier_enabled?
|
|
return if legacy_v1_handoff_token?
|
|
|
|
classification = Captain::Llm::AssistantActionClassifierService.new(
|
|
assistant: @assistant,
|
|
conversation: @conversation
|
|
).classify(message_history: message_history, assistant_response: @response['response'])
|
|
|
|
apply_v1_action_classification(classification)
|
|
rescue StandardError => e
|
|
ChatwootExceptionTracker.new(e, account: account).capture_exception
|
|
Rails.logger.warn(
|
|
"[CAPTAIN][ResponseBuilderJob] V1 action classifier failed for account=#{account.id} " \
|
|
"conversation=#{@conversation.display_id}: #{e.class.name}: #{e.message}"
|
|
)
|
|
end
|
|
|
|
def apply_v1_action_classification(classification)
|
|
action = classification['action']
|
|
return log_invalid_v1_action_classification(classification) unless valid_v1_action_classification?(action)
|
|
|
|
@response.merge!(
|
|
'action' => action,
|
|
'action_reason' => classification['action_reason'],
|
|
'action_source' => 'classifier',
|
|
'action_classifier_model' => classification['model']
|
|
)
|
|
|
|
log_v1_action_classification(action, classification)
|
|
end
|
|
|
|
def log_v1_action_classification(action, classification)
|
|
Rails.logger.info(
|
|
"[CAPTAIN][ResponseBuilderJob] V1 action classifier account=#{account.id} conversation=#{@conversation.display_id} " \
|
|
"action=#{action} reason=#{classification['action_reason']} model=#{classification['model']}"
|
|
)
|
|
end
|
|
|
|
def valid_v1_action_classification?(action)
|
|
Captain::AssistantActionSchema::ACTIONS.include?(action)
|
|
end
|
|
|
|
def log_invalid_v1_action_classification(classification)
|
|
Rails.logger.warn(
|
|
'[CAPTAIN][ResponseBuilderJob] V1 action classifier returned invalid action; falling back to assistant response ' \
|
|
"for account=#{account.id} conversation=#{@conversation.display_id}: #{classification['error'] || classification['raw_response']}"
|
|
)
|
|
end
|
|
end
|