ollama_chat 0.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.all_images.yml +17 -0
- data/.gitignore +9 -0
- data/Gemfile +5 -0
- data/README.md +159 -0
- data/Rakefile +58 -0
- data/VERSION +1 -0
- data/bin/ollama_chat +5 -0
- data/lib/ollama_chat/chat.rb +398 -0
- data/lib/ollama_chat/clipboard.rb +23 -0
- data/lib/ollama_chat/dialog.rb +94 -0
- data/lib/ollama_chat/document_cache.rb +16 -0
- data/lib/ollama_chat/follow_chat.rb +60 -0
- data/lib/ollama_chat/information.rb +113 -0
- data/lib/ollama_chat/message_list.rb +216 -0
- data/lib/ollama_chat/message_type.rb +5 -0
- data/lib/ollama_chat/model_handling.rb +29 -0
- data/lib/ollama_chat/ollama_chat_config.rb +103 -0
- data/lib/ollama_chat/parsing.rb +159 -0
- data/lib/ollama_chat/source_fetching.rb +173 -0
- data/lib/ollama_chat/switches.rb +119 -0
- data/lib/ollama_chat/utils/cache_fetcher.rb +38 -0
- data/lib/ollama_chat/utils/chooser.rb +53 -0
- data/lib/ollama_chat/utils/fetcher.rb +175 -0
- data/lib/ollama_chat/utils/file_argument.rb +34 -0
- data/lib/ollama_chat/utils.rb +7 -0
- data/lib/ollama_chat/version.rb +8 -0
- data/lib/ollama_chat.rb +20 -0
- data/ollama_chat.gemspec +50 -0
- data/spec/assets/api_show.json +63 -0
- data/spec/assets/api_tags.json +21 -0
- data/spec/assets/conversation.json +14 -0
- data/spec/assets/duckduckgo.html +757 -0
- data/spec/assets/example.atom +26 -0
- data/spec/assets/example.csv +5 -0
- data/spec/assets/example.html +10 -0
- data/spec/assets/example.pdf +139 -0
- data/spec/assets/example.ps +4 -0
- data/spec/assets/example.rb +1 -0
- data/spec/assets/example.rss +25 -0
- data/spec/assets/example.xml +7 -0
- data/spec/assets/kitten.jpg +0 -0
- data/spec/assets/prompt.txt +1 -0
- data/spec/ollama_chat/chat_spec.rb +105 -0
- data/spec/ollama_chat/clipboard_spec.rb +29 -0
- data/spec/ollama_chat/follow_chat_spec.rb +46 -0
- data/spec/ollama_chat/information_spec.rb +50 -0
- data/spec/ollama_chat/message_list_spec.rb +132 -0
- data/spec/ollama_chat/model_handling_spec.rb +35 -0
- data/spec/ollama_chat/parsing_spec.rb +240 -0
- data/spec/ollama_chat/source_fetching_spec.rb +54 -0
- data/spec/ollama_chat/switches_spec.rb +167 -0
- data/spec/ollama_chat/utils/cache_fetcher_spec.rb +43 -0
- data/spec/ollama_chat/utils/fetcher_spec.rb +137 -0
- data/spec/ollama_chat/utils/file_argument_spec.rb +17 -0
- data/spec/spec_helper.rb +46 -0
- data/tmp/.keep +0 -0
- metadata +476 -0
@@ -0,0 +1,94 @@
|
|
1
|
+
module OllamaChat::Dialog
|
2
|
+
def choose_model(cli_model, current_model)
|
3
|
+
models = ollama.tags.models.map(&:name).sort
|
4
|
+
model = if cli_model == ''
|
5
|
+
OllamaChat::Utils::Chooser.choose(models) || current_model
|
6
|
+
else
|
7
|
+
cli_model || current_model
|
8
|
+
end
|
9
|
+
ensure
|
10
|
+
STDOUT.puts green { "Connecting to #{model}@#{ollama.base_url} now…" }
|
11
|
+
end
|
12
|
+
|
13
|
+
def ask?(prompt:)
|
14
|
+
print prompt
|
15
|
+
STDIN.gets.chomp
|
16
|
+
end
|
17
|
+
|
18
|
+
def choose_collection(current_collection)
|
19
|
+
collections = [ current_collection ] + @documents.collections
|
20
|
+
collections = collections.compact.map(&:to_s).uniq.sort
|
21
|
+
collections.unshift('[EXIT]').unshift('[NEW]')
|
22
|
+
collection = OllamaChat::Utils::Chooser.choose(collections) || current_collection
|
23
|
+
case collection
|
24
|
+
when '[NEW]'
|
25
|
+
@documents.collection = ask?(prompt: "Enter name of the new collection: ")
|
26
|
+
when nil, '[EXIT]'
|
27
|
+
STDOUT.puts "Exiting chooser."
|
28
|
+
when /./
|
29
|
+
@documents.collection = collection
|
30
|
+
end
|
31
|
+
ensure
|
32
|
+
STDOUT.puts "Using collection #{bold{@documents.collection}}."
|
33
|
+
info
|
34
|
+
end
|
35
|
+
|
36
|
+
attr_writer :document_policy
|
37
|
+
|
38
|
+
def choose_document_policy
|
39
|
+
policies = %w[ importing embedding summarizing ignoring ].sort
|
40
|
+
current = if policies.index(@document_policy)
|
41
|
+
@document_policy
|
42
|
+
elsif policies.index(config.document_policy)
|
43
|
+
config.document_policy
|
44
|
+
else
|
45
|
+
policies.first
|
46
|
+
end
|
47
|
+
policies.unshift('[EXIT]')
|
48
|
+
policy = OllamaChat::Utils::Chooser.choose(policies)
|
49
|
+
case policy
|
50
|
+
when nil, '[EXIT]'
|
51
|
+
STDOUT.puts "Exiting chooser."
|
52
|
+
policy = current
|
53
|
+
end
|
54
|
+
self.document_policy = policy
|
55
|
+
ensure
|
56
|
+
STDOUT.puts "Using document policy #{bold{@document_policy}}."
|
57
|
+
info
|
58
|
+
end
|
59
|
+
|
60
|
+
def change_system_prompt(default, system: nil)
|
61
|
+
selector = Regexp.new(system.to_s[1..-1].to_s)
|
62
|
+
prompts = config.system_prompts.attribute_names.compact.grep(selector)
|
63
|
+
if prompts.size == 1
|
64
|
+
system = config.system_prompts.send(prompts.first)
|
65
|
+
else
|
66
|
+
prompts.unshift('[EXIT]').unshift('[NEW]')
|
67
|
+
chosen = OllamaChat::Utils::Chooser.choose(prompts)
|
68
|
+
system =
|
69
|
+
case chosen
|
70
|
+
when '[NEW]'
|
71
|
+
ask?(prompt: "Enter new system prompt to use: ")
|
72
|
+
when '[EXIT]'
|
73
|
+
STDOUT.puts "Exiting chooser."
|
74
|
+
return
|
75
|
+
when nil
|
76
|
+
default
|
77
|
+
when *prompts
|
78
|
+
config.system_prompts.send(chosen)
|
79
|
+
else
|
80
|
+
default
|
81
|
+
end
|
82
|
+
end
|
83
|
+
@messages.set_system_prompt(system)
|
84
|
+
end
|
85
|
+
|
86
|
+
def change_voice
|
87
|
+
chosen = OllamaChat::Utils::Chooser.choose(config.voice.list)
|
88
|
+
@current_voice = chosen.full? || config.voice.default
|
89
|
+
end
|
90
|
+
|
91
|
+
def message_list
|
92
|
+
MessageList.new(self)
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module OllamaChat::DocumentCache
|
2
|
+
def document_cache_class
|
3
|
+
Object.const_get(config.cache)
|
4
|
+
end
|
5
|
+
|
6
|
+
def configure_cache
|
7
|
+
if @opts[?M]
|
8
|
+
Documentrix::Documents::MemoryCache
|
9
|
+
else
|
10
|
+
document_cache_class
|
11
|
+
end
|
12
|
+
rescue => e
|
13
|
+
STDERR.puts "Caught #{e.class}: #{e} => Falling back to MemoryCache."
|
14
|
+
Documentrix::Documents::MemoryCache
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
class OllamaChat::FollowChat
|
2
|
+
include Ollama
|
3
|
+
include Ollama::Handlers::Concern
|
4
|
+
include Term::ANSIColor
|
5
|
+
include OllamaChat::MessageType
|
6
|
+
|
7
|
+
def initialize(messages:, markdown: false, voice: nil, output: STDOUT)
|
8
|
+
super(output:)
|
9
|
+
@output.sync = true
|
10
|
+
@markdown = markdown
|
11
|
+
@say = voice ? Handlers::Say.new(voice:) : NOP
|
12
|
+
@messages = messages
|
13
|
+
@user = nil
|
14
|
+
end
|
15
|
+
|
16
|
+
def call(response)
|
17
|
+
OllamaChat::Chat.config.debug and jj response
|
18
|
+
if response&.message&.role == 'assistant'
|
19
|
+
if @messages&.last&.role != 'assistant'
|
20
|
+
@messages << Message.new(role: 'assistant', content: '')
|
21
|
+
@user = message_type(@messages.last.images) + " " +
|
22
|
+
bold { color(111) { 'assistant:' } }
|
23
|
+
@output.puts @user unless @markdown
|
24
|
+
end
|
25
|
+
if content = response.message&.content
|
26
|
+
content = content.gsub(%r(<think>), "💭\n").gsub(%r(</think>), "\n💬")
|
27
|
+
end
|
28
|
+
@messages.last.content << content
|
29
|
+
if @markdown and content = @messages.last.content.full?
|
30
|
+
markdown_content = Kramdown::ANSI.parse(content)
|
31
|
+
@output.print clear_screen, move_home, @user, ?\n, markdown_content
|
32
|
+
else
|
33
|
+
@output.print content
|
34
|
+
end
|
35
|
+
@say.call(response)
|
36
|
+
end
|
37
|
+
if response.done
|
38
|
+
@output.puts "", eval_stats(response)
|
39
|
+
end
|
40
|
+
self
|
41
|
+
end
|
42
|
+
|
43
|
+
def eval_stats(response)
|
44
|
+
eval_duration = response.eval_duration / 1e9
|
45
|
+
prompt_eval_duration = response.prompt_eval_duration / 1e9
|
46
|
+
stats_text = {
|
47
|
+
eval_duration: Tins::Duration.new(eval_duration),
|
48
|
+
eval_count: response.eval_count.to_i,
|
49
|
+
eval_rate: bold { "%.2f c/s" % (response.eval_count.to_i / eval_duration) } + color(111),
|
50
|
+
prompt_eval_duration: Tins::Duration.new(prompt_eval_duration),
|
51
|
+
prompt_eval_count: response.prompt_eval_count.to_i,
|
52
|
+
prompt_eval_rate: bold { "%.2f c/s" % (response.prompt_eval_count.to_i / prompt_eval_duration) } + color(111),
|
53
|
+
total_duration: Tins::Duration.new(response.total_duration / 1e9),
|
54
|
+
load_duration: Tins::Duration.new(response.load_duration / 1e9),
|
55
|
+
}.map { _1 * '=' } * ' '
|
56
|
+
'📊 ' + color(111) {
|
57
|
+
Kramdown::ANSI::Width.wrap(stats_text, percentage: 90).gsub(/(?<!\A)^/, ' ')
|
58
|
+
}
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
module OllamaChat::Information
|
2
|
+
extend Tins::Concern
|
3
|
+
|
4
|
+
included do
|
5
|
+
include UserAgent
|
6
|
+
extend UserAgent
|
7
|
+
end
|
8
|
+
|
9
|
+
module UserAgent
|
10
|
+
def progname
|
11
|
+
'ollama_chat'
|
12
|
+
end
|
13
|
+
|
14
|
+
def user_agent
|
15
|
+
[ progname, OllamaChat::VERSION ] * ?/
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def collection_stats
|
20
|
+
STDOUT.puts <<~EOT
|
21
|
+
Current Collection
|
22
|
+
Name: #{bold{@documents.collection}}
|
23
|
+
#Embeddings: #{@documents.size}
|
24
|
+
#Tags: #{@documents.tags.size}
|
25
|
+
Tags: #{@documents.tags}
|
26
|
+
EOT
|
27
|
+
nil
|
28
|
+
end
|
29
|
+
|
30
|
+
def info
|
31
|
+
STDOUT.puts "Current model is #{bold{@model}}."
|
32
|
+
if @model_options.present?
|
33
|
+
STDOUT.puts " Options: #{JSON.pretty_generate(@model_options).gsub(/(?<!\A)^/, ' ')}"
|
34
|
+
end
|
35
|
+
@embedding.show
|
36
|
+
if @embedding.on?
|
37
|
+
STDOUT.puts "Embedding model is #{bold{@embedding_model}}"
|
38
|
+
if @embedding_model_options.present?
|
39
|
+
STDOUT.puts " Options: #{JSON.pretty_generate(@embedding_model_options).gsub(/(?<!\A)^/, ' ')}"
|
40
|
+
end
|
41
|
+
STDOUT.puts "Text splitter is #{bold{config.embedding.splitter.name}}."
|
42
|
+
collection_stats
|
43
|
+
end
|
44
|
+
STDOUT.puts "Documents database cache is #{@documents.nil? ? 'n/a' : bold{@documents.cache.class}}"
|
45
|
+
@markdown.show
|
46
|
+
@stream.show
|
47
|
+
@location.show
|
48
|
+
STDOUT.puts "Document policy for references in user text: #{bold{@document_policy}}"
|
49
|
+
if @voice.on?
|
50
|
+
STDOUT.puts "Using voice #{bold{@current_voice}} to speak."
|
51
|
+
end
|
52
|
+
@messages.show_system_prompt
|
53
|
+
nil
|
54
|
+
end
|
55
|
+
|
56
|
+
def display_chat_help
|
57
|
+
STDOUT.puts <<~EOT
|
58
|
+
/copy to copy last response to clipboard
|
59
|
+
/paste to paste content
|
60
|
+
/markdown toggle markdown output
|
61
|
+
/stream toggle stream output
|
62
|
+
/location toggle location submission
|
63
|
+
/voice( change) toggle voice output or change the voice
|
64
|
+
/list [n] list the last n / all conversation exchanges
|
65
|
+
/clear clear the whole conversation
|
66
|
+
/clobber clear the conversation, links, and collection
|
67
|
+
/drop [n] drop the last n exchanges, defaults to 1
|
68
|
+
/model change the model
|
69
|
+
/system change system prompt (clears conversation)
|
70
|
+
/regenerate the last answer message
|
71
|
+
/collection( clear|change) change (default) collection or clear
|
72
|
+
/info show information for current session
|
73
|
+
/config output current configuration (#{@ollama_chat_config.filename.to_s.inspect})
|
74
|
+
/document_policy pick a scan policy for document references
|
75
|
+
/import source import the source's content
|
76
|
+
/summarize [n] source summarize the source's content in n words
|
77
|
+
/embedding toggle embedding paused or not
|
78
|
+
/embed source embed the source's content
|
79
|
+
/web [n] query query web search & return n or 1 results
|
80
|
+
/links( clear) display (or clear) links used in the chat
|
81
|
+
/save filename store conversation messages
|
82
|
+
/load filename load conversation messages
|
83
|
+
/quit to quit
|
84
|
+
/help to view this help
|
85
|
+
EOT
|
86
|
+
nil
|
87
|
+
end
|
88
|
+
|
89
|
+
def usage
|
90
|
+
STDOUT.puts <<~EOT
|
91
|
+
Usage: #{progname} [OPTIONS]
|
92
|
+
|
93
|
+
-f CONFIG config file to read
|
94
|
+
-u URL the ollama base url, OLLAMA_URL
|
95
|
+
-m MODEL the ollama model to chat with, OLLAMA_CHAT_MODEL
|
96
|
+
-s SYSTEM the system prompt to use as a file, OLLAMA_CHAT_SYSTEM
|
97
|
+
-c CHAT a saved chat conversation to load
|
98
|
+
-C COLLECTION name of the collection used in this conversation
|
99
|
+
-D DOCUMENT load document and add to embeddings collection (multiple)
|
100
|
+
-M use (empty) MemoryCache for this chat session
|
101
|
+
-E disable embeddings for this chat session
|
102
|
+
-V display the current version number and quit
|
103
|
+
-h this help
|
104
|
+
|
105
|
+
EOT
|
106
|
+
0
|
107
|
+
end
|
108
|
+
|
109
|
+
def version
|
110
|
+
STDOUT.puts "%s %s" % [ progname, OllamaChat::VERSION ]
|
111
|
+
0
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,216 @@
|
|
1
|
+
class OllamaChat::MessageList
|
2
|
+
include Term::ANSIColor
|
3
|
+
include OllamaChat::MessageType
|
4
|
+
|
5
|
+
# The initialize method sets up the message list for an OllamaChat session.
|
6
|
+
#
|
7
|
+
# @param chat [ OllamaChat::Chat ] the chat object that this message list
|
8
|
+
# belongs to
|
9
|
+
def initialize(chat)
|
10
|
+
@chat = chat
|
11
|
+
@messages = []
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_reader :system
|
15
|
+
|
16
|
+
attr_reader :messages
|
17
|
+
|
18
|
+
# Returns the number of messages stored in the message list.
|
19
|
+
#
|
20
|
+
# @return [ Integer ] The size of the message list.
|
21
|
+
def size
|
22
|
+
@messages.size
|
23
|
+
end
|
24
|
+
|
25
|
+
# The clear method removes all non-system messages from the message list.
|
26
|
+
#
|
27
|
+
# @return [ OllamaChat::MessageList ] self
|
28
|
+
def clear
|
29
|
+
@messages.delete_if { _1.role != 'system' }
|
30
|
+
self
|
31
|
+
end
|
32
|
+
|
33
|
+
# The << operator appends a message to the list of messages and returns self.
|
34
|
+
#
|
35
|
+
# @param message [ Ollama::Message ] the message to append
|
36
|
+
#
|
37
|
+
# @return [ OllamaChat::MessageList ] self
|
38
|
+
def <<(message)
|
39
|
+
@messages << message
|
40
|
+
self
|
41
|
+
end
|
42
|
+
|
43
|
+
# Returns the last message from the conversation.
|
44
|
+
#
|
45
|
+
# @return [ Ollama::Message ] The last message in the conversation, or nil if
|
46
|
+
# there are no messages.
|
47
|
+
def last
|
48
|
+
@messages.last
|
49
|
+
end
|
50
|
+
|
51
|
+
# The second_last method returns the second-to-last message from the
|
52
|
+
# conversation if there are more than one non-system messages.
|
53
|
+
#
|
54
|
+
# @return [ Ollama::Message ] the second-to-last message
|
55
|
+
def second_last
|
56
|
+
if @messages.reject { _1.role == 'system' }.size > 1
|
57
|
+
@messages[-2]
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# The load_conversation method loads a conversation from a file and populates
|
62
|
+
# the message list.
|
63
|
+
#
|
64
|
+
# @param filename [ String ] the path to the file containing the conversation
|
65
|
+
#
|
66
|
+
# @return [ OllamaChat::MessageList ] self
|
67
|
+
def load_conversation(filename)
|
68
|
+
unless File.exist?(filename)
|
69
|
+
STDOUT.puts "File #{filename} doesn't exist. Choose another filename."
|
70
|
+
return
|
71
|
+
end
|
72
|
+
@messages =
|
73
|
+
File.open(filename, 'r') do |output|
|
74
|
+
JSON(output.read).map { Ollama::Message.from_hash(_1) }
|
75
|
+
end
|
76
|
+
self
|
77
|
+
end
|
78
|
+
|
79
|
+
# The save_conversation method saves the current conversation to a file.
|
80
|
+
#
|
81
|
+
# @param filename [ String ] the path where the conversation will be saved
|
82
|
+
#
|
83
|
+
# @return [ OllamaChat::MessageList ] self
|
84
|
+
def save_conversation(filename)
|
85
|
+
if File.exist?(filename)
|
86
|
+
STDOUT.puts "File #{filename} already exists. Choose another filename."
|
87
|
+
return
|
88
|
+
end
|
89
|
+
File.open(filename, 'w') do |output|
|
90
|
+
output.puts JSON(@messages)
|
91
|
+
end
|
92
|
+
self
|
93
|
+
end
|
94
|
+
|
95
|
+
# The list_conversation method displays the last n messages from the conversation.
|
96
|
+
#
|
97
|
+
# @param last [ Integer ] the number of messages to display (default: nil)
|
98
|
+
#
|
99
|
+
# @return [ OllamaChat::MessageList ]
|
100
|
+
def list_conversation(last = nil)
|
101
|
+
last = (last || @messages.size).clamp(0, @messages.size)
|
102
|
+
@messages[-last..-1].to_a.each do |m|
|
103
|
+
role_color = case m.role
|
104
|
+
when 'user' then 172
|
105
|
+
when 'assistant' then 111
|
106
|
+
when 'system' then 213
|
107
|
+
else 210
|
108
|
+
end
|
109
|
+
content = m.content.full? { @chat.markdown.on? ? Kramdown::ANSI.parse(_1) : _1 }
|
110
|
+
message_text = message_type(m.images) + " "
|
111
|
+
message_text += bold { color(role_color) { m.role } }
|
112
|
+
message_text += ":\n#{content}"
|
113
|
+
m.images.full? { |images|
|
114
|
+
message_text += "\nImages: " + italic { images.map(&:path) * ', ' }
|
115
|
+
}
|
116
|
+
STDOUT.puts message_text
|
117
|
+
end
|
118
|
+
self
|
119
|
+
end
|
120
|
+
|
121
|
+
# The drop method removes the last n exchanges from the message list and returns the number of removed exchanges.
|
122
|
+
#
|
123
|
+
# @param n [ Integer ] the number of exchanges to remove
|
124
|
+
#
|
125
|
+
# @return [ Integer ] the number of removed exchanges, or 0 if there are no more exchanges to pop
|
126
|
+
def drop(n)
|
127
|
+
if @messages.reject { _1.role == 'system' }.size > 1
|
128
|
+
n = n.to_i.clamp(1, Float::INFINITY)
|
129
|
+
r = @messages.pop(2 * n)
|
130
|
+
m = r.size / 2
|
131
|
+
STDOUT.puts "Popped the last #{m} exchanges."
|
132
|
+
m
|
133
|
+
else
|
134
|
+
STDOUT.puts "No more exchanges you can pop."
|
135
|
+
0
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
# The set_system_prompt method sets the system prompt for the chat session.
|
140
|
+
# This implies deleting all of the messages in the message list, so it only
|
141
|
+
# contains the system prompt at the end.
|
142
|
+
#
|
143
|
+
# @param system [ String ] the new system prompt
|
144
|
+
#
|
145
|
+
# @return [ OllamaChat::MessageList ] the message list instance itself, allowing for chaining.
|
146
|
+
def set_system_prompt(system)
|
147
|
+
@system = system.to_s
|
148
|
+
@messages.clear
|
149
|
+
@messages << Ollama::Message.new(role: 'system', content: self.system)
|
150
|
+
self
|
151
|
+
end
|
152
|
+
|
153
|
+
# The show_system_prompt method displays the system prompt configured for the
|
154
|
+
# chat session.
|
155
|
+
#
|
156
|
+
# It retrieves the system prompt from the @system instance variable, parses
|
157
|
+
# it using Kramdown::ANSI, and removes any trailing newlines. If the
|
158
|
+
# resulting string is empty, the method returns immediately.
|
159
|
+
#
|
160
|
+
# Otherwise, it prints a formatted message to the console, including the
|
161
|
+
# configured system prompt and its length in characters.
|
162
|
+
#
|
163
|
+
# @return [self, NilClass] nil if the system prompt is empty, otherwise self.
|
164
|
+
def show_system_prompt
|
165
|
+
system_prompt = Kramdown::ANSI.parse(system.to_s).gsub(/\n+\z/, '').full?
|
166
|
+
system_prompt or return
|
167
|
+
STDOUT.puts <<~EOT
|
168
|
+
Configured system prompt is:
|
169
|
+
#{system_prompt}
|
170
|
+
|
171
|
+
System prompt length: #{bold{system_prompt.size}} characters.
|
172
|
+
EOT
|
173
|
+
self
|
174
|
+
end
|
175
|
+
|
176
|
+
# The to_ary method converts the message list into an array of
|
177
|
+
# Ollama::Message objects. If location support was enabled and the message
|
178
|
+
# list contains a system message, the system messages is decorated with the
|
179
|
+
# curent location, time, and unit preferences.
|
180
|
+
#
|
181
|
+
# @return [Array] An array of Ollama::Message objects representing the
|
182
|
+
# messages in the list.
|
183
|
+
def to_ary
|
184
|
+
location = at_location.full?
|
185
|
+
@messages.map do |message|
|
186
|
+
if message.role == 'system' && location
|
187
|
+
content = message.content + "\n\n#{location}"
|
188
|
+
Ollama::Message.new(role: message.role, content:)
|
189
|
+
else
|
190
|
+
message
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
# The at_location method returns the location/time/units information as a
|
196
|
+
# string if location is enabled.
|
197
|
+
#
|
198
|
+
# @return [ String ] the location information
|
199
|
+
def at_location
|
200
|
+
if @chat.location.on?
|
201
|
+
location_name = config.location.name
|
202
|
+
location_decimal_degrees = config.location.decimal_degrees * ', '
|
203
|
+
localtime = Time.now.iso8601
|
204
|
+
units = config.location.units
|
205
|
+
config.prompts.location % {
|
206
|
+
location_name:, location_decimal_degrees:, localtime:, units:,
|
207
|
+
}
|
208
|
+
end.to_s
|
209
|
+
end
|
210
|
+
|
211
|
+
private
|
212
|
+
|
213
|
+
def config
|
214
|
+
@chat.config
|
215
|
+
end
|
216
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module OllamaChat::ModelHandling
|
2
|
+
def model_present?(model)
|
3
|
+
ollama.show(name: model) { return _1.system.to_s }
|
4
|
+
rescue Ollama::Errors::NotFoundError
|
5
|
+
false
|
6
|
+
end
|
7
|
+
|
8
|
+
def pull_model_from_remote(model)
|
9
|
+
STDOUT.puts "Model #{bold{model}} not found locally, attempting to pull it from remote now…"
|
10
|
+
ollama.pull(name: model)
|
11
|
+
end
|
12
|
+
|
13
|
+
def pull_model_unless_present(model, options)
|
14
|
+
if system = model_present?(model)
|
15
|
+
return system.full?
|
16
|
+
else
|
17
|
+
pull_model_from_remote(model)
|
18
|
+
if system = model_present?(model)
|
19
|
+
return system.full?
|
20
|
+
else
|
21
|
+
STDOUT.puts "Model #{bold{model}} not found remotely. => Exiting."
|
22
|
+
exit 1
|
23
|
+
end
|
24
|
+
end
|
25
|
+
rescue Ollama::Errors::Error => e
|
26
|
+
warn "Caught #{e.class} while pulling model: #{e} => Exiting."
|
27
|
+
exit 1
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
class OllamaChat::OllamaChatConfig
|
2
|
+
include ComplexConfig
|
3
|
+
include FileUtils
|
4
|
+
|
5
|
+
DEFAULT_CONFIG = <<~EOT
|
6
|
+
---
|
7
|
+
url: <%= ENV['OLLAMA_URL'] || 'http://%s' % ENV.fetch('OLLAMA_HOST') %>
|
8
|
+
proxy: null # http://localhost:8080
|
9
|
+
model:
|
10
|
+
name: <%= ENV.fetch('OLLAMA_CHAT_MODEL', 'llama3.1') %>
|
11
|
+
options:
|
12
|
+
num_ctx: 8192
|
13
|
+
location:
|
14
|
+
enabled: false
|
15
|
+
name: Berlin
|
16
|
+
decimal_degrees: [ 52.514127, 13.475211 ]
|
17
|
+
units: SI (International System of Units) # or USCS (United States Customary System)
|
18
|
+
prompts:
|
19
|
+
embed: "This source was now embedded: %{source}"
|
20
|
+
summarize: |
|
21
|
+
Generate an abstract summary of the content in this document using
|
22
|
+
%{words} words:
|
23
|
+
|
24
|
+
%{source_content}
|
25
|
+
web: |
|
26
|
+
Answer the the query %{query} using these sources and summaries:
|
27
|
+
|
28
|
+
%{results}
|
29
|
+
system_prompts:
|
30
|
+
default: <%= ENV.fetch('OLLAMA_CHAT_SYSTEM', 'null') %>
|
31
|
+
voice:
|
32
|
+
enabled: false
|
33
|
+
default: Samantha
|
34
|
+
list: <%= `say -v ? 2>/dev/null`.lines.map { _1[/^(.+?)\s+[a-z]{2}_[a-zA-Z0-9]{2,}/, 1] }.uniq.sort.to_s.force_encoding('ASCII-8BIT') %>
|
35
|
+
markdown: true
|
36
|
+
stream: true
|
37
|
+
document_policy: importing
|
38
|
+
embedding:
|
39
|
+
enabled: true
|
40
|
+
model:
|
41
|
+
name: mxbai-embed-large
|
42
|
+
embedding_length: 1024
|
43
|
+
options: {}
|
44
|
+
# Retrieval prompt template:
|
45
|
+
prompt: 'Represent this sentence for searching relevant passages: %s'
|
46
|
+
batch_size: 10
|
47
|
+
database_filename: null # ':memory:'
|
48
|
+
collection: <%= ENV['OLLAMA_CHAT_COLLECTION'] %>
|
49
|
+
found_texts_size: 4096
|
50
|
+
found_texts_count: 10
|
51
|
+
splitter:
|
52
|
+
name: RecursiveCharacter
|
53
|
+
chunk_size: 1024
|
54
|
+
cache: Documentrix::Documents::SQLiteCache
|
55
|
+
redis:
|
56
|
+
documents:
|
57
|
+
url: <%= ENV.fetch('REDIS_URL', 'null') %>
|
58
|
+
expiring:
|
59
|
+
url: <%= ENV.fetch('REDIS_EXPIRING_URL', 'null') %>
|
60
|
+
ex: 86400
|
61
|
+
debug: <%= ENV['OLLAMA_CHAT_DEBUG'].to_i == 1 ? true : false %>
|
62
|
+
ssl_no_verify: []
|
63
|
+
copy: pbcopy
|
64
|
+
EOT
|
65
|
+
|
66
|
+
def initialize(filename = nil)
|
67
|
+
@filename = filename || default_path
|
68
|
+
unless File.directory?(cache_dir_path)
|
69
|
+
mkdir_p cache_dir_path.to_s
|
70
|
+
end
|
71
|
+
@config = Provider.config(@filename, '⚙️')
|
72
|
+
retried = false
|
73
|
+
rescue ConfigurationFileMissing
|
74
|
+
if @filename == default_path && !retried
|
75
|
+
retried = true
|
76
|
+
mkdir_p config_dir_path.to_s
|
77
|
+
File.secure_write(default_path, DEFAULT_CONFIG)
|
78
|
+
retry
|
79
|
+
else
|
80
|
+
raise
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
attr_reader :filename
|
85
|
+
|
86
|
+
attr_reader :config
|
87
|
+
|
88
|
+
def default_path
|
89
|
+
config_dir_path + 'config.yml'
|
90
|
+
end
|
91
|
+
|
92
|
+
def config_dir_path
|
93
|
+
XDG.new.config_home + 'ollama_chat'
|
94
|
+
end
|
95
|
+
|
96
|
+
def cache_dir_path
|
97
|
+
XDG.new.cache_home + 'ollama_chat'
|
98
|
+
end
|
99
|
+
|
100
|
+
def database_path
|
101
|
+
cache_dir_path + 'documents.db'
|
102
|
+
end
|
103
|
+
end
|