ruboty-openai_chat 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 353bed59e624fc927b316d6336c367e23475d0bf534ed126db6b07fcefc497ce
4
- data.tar.gz: 0d50dd6f60af89192adb12f64b156914a1ce2663ff8b9c9d7238ba18eae1a596
3
+ metadata.gz: 56d165955c63d1811168c341cc949fa01edef6afefdbee4fca59968f4d45bcc0
4
+ data.tar.gz: 035fe05f51a35bb5c94bcb2347bf4e3f783455d741d1922ae04e70928c0bd71d
5
5
  SHA512:
6
- metadata.gz: 7598b2b3644f09cf7b78358c4a9a8fb78a6aa6e3944a83c0f19037f80cd9ab60a8d416bb34675c94cbd70680bc8e07b8ecc1f9744775211b9a7b156498727cd0
7
- data.tar.gz: eabeb98f4d348d33868554a3abe3c473aac32895d7d3e33a236831fa72a7f1b9df0ae78a868b695759e7199bec3f78f6c24287bf8f71b31b7f4831b03f12b8a0
6
+ metadata.gz: 8fd13b2d9393cc7971eb13555c98a1ff3c704e01d48cfbace79958f3b5adc3c18a8ecb1f1e3c928e2844a8f6ba22ec5d8dd69df98bafd5c5f39e9b32849b04b2
7
+ data.tar.gz: 272e9f664c55bf3d46542a06f1360018743a4a01228c92ab32cd27bb1af0157133a839e76912033ff494b500cd87ea262501941636754b5abff0b4ecf285b8a1
@@ -31,8 +31,9 @@ module Ruboty
31
31
  @memory ||= Memory.new(robot)
32
32
  end
33
33
 
34
- def pretext
35
- [ENV["OPENAI_CHAT_PRETEXT"], memory.namespace(NAMESPACE)[PROFILE_KEY]].compact.map { |text| text.strip.gsub(/\R/, " ") }.join(" ")
34
+ # @return [Array<String>]
35
+ def pretexts
36
+ [ENV["OPENAI_CHAT_PRETEXT"], memory.namespace(NAMESPACE)[PROFILE_KEY]].compact
36
37
  end
37
38
  end
38
39
  end
@@ -11,70 +11,71 @@ module Ruboty
11
11
 
12
12
  def call
13
13
  human_comment = message[:body]
14
- response = complete(human_comment)
15
- p response if ENV["OPENAI_CHAT_DEBUG"]
14
+ response = request_chat(human_comment)
15
+ p response if Ruboty::OpenAIChat.debug_mode?
16
16
  raise response.body if response.code >= 400
17
17
 
18
- ai_comment = response.dig("choices", 0, "text").gsub(/\A\s+/, "") || ""
18
+ ai_comment = response.dig("choices", 0, "message", "content").gsub(/\A\s+/, "") || ""
19
19
 
20
- remember_dialog(Dialog.new(human_comment: human_comment, ai_comment: ai_comment, expire_at: expire_at))
20
+ remember_messages(
21
+ Message.new(role: :user, content: human_comment, expire_at: expire_at),
22
+ Message.new(role: :assistant, content: ai_comment, expire_at: expire_at)
23
+ )
21
24
  message.reply(ai_comment)
22
25
  rescue StandardError => e
23
26
  forget
24
27
  message.reply(e.message, code: true)
25
- raise e if ENV["OPENAI_CHAT_DEBUG"]
28
+ raise e if Ruboty::OpenAIChat.debug_mode?
26
29
 
27
30
  true
28
31
  end
29
32
 
30
33
  private
31
34
 
32
- def complete(human_comment)
35
+ def request_chat(human_comment)
33
36
  # https://beta.openai.com/examples/default-chat
