ruboty-openai_chat 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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