jambots 0.1.4 → 0.2.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: b0e56f51936ffa6d73d5a24632cf6b0daae03274d2ad1f8e6288bfcddc85d45d
4
- data.tar.gz: 539ba6ee737fc185ef1f681a4125de724a3f9432237d65ffa7065c280c40f4fd
3
+ metadata.gz: 82318d110379c68c81efc2636e9275a8fd68acfaa6ee6417c11ca2d4a6dbb15c
4
+ data.tar.gz: b7ff01c3e5eef32670225e5a533a08b27822a0cd2eedb1393ccb85df3f320cec
5
5
  SHA512:
6
- metadata.gz: fb68e774266dd90ae2e92674841747075a4c9a9807d7cc751e77b318d821f6dc56a7df33bf36fc98586f462670f0cf8b0f31d28d26699ce07e53c4a3cd893a88
7
- data.tar.gz: 1a65ab5993b6e090c7f87e8b1c20727edd33fb34da5ddc24c8104af0fe416411336019c3309a32f021b63eeab07995c98cac6038a593f7bac17f09fa7573f38b
6
+ metadata.gz: ddf78bd4e22f86a2737ea4db1f5956ee3ec2361ea0b2ff38c5a56636eee17ef3301a82b5e23ff482f33a57c34823f8e574248964a3d7addd609fb8a145261148
7
+ data.tar.gz: 4e639e9e4b7ba7f2815912c5cb0fca65856a490eb6966e16afabb948329c8fc0bd84f76a693e29cc1188e6d31216b2534da0eb0a5ddb294cd823b9359b5c4d58
data/.rspec CHANGED
@@ -1,3 +1,3 @@
1
- --format documentation
1
+ --format progress
2
2
  --color
3
3
  --require spec_helper
data/CHANGELOG.md CHANGED
@@ -1,3 +1,13 @@
1
- ## [0.1.3] - 2023-05-07
1
+ ### Unreleased
2
2
 
