func_bot 0.1.0 → 0.1.1

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: 93f6e105ec4bd586c7570f0400d9d25075c2d0504e81d2db43392196965d03f9
4
- data.tar.gz: 0dc35e731b9f25a08ac0a664630a804c67c3019fa8e406df8445d707c7b154bc
3
+ metadata.gz: 68e7cc778676e7b037cff5572cd69c2f61559763fd57b8e31328f487fa839148
4
+ data.tar.gz: cb9ca4334b4c08384216a03988e7b3343809cf7d0ee9e0ba69706b17a3c91e48
5
5
  SHA512:
6
- metadata.gz: bde8ef39474d86f7cf309ad37afb5c409e6dc87da0447ceec1252b1676360aa035418c66906b5e7a65fbe7e8906b73ddac3f6e7f4b7bfbe5d37e54917f905130
7
- data.tar.gz: edefb12cb6eb69ff791c8db07703418e5bfe17f2a5284b8bfec8d457b1bb3c3d333c0c061f099a1d7dc285f9fec2c8c19d2193848f042a838515dd04a492be1c
6
+ metadata.gz: b22ff1a5ebedc9261dc022bed342d8272b1bd15ed73c749038c4c2711f7785fa9dae7452fe0e8a40337dc46ae43daa2df7bc7ba7b4af28a8fa3f0844aff9efe3
7
+ data.tar.gz: 45ac6d18634f9615dcbd3807726543bab58409a7c6e20d80c8a0ad039a5f9bbcccf1c2e801c6b1070bce3aaf4632d435538aa6313f468e22dfe889efca24c156
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # FuncBot
2
2
 
