ask-ai 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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: []