feat: Interface to validate response_source (#8894)
- This PR adds a UI to validate the response source quality quickly. It also helps to test with sample questions and update responses in the database when missing. Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
class SuperAdmin::EnterpriseBaseController < SuperAdmin::ApplicationController
|
||||
before_action :prepend_view_paths
|
||||
|
||||
# Prepend the view path to the enterprise/app/views won't be available by default
|
||||
def prepend_view_paths
|
||||
prepend_view_path 'enterprise/app/views/'
|
||||
end
|
||||
end
|
||||
@@ -1,4 +1,4 @@
|
||||
class SuperAdmin::ResponseDocumentsController < SuperAdmin::ApplicationController
|
||||
class SuperAdmin::ResponseDocumentsController < SuperAdmin::EnterpriseBaseController
|
||||
# Overwrite any of the RESTful controller actions to implement custom behavior
|
||||
# For example, you may want to send an email after a foo is updated.
|
||||
#
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
class SuperAdmin::ResponseSourcesController < SuperAdmin::ApplicationController
|
||||
class SuperAdmin::ResponseSourcesController < SuperAdmin::EnterpriseBaseController
|
||||
# Overwrite any of the RESTful controller actions to implement custom behavior
|
||||
# For example, you may want to send an email after a foo is updated.
|
||||
#
|
||||
@@ -41,4 +41,36 @@ class SuperAdmin::ResponseSourcesController < SuperAdmin::ApplicationController
|
||||
|
||||
# See https://administrate-demo.herokuapp.com/customizing_controller_actions
|
||||
# for more information
|
||||
|
||||
before_action :set_response_source, only: %i[chat process_chat]
|
||||
|
||||
def chat; end
|
||||
|
||||
def process_chat
|
||||
previous_messages = []
|
||||
get_previous_messages(previous_messages)
|
||||
robin_response = ChatGpt.new(
|
||||
Enterprise::MessageTemplates::ResponseBotService.response_sections(params[:message], @response_source)
|
||||
).generate_response(
|
||||
params[:message], previous_messages
|
||||
)
|
||||
message_content = robin_response['response']
|
||||
if robin_response['context_ids'].present?
|
||||
message_content += Enterprise::MessageTemplates::ResponseBotService.generate_sources_section(robin_response['context_ids'])
|
||||
end
|
||||
render json: { message: message_content }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def get_previous_messages(previous_messages)
|
||||
params[:previous_messages].each do |message|
|
||||
role = message['type'] == 'user' ? 'user' : 'system'
|
||||
previous_messages << { content: message['message'], role: role }
|
||||
end
|
||||
end
|
||||
|
||||
def set_response_source
|
||||
@response_source = requested_resource
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
class SuperAdmin::ResponsesController < SuperAdmin::ApplicationController
|
||||
class SuperAdmin::ResponsesController < SuperAdmin::EnterpriseBaseController
|
||||
# Overwrite any of the RESTful controller actions to implement custom behavior
|
||||
# For example, you may want to send an email after a foo is updated.
|
||||
#
|
||||
|
||||
@@ -17,6 +17,7 @@ class ResponseBuilderJob < ApplicationJob
|
||||
def prepare_data(response_document)
|
||||
{
|
||||
model: 'gpt-3.5-turbo',
|
||||
response_format: { type: 'json_object' },
|
||||
messages: [
|
||||
{
|
||||
role: 'system',
|
||||
@@ -32,16 +33,15 @@ class ResponseBuilderJob < ApplicationJob
|
||||
|
||||
def system_message_content
|
||||
<<~SYSTEM_MESSAGE_CONTENT
|
||||
You are a content writer looking to convert user content into short FAQs which can be added to your website's helper centre.
|
||||
Format the webpage content provided in the message to FAQ format like the following example.#{' '}
|
||||
Ensure that you only generate faqs from the information provider in the message.#{' '}
|
||||
Ensure that output is always valid json.#{' '}
|
||||
If no match is available, return an empty JSON.
|
||||
```
|
||||
[ { "question": "What is the pricing?",
|
||||
"answer" : " There are different pricing tiers available."
|
||||
}]
|
||||
```
|
||||
You are a content writer looking to convert user content into short FAQs which can be added to your website's helper centre.
|
||||
Format the webpage content provided in the message to FAQ format mentioned below in the json
|
||||
Ensure that you only generate faqs from the information provider in the message.
|
||||
Ensure that output is always valid json.
|
||||
If no match is available, return an empty JSON.
|
||||
|
||||
```json
|
||||
{faqs: [{question: '', answer: ''}]
|
||||
```
|
||||
SYSTEM_MESSAGE_CONTENT
|
||||
end
|
||||
|
||||
@@ -67,7 +67,7 @@ class ResponseBuilderJob < ApplicationJob
|
||||
|
||||
return if content.nil?
|
||||
|
||||
faqs = JSON.parse(content.strip)
|
||||
faqs = JSON.parse(content.strip).fetch('faqs', [])
|
||||
|
||||
faqs.each do |faq|
|
||||
response_document.responses.create!(
|
||||
|
||||
@@ -25,4 +25,9 @@ class ResponseSource < ApplicationRecord
|
||||
has_many :responses, dependent: :destroy_async
|
||||
|
||||
accepts_nested_attributes_for :response_documents
|
||||
|
||||
def get_responses(query)
|
||||
embedding = Openai::EmbeddingsService.new.get_embedding(query)
|
||||
responses.active.nearest_neighbors(:embedding, embedding, distance: 'cosine').first(5)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,6 +1,39 @@
|
||||
class Enterprise::MessageTemplates::ResponseBotService
|
||||
pattr_initialize [:conversation!]
|
||||
|
||||
def self.generate_sources_section(article_ids)
|
||||
sources_content = ''
|
||||
|
||||
articles_hash = get_article_hash(article_ids.uniq)
|
||||
|
||||
articles_hash.first(3).each do |article_hash|
|
||||
sources_content += " - [#{article_hash[:response].question}](#{article_hash[:response_document].document_link}) \n"
|
||||
end
|
||||
sources_content = "\n \n \n **Sources** \n#{sources_content}" if sources_content.present?
|
||||
sources_content
|
||||
end
|
||||
|
||||
def self.get_article_hash(article_ids)
|
||||
seen_documents = Set.new
|
||||
article_ids.uniq.filter_map do |article_id|
|
||||
response = Response.find(article_id)
|
||||
response_document = response.response_document
|
||||
next if response_document.blank? || seen_documents.include?(response_document)
|
||||
|
||||
seen_documents << response_document
|
||||
{ response: response, response_document: response_document }
|
||||
end
|
||||
end
|
||||
|
||||
def self.response_sections(content, response_source)
|
||||
sections = ''
|
||||
|
||||
response_source.get_responses(content).each do |response|
|
||||
sections += "{context_id: #{response.id}, context: #{response.question} ? #{response.answer}},"
|
||||
end
|
||||
sections
|
||||
end
|
||||
|
||||
def perform
|
||||
ActiveRecord::Base.transaction do
|
||||
@response = get_response(conversation.messages.incoming.last.content)
|
||||
@@ -12,15 +45,6 @@ class Enterprise::MessageTemplates::ResponseBotService
|
||||
true
|
||||
end
|
||||
|
||||
def response_sections(content)
|
||||
sections = ''
|
||||
|
||||
inbox.get_responses(content).each do |response|
|
||||
sections += "{context_id: #{response.id}, context: #{response.question} ? #{response.answer}},"
|
||||
end
|
||||
sections
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
delegate :contact, :account, :inbox, to: :conversation
|
||||
@@ -28,7 +52,7 @@ class Enterprise::MessageTemplates::ResponseBotService
|
||||
def get_response(content)
|
||||
previous_messages = []
|
||||
get_previous_messages(previous_messages)
|
||||
ChatGpt.new(response_sections(content)).generate_response('', previous_messages)
|
||||
ChatGpt.new(self.class.response_sections(content, inbox)).generate_response('', previous_messages)
|
||||
end
|
||||
|
||||
def get_previous_messages(previous_messages)
|
||||
@@ -63,24 +87,11 @@ class Enterprise::MessageTemplates::ResponseBotService
|
||||
|
||||
def create_messages
|
||||
message_content = @response['response']
|
||||
message_content += generate_sources_section if @response['context_ids'].present?
|
||||
message_content += self.class.generate_sources_section(@response['context_ids']) if @response['context_ids'].present?
|
||||
|
||||
create_outgoing_message(message_content)
|
||||
end
|
||||
|
||||
def generate_sources_section
|
||||
article_ids = @response['context_ids']
|
||||
sources_content = ''
|
||||
|
||||
articles_hash = get_article_hash(article_ids.uniq)
|
||||
|
||||
articles_hash.first(3).each do |article_hash|
|
||||
sources_content += " - [#{article_hash[:response].question}](#{article_hash[:response_document].document_link}) \n"
|
||||
end
|
||||
sources_content = "\n \n \n **Sources** \n#{sources_content}" if sources_content.present?
|
||||
sources_content
|
||||
end
|
||||
|
||||
def create_outgoing_message(message_content)
|
||||
conversation.messages.create!(
|
||||
{
|
||||
@@ -91,16 +102,4 @@ class Enterprise::MessageTemplates::ResponseBotService
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
def get_article_hash(article_ids)
|
||||
seen_documents = Set.new
|
||||
article_ids.uniq.filter_map do |article_id|
|
||||
response = Response.find(article_id)
|
||||
response_document = response.response_document
|
||||
next if response_document.blank? || seen_documents.include?(response_document)
|
||||
|
||||
seen_documents << response_document
|
||||
{ response: response, response_document: response_document }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
<% content_for :title, "Robin AI playground: #{@response_source.name}" %>
|
||||
<%= render_vue_component('PlaygroundIndex', {
|
||||
responseSourceName: @response_source.name,
|
||||
responseSourcePath: super_admin_response_source_path(@response_source)
|
||||
}) %>
|
||||
@@ -0,0 +1,71 @@
|
||||
<%#
|
||||
# Show
|
||||
|
||||
This view is the template for the show page.
|
||||
It renders the attributes of a resource,
|
||||
as well as a link to its edit page.
|
||||
|
||||
## Local variables:
|
||||
|
||||
- `page`:
|
||||
An instance of [Administrate::Page::Show][1].
|
||||
Contains methods for accessing the resource to be displayed on the page,
|
||||
as well as helpers for describing how each attribute of the resource
|
||||
should be displayed.
|
||||
|
||||
[1]: http://www.rubydoc.info/gems/administrate/Administrate/Page/Show
|
||||
%>
|
||||
|
||||
<% content_for(:title) { t("administrate.actions.show_resource", name: page.page_title) } %>
|
||||
|
||||
<header class="main-content__header">
|
||||
<h1 class="main-content__page-title">
|
||||
<%= content_for(:title) %>
|
||||
</h1>
|
||||
|
||||
<div>
|
||||
<%= link_to(
|
||||
"Chat",
|
||||
[:chat, namespace, page.resource],
|
||||
class: "button"
|
||||
) %>
|
||||
|
||||
<%= link_to(
|
||||
"Edit",
|
||||
[:edit, namespace, page.resource],
|
||||
class: "button",
|
||||
) if accessible_action?(page.resource, :edit) %>
|
||||
|
||||
<%= link_to(
|
||||
t("administrate.actions.destroy"),
|
||||
[namespace, page.resource],
|
||||
class: "button button--danger",
|
||||
method: :delete,
|
||||
data: { confirm: t("administrate.actions.confirm") }
|
||||
) if accessible_action?(page.resource, :destroy) %>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<section class="main-content__body">
|
||||
<dl>
|
||||
<% page.attributes.each do |title, attributes| %>
|
||||
<fieldset class="<%= "field-unit--nested" if title.present? %>">
|
||||
<% if title.present? %>
|
||||
<legend><%= t "helpers.label.#{page.resource_name}.#{title}", default: title %></legend>
|
||||
<% end %>
|
||||
|
||||
<% attributes.each do |attribute| %>
|
||||
<dt class="attribute-label" id="<%= attribute.name %>">
|
||||
<%= t(
|
||||
"helpers.label.#{resource_name}.#{attribute.name}",
|
||||
default: page.resource.class.human_attribute_name(attribute.name),
|
||||
) %>
|
||||
</dt>
|
||||
|
||||
<dd class="attribute-data attribute-data--<%=attribute.html_class%>"
|
||||
><%= render_field attribute, page: page %></dd>
|
||||
<% end %>
|
||||
</fieldset>
|
||||
<% end %>
|
||||
</dl>
|
||||
</section>
|
||||
@@ -4,7 +4,7 @@ class ChatGpt
|
||||
end
|
||||
|
||||
def initialize(context_sections = '')
|
||||
@model = 'gpt-4-1106-preview'
|
||||
@model = 'gpt-4-0125-preview'
|
||||
@messages = [system_message(context_sections)]
|
||||
end
|
||||
|
||||
|
||||
Reference in New Issue
Block a user