func_bot 0.1.0 → 0.1.2

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: 93f6e105ec4bd586c7570f0400d9d25075c2d0504e81d2db43392196965d03f9
4
- data.tar.gz: 0dc35e731b9f25a08ac0a664630a804c67c3019fa8e406df8445d707c7b154bc
3
+ metadata.gz: e2642f560765801d53e14efe88c613738759eb83c5ea65bac70696987eb38783
4
+ data.tar.gz: fc5683415acbaed9f68013aad668dbb180d1059b1496c514bcbcd05d5be6044c
5
5
  SHA512:
6
- metadata.gz: bde8ef39474d86f7cf309ad37afb5c409e6dc87da0447ceec1252b1676360aa035418c66906b5e7a65fbe7e8906b73ddac3f6e7f4b7bfbe5d37e54917f905130
7
- data.tar.gz: edefb12cb6eb69ff791c8db07703418e5bfe17f2a5284b8bfec8d457b1bb3c3d333c0c061f099a1d7dc285f9fec2c8c19d2193848f042a838515dd04a492be1c
6
+ metadata.gz: a17a9594c4a6ec3cdfbf31db3531afb2074e314fd092851c76d73fa41f221a3e03c35a183ef2a7a86618e15bbd20cd9ff7d642e5efb5dbb9c55e48495eef1165
7
+ data.tar.gz: 615b2b10f8c235b67dd7489ea3624a83c3b53a9b0eb57843d816d66dc1672a4c6692cfd615dd7c2238e3c62c5521093dfd32b8186c358fb595b7eedaca201499
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
- functions: FuncBot::Functions::List.call
18
+ functions: FuncBot::Functions::List.call(bot)
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
@@ -3,14 +3,16 @@
3
3
  module FuncBot
4
4
  module Functions
5
5
  class BaseFunction
6
- attr_reader :response
6
+ attr_reader :bot
7
7
 
8
- def initialize(response)
9
- @response = response
8
+ def initialize(bot)
9
+ @bot = bot
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
+ bot.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).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
@@ -3,11 +3,15 @@
3
3
  module FuncBot
4
4
  module Functions
5
5
  class List
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"]
6
+ def self.call(bot)
7
+ if bot.include_functions
8
+ if File.exist?(Rails.root.join("app", "lib", "func_bot", "functions", "list.yml"))
9
+ YAML.load_file(Rails.root.join("app", "lib", "func_bot", "functions", "list.yml"))["functions"]
10
+ else
11
+ raise "app/lib/func_bot/functions/list.yml file not found. Please create it by running rails func_bot:install."
12
+ end
9
13
  else
10
- raise "lib/func_bot/functions/list.yml file not found. Please create it by running rails func_bot:install."
14
+ []
11
15
  end
12
16
  end
13
17
  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.2"
5
5
  end
data/lib/func_bot.rb CHANGED
@@ -3,14 +3,40 @@ 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, :include_functions
16
+ attr_reader :client, :history
17
+
18
+ def initialize
19
+ @history = Bots::History.new
20
+ @client = Bots::Client.new(self)
21
+ @include_functions = true
22
+ end
23
+
24
+ def ask(prompt)
25
+ history.chronicle("user", prompt)
26
+ self.response = client.call
27
+ self.response = Functions::Handler.new(self).handle if available_function?
28
+ history.chronicle("assistant", content)
29
+ history.messages.last.content
30
+ end
31
+
32
+ private
33
+
34
+ def available_function?
35
+ response.dig("choices", 0, "message", "function_call").present?
36
+ end
37
+
38
+ def content
39
+ response.dig("choices", 0, "message", "content")
40
+ end
41
+ end
16
42
  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
@@ -7,6 +7,7 @@ module FuncBot
7
7
  module Functions
8
8
  class WeatherFunction < BaseFunction
9
9
  def execute
10
+ bot.include_functions = false
10
11
  weather_info = {
11
12
  location: parsed_response["location"],
12
13
  temperature: 98,
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.2
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