3
- - Initial release
3
+ ### [0.2.0] - 2023-05-12
4
+
5
+ - Handle OpenAI errors [#6](https://github.com/artero/jambots/issues/6).
6
+ - Add directory for experiments and examples, and add example "Bot with option references for Ruby development" [#10](https://github.com/artero/jambots/pull/10).
7
+ - Add no_pretty renderer option to chat command [#9](https://github.com/artero/jambots/pull/9).
8
+ - Fix Readme.
9
+ - Remove unnecessary files.
10
+
11
+ ### [0.1.3] - 2023-05-07
12
+
13
+ - Initial release.
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- jambots (0.1.4)
4
+ jambots (0.2.0)
5
5
  pastel (~> 0.8.0)
6
6
  ruby-openai (~> 3.7)
7
7
  thor (~> 1.2.1)
data/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # Jambots CLI
2
2
 
3
+ :warning: **Important notice:** This gem is in an early stage of development. Changes in commands or class interfaces may be introduced in future versions. Use it at your own risk and make sure to stay up-to-date with updates. :warning:
4
+
3
5
  Jambots is a command-line interface (CLI) tool for interacting with chatbots powered by OpenAI's GPT. It allows you to create new chatbots, manage chatbot conversations, and send messages to chatbots.
4
6
 
5
7
  ## Installation
@@ -67,6 +69,7 @@ Options:
67
69
  - `--conversation` or `-c`: Name of the conversation key
68
70
  - `--path` or `-p`: Path where the bot and the conversation directory are located (default: "./.jambots or it it doesn't exist ~/.jambots")
69
71
  - `--last` or `-l`: Continue with the last conversation created
72
+ - `--no_pretty` or `-n`: Disables pretty formatting for the output
70
73
 
71
74
  #### Conversation example
72
75
 
@@ -177,6 +180,13 @@ Now, if you don't mind, I got some partying to do.
177
180
  20230501122918 ───────────────────────
178
181
  ```
179
182
 
183
+ ## Experiments
184
+
185
+ In this directory, you can find examples of how to use Jambots and experiments that we consider attractive.
186
+
187
+ - [Bot with option references for Ruby development](experiments/bot_with_option_references)
188
+
189
+
180
190
  ## Contributing
181
191
 
182
192
  Bug reports and pull requests are welcome on GitHub at https://github.com/artero/jambots.
@@ -0,0 +1,33 @@
1
+ # Bot with option references for Ruby development.
2
+
3
+ This experiment is useful to use it in Ruby development. It covers creating a local bot with basic instructions and implementing a Jambots CLI executable with reference options to add files as context in the conversation.
4
+
5
+ 1. Initialize a new Jambot named `dev`:
6
+
7
+ ```
8
+ jambots init dev
9
+ ```
10
+
11
+ 2. Edit the `./.jambots/dev/bot.yml` file with your desired settings:
12
+
13
+ ```
14
+ model: gpt-3.5-turbo
15
+ prompt: |-
16
+ You will help me with programming in general and Ruby in particular.
17
+ Give short, concise one-sentence answers if possible.
18
+ ```
19
+
20
+ > Note: If you have access to gpt-4 beta, use it, the results are lot better 🙂.
21
+
22
+ 3. Execute the `dev` script. This command runs `jambots chat -b dev` with the `--refs` option, creates a message in the conversation for each file with the file path and file content as a reference to the OpenAI Chat API:
23
+
24
+ ```
25
+ ./dev "Create tests in rspec for the class Jambots::Conversation" --refs lib/jambots/conversation.rb
26
+ ```
27
+
28
+ Take in consideration that this option sends the file name as reference too. Some times the path offers to the model information.
29
+
30
+ ### Tips
31
+
32
+ - You can use `--refs` to send example files. For instance, when creating a new spec file, you can send another project's spec file to help OpenAI Chat replicate its style.
33
+ - Keep in mind the size of the files you send as references, as it may significantly increase the number of tokens in the conversation. OpenAI Chat counts content from all messages to calculate tokens, and each model has a different token limit.
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "jambots"
5
+
6
+ BASIC_ARGS = ["chat", "-b", "dev"]
7
+
8
+ class DevBot < Jambots::Cli
9
+ desc "chat MESSAGE", "Start a chat with the bot and send a message"
10
+ option :bot, aliases: "-b", desc: "Name of the bot"
11
+ option :conversation, aliases: "-c", desc: "Name of the conversation key"
12
+ option :path, aliases: "-p", desc: "Path where the bot and the conversation directory are located"
13
+ option :last, type: :boolean, aliases: "-l", desc: "Continue with the last conversation created"
14
+ option :no_pretty, type: :boolean, aliases: "-n", desc: "Disables pretty formatting"
15
+ option :refs, type: :array, desc: "Add reference messages from files"
16
+ def chat(query)
17
+ chat_controller = Jambots::Controllers::ChatController.new(options)
18
+ add_reference_messages(chat_controller.conversation, options)
19
+ chat_controller.chat(query)
20
+ end
21
+
22
+ private
23
+
24
+ def add_reference_messages(conversation, options = {})
25
+ return unless options[:refs]
26
+
27
+ options[:refs].each do |file_path|
28
+ file_content = File.read(file_path)
29
+ conversation.add_message("system", "#{file_path}\n---\n #{file_content}")
30
+ end
31
+ end
32
+ end
33
+
34
+ DevBot.start(BASIC_ARGS + ARGV)
data/lib/jambots/bot.rb CHANGED
@@ -6,8 +6,6 @@ require "fileutils"
6
6
  require "yaml"
7
7
 
8
8
  module Jambots
9
- class OpenAIMessageError < StandardError; end
10
-
11
9
  class Bot
12
10
  DEFAULT_MODEL = "gpt-3.5-turbo"
13
11
  DEFAULT_GLOBAL_BOTS_DIR = "#{ENV["HOME"]}/.jambots"
@@ -48,17 +46,14 @@ module Jambots
48
46
  def initialize(name, args = {})
49
47
  @bot_dir = "#{find_path(args[:path])}/#{name}"
50
48
 
51
- raise "Bot #{name} doesn't exist." unless File.exist?("#{bot_dir}/bot.yml")
52
-
53
- # Load options from bot.yml file if it exists
54
49
  bot_yml_path = "#{@bot_dir}/bot.yml"
55
- if File.exist?(bot_yml_path)
56
- bot_yml_options = YAML.safe_load(File.read(bot_yml_path), permitted_classes: [Symbol], symbolize_names: true)
57
- args = bot_yml_options.merge(args)
58
- end
50
+
51
+ bot_options = load_bot_options(bot_yml_path)
52
+ args = bot_options.merge(args)
59
53
 
60
54
  openai_api_key = args[:openai_api_key] || ENV["OPENAI_API_KEY"]
61
- @client = OpenAI::Client.new(access_token: openai_api_key, request_timeout: 240)
55
+ request_timeout = args[:request_timeout]
56
+ @client = OpenAI::Client.new(access_token: openai_api_key, request_timeout: request_timeout)
62
57
 
63
58
  @name = name
64
59
  @model = args[:model] || DEFAULT_MODEL
@@ -78,7 +73,7 @@ module Jambots
78
73
 
79
74
  message = response.dig("choices", 0, "message")
80
75
 
81
- raise OpenAIMessageError, response if message.nil?
76
+ raise ChatClientError, handle_error(response) if response["error"]
82
77
 
83
78
  conversation.add_message("assistant", message["content"])
84
79
  conversation.save
@@ -123,5 +118,30 @@ module Jambots
123
118
  total_files = Dir.glob("#{@conversations_dir}/*").count
124
119
  "#{total_files + 1}.yml"
125
120
  end
121
+
122
+ def load_bot_options(bot_yml_path)
123
+ raise "Bot #{name} doesn't exist." unless File.exist?(bot_yml_path)
124
+
125
+ YAML.safe_load(File.read(bot_yml_path), permitted_classes: [Symbol], symbolize_names: true)
126
+ end
127
+
128
+ def handle_error(response)
129
+ if response.dig("error", "code") == "invalid_api_key"
130
+ <<~HEREDOC
131
+ Invalid OpenAI API key. Please set the OPENAI_API_KEY environment variable to your OpenAI API key.
132
+ You can find your API key at https://beta.openai.com/account/api-keys.
133
+ HEREDOC
134
+ elsif response.dig("error", "code") == "max_tokens"
135
+ <<~HEREDOC
136
+ The chat is too long and exceeds the maximum number of tokens. The chat is very long and exceeds the maximum number of tokens.
137
+ Check the limitations of the model "#{model}" https://platform.openai.com/docs/models/overview
138
+ HEREDOC
139
+ else
140
+ <<~HEREDOC
141
+ OpenAI error - #{response["error"]["message"]}.
142
+ #{response}
143
+ HEREDOC
144
+ end
145
+ end
126
146
  end
127
147
  end
data/lib/jambots/cli.rb CHANGED
@@ -2,6 +2,10 @@ require "thor"
2
2
 
3
3
  module Jambots
4
4
  class Cli < Thor
5
+ def self.exit_on_failure?
6
+ false
7
+ end
8
+
5
9
  DEFAULT_BOT = "jambot"
6
10
 
7
11
  desc "init", "Initialize a jambots path"
@@ -17,6 +21,7 @@ module Jambots
17
21
  option :conversation, aliases: "-c", desc: "Name of the conversation key"
18
22
  option :path, aliases: "-p", desc: "Path where the bot and the conversation directory are located"
19
23
  option :last, type: :boolean, aliases: "-l", desc: "Continue with the last conversation created"
24
+ option :no_pretty, type: :boolean, aliases: "-n", desc: "Disables pretty formatting"
20
25
  def chat(query)
21
26
  chat_controller = Controllers::ChatController.new(options)
22
27
  chat_controller.chat(query)
@@ -3,27 +3,64 @@
3
3
  module Jambots::Controllers
4
4
  class ChatController
5
5
  DEFAULT_BOT = "jambot"
6
+ DEFAULT_TIMEOUT = 240
7
+
8
+ attr_reader :bot, :conversation, :renderer
6
9
 
7
10
  def initialize(options)
8
- @options = options
9
- @renderer = Jambots::Renderer.new
11
+ bot_name = bot_name(options)
12
+ bot_options = bot_options(options)
13
+ @bot = Jambots::Bot.new(bot_name, bot_options)
14
+
15
+ conversation_options = conversation_options(options)
16
+ @conversation = load_conversation(conversation_options)
17
+
18
+ @renderer = load_renderer(options)
10
19
  end
11
20
 
12
21
  def chat(query)
13
- bot = Jambots::Bot.new(
14
- @options[:bot] || DEFAULT_BOT,
15
- path: Jambots::Bot.find_path(@options[:path])
16
- )
22
+ renderer.render(conversation) do
23
+ bot.message(query, conversation)
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def bot_name(options)
30
+ options[:bot] || DEFAULT_BOT
31
+ end
17
32
 
18
- last = @options[:last]
19
- previous_conversation = last ? bot.conversations.last : bot.load_conversation(@options[:conversation])
33
+ def bot_options(options)
34
+ bot_options = {}
35
+ bot_options[:path] = options[:path] if options[:path]
36
+ bot_options[:model] = options[:model] if options[:model]
37
+ bot_options[:prompt] = options[:prompt] if options[:prompt]
38
+ bot_options[:openai_api_key] = options[:openai_api_key] if options[:openai_api_key]
39
+ bot_options[:request_timeout] = options[:request_timeout] if options[:request_timeout]
20
40
 
21
- conversation = previous_conversation || bot.new_conversation
41
+ bot_options
42
+ end
43
+
44
+ def conversation_options(options)
45
+ {
46
+ conversation: options[:conversation],
47
+ last: options[:last]
48
+ }
49
+ end
50
+
51
+ def load_conversation(options)
52
+ last = options[:last]
53
+ previous_conversation = last ? bot.conversations.last : bot.load_conversation(options[:conversation])
54
+
55
+ previous_conversation || bot.new_conversation
56
+ end
22
57
 
23
- @renderer.spinner.auto_spin
24
- message = bot.message(query, conversation)
25
- @renderer.spinner.success
26
- @renderer.render(message, conversation)
58
+ def load_renderer(options)
59
+ if options[:no_pretty]
60
+ Jambots::Renderers::MinimalRenderer.new
61
+ else
62
+ Jambots::Renderers::CliRenderer.new
63
+ end
27
64
  end
28
65
  end
29
66
  end
@@ -2,11 +2,12 @@
2
2
 
3
3
  module Jambots
4
4
  class Conversation
5
- attr_accessor :messages, :file_name, :file_path
5
+ attr_accessor :messages, :file_name, :file_path, :key
6
6
 
7
7
  def initialize(file_path)
8
8
  @file_path = file_path
9
9
  @file_name = File.basename(file_path)
10
+ @key = File.basename(file_name, File.extname(file_name))
10
11
  @messages = load_messages
11
12
  end
12
13
 
@@ -3,8 +3,23 @@
3
3
  require "tty-spinner"
4
4
  require "pastel"
5
5
 
6
- module Jambots
7
- class Renderer
6
+ module Jambots::Renderers
7
+ class CliRenderer
8
+ def render(conversation, &block)
9
+ spinner.auto_spin
10
+ message = block.call
11
+ spinner.success
12
+ print_line(role_header(message[:role]))
13
+ puts pastel.magenta(message[:content])
14
+ print_line("#{conversation.key} ")
15
+ rescue Jambots::ChatClientError => e
16
+ spinner.success
17
+ warn "ERROR: #{e.message}"
18
+ exit(1)
19
+ end
20
+
21
+ private
22
+
8
23
  def spinner
9
24
  @spinner ||= TTY::Spinner.new(
10
25
  "(🤖) [#{pastel.green(":spinner")}] ",
@@ -17,16 +32,6 @@ module Jambots
17
32
  @pastel ||= Pastel.new
18
33
  end
19
34
 
20
- def render(message, conversation)
21
- print_line(role_header(message[:role]))
22
- puts pastel.magenta(message[:content])
23
- file_name = conversation.file_name
24
- conversation_name = File.basename(file_name, File.extname(file_name))
25
- print_line("#{conversation_name} ")
26
- end
27
-
28
- private
29
-
30
35
  def role_header(rol)
31
36
  case rol.to_sym
32
37
  when :system
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jambots::Renderers
4
+ class MinimalRenderer
5
+ def render(conversation, &block)
6
+ message = block.call
7
+ puts message[:content]
8
+ rescue Jambots::ChatClientError => e
9
+ warn "ERROR: #{e.message}"
10
+ exit(1)
11
+ end
12
+ end
13
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Jambots
4
- VERSION = "0.1.4"
4
+ VERSION = "0.2.0"
5
5
  end
data/lib/jambots.rb CHANGED
@@ -3,13 +3,15 @@
3
3
  require_relative "jambots/version"
4
4
  require_relative "jambots/bot"
5
5
  require_relative "jambots/conversation"
6
- require_relative "jambots/renderer"
7
6
  require_relative "jambots/cli"
7
+ require_relative "jambots/renderers/cli_renderer"
8
+ require_relative "jambots/renderers/minimal_renderer"
8
9
  require_relative "jambots/controllers/init_controller"
9
10
  require_relative "jambots/controllers/chat_controller"
10
11
  require_relative "jambots/controllers/new_controller"
11
12
 
12
13
  module Jambots
13
14
  class Error < StandardError; end
14
- # Your code goes here...
15
+
16
+ class ChatClientError < Error; end
15
17
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jambots
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Juan Artero
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-05-08 00:00:00.000000000 Z
11
+ date: 2023-05-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ruby-openai
@@ -100,7 +100,6 @@ description: " Jambots is a command-line interface (CLI) tool for interacting
100
100
  email:
101
101
  - juan.artero@marsbased.com
102
102
  executables:
103
- - bot_sample.yml
104
103
  - jambots
105
104
  extensions: []
106
105
  extra_rdoc_files: []
@@ -114,8 +113,9 @@ files:
114
113
  - LICENSE.txt
115
114
  - README.md
116
115
  - Rakefile
117
- - exe/bot_sample.yml
118
116
  - exe/jambots
117
+ - experiments/bot_with_option_references/README.mb
118
+ - experiments/bot_with_option_references/dev
119
119
  - lib/jambots.rb
120
120
  - lib/jambots/bot.rb
121
121
  - lib/jambots/cli.rb
@@ -123,9 +123,9 @@ files:
123
123
  - lib/jambots/controllers/init_controller.rb
124
124
  - lib/jambots/controllers/new_controller.rb
125
125
  - lib/jambots/conversation.rb
126
- - lib/jambots/renderer.rb
126
+ - lib/jambots/renderers/cli_renderer.rb
127
+ - lib/jambots/renderers/minimal_renderer.rb
127
128
  - lib/jambots/version.rb
128
- - sig/jambots.rbs
129
129
  homepage: https://github.com/artero/jambots
130
130
  licenses:
131
131
  - MIT
data/exe/bot_sample.yml DELETED
@@ -1,4 +0,0 @@
1
- model: "gpt-4"
2
- prompt: |-
3
- Tu nombre es Jam, Me ayudarás con programación en general y de Ruby en particular.
4
- Darás respuestas cortas y concisas de una frase.
data/sig/jambots.rbs DELETED
@@ -1,4 +0,0 @@
1
- module Jambots
2
- VERSION: String
3
- # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
- end