Skip to content

Commit

Permalink
conversations & bots added (#913)
Browse files Browse the repository at this point in the history
* conversations & bots added

* cache strategy
  • Loading branch information
michelson committed Mar 4, 2023
1 parent a58cc34 commit 7f072d0
Show file tree
Hide file tree
Showing 7 changed files with 176 additions and 73 deletions.
102 changes: 45 additions & 57 deletions app/services/message_apis/open_ai/api.rb
Expand Up @@ -4,15 +4,15 @@ module MessageApis::OpenAi
class Api < MessageApis::BasePackage
include MessageApis::Helpers

BASE_URL = "https://api.openai.com/v1"
BASE_URL = "https://api.openai.com"
PROVIDER = "openai"

attr_accessor :url, :api_secret, :conn

def initialize(config:)
@api_secret = config["api_secret"]

@url = "#{BASE_URL}/engines/davinci/completions"
@url = "#{BASE_URL}/v1/chat/completions"

@conn = Faraday.new(
request: {
Expand Down Expand Up @@ -71,52 +71,39 @@ def locked_for_channel?(conversation, part)
end

def notify_message(conversation:, part:, channel:)
return if conversation.conversation_channels.blank?
gpt_channel = conversation.conversation_channels.find_by(provider: "open_ai")
return if gpt_channel.blank?
return unless part.messageable.is_a?(ConversationPartContent)
return true if locked_for_channel?(conversation, part)
return if part.conversation_part_channel_sources.where(provider: "open_ai").any?

Rails.logger.info "ENTRA #{part.id}"
Rails.logger.info "NOTIFY MESSAGE OPEN AI #{part.id}"

unless part.authorable.is_a?(Agent)
#####
## cache this
messages = conversation.messages.where(
messageable_type: "ConversationPartContent"
).where.not(id: part.id).order("id")
#####

# conversation.conversation_channels.find_by(provider_channel_id: channel)
# cache this thing:
previous = messages.map do |m|
{
text: m.message.text_from_serialized,
from: m.authorable_type
}
end
previous = previous.map do |item|
"#{item[:from] == 'Agent' ? "\nAI:" : "\nHuman:"}#{item[:text]}"
end.join("\n")

start_log = "'''The following is a conversation with an AI assistant. The assistant is helpful, creative, clever, and very friendly.
Human: Hello, who are you?
AI: I am an AI created by OpenAI. How can I help you today?
#{previous}
Human:'''"
previous = previous_messages(conversation, part)

parsed_content = part&.message&.parsed_content
human_input = parsed_content["blocks"]
human_input = human_input&.map do |o|
o["text"]
end&.join(" ")

prompt = "#{start_log}\nHuman: #{human_input}\nAI:"
messages = previous << { role: "user", content: human_input }

Rails.cache.write("/conversation/#{conversation.key}/openai", messages)

Rails.logger.info "PROMPT: #{prompt}"
data = prompt_settings(prompt)
Rails.logger.info "PROMPT: #{messages}"

gpt_result = get_gpt_response(data)
gpt_result = get_gpt_response(gpt_channel.data["prompt"], messages, part.authorable.id.to_s)

Rails.logger.info(gpt_result)
text = begin
gpt_result["choices"].first["message"]["content"]
rescue StandardError
nil
end

text = gpt_result[:text]
return if text.nil?

blocks = {
Expand All @@ -135,22 +122,20 @@ def notify_message(conversation:, part:, channel:)
end
end

def prompt_settings(prompt)
{
prompt: prompt,
stop: ["\n", "\nHuman:", "\nAI:"],
temperature: 0.9,
top_p: 1,
frequency_penalty: 0,
presence_penalty: 0.6,
best_of: 1,
max_tokens: 150
# frequency_penalty: 0
# length: 150
# presence_penalty: 0.6
# temperature: 0.9
# top_p: 1
}
def previous_messages(conversation, part)
Rails.cache.fetch("/conversation/#{conversation.key}/openai", expires_in: 1.hour) do
messages = conversation.messages.where(
messageable_type: "ConversationPartContent"
).where.not(id: part.id)
.order("id")

messages.map do |m|
{
"content" => m.message.text_from_serialized,
"role" => m.authorable_type == "Agent" ? "assistant" : "user"
}
end
end
end

def add_message(conversation:, from:, text:, blocks:, message_id:)
Expand Down Expand Up @@ -178,18 +163,21 @@ def post_data(url, data)
end
end

def get_gpt_response(data)
response = post_data(@url, data)
def get_gpt_response(prompt, data, user_key)
system_prompt = { role: "system", content: prompt }
messages = []
messages << system_prompt
messages << data

return nil unless response.success?
message_data = {
model: "gpt-3.5-turbo",
messages: messages.flatten,
user: user_key
}

if (json_body = JSON.parse(response.body)) && json_body
json_body
Rails.logger.info "GOT RESPONSE FROM GPT-3: #{json_body}"
end
Rails.logger.debug message_data

text = json_body["choices"].map { |o| o["text"] }.join(" ")
{ text: text, id: json_body["id"] }
JSON.parse(post_data(@url, message_data).body)
end

def process_event(params, package)
Expand Down
111 changes: 103 additions & 8 deletions app/services/message_apis/open_ai/presenter.rb
Expand Up @@ -4,7 +4,7 @@ class Presenter
# Sent when an app has been inserted into a conversation, message or
# the home screen, so that you can render the app.
def self.initialize_hook(kind:, ctx:)
record = PromptRecord.new(prompt: ctx.dig(:values, :prompt))
record = MessageApis::OpenAi::PromptRecord.new(prompt: ctx.dig(:values, :prompt))
{
kind: kind,
# ctx: ctx,
Expand All @@ -21,9 +21,13 @@ def self.submit_hook(kind:, ctx:)
message = ConversationPart.find_by(key: ctx["message_key"])

conversation = message.conversation

prompt_field = message.message.blocks["schema"].find { |o| o["id"] == "prompt-value" }

conversation.conversation_channels.create({
provider: "open_ai",
provider_channel_id: conversation.id
provider_channel_id: conversation.id,
data: { prompt: prompt_field["value"] }
})

return {
Expand Down Expand Up @@ -57,16 +61,12 @@ def self.configure_hook(kind:, ctx:)
label = "epa"
app = ctx[:package].app

default_prompt = <<~HEREDOC
The following is a conversation with an AI assistant. The assistant is helpful, creative, clever, and very friendly.
Human: Hello, who are you?
AI: I am an AI created by OpenAI. How can I help you today?
HEREDOC
default_prompt = ctx[:package].settings["main_prompt"]

value = ctx.dig(:values, :prompt)
value = default_prompt if ctx.dig(:field, :action, :type) != "submit"

record = PromptRecord.new(prompt: value)
record = MessageApis::OpenAi::PromptRecord.new(prompt: value)
schema = record.default_schema

if ctx.dig(:field, :action, :type) != "submit"
Expand Down Expand Up @@ -104,4 +104,99 @@ def self.sheet_hook(params)
[]
end
end

class PromptRecord
include ActiveModel::Model
include ActiveModel::Validations
attr_accessor :prompt

def initialize(prompt:)
self.prompt = prompt
end

def default_schema
[
{ type: "text", text: "Open AI ChatGPT", style: "header" },
{ type: "text", text: "Configure your bot", style: "muted" },
{ type: "textarea",
id: "prompt",
name: "prompt",
label: "System prompt",
placeholder: "Enter prompt here...",
value: send(:prompt),
errors: errors[:prompt]&.uniq&.join(", ") },
{
type: "button",
id: "add-prompt",
variant: "outlined",
size: "small",
label: "save prompt",
action: {
type: "submit"
}
}
]
end

def error_schema
[
{ type: "text", text: "This is a header", style: "header" },
{ type: "text", text: "This is a header", style: "muted" },
{ type: "textarea",
id: "textarea-3",
name: "textarea-3",
label: "Error",
placeholder: "Enter text here...",
value: send(:prompt),
errors: errors[:prompt]&.uniq&.join(", ") },
{
type: "button",
id: "add-prompt",
variant: "outlined",
size: "small",
label: "save prompt",
action: {
type: "submit"
}
}
]
end

def schema
[
{ type: "text", text: "This is a header", style: "header" },
{ type: "text", text: "This is a header", style: "muted" }
]
end

def success_schema
[
{ type: "text", text: "Open AI conversation", style: "header" },
{ type: "text", text: "you are going to start a conversation with GPT-3 bot", style: "muted" },
{ type: "hidden", value: send(:prompt), id: "prompt-value" },
{
type: "button",
id: "prompt-ok",
variant: "success",
align: "center",
size: "medium",
label: "Start chat",
action: {
type: "submit"
}
},
{
type: "button",
id: "prompt-no",
variant: "link",
size: "medium",
align: "center",
label: "Cancel",
action: {
type: "submit"
}
}
]
end
end
end
5 changes: 5 additions & 0 deletions db/migrate/20230303214128_add_data_to_conversation_channel.rb
@@ -0,0 +1,5 @@
class AddDataToConversationChannel < ActiveRecord::Migration[7.0]
def change
add_column :conversation_channels, :data, :jsonb
end
end
3 changes: 2 additions & 1 deletion db/schema.rb
Expand Up @@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema[7.0].define(version: 2022_10_05_144614) do
ActiveRecord::Schema[7.0].define(version: 2023_03_03_214128) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"

Expand Down Expand Up @@ -457,6 +457,7 @@
t.bigint "conversation_id", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.jsonb "data"
t.index ["conversation_id"], name: "index_conversation_channels_on_conversation_id"
t.index ["provider"], name: "index_conversation_channels_on_provider"
t.index ["provider_channel_id"], name: "index_conversation_channels_on_provider_channel_id"
Expand Down
11 changes: 10 additions & 1 deletion lib/app_packages_catalog.rb
Expand Up @@ -177,14 +177,23 @@ def self.packages(dev_packages: false)
description: "Open AI GPT-3 tasks",
icon: "https://logo.clearbit.com/openai.com",
state: "enabled",
capability_list: ["conversations"],
capability_list: %w[conversations bots],
definitions: [
{
name: "api_secret",
label: "Auth Token",
type: "string",
required: true,
grid: { xs: "w-full", sm: "w-full" }
},
{
name: "main_prompt",
label: "Main prompt",
type: "textarea",
hint: "You can change this later, on demand",
placeholder: "You are the Chaskiq chatbot, you are friendly and playful.",
required: true,
grid: { xs: "w-full", sm: "w-full" }
}
]
},
Expand Down
15 changes: 9 additions & 6 deletions spec/controllers/api/v1/hooks/openai_spec.rb
Expand Up @@ -84,16 +84,20 @@
# )

@pkg = app.app_package_integrations.create(
api_secret: "sk-xxx",
app_package: app_package
app_package: app_package,
settings: {
main_prompt: "system prompt",
api_secret: "sk-xxx"
}
)
end

describe "single hook" do
it "receive message" do
conversation.conversation_channels.create({
provider: "open_ai",
provider_channel_id: conversation.id
provider_channel_id: conversation.id,
data: { prompt: "foofof" }
})

channel = conversation.conversation_channels.find_by(provider: "open_ai")
Expand All @@ -111,7 +115,7 @@
).to receive(
:get_gpt_response
).and_return(
{ text: "yay", id: 1 }
{ "id" => "xx", "choices" => [{ "message" => { "content" => "bla bla bla" } }] }
)

perform_enqueued_jobs do
Expand All @@ -125,8 +129,7 @@
end

expect(conversation.messages.last.authorable).to be_a(Agent)

expect(conversation.messages.last.messageable.html_content).to be == "yay"
expect(conversation.messages.last.messageable.html_content).to be == "bla bla bla"
end
end
end
Expand Down
2 changes: 2 additions & 0 deletions spec/rails_helper.rb
Expand Up @@ -40,6 +40,8 @@

# DatabaseCleaner.strategy = :truncation

# include ActiveSupport::Testing::TaggedLogging

RSpec.configure do |config|
# https://github.com/rspec/rspec-rails/issues/2410
config.include ActiveSupport::Testing::Assertions
Expand Down

0 comments on commit 7f072d0

Please sign in to comment.