3
- FuncBot is a Rails gem built on top of the [ruby-openai](https://github.com/alexrudall/ruby-openai) gem that allows you to easily create chatbots that can answer questions by calling on functions that you define.
3
+ FuncBot is a Rails gem built on top of the [ruby-openai](https://github.com/alexrudall/ruby-openai) gem. It allows you to easily create chatbots that can answer questions by calling on functions you define. It's goal is to provide a simple interface to consume [OpenAI's Function Calling API](https://openai.com/blog/function-calling-and-other-api-updates?ref=upstract.com).
4
4
 
5
5
  ## Usage
6
6
 
@@ -10,7 +10,7 @@ FuncBot is a Rails gem built on top of the [ruby-openai](https://github.com/alex
10
10
  rails g func_bot:function <function_name>
11
11
  ```
12
12
 
13
- - Update the function in `lib/func_bot/functions/<function_name>.rb`
13
+ - Update the function in `app/lib/func_bot/functions/<function_name>.rb`
14
14
 
15
15
  - A function can be as simple or as complex as you need it to be. Your bot will process the results and express them to the user.
16
16
  - All functions must have an `execute` method.
@@ -38,7 +38,7 @@ rails g func_bot:function <function_name>
38
38
  - `parsed_response` is a hash that contains the response relevant to your function from OpenAI.
39
39
  - `response` is the raw response from OpenAI.
40
40
 
41
- - Update your new function in the list of functions in `lib/func_bot/functions/list.yml`.
41
+ - Update your new function in the list of functions in `app/lib/func_bot/functions/list.yml`.
42
42
  - This list of functions will be available to the bot with every request.
43
43
  - Adding good descriptions to the functions will help the bot infer when to use which function.
44
44
  - If the user asks a question that is not related to a function in your list, the bot will just ask ChatGPT.
@@ -80,6 +80,13 @@ rails g func_bot:install
80
80
 
81
81
  ```
82
82
 
83
+ - Make sure to add your OpenAI API key to your credentials file or update the `config/initializers/openai.rb` file accordingly.
84
+
85
+ ```yml
86
+ openai:
87
+ api_key: your-private-key
88
+ ```
89
+
83
90
  ## Testing
84
91
 
85
92
  ```bash
@@ -3,18 +3,26 @@
3
3
  module FuncBot
4
4
  module Bots
5
5
  class Client
6
- def self.call(messages)
7
- client.chat(
6
+ attr_accessor :bot
7
+
8
+ def initialize(bot)
9
+ @bot = bot
10
+ end
11
+
12
+ def call
13
+ open_ai.chat(
8
14
  parameters: {
9
15
  model: "gpt-3.5-turbo-0613",
10
- messages: messages,
16
+ messages: bot.history.payload,
11
17
  temperature: 0.7,
12
18
  functions: FuncBot::Functions::List.call
13
19
  }
14
20
  )
15
21
  end
16
22
 
17
- def self.client
23
+ private
24
+
25
+ def open_ai
18
26
  @client ||= OpenAI::Client.new
19
27
  end
20
28
  end
@@ -1,13 +1,18 @@
1
1
  module FuncBot
2
2
  module Bots
3
3
  class History
4
- attr_accessor :history
4
+ attr_accessor :messages
5
+
5
6
  def initialize
6
- @history = []
7
+ @messages = []
8
+ end
9
+
10
+ def chronicle(role, prompt, name = nil)
11
+ messages << Message.new(role, prompt, name)
7
12
  end
8
13
 
9
- def push_prompt(role, prompt)
10
- @history << Message.new(role, prompt).data
14
+ def payload
15
+ messages.map(&:data)
11
16
  end
12
17
  end
13
18
  end
@@ -1,19 +1,19 @@
1
1
  module FuncBot
2
2
  module Bots
3
3
  class Message
4
- attr_accessor :role, :prompt, :name
4
+ attr_accessor :role, :content, :name
5
5
 
6
- def initialize(role, prompt, name = nil)
6
+ def initialize(role, content, name = nil)
7
7
  @role = role
8
- @prompt = prompt
8
+ @content = content
9
9
  @name = name
10
10
  end
11
11
 
12
12
  def data
13
13
  if name.nil?
14
- {role: role, content: prompt}
14
+ {role: role, content: content}
15
15
  else
16
- {role: role, content: prompt, name: name}
16
+ {role: role, content: content, name: name}
17
17
  end
18
18
  end
19
19
  end
@@ -10,7 +10,9 @@ module FuncBot
10
10
  end
11
11
 
12
12
  def parsed_response
13
- JSON.parse(response.dig("choices", 0, "message", "function_call", "arguments"))
13
+ JSON.parse(
14
+ response.dig("choices", 0, "message", "function_call", "arguments")
15
+ )
14
16
  end
15
17
  end
16
18
  end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FuncBot
4
+ module Functions
5
+ class Handler
6
+ attr_accessor :prompt, :bot
7
+
8
+ def initialize(bot)
9
+ @bot = bot
10
+ end
11
+
12
+ def handle
13
+ bot.history.chronicle("function", function_data, function_name)
14
+ bot.client.call
15
+ end
16
+
17
+ private
18
+
19
+ def function_data
20
+ constantize_function.new(bot.response).execute
21
+ end
22
+
23
+ def constantize_function
24
+ "FuncBot::Functions::#{function_name}".constantize
25
+ end
26
+
27
+ def function_name
28
+ bot.response.dig("choices", 0, "message", "function_call", "name")
29
+ end
30
+
31
+ def dig_for_content
32
+ bot.response.dig("choices", 0, "message", "content")
33
+ end
34
+ end
35
+ end
36
+ end
@@ -4,10 +4,10 @@ module FuncBot
4
4
  module Functions
5
5
  class List
6
6
  def self.call
7
- if File.exist?(Rails.root.join("lib", "func_bot", "functions", "list.yml"))
8
- YAML.load_file(Rails.root.join("lib", "func_bot", "functions", "list.yml"))["functions"]
7
+ if File.exist?(Rails.root.join("app", "lib", "func_bot", "functions", "list.yml"))
8
+ YAML.load_file(Rails.root.join("app", "lib", "func_bot", "functions", "list.yml"))["functions"]
9
9
  else
10
- raise "lib/func_bot/functions/list.yml file not found. Please create it by running rails func_bot:install."
10
+ raise "app/lib/func_bot/functions/list.yml file not found. Please create it by running rails func_bot:install."
11
11
  end
12
12
  end
13
13
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module FuncBot
4
- VERSION = "0.1.0"
4
+ VERSION = "0.1.1"
5
5
  end
data/lib/func_bot.rb CHANGED
@@ -3,14 +3,39 @@ require "openai"
3
3
  require "func_bot/version"
4
4
  require "func_bot/engine"
5
5
 
6
- require_relative "func_bot/bot"
7
6
  require_relative "func_bot/bots/client"
8
7
  require_relative "func_bot/bots/history"
9
8
  require_relative "func_bot/bots/message"
10
9
  require_relative "func_bot/functions/base_function"
10
+ require_relative "func_bot/functions/handler"
11
11
  require_relative "func_bot/functions/list"
12
- require_relative "func_bot/handlers/bot_handler"
13
- require_relative "func_bot/handlers/function_handler"
14
12
 
15
13
  module FuncBot
14
+ class Bot
15
+ attr_accessor :response
16
+ attr_reader :client, :history
17
+
18
+ def initialize
19
+ @history = Bots::History.new
20
+ @client = Bots::Client.new(self)
21
+ end
22
+
23
+ def ask(prompt)
24
+ history.chronicle("user", prompt)
25
+ self.response = client.call
26
+ self.response = Functions::Handler.new(self).handle if available_function?
27
+ history.chronicle("assistant", content)
28
+ history.messages.last.content
29
+ end
30
+
31
+ private
32
+
33
+ def available_function?
34
+ response.dig("choices", 0, "message", "function_call").present?
35
+ end
36
+
37
+ def content
38
+ response.dig("choices", 0, "message", "content")
39
+ end
40
+ end
16
41
  end
@@ -3,7 +3,7 @@ module FuncBot
3
3
  source_root File.expand_path("templates", __dir__)
4
4
 
5
5
  def generate_function
6
- template "function.rb", "lib/func_bot/functions/#{file_name}_function.rb"
6
+ template "function.rb", "app/lib/func_bot/functions/#{file_name}_function.rb"
7
7
  end
8
8
 
9
9
  def append_to_functions_list
@@ -15,22 +15,24 @@ module FuncBot
15
15
  private
16
16
 
17
17
  def yml_file
18
- Rails.root.join("lib", "func_bot", "functions", "list.yml")
18
+ Rails.root.join("app", "lib", "func_bot", "functions", "list.yml")
19
19
  end
20
20
 
21
21
  def function_template
22
- {name: "#{class_name}Function",
23
- description: "TODO: Write a description for this function.",
24
- parameters: {
25
- type: "TODO: choose from string, integer, boolean, object, etc.",
26
- properties: {
27
- location: {
28
- type: "TODO: Write a type for this parameter. e.g. string, integer, etc.",
29
- description: "TODO: Write a description for this parameter."
30
- }
31
- },
32
- required: ["TODO: list the required parameters here"]
33
- }}
22
+ {
23
+ name: "#{class_name}Function",
24
+ description: "TODO: Write a description for this function.",
25
+ parameters: {
26
+ type: "TODO: choose from string, integer, boolean, object, etc.",
27
+ properties: {
28
+ location: {
29
+ type: "TODO: Write a type for this parameter. e.g. string, integer, etc.",
30
+ description: "TODO: Write a description for this parameter."
31
+ }
32
+ },
33
+ required: ["TODO: list the required parameters here"]
34
+ }
35
+ }
34
36
  end
35
37
  end
36
38
  end
@@ -4,11 +4,8 @@ module FuncBot
4
4
 
5
5
  def move_files_to_lib
6
6
  copy_file "openai.rb", "config/initializers/openai.rb"
7
- copy_file "list.yml", "lib/func_bot/functions/list.yml"
8
- copy_file "weather_function.rb", "lib/func_bot/functions/weather_function.rb"
9
- application do
10
- "config.autoload_paths << Rails.root.join('lib')"
11
- end
7
+ copy_file "list.yml", "app/lib/func_bot/functions/list.yml"
8
+ copy_file "weather_function.rb", "app/lib/func_bot/functions/weather_function.rb"
12
9
  end
13
10
  end
14
11
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: func_bot
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - lbp
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-06-19 00:00:00.000000000 Z
11
+ date: 2023-06-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -192,7 +192,10 @@ dependencies:
192
192
  - - ">="
193
193
  - !ruby/object:Gem::Version
194
194
  version: '0'
195
- description: Extensible AI FuncBot with Function calls.
195
+ description: FuncBot is a Rails gem built on top of the ruby-openai gem. It helps
196
+ you create chatbots that can answer questions by calling on functions you define.
197
+ It's goal is to provide a simple interface to consume OpenAI's Function Calling
198
+ API.
196
199
  email:
197
200
  - 43428385+leopolicastro@users.noreply.github.com
198
201
  executables: []
@@ -213,15 +216,13 @@ files:
213
216
  - config/initializers/openai.rb
214
217
  - config/routes.rb
215
218
  - lib/func_bot.rb
216
- - lib/func_bot/bot.rb
217
219
  - lib/func_bot/bots/client.rb
218
220
  - lib/func_bot/bots/history.rb
219
221
  - lib/func_bot/bots/message.rb
220
222
  - lib/func_bot/engine.rb
221
223
  - lib/func_bot/functions/base_function.rb
224
+ - lib/func_bot/functions/handler.rb
222
225
  - lib/func_bot/functions/list.rb
223
- - lib/func_bot/handlers/bot_handler.rb
224
- - lib/func_bot/handlers/function_handler.rb
225
226
  - lib/func_bot/version.rb
226
227
  - lib/generators/func_bot/function_generator.rb
227
228
  - lib/generators/func_bot/install_generator.rb
@@ -255,5 +256,7 @@ requirements: []
255
256
  rubygems_version: 3.4.13
256
257
  signing_key:
257
258
  specification_version: 4
258
- summary: Extensible AI FuncBot with Function calls.
259
+ summary: FuncBot is a Rails gem built on top of the ruby-openai gem. It helps you
260
+ create chatbots that can answer questions by calling on functions you define. It's
261
+ goal is to provide a simple interface to consume OpenAI's Function Calling API.
259
262
  test_files: []
data/lib/func_bot/bot.rb DELETED
@@ -1,41 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module FuncBot
4
- class Bot
5
- attr_accessor :role, :prompt
6
-
7
- delegate :history, to: :@history
8
-
9
- def initialize
10
- @history = Bots::History.new
11
- end
12
-
13
- def ask(prompt)
14
- @prompt = prompt
15
- @role = "user"
16
- handle_response(call_openai)
17
- end
18
-
19
- private
20
-
21
- def call_openai
22
- Bots::Client.call(chat_history)
23
- end
24
-
25
- def handle_response(response)
26
- if function_call?(response)
27
- Handlers::FunctionHandler.call(response, history)
28
- else
29
- Handlers::BotHandler.call(response, history)
30
- end
31
- end
32
-
33
- def function_call?(response)
34
- response.dig("choices", 0, "message", "function_call").present?
35
- end
36
-
37
- def chat_history
38
- @history.push_prompt(role, prompt)
39
- end
40
- end
41
- end
@@ -1,20 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module FuncBot
4
- module Handlers
5
- class BotHandler
6
- class << self
7
- def call(response, history)
8
- history << Bots::Message.new("assistant", dig_for_content(response)).data
9
- dig_for_content(response)
10
- end
11
-
12
- private
13
-
14
- def dig_for_content(response)
15
- response.dig("choices", 0, "message", "content")
16
- end
17
- end
18
- end
19
- end
20
- end
@@ -1,41 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module FuncBot
4
- module Handlers
5
- class FunctionHandler
6
- attr_accessor :prompt, :history
7
-
8
- class << self
9
- def call(response, history)
10
- @function_name = nil
11
- @history = history
12
- function_return = constantize_function(response).new(response).execute
13
- response = respond_to(function_return)
14
- history << Bots::Message.new("assistant", dig_for_content(response)).data
15
- dig_for_content(response)
16
- end
17
-
18
- def constantize_function(response)
19
- "FuncBot::Functions::#{function_name(response)}".constantize
20
- end
21
-
22
- def function_name(response = {})
23
- @function_name ||= response.dig("choices", 0, "message", "function_call", "name")
24
- end
25
-
26
- def dig_for_content(response)
27
- response.dig("choices", 0, "message", "content")
28
- end
29
-
30
- def respond_to(prompt)
31
- @prompt = prompt
32
- Bots::Client.call(messages)
33
- end
34
-
35
- def messages
36
- @history << Bots::Message.new("function", @prompt, function_name).data
37
- end
38
- end
39
- end
40
- end
41
- end