34
- client.completions(
37
+ client.chat(
35
38
  parameters: {
36
- model: "text-davinci-003",
37
- temperature: 0.9,
38
- max_tokens: 512,
39
- top_p: 1,
40
- frequency_penalty: 0,
41
- presence_penalty: 0.6,
42
- stop: Dialog::STOP_SEQUENCES,
43
- prompt: build_prompt(human_comment)
39
+ model: "gpt-3.5-turbo",
40
+ messages: build_messages(human_comment).map(&:to_api_hash),
41
+ temperature: 0.7
44
42
  }
45
43
  )
46
44
  end
47
45
 
48
- def build_prompt(human_comment)
49
- prefix = [prompt_prefix]
50
- prefix += [pretext].compact
46
+ # @return [Array<Message>]
47
+ def build_messages(human_comment)
48
+ settings = <<~STRING.chomp
49
+ The following is a conversation with an AI assistant. The assistant is helpful, creative, clever, and very friendly. The AI assistant's name is #{robot.name}.
50
+ STRING
51
51
 
52
- dialogs = [example_dialog, *dialogs_from_memory,
53
- Dialog.new(human_comment: human_comment, ai_comment: "")].map do |dialog|
54
- dialog.to_prompt.chomp
55
- end.join("\n")
52
+ system_messages = [Message.new(role: :system, content: settings)]
53
+ pretexts.each do |pretext|
54
+ system_messages << Message.new(role: :system, content: pretext.chomp)
55
+ end
56
56
 
57
- <<~STRING.chomp
58
- #{prefix.join(" ")}
57
+ pre_messages = [*example_dialog, *messages_from_memory]
59
58
 
60
- #{dialogs}
61
- STRING
59
+ [*system_messages, *pre_messages, Message.new(role: :user, content: human_comment)]
62
60
  end
63
61
 
64
62
  # @return [Array<Dialog>]
65
- def dialogs_from_memory
66
- raw_dialogs.reject! { |hash| Dialog.from_hash(hash).expired? }
67
- raw_dialogs.map { |hash| Dialog.from_hash(hash) }
63
+ def messages_from_memory
64
+ current = Time.now
65
+ raw_messages.reject! { |hash| Message.from_hash(hash).expired?(current) }
66
+ raw_messages.map { |hash| Message.from_hash(hash) }
68
67
  end
69
68
 
70
- # @param dialog [Dialog]
71
- def remember_dialog(dialog)
72
- raw_dialogs << dialog.to_h
69
+ # @param messages [Array<Message>]
70
+ def remember_messages(*messages)
71
+ messages.each do |message|
72
+ raw_messages << message.to_h
73
+ end
73
74
  end
74
75
 
75
76
  # @return [Array<Hash>]
76
- def raw_dialogs
77
- memory.namespace(NAMESPACE, message.from || "general")[:dialogs] ||= []
77
+ def raw_messages
78
+ memory.namespace(NAMESPACE, message.from || "general")[:messages] ||= []
78
79
  end
79
80
 
80
81
  def forget
@@ -83,26 +84,22 @@ module Ruboty
83
84
 
84
85
  # @return [Time]
85
86
  def expire_at
86
- Time.now + ENV.fetch("OPENAI_CHAT_MEMORIZE_SECONDS") { 5 * 60 }.to_i
87
- end
88
-
89
- # @return [String]
90
- def prompt_prefix
91
- "The following is a conversation with an AI assistant. The assistant is helpful, creative, clever, and very friendly. The AI assistant's name is #{robot.name}."
87
+ Time.now + ENV.fetch("OPENAI_CHAT_MEMORIZE_SECONDS") { 15 * 60 }.to_i
92
88
  end
93
89
 
90
+ # @return [Array<Message>]
94
91
  def example_dialog
95
92
  case language
96
93
  when :ja
97
- Dialog.new(
98
- human_comment: "こんにちは。あなたは誰ですか?",
99
- ai_comment: "私は OpenAI 製の AI アシスタントの #{robot.name} です。なにかお手伝いできることはありますか?"
100
- )
94
+ [
95
+ Message.new(role: :user, content: "こんにちは。あなたは誰ですか?"),
96
+ Message.new(role: :assistant, content: "私は AI アシスタントの #{robot.name} です。なにかお手伝いできることはありますか?")
97
+ ]
101
98
  else
102
- Dialog.new(
103
- human_comment: "Hello, who are you?",
104
- ai_comment: "I'm #{robot.name}, an AI assistant created by OpenAI. How can I help you today?"
105
- )
99
+ [
100
+ Message.new(role: :user, content: "Hello, who are you?"),
101
+ Message.new(role: :assistant, content: "I'm #{robot.name}, an AI assistant. How can I help you today?")
102
+ ]
106
103
  end
107
104
  end
108
105
 
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ruboty
4
+ module OpenAIChat
5
+ class Message
6
+ ROLES = %i[system user assistant].freeze
7
+
8
+ # @return [:system, :user, :assistant]
9
+ attr_reader :role
10
+
11
+ # @return [String]
12
+ attr_reader :content
13
+
14
+ # @return [Time]
15
+ attr_reader :expire_at
16
+
17
+ def self.from_hash(hash)
18
+ new(**hash.transform_keys(&:to_sym))
19
+ end
20
+
21
+ # @param role [:system, :user, :assistant]
22
+ # @param content [String]
23
+ # @param expire_at [Time, Integer, nil]
24
+ def initialize(role:, content:, expire_at: nil)
25
+ @role = role.to_sym
26
+ raise ArgumentError, "role must be :system, :user, or :assistant" unless ROLES.include?(@role)
27
+
28
+ @content = content
29
+ @expire_at = expire_at&.yield_self { |t| Time.at(t) }
30
+ end
31
+
32
+ # @return [Hash]
33
+ def to_h
34
+ { role: role, content: content, expire_at: expire_at&.to_i }
35
+ end
36
+
37
+ def to_api_hash
38
+ { role: role, content: content }
39
+ end
40
+
41
+ # @return [Boolean]
42
+ def expired?(from = Time.now)
43
+ expire_at && (expire_at <= from)
44
+ end
45
+ end
46
+ end
47
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Ruboty
4
4
  module OpenAIChat
5
- VERSION = "0.2.0"
5
+ VERSION = "0.3.0"
6
6
  end
7
7
  end
@@ -1,15 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "ruboty"
4
- require "ruby/openai"
4
+ require "openai"
5
5
 
6
6
  require_relative "openai_chat/version"
7
7
  require_relative "openai_chat/actions/base"
8
8
  require_relative "openai_chat/actions/chat"
9
9
  require_relative "openai_chat/actions/remember_profile"
10
10
  require_relative "openai_chat/actions/show_profile"
11
- require_relative "openai_chat/dialog"
12
11
  require_relative "openai_chat/memory"
12
+ require_relative "openai_chat/message"
13
13
 
14
14
  require_relative "handlers/openai_chat"
15
15
 
@@ -17,5 +17,10 @@ module Ruboty
17
17
  module OpenAIChat
18
18
  class Error < StandardError; end
19
19
  # Your code goes here...
20
+
21
+ # @return [Boolean]
22
+ def self.debug_mode?
23
+ ENV["OPENAI_CHAT_DEBUG"]&.length&.positive?
24
+ end
20
25
  end
21
26
  end
@@ -31,7 +31,7 @@ Gem::Specification.new do |spec|
31
31
  # Uncomment to register a new dependency of your gem
32
32
  # spec.add_dependency "example-gem", "~> 1.0"
33
33
  spec.add_dependency "ruboty"
34
- spec.add_dependency "ruby-openai", "~> 2.0"
34
+ spec.add_dependency "ruby-openai", "~> 3.5"
35
35
 
36
36
  # For more information and examples about making a new gem, check out our
37
37
  # guide at: https://bundler.io/guides/creating_gem.html
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruboty-openai_chat
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tomoya Chiba
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-12-26 00:00:00.000000000 Z
11
+ date: 2023-03-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ruboty
@@ -30,14 +30,14 @@ dependencies:
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '2.0'
33
+ version: '3.5'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '2.0'
40
+ version: '3.5'
41
41
  description:
42
42
  email:
43
43
  - tomo.asleep@gmail.com
@@ -61,8 +61,8 @@ files:
61
61
  - lib/ruboty/openai_chat/actions/chat.rb
62
62
  - lib/ruboty/openai_chat/actions/remember_profile.rb
63
63
  - lib/ruboty/openai_chat/actions/show_profile.rb
64
- - lib/ruboty/openai_chat/dialog.rb
65
64
  - lib/ruboty/openai_chat/memory.rb
65
+ - lib/ruboty/openai_chat/message.rb
66
66
  - lib/ruboty/openai_chat/version.rb
67
67
  - ruboty-openai_chat.gemspec
68
68
  homepage: https://github.com/tomoasleep/ruboty-openai_chat
@@ -1,53 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Ruboty
4
- module OpenAIChat
5
- class Dialog
6
- STOP_SEQUENCES = [">> Human: ", ">> AI: "].freeze
7
- STOP_SEQUENCE_PATTERN = />> (Human|AI): /.freeze
8
-
9
- # @return [String]
10
- attr_reader :human_comment, :ai_comment
11
-
12
- # @return [Time]
13
- attr_reader :expire_at
14
-
15
- def self.from_hash(hash)
16
- new(**hash.transform_keys(&:to_sym))
17
- end
18
-
19
- # @param human_comment [String]
20
- # @param ai_comment [String]
21
- # @param expire_at [Time, Integer, nil]
22
- def initialize(human_comment:, ai_comment:, expire_at: nil)
23
- @human_comment = human_comment
24
- @ai_comment = ai_comment
25
- @expire_at = expire_at&.yield_self { |t| Time.at(t) }
26
- end
27
-
28
- # @return [String]
29
- def to_prompt
30
- <<~STRING
31
- >> Human: #{escape(human_comment).chomp}
32
- >> AI: #{escape(ai_comment).chomp}
33
- STRING
34
- end
35
-
36
- # @return [Hash]
37
- def to_h
38
- { human_comment: human_comment, ai_comment: ai_comment, expire_at: expire_at&.to_i }
39
- end
40
-
41
- # @return [Boolean]
42
- def expired?
43
- expire_at && (expire_at <= Time.now)
44
- end
45
-
46
- private
47
-
48
- def escape(text)
49
- text.gsub(STOP_SEQUENCE_PATTERN, &:downcase)
50
- end
51
- end
52
- end
53
- end