func_bot 0.1.0 → 0.1.1

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: 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