ask-ai 0.0.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: f66251ade1f9986ae077dc13cb177f5f651b5c3638115fb7b5a5ef54d9510972
4
+ data.tar.gz: ec6673e17d5cdd7e6e202ad33d87f30b4e132afdbde347c67917aee6a7483291
5
+ SHA512:
6
+ metadata.gz: c4c71fbb474ebbea5684fc849007f04af964c105ae9a8827790da443a4c45168b59ba5c49b90644bdd8d3f46ca03d5ad31535bf244eace5b00d017f43aae36e1
7
+ data.tar.gz: 69dea278d20a873ba3a91f93dd41e97d148e754cf654ac32c6ca3ad168bc57a66bd3816db23aa38362bf81a18be3453d08e2201d1ec134ed1230b5a792f70000
data/bin/aa ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative '../lib/main.rb'
4
+
5
+ Main::run
data/config/config.yml ADDED
@@ -0,0 +1 @@
1
+
File without changes
File without changes
data/lib/context.rb ADDED
@@ -0,0 +1,79 @@
1
+ class Context
2
+ def self.load_context()
3
+ if File.exist?(CONTEXT_PATH)
4
+ conversation = File.readlines(CONTEXT_PATH).map { |line| JSON.parse(line) }
5
+ else
6
+ conversation = []
7
+ end
8
+
9
+ if conversation.length > 0
10
+ context_as_string = "This is our previous conversation:\n"
11
+ else
12
+ context_as_string = ""
13
+ end
14
+
15
+ if conversation.length > 0
16
+ conversation.each_with_index do |v, i|
17
+ context_as_string += "My #{i + 1} input was: #{v['input']}\nYour #{i + 1} response was: #{v['response']}\n"
18
+ end
19
+ end
20
+
21
+ return context_as_string
22
+ end
23
+
24
+ ## Here we save the context to a file.
25
+ ## Max 10 previous Q / A to save tokens.
26
+ def self.save_context(context)
27
+ tmp_arr = []
28
+ unless File.exist?(CONTEXT_PATH)
29
+ File.open(CONTEXT_PATH, "w") {}
30
+ end
31
+ File.readlines(CONTEXT_PATH).map { |line| tmp_arr.push(JSON.parse(line)) }
32
+ if tmp_arr.length > 9
33
+ tmp_arr.shift()
34
+ end
35
+ File.truncate(CONTEXT_PATH, 0)
36
+ tmp_arr.each { |line| File.open(CONTEXT_PATH, "a") { |file| file.write("#{line.to_json}\n") } }
37
+ File.open(CONTEXT_PATH, "a") { |file| file.write("#{context.to_json}\n") }
38
+ end
39
+
40
+ def self.delete_context()
41
+ puts "Deleting previous context."
42
+ File.truncate(CONTEXT_PATH, 0)
43
+ end
44
+
45
+ def self.save_context_file(file_path)
46
+ unless file_path.nil?
47
+ file_in = File.open(file_path, 'r')
48
+ file_out = File.open(CONTEXT_FILE_PATH, 'w')
49
+ char_count = 0
50
+ file_in.each do |line|
51
+ char_count += line.length
52
+ file_out.write(line)
53
+ end
54
+
55
+ if char_count > 10000
56
+ puts "Warning: The file you are trying to feed to the API is #{char_count} characters long. This consumes a lot of tokens."
57
+ end
58
+ else
59
+ puts "No file path given."
60
+ end
61
+ rescue Errno::ENOENT
62
+ puts "No file at '#{file_path}' found."
63
+ end
64
+
65
+ def self.load_context_file()
66
+ file = File.open(CONTEXT_FILE_PATH, 'r')
67
+ file_as_string = ""
68
+ file.each do |line|
69
+ file_as_string += line
70
+ end
71
+
72
+ return file_as_string
73
+ rescue Errno::ENOENT
74
+ puts "No file at '#{CONTEXT_FILE_PATH}' found."
75
+ puts "Load a file with 'aa -lf <file_path>'"
76
+ return ""
77
+ end
78
+
79
+ end
@@ -0,0 +1,61 @@
1
+ class HandleArgs
2
+ def self.permitted_options()
3
+ ## TODO: Add more options.
4
+ ## -t --translate
5
+ ## -temp --temperature (need to handle value after input)
6
+
7
+ [
8
+ '-h',
9
+ '--help',
10
+ '-v',
11
+ '--version',
12
+ '-f',
13
+ '--file',
14
+ '-lf',
15
+ '--loadfile',
16
+ '-d',
17
+ '--delete',
18
+ '-c',
19
+ '--conversation',
20
+ '--finetune',
21
+ '-w', '--whisper',
22
+ '-t', '--translate',
23
+ '-i', '--interactive',
24
+ '--key',
25
+ ]
26
+ end
27
+
28
+ def self.handle_args()
29
+ p_args = HandleArgs.permitted_options()
30
+ ## This is the hash that will be returned.
31
+ args_hash = {}
32
+
33
+ ## This handles args then called as ruby script.
34
+ ARGV.each_with_index do |arg, i|
35
+ #puts "Arg: #{arg}"
36
+
37
+ if arg.start_with?('-')
38
+ ## This is an option.
39
+ if p_args.include?(arg)
40
+ ## This is a permitted / available option.
41
+ #puts "Option: #{arg}"
42
+ args_hash["option_#{i}"] = arg
43
+ else
44
+ ## This is an unknown option.
45
+ ## TODO: Handle unknown option. display help? discard?
46
+ puts "Unknown option: #{arg}"
47
+ args_hash["option_#{i}"] = arg
48
+ end
49
+ else
50
+ ## This is input.
51
+ ## This if statement append all 'input' args to one string.
52
+ if args_hash["input"].nil?
53
+ args_hash["input"] = arg
54
+ else
55
+ args_hash["input"] = "#{args_hash['input']} #{arg}"
56
+ end
57
+ end
58
+ end
59
+ return args_hash
60
+ end
61
+ end
data/lib/help.rb ADDED
@@ -0,0 +1,48 @@
1
+ require 'rubygems'
2
+
3
+ class Help
4
+ def self.display_help()
5
+ puts "Usage: aa [options] [input]"
6
+ puts " -lf, --loadfile <path>: Load file into context"
7
+ puts " -f, --file: Read from context file"
8
+ puts " -c, --conversation: Append to conversation (max 10 Questions / Answers pairs saved)"
9
+ puts " -d, --delete: Delete conversation"
10
+ puts " -i, --interactive: Interactive mode, always a conversation. Clear context with 'clear' (exit with 'exit' or 'quit')"
11
+ puts " -w, --whisper <path>: Transcribe audio file"
12
+ puts " -t, --translate <path>: Translate audio file"
13
+ puts "\n Options:"
14
+ puts " --config: Edit config file"
15
+ puts " -v, --version: Display version"
16
+ puts " -h, --help: Display this help message"
17
+
18
+ end
19
+
20
+ def self.display_api_key()
21
+ puts "You need to set your API key in the file: ./config/config.yml"
22
+ puts "Create the file if it doesn't exist."
23
+ puts "Add the following line to the file:"
24
+ puts " OPENAI_API_KEY: <your API key>"
25
+ puts "You can get your API key from: https://openai.com/"
26
+ end
27
+
28
+ ## This don't work yet. Need to rework the way we handle args.
29
+ def self.display_help_file()
30
+ ## TODO: How to work with files
31
+ end
32
+
33
+ ## This don't work yet. Need to rework the way we handle args.
34
+ def self.display_help_conversation()
35
+ ## TODO: How to work with conversation
36
+ end
37
+
38
+ def self.display_version()
39
+ spec = Gem::Specification::load("aa.gemspec")
40
+ puts "Version: #{spec.version}"
41
+ end
42
+
43
+ def self.display_usage()
44
+ puts "Usage: ./main.rb [options] [input]"
45
+
46
+ puts "There are two types of options, flags and arguments."
47
+ end
48
+ end
data/lib/main.rb ADDED
@@ -0,0 +1,168 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'fileutils'
4
+ require 'yaml'
5
+ require 'readline'
6
+ require 'io/console'
7
+ [
8
+ './prompt.rb',
9
+ './handle_args.rb',
10
+ './help.rb',
11
+ './context.rb'
12
+ ].each { |f| require_relative f }
13
+
14
+ CONTEXT_PATH = File.expand_path("./../files/context.jsonl", __dir__)
15
+ FILE_PATH = File.expand_path("./../files/", __dir__)
16
+ CONFIG_PATH = File.expand_path("./../config/config.yml", __dir__)
17
+ CONTEXT_FILE_PATH = File.expand_path("./../files/context_file.txt", __dir__)
18
+ class Main
19
+
20
+ def self.run()
21
+ config = load_env()
22
+
23
+ ## This does not work. Need to fix this.
24
+ if config.nil? || config['OPENAI_API_KEY'].nil?
25
+ puts "No API key found."
26
+ Help.display_api_key()
27
+ puts 'If you want to set your API key now, type (y)es and press enter.'
28
+ #puts "If you have an API key you can set it here."
29
+ #puts "Enter API key: (or press enter to exit)"
30
+
31
+ while input = Readline.readline("> ", true) do
32
+ if input.empty?
33
+ puts "Exiting."
34
+ exit
35
+ elsif input == "y" || input == "yes" || input == "Y" || input == "Yes"
36
+ set_key()
37
+ exit
38
+ else
39
+ puts 'Invalid input (y)es or press enter to exit.'
40
+ end
41
+ end
42
+ end
43
+
44
+ context = Context.load_context()
45
+ options_and_input = HandleArgs.handle_args()
46
+ options = options_and_input.select { |k, v| k.start_with?("option_") }
47
+ input = options_and_input["input"]
48
+
49
+ halt_options = ["-h", "--help", "-v", "--version", "--key"]
50
+
51
+ ## Hack... Need to fix this.
52
+ if options.empty?
53
+ options = { "option_0" => "simple" }
54
+ end
55
+
56
+ options.each do |k, v|
57
+ if halt_options.include?(v)
58
+ ## Options that halt the program.
59
+ case v
60
+ when "-h", "--help"
61
+ Help.display_help()
62
+ exit
63
+ when "-v", "--version"
64
+ Help.display_version()
65
+ exit
66
+
67
+ when "--key"
68
+ set_key(api_key: nil)
69
+ else
70
+ Help.display_help()
71
+ exit
72
+ end
73
+ else
74
+ ## Options that don't halt the program.
75
+ case v
76
+ when "-f", "--file"
77
+ file_as_string = Context.load_context_file()
78
+ if file_as_string.empty?
79
+ exit
80
+ end
81
+ Prompt.stream_prompt(input, file_as_string)
82
+ when "-lf", "--loadfile"
83
+ puts "Loading File #{input}"
84
+ Context.save_context_file(input)
85
+ when "-d", "--delete"
86
+ if input.nil?
87
+ Context.delete_context()
88
+ else
89
+ Context.delete_context()
90
+ Context.save_context(Prompt.stream_prompt(input, context))
91
+ end
92
+ when "-c", "--conversation"
93
+ puts input
94
+ Context.save_context(Prompt.stream_prompt(input, context))
95
+ when "-w", "--whisper"
96
+ puts Prompt.whisper_transcribe(input)
97
+ when "-t", "--translate"
98
+ puts Prompt.whisper_translate(input)
99
+ when "-i", "--interactive"
100
+ puts "Interactive mode..."
101
+ puts "Type 'exit' or 'quit' to exit."
102
+ puts "Type 'clear' to clear context."
103
+
104
+ while input = Readline.readline("\n> ", true) do
105
+ if (input == "exit" || input == "quit")
106
+ break
107
+ end
108
+ if input == "clear"
109
+ puts "Clearing context..."
110
+ Context.delete_context()
111
+ next
112
+ end
113
+ options_and_input = HandleArgs.handle_args()
114
+ context = Context.load_context()
115
+ puts "\n"
116
+ Context.save_context(Prompt.stream_prompt(input, context))
117
+ puts "\n"
118
+ end
119
+ puts "Exiting..."
120
+ #Context.delete_context()
121
+ when "simple"
122
+ if !input.nil?
123
+ Prompt.stream_prompt(input)
124
+ else
125
+ puts "No input given."
126
+ Help.display_help()
127
+ end
128
+ else
129
+ Help.display_help()
130
+ end
131
+ end
132
+ end
133
+ end
134
+
135
+ private
136
+
137
+ def self.set_key(api_key: nil)
138
+ if api_key.nil?
139
+ puts "Setting API key..."
140
+ puts "Enter API key: (or press enter to exit)"
141
+
142
+ while input = Readline.readline("> ", true) do
143
+ if input.empty?
144
+ puts "Exiting."
145
+ exit
146
+ else
147
+ api_key = input.strip
148
+ break
149
+ end
150
+ end
151
+ puts "Saving API key..."
152
+ end
153
+
154
+ FileUtils.mkdir_p(File.dirname(CONFIG_PATH))
155
+ File.open(CONFIG_PATH, "w") do |f|
156
+ f.write(YAML.dump({ "OPENAI_API_KEY" => api_key }))
157
+ end
158
+ puts "API key saved."
159
+ end
160
+
161
+ def self.load_env()
162
+ YAML.load(File.read(CONFIG_PATH))
163
+
164
+ rescue Errno::ENOENT
165
+ puts "No config.yml found."
166
+ end
167
+ end
168
+
data/lib/prompt.rb ADDED
@@ -0,0 +1,102 @@
1
+ require "openai"
2
+
3
+ class Prompt
4
+
5
+ ## Streams the response, VERY NICE
6
+ def self.stream_prompt(input, conversation = '', temp = 0.7)
7
+ if conversation.length == 0
8
+ conversation += input
9
+ else
10
+ conversation += "\n My question: #{input}"
11
+ end
12
+
13
+ response = ''
14
+ client.chat(
15
+ parameters: {
16
+ model: "gpt-3.5-turbo",
17
+ messages: [{ role: "user", content: conversation}],
18
+ temperature: temp, ## Should be a parameter
19
+ stream: proc do |chunk, _bytesize|
20
+ response += chunk.dig("choices", 0, "delta", "content") unless chunk.dig("choices", 0, "delta", "content").nil?
21
+ print chunk.dig("choices", 0, "delta", "content")
22
+ end
23
+ }
24
+ )
25
+ context = {
26
+ "input" => input,
27
+ "response" => response,
28
+ }
29
+ return context
30
+ end
31
+
32
+ ## Not implemented only scaffolding
33
+ def self.file_finetune()
34
+ return
35
+ client.files.upload(parameters: { file: "./test.json", purpose: "fine-tune" })
36
+ client.files.lisr
37
+ client.files.retrieve(id: "file-123")
38
+ client.files.content(id: "file-123")
39
+ client.files.delete(id: "file-123")
40
+ end
41
+
42
+ def self.whisper_translate(file_path)
43
+ if (file_path.nil? || !file_path.end_with?(*['.mp3', '.wav', '.m4a', '.webm', '.mpeg', '.mpga']))
44
+ puts "No file given or wrong file type"
45
+ exit
46
+ else
47
+ size = File.size(file_path).to_f / 2**20
48
+ if size > 24
49
+ warning("The file is above the maximum size of 25MB")
50
+ exit
51
+ else
52
+ response = client.audio.translate(
53
+ parameters: {
54
+ model: "whisper-1",
55
+ file: File.open(file_path, "rb"),
56
+ })
57
+ if (response["text"].nil? || response["text"].empty?)
58
+ puts "No text found"
59
+ exit
60
+ end
61
+ puts response["text"]
62
+ end
63
+ end
64
+ rescue Errno::ENOENT => e
65
+ puts "File not found"
66
+ end
67
+
68
+ def self.whisper_transcribe(file_path)
69
+ if (file_path.nil? || !file_path.end_with?(*['.mp3', '.wav', '.m4a', '.webm', '.mpeg', '.mpga']))
70
+ puts "No file given"
71
+ exit
72
+ else
73
+ size = File.size(file_path).to_f / 2**20
74
+ if size > 24
75
+ warning("The file is above the maximum size of 25MB, this may take")
76
+ exit
77
+ else
78
+ response = client.audio.transcribe(
79
+ parameters: {
80
+ model: "whisper-1",
81
+ file: File.open(file_path, "rb"),
82
+ })
83
+ if (response["text"].nil? || response["text"].empty?)
84
+ puts "No text found"
85
+ exit
86
+ end
87
+ puts response["text"]
88
+ end
89
+ end
90
+ rescue Errno::ENOENT => e
91
+ puts "File not found"
92
+ end
93
+
94
+ private
95
+
96
+ def self.client()
97
+ conf = YAML.load(File.read(CONFIG_PATH))
98
+ key = conf["OPENAI_API_KEY"]
99
+
100
+ OpenAI::Client.new(access_token: key)
101
+ end
102
+ end
metadata ADDED
@@ -0,0 +1,66 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ask-ai
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Oskar Franck
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2023-09-22 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: ruby-openai
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 5.1.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 5.1.0
27
+ description: A CLI tool for using OpenAI's API. The idea is to make it easy to use
28
+ the API with an option to pass files for help with diffrent tasks.
29
+ email: contact.oskarfranck.se
30
+ executables:
31
+ - aa
32
+ extensions: []
33
+ extra_rdoc_files: []
34
+ files:
35
+ - bin/aa
36
+ - config/config.yml
37
+ - files/context.jsonl
38
+ - files/context_file.txt
39
+ - lib/context.rb
40
+ - lib/handle_args.rb
41
+ - lib/help.rb
42
+ - lib/main.rb
43
+ - lib/prompt.rb
44
+ homepage:
45
+ licenses: []
46
+ metadata: {}
47
+ post_install_message:
48
+ rdoc_options: []
49
+ require_paths:
50
+ - lib
51
+ required_ruby_version: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ required_rubygems_version: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ requirements: []
62
+ rubygems_version: 3.3.7
63
+ signing_key:
64
+ specification_version: 4
65
+ summary: A simple CLI for OpenAI's GPT-3 API.
66
+ test_files: []