ollama_chat 0.0.95 → 0.0.97
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 +4 -4
- data/CHANGES.md +106 -0
- data/README.md +115 -2
- data/Rakefile +1 -1
- data/lib/ollama_chat/chat.rb +34 -17
- data/lib/ollama_chat/commands.rb +27 -15
- data/lib/ollama_chat/database/migrations/005_add_context_format_to_sessions.rb +5 -0
- data/lib/ollama_chat/database/models/model_options.rb +1 -0
- data/lib/ollama_chat/database/models/prompt.rb +1 -3
- data/lib/ollama_chat/database/models/session.rb +18 -0
- data/lib/ollama_chat/follow_chat.rb +4 -5
- data/lib/ollama_chat/information.rb +2 -1
- data/lib/ollama_chat/input_content.rb +1 -1
- data/lib/ollama_chat/message_list.rb +2 -4
- data/lib/ollama_chat/ollama_chat_config/default_config.yml +2 -1
- data/lib/ollama_chat/parsing.rb +10 -6
- data/lib/ollama_chat/personae_management.rb +25 -11
- data/lib/ollama_chat/prompt_management.rb +25 -11
- data/lib/ollama_chat/session_management.rb +24 -30
- data/lib/ollama_chat/source_fetching.rb +1 -1
- data/lib/ollama_chat/state_selectors.rb +14 -0
- data/lib/ollama_chat/system_prompt_management.rb +21 -0
- data/lib/ollama_chat/think_control.rb +0 -1
- data/lib/ollama_chat/token_estimator/crude.rb +25 -0
- data/lib/ollama_chat/token_estimator.rb +36 -0
- data/lib/ollama_chat/tool_calling.rb +1 -1
- data/lib/ollama_chat/tools/compute_bmi.rb +6 -4
- data/lib/ollama_chat/tools/execute_grep.rb +11 -1
- data/lib/ollama_chat/tools/file_context.rb +3 -2
- data/lib/ollama_chat/tools/generate_password.rb +4 -1
- data/lib/ollama_chat/tools/get_current_weather.rb +11 -0
- data/lib/ollama_chat/tools/get_time.rb +13 -2
- data/lib/ollama_chat/tools/open_file_in_editor.rb +3 -3
- data/lib/ollama_chat/tools/read_file.rb +3 -0
- data/lib/ollama_chat/tools/retrieve_document_snippets.rb +18 -11
- data/lib/ollama_chat/tools/roll_dice.rb +52 -11
- data/lib/ollama_chat/tools/run_tests.rb +23 -11
- data/lib/ollama_chat/tools/write_file.rb +2 -6
- data/lib/ollama_chat/utils/fetcher.rb +2 -2
- data/lib/ollama_chat/utils/png_metadata_extractor.rb +5 -4
- data/lib/ollama_chat/utils.rb +0 -1
- data/lib/ollama_chat/version.rb +1 -1
- data/lib/ollama_chat.rb +1 -0
- data/ollama_chat.gemspec +6 -6
- data/spec/ollama_chat/database/models/favourite_spec.rb +7 -20
- data/spec/ollama_chat/server_socket_spec.rb +11 -20
- data/spec/ollama_chat/source_fetching_spec.rb +1 -1
- data/spec/ollama_chat/state_selectors_spec.rb +4 -9
- data/spec/ollama_chat/switches_spec.rb +1 -1
- data/spec/ollama_chat/token_estimator_spec.rb +41 -0
- data/spec/ollama_chat/tools/compute_bmi_spec.rb +14 -5
- data/spec/ollama_chat/tools/copy_to_clipboard_spec.rb +5 -5
- data/spec/ollama_chat/tools/execute_grep_spec.rb +10 -0
- data/spec/ollama_chat/tools/generate_image_spec.rb +8 -8
- data/spec/ollama_chat/tools/generate_password_spec.rb +13 -0
- data/spec/ollama_chat/tools/get_current_weather_spec.rb +4 -0
- data/spec/ollama_chat/tools/get_endoflife_spec.rb +3 -3
- data/spec/ollama_chat/tools/get_rfc_spec.rb +2 -2
- data/spec/ollama_chat/tools/get_time_spec.rb +4 -0
- data/spec/ollama_chat/tools/get_url_spec.rb +7 -7
- data/spec/ollama_chat/tools/paste_from_clipboard_spec.rb +4 -4
- data/spec/ollama_chat/tools/read_file_spec.rb +3 -0
- data/spec/ollama_chat/tools/retrieve_document_snippets_spec.rb +38 -5
- data/spec/ollama_chat/tools/roll_dice_spec.rb +4 -4
- data/spec/ollama_chat/tools/run_tests_spec.rb +5 -5
- data/spec/ollama_chat/tools/search_web_spec.rb +3 -3
- data/spec/ollama_chat/tools/write_file_spec.rb +2 -0
- metadata +11 -5
- data/lib/ollama_chat/utils/token_estimator.rb +0 -15
|
@@ -262,13 +262,14 @@ module OllamaChat::PersonaeManagement
|
|
|
262
262
|
# Prompts the user to select a persona, copies its filesystem path to the
|
|
263
263
|
# clipboard, and sets it as the prefill prompt for the next interaction.
|
|
264
264
|
#
|
|
265
|
+
# @param [Boolean] no_prefill Whether to skip updating the prefill prompt.
|
|
265
266
|
# @return [String, nil] the filesystem path of the selected persona,
|
|
266
267
|
# or nil if the selection was cancelled.
|
|
267
|
-
def select_persona_path
|
|
268
|
+
def select_persona_path(no_prefill: false)
|
|
268
269
|
persona = choose_persona(prompt: "Which persona's path do you need? %s") or return
|
|
269
270
|
path = persona_name_to_pathname(persona).to_s
|
|
270
271
|
perform_copy_to_clipboard(text: path, edit: false)
|
|
271
|
-
@prefill_prompt = path
|
|
272
|
+
no_prefill or @prefill_prompt = path
|
|
272
273
|
path
|
|
273
274
|
end
|
|
274
275
|
|
|
@@ -351,14 +352,11 @@ module OllamaChat::PersonaeManagement
|
|
|
351
352
|
[ pathname, pathname.size ]
|
|
352
353
|
end.compact.sort_by(&:last).reverse_each do |pathname, size_bytes|
|
|
353
354
|
persona_name = pathname.basename.sub_ext('').to_s
|
|
354
|
-
|
|
355
|
-
size = format_bytes(size_bytes)
|
|
356
|
-
tokens = OllamaChat::Utils::TokenEstimator.estimate(size_bytes)
|
|
357
|
-
tokens_size = format_tokens(tokens)
|
|
355
|
+
es = OllamaChat::TokenEstimator.estimate(size_bytes)
|
|
358
356
|
is_default = default_persona_name == persona_name
|
|
359
357
|
display_name = prefix_favourite(is_default ? bold { persona_name } : persona_name, favs[persona_name])
|
|
360
358
|
|
|
361
|
-
table << [ display_name,
|
|
359
|
+
table << [ display_name, es.bytes_formatted, es.tokens_formatted, ]
|
|
362
360
|
end
|
|
363
361
|
|
|
364
362
|
table.align_column 1, :right
|
|
@@ -554,24 +552,40 @@ module OllamaChat::PersonaeManagement
|
|
|
554
552
|
persona_name
|
|
555
553
|
end
|
|
556
554
|
|
|
555
|
+
# Imports a character persona from JSON data, prompts for a name,
|
|
556
|
+
# and saves the resulting Markdown profile to disk.
|
|
557
|
+
#
|
|
558
|
+
# @param json_data [String] The raw character data in JSON format.
|
|
559
|
+
# @return [String, nil] The name of the created persona if successful,
|
|
560
|
+
# or nil if the process was cancelled or failed.
|
|
561
|
+
def import_persona_from_json(json_data)
|
|
562
|
+
persona_name = determine_valid_new_name_for_persona('to import from JSON/PNG') or return
|
|
563
|
+
markdown = convert_json_character_to_markdown(json_data, persona_name)
|
|
564
|
+
persona_pathname = persona_name_to_pathname(persona_name)
|
|
565
|
+
persona_pathname.write(markdown)
|
|
566
|
+
persona_name
|
|
567
|
+
end
|
|
568
|
+
|
|
557
569
|
# Transforms raw character data (JSON or YAML) into a high-fidelity,
|
|
558
570
|
# structured Markdown persona profile using the persona architect prompt and
|
|
559
571
|
# the current persona template.
|
|
560
572
|
#
|
|
561
573
|
# This method leverages the LLM to interpret raw attributes and expand them
|
|
562
574
|
# into evocative prose, ensuring the final output conforms to the system's
|
|
563
|
-
# standard persona structure. It also normalizes
|
|
564
|
-
#
|
|
575
|
+
# standard persona structure. It also normalizes placeholders: {{user}} is
|
|
576
|
+
# converted to %{user} for runtime personalization, and {{char}} is replaced
|
|
577
|
+
# with the actual character name provided.
|
|
565
578
|
#
|
|
566
579
|
# @param character [String] the raw character data in JSON format
|
|
580
|
+
# @param persona_name [String] the name of the character to replace {{char}} with
|
|
567
581
|
# @return [String] the resulting structured Markdown persona profile
|
|
568
|
-
def convert_json_character_to_markdown(character)
|
|
582
|
+
def convert_json_character_to_markdown(character, persona_name)
|
|
569
583
|
generate(
|
|
570
584
|
prompt: prompt(:persona_architect).to_s % {
|
|
571
585
|
character:,
|
|
572
586
|
persona_template: prompt(:persona).to_s
|
|
573
587
|
}
|
|
574
|
-
).
|
|
588
|
+
).gsub(/{{user}}/i, '%{user}').gsub(/{{char}}/i, persona_name)
|
|
575
589
|
end
|
|
576
590
|
|
|
577
591
|
# Interactively exports a persona profile to a specified file.
|
|
@@ -232,24 +232,38 @@ module OllamaChat::PromptManagement
|
|
|
232
232
|
# Interactively generates follow-up prompt suggestions based on the current
|
|
233
233
|
# session.
|
|
234
234
|
#
|
|
235
|
-
# This method
|
|
236
|
-
#
|
|
237
|
-
#
|
|
238
|
-
#
|
|
235
|
+
# This method constructs a prompt containing the conversation history and an
|
|
236
|
+
# instruction (either selected from a template or provided manually) and
|
|
237
|
+
# requests a generation from the AI model. The resulting suggestions are then
|
|
238
|
+
# opened in the editor for final refinement before being returned.
|
|
239
|
+
#
|
|
240
|
+
# @param edit [Boolean] If true, allows the user to write a custom suggestion
|
|
241
|
+
# instruction on the fly; otherwise, prompts the user to pick a template.
|
|
239
242
|
#
|
|
240
243
|
# @return [String, nil] The refined suggestion text, or nil if the process
|
|
241
244
|
# was cancelled.
|
|
242
|
-
def suggest_prompts
|
|
243
|
-
|
|
244
|
-
|
|
245
|
+
def suggest_prompts(edit: false)
|
|
246
|
+
instruction = nil
|
|
247
|
+
if edit
|
|
248
|
+
# Let the user write a suggestion instruction on the fly
|
|
249
|
+
instruction = edit_text('')
|
|
250
|
+
else
|
|
251
|
+
# Let the user pick a prompt template (e.g., suggest_coding, suggest_roleplaying)
|
|
252
|
+
instruction = choose_prompt(prompt: 'Which suggestion strategy shall we employ? %s') or return
|
|
253
|
+
end
|
|
245
254
|
|
|
246
255
|
# Build the context by gathering all current conversation messages
|
|
247
256
|
history = prepare_conversation_history
|
|
248
|
-
full_prompt =
|
|
257
|
+
full_prompt = <<~EOT
|
|
258
|
+
Conversation History:
|
|
259
|
+
#{history}
|
|
260
|
+
|
|
261
|
+
Instruction:
|
|
262
|
+
#{instruction}
|
|
263
|
+
EOT
|
|
249
264
|
|
|
250
|
-
# Execute a silent
|
|
251
|
-
|
|
252
|
-
suggestions = response.response
|
|
265
|
+
# Execute a silent chat oneshot call (doesn't add to history)
|
|
266
|
+
suggestions = generate(prompt: full_prompt)
|
|
253
267
|
|
|
254
268
|
# Pass the AI's suggestions through the editor for final refinement
|
|
255
269
|
edit_text(suggestions)
|
|
@@ -104,15 +104,12 @@ module OllamaChat::SessionManagement
|
|
|
104
104
|
else
|
|
105
105
|
name
|
|
106
106
|
end
|
|
107
|
-
|
|
108
|
-
size = format_bytes(size_bytes)
|
|
109
|
-
tokens = OllamaChat::Utils::TokenEstimator.estimate(size_bytes)
|
|
110
|
-
tokens_size = format_tokens(tokens)
|
|
107
|
+
es = OllamaChat::TokenEstimator.estimate(s.messages.to_s)
|
|
111
108
|
table << [
|
|
112
109
|
s.id.to_s,
|
|
113
110
|
name,
|
|
114
|
-
|
|
115
|
-
|
|
111
|
+
es.bytes_formatted,
|
|
112
|
+
es.tokens_formatted,
|
|
116
113
|
s.messages.to_s.count(?\n),
|
|
117
114
|
s.age(now:),
|
|
118
115
|
]
|
|
@@ -130,12 +127,10 @@ module OllamaChat::SessionManagement
|
|
|
130
127
|
#
|
|
131
128
|
# @param output [IO] the output stream to write the information to (default: STDOUT)
|
|
132
129
|
def show_session(output: STDOUT)
|
|
133
|
-
size_bytes
|
|
134
|
-
|
|
135
|
-
tokens = OllamaChat::Utils::TokenEstimator.estimate(size_bytes)
|
|
136
|
-
tokens_size = format_tokens(tokens)
|
|
130
|
+
size_bytes = session.messages.to_s.size
|
|
131
|
+
es = OllamaChat::TokenEstimator.estimate(size_bytes)
|
|
137
132
|
messages_count = session.messages.to_s.count(?\n)
|
|
138
|
-
output.puts "#{bold{session.name}} (#{italic{session.id}}), #{
|
|
133
|
+
output.puts "#{bold{session.name}} (#{italic{session.id}}), #{es.bytes_formatted}/#{es.tokens_formatted}, #{messages_count} messages"
|
|
139
134
|
end
|
|
140
135
|
|
|
141
136
|
# Interactively prompts the user for a unique session name.
|
|
@@ -227,7 +222,7 @@ module OllamaChat::SessionManagement
|
|
|
227
222
|
# @return [OllamaChat::Database::Models::Session] the initialized session
|
|
228
223
|
def setup_session
|
|
229
224
|
@session = if session_name = @opts[?l]
|
|
230
|
-
choose_session(session_name)
|
|
225
|
+
choose_session(session_name, offer_new_session: true)
|
|
231
226
|
elsif @opts[?n]
|
|
232
227
|
new_session
|
|
233
228
|
else
|
|
@@ -258,7 +253,8 @@ module OllamaChat::SessionManagement
|
|
|
258
253
|
will be deleted, pick a new session to switch to.
|
|
259
254
|
EOT
|
|
260
255
|
confirm?(prompt: "\n⏎ Press any key to continue (%s). ", timeout: 3)
|
|
261
|
-
if chosen_session = choose_session(??, except_id: current_session_id)
|
|
256
|
+
if chosen_session = choose_session(??, except_id: current_session_id, offer_new_session: true)
|
|
257
|
+
chosen_session.save
|
|
262
258
|
confirm?(
|
|
263
259
|
prompt: "🔔 Delete session #{current_session_name.inspect} (#{current_session_id})? (y/n) ",
|
|
264
260
|
yes: /\Ay/i
|
|
@@ -353,7 +349,7 @@ module OllamaChat::SessionManagement
|
|
|
353
349
|
prompt: prompt(:session_summarize).to_s % {
|
|
354
350
|
sender_name:, unit:, message_content:, message_thinking:, context:
|
|
355
351
|
}
|
|
356
|
-
)
|
|
352
|
+
)
|
|
357
353
|
content = if pretty
|
|
358
354
|
'**%s**: %s' % [ sender_name_output, summary ]
|
|
359
355
|
else
|
|
@@ -384,7 +380,7 @@ module OllamaChat::SessionManagement
|
|
|
384
380
|
c << "%s: %s\n\n" % [ sender_name, message.content ]
|
|
385
381
|
end
|
|
386
382
|
prompt = prompt(:session_title).to_s % { length:, content: }
|
|
387
|
-
generate(prompt:).
|
|
383
|
+
generate(prompt:).full? do |name|
|
|
388
384
|
name = name.
|
|
389
385
|
gsub(/(\A(\s|[^A-Za-z])+|(\s|[^A-Za-z])+\z)/m, '').
|
|
390
386
|
gsub(/\s+/, ' ')
|
|
@@ -476,22 +472,20 @@ module OllamaChat::SessionManagement
|
|
|
476
472
|
elsif selector
|
|
477
473
|
now = Time.now
|
|
478
474
|
sessions = session_query.order(Sequel.desc(:updated_at)).map { |session|
|
|
479
|
-
duration
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
' '
|
|
492
|
-
end
|
|
475
|
+
duration = session.age(now:)
|
|
476
|
+
es = OllamaChat::TokenEstimator.estimate(session.messages.to_s)
|
|
477
|
+
count = session.messages.to_s.count(?\n)
|
|
478
|
+
locked = if pid = session.locked?
|
|
479
|
+
if pid == $$
|
|
480
|
+
" 🔓#{pid} "
|
|
481
|
+
else
|
|
482
|
+
" 🔐#{pid} "
|
|
483
|
+
end
|
|
484
|
+
else
|
|
485
|
+
' '
|
|
486
|
+
end
|
|
493
487
|
display = <<~EOT.strip
|
|
494
|
-
#{session.name} 🆔#{session.id}#{locked}📨#{count} 🧩#{
|
|
488
|
+
#{session.name} 🆔#{session.id}#{locked}📨#{count} 🧩#{es.tokens_formatted} ⏳#{duration}
|
|
495
489
|
EOT
|
|
496
490
|
SearchUI::Wrapper.new(
|
|
497
491
|
session.name,
|
|
@@ -241,6 +241,14 @@ module OllamaChat::StateSelectors
|
|
|
241
241
|
# that manages the currently selected voice for the chat session
|
|
242
242
|
attr_reader :voices
|
|
243
243
|
|
|
244
|
+
# The context_format reader returns the context format selector for the
|
|
245
|
+
# chat session.
|
|
246
|
+
#
|
|
247
|
+
# @return [ OllamaChat::StateSelector ] the context format selector object
|
|
248
|
+
# that manages the format used for project context generation
|
|
249
|
+
# (e.g., 'JSON', 'TOON')
|
|
250
|
+
attr_reader :context_format
|
|
251
|
+
|
|
244
252
|
# Sets up state selectors for document policy and think mode based on the
|
|
245
253
|
# provided configuration.
|
|
246
254
|
#
|
|
@@ -268,5 +276,11 @@ module OllamaChat::StateSelectors
|
|
|
268
276
|
states: config.voice.list,
|
|
269
277
|
allow_empty: true
|
|
270
278
|
)
|
|
279
|
+
@context_format = DatabaseStateSelector.new(
|
|
280
|
+
chat: self,
|
|
281
|
+
attribute: :context_format,
|
|
282
|
+
name: 'Context format',
|
|
283
|
+
states: %w[ JSON TOON ],
|
|
284
|
+
)
|
|
271
285
|
end
|
|
272
286
|
end
|
|
@@ -41,6 +41,27 @@ module OllamaChat::SystemPromptManagement
|
|
|
41
41
|
messages.system_name
|
|
42
42
|
end
|
|
43
43
|
|
|
44
|
+
# Resolves the currently active system prompt name to its raw text content.
|
|
45
|
+
#
|
|
46
|
+
# If the current system prompt is set to 'model_default', this method
|
|
47
|
+
# retrieves the default prompt associated with the loaded model. Otherwise,
|
|
48
|
+
# it fetches the content of the specifically named system prompt from the
|
|
49
|
+
# database.
|
|
50
|
+
#
|
|
51
|
+
# The returned string represents the base template; any dynamic placeholders
|
|
52
|
+
# (e.g., %{persona} and %{runtime_info}) are nullified to ensure only the raw
|
|
53
|
+
# structure is returned.
|
|
54
|
+
#
|
|
55
|
+
# @return [String] The raw text content of the active system prompt.
|
|
56
|
+
def raw_system_prompt
|
|
57
|
+
system_name = current_system_prompt_name
|
|
58
|
+
if system_name == 'model_default'
|
|
59
|
+
model_default_system_prompt.to_s
|
|
60
|
+
else
|
|
61
|
+
system_prompt(system_name).to_s
|
|
62
|
+
end % { persona: nil, runtime_info: nil }
|
|
63
|
+
end
|
|
64
|
+
|
|
44
65
|
# Retrieves the content of the system prompt currently active in the chat
|
|
45
66
|
# session.
|
|
46
67
|
#
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Provides a "best-effort" estimation of token counts based on the
|
|
2
|
+
# character count or byte size of the input content.
|
|
3
|
+
class OllamaChat::TokenEstimator::Crude
|
|
4
|
+
# Initializes a new crude estimator with the provided source.
|
|
5
|
+
#
|
|
6
|
+
# @param arg [String, Integer] The content to estimate (string or raw byte count).
|
|
7
|
+
# @raise [ArgumentError] if the input is not a string or an integer.
|
|
8
|
+
def initialize(arg)
|
|
9
|
+
if text = arg.ask_and_send(:to_str)
|
|
10
|
+
@bytes = text.size
|
|
11
|
+
elsif bytes = arg.ask_and_send(:to_int)
|
|
12
|
+
@bytes = bytes
|
|
13
|
+
else
|
|
14
|
+
raise ArgumentError, "#{arg.inspect} cannot be used to estimate"
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Performs the estimation calculation and returns an Estimate object.
|
|
19
|
+
#
|
|
20
|
+
# @return [OllamaChat::TokenEstimator::Estimate]
|
|
21
|
+
def perform
|
|
22
|
+
tokens = (@bytes.to_f / 3.5).ceil
|
|
23
|
+
OllamaChat::TokenEstimator::Estimate.new(bytes: @bytes, tokens:)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Provides tools for estimating token counts across different models and contexts.
|
|
2
|
+
module OllamaChat::TokenEstimator
|
|
3
|
+
end
|
|
4
|
+
|
|
5
|
+
# Requires the core estimation implementations.
|
|
6
|
+
require 'ollama_chat/token_estimator/crude'
|
|
7
|
+
|
|
8
|
+
# A data structure that holds the results of a token estimation,
|
|
9
|
+
# providing convenient formatting methods for both byte size and token count.
|
|
10
|
+
module OllamaChat::TokenEstimator
|
|
11
|
+
# Represents the result of a calculation including raw values
|
|
12
|
+
# and their human-readable formatted strings.
|
|
13
|
+
class Estimate < Struct.new(:bytes, :tokens)
|
|
14
|
+
include OllamaChat::Utils::ValueFormatter
|
|
15
|
+
|
|
16
|
+
# Returns the byte count in a formatted string (e.g., "1.2 KB").
|
|
17
|
+
# @return [String] The formatted byte size.
|
|
18
|
+
def tokens_formatted
|
|
19
|
+
format_tokens(tokens)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Returns the byte count as a formatted string.
|
|
23
|
+
# @return [String] The formatted byte size.
|
|
24
|
+
def bytes_formatted
|
|
25
|
+
format_bytes(bytes)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Estimates token count for a given piece of content.
|
|
30
|
+
#
|
|
31
|
+
# @param text [String, Integer] The content to estimate (string or raw byte count).
|
|
32
|
+
# @return [OllamaChat::TokenEstimator::Estimate] An object containing the results.
|
|
33
|
+
def self.estimate(text)
|
|
34
|
+
OllamaChat::TokenEstimator::Crude.new(text).perform
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -55,7 +55,7 @@ module OllamaChat::ToolCalling
|
|
|
55
55
|
# @return [ Hash ] a hash containing the registered tools
|
|
56
56
|
def tools
|
|
57
57
|
tools_support.off? and return []
|
|
58
|
-
enabled_tools.filter_map { OllamaChat::Tools.registered[
|
|
58
|
+
enabled_tools.filter_map { OllamaChat::Tools.registered[_1]&.to_hash }
|
|
59
59
|
end
|
|
60
60
|
|
|
61
61
|
# The default_enabled_tools method returns an array of tool names that are
|
|
@@ -85,13 +85,15 @@ class OllamaChat::Tools::ComputeBMI
|
|
|
85
85
|
raise OllamaChat::ToolFunctionArgumentError, 'Height must be greater than zero and in kg/lbs' if height <= 0
|
|
86
86
|
raise OllamaChat::ToolFunctionArgumentError, 'Weight must be less than 3m and in meter/feet' if height > 3
|
|
87
87
|
|
|
88
|
-
bmi = ( weight /
|
|
88
|
+
bmi = ( weight / height**2 ).round(2)
|
|
89
89
|
category = calculate_category(bmi)
|
|
90
|
+
message = "This BMI is #{bmi}, which falls into the #{category} category."
|
|
90
91
|
|
|
91
92
|
{
|
|
92
|
-
bmi
|
|
93
|
-
category
|
|
94
|
-
units
|
|
93
|
+
bmi:,
|
|
94
|
+
category:,
|
|
95
|
+
units:,
|
|
96
|
+
message:,
|
|
95
97
|
}.to_json
|
|
96
98
|
rescue => e
|
|
97
99
|
{ error: e.class, message: e.message }.to_json
|
|
@@ -88,7 +88,17 @@ class OllamaChat::Tools::ExecuteGrep
|
|
|
88
88
|
context = normalize_number(args.context)
|
|
89
89
|
cmd = eval_template(config, pattern, path, max_results, ignore_case, before, after, context)
|
|
90
90
|
result = OllamaChat::Utils::Fetcher.execute(cmd, &:read)
|
|
91
|
-
|
|
91
|
+
message =
|
|
92
|
+
if result.blank?
|
|
93
|
+
"No matches found for #{args.pattern.inspect} in #{path.inspect}."
|
|
94
|
+
else
|
|
95
|
+
"Found some matches for #{args.pattern.inspect} in #{path.inspect}."
|
|
96
|
+
end
|
|
97
|
+
{
|
|
98
|
+
cmd:,
|
|
99
|
+
result:,
|
|
100
|
+
message:,
|
|
101
|
+
}.to_json
|
|
92
102
|
rescue => e
|
|
93
103
|
{ error: e.class, message: e.message }.to_json
|
|
94
104
|
end
|
|
@@ -65,9 +65,10 @@ class OllamaChat::Tools::FileContext
|
|
|
65
65
|
# @return [String] the generated context data in the configured format (JSON by default)
|
|
66
66
|
# @return [String] a JSON string containing error information if the operation fails
|
|
67
67
|
def execute(tool_call, **opts)
|
|
68
|
-
|
|
68
|
+
chat = opts[:chat]
|
|
69
|
+
config = chat.config
|
|
69
70
|
pattern = tool_call.function.arguments.pattern
|
|
70
|
-
format =
|
|
71
|
+
format = chat.context_format.selected
|
|
71
72
|
|
|
72
73
|
directory = Pathname.new(tool_call.function.arguments.directory || ?.)
|
|
73
74
|
search_path = directory + pattern
|
|
@@ -137,13 +137,16 @@ class OllamaChat::Tools::GeneratePassword
|
|
|
137
137
|
Tins::Token.new(length:, alphabet:)
|
|
138
138
|
end
|
|
139
139
|
|
|
140
|
+
message = "Successfully generated a #{token.length}-character secure password using the #{alphabet_type} alphabet."
|
|
141
|
+
|
|
140
142
|
result = {
|
|
141
143
|
password: token,
|
|
142
144
|
length: token.length,
|
|
143
145
|
bits: token.bits,
|
|
144
|
-
alphabet_type:
|
|
146
|
+
alphabet_type: ,
|
|
145
147
|
uppercase: ,
|
|
146
148
|
extended: ,
|
|
149
|
+
message: ,
|
|
147
150
|
generated_at: Time.now.iso8601
|
|
148
151
|
}
|
|
149
152
|
if alphabet_type == 'default'
|
|
@@ -53,8 +53,19 @@ class OllamaChat::Tools::GetCurrentWeather
|
|
|
53
53
|
chat = opts[:chat]
|
|
54
54
|
config = chat.config
|
|
55
55
|
units = config.location.units =~ /SI/ ? 'si' : 'us'
|
|
56
|
+
|
|
56
57
|
data = { current_time: Time.now, units: } |
|
|
57
58
|
JSON(get_weather_data(chat, config, units)).deep_symbolize_keys
|
|
59
|
+
|
|
60
|
+
temp = data.dig(:currently, :temperature)
|
|
61
|
+
curr_sum = data.dig(:currently, :summary)
|
|
62
|
+
daily_sum = data.dig(:daily, :summary)
|
|
63
|
+
unit_sym = units == 'si' ? '°C' : '°F'
|
|
64
|
+
|
|
65
|
+
message = "Currently #{temp}#{unit_sym} and #{curr_sum}. Today's forecast: #{daily_sum}"
|
|
66
|
+
|
|
67
|
+
data[:message] = message
|
|
68
|
+
|
|
58
69
|
data.to_json
|
|
59
70
|
rescue => e
|
|
60
71
|
{
|
|
@@ -54,8 +54,19 @@ class OllamaChat::Tools::GetTime
|
|
|
54
54
|
# execute(tool_call, config:)
|
|
55
55
|
# # => {"time":"2026-02-09T14:32:00+01:00","weekday":"Monday"}
|
|
56
56
|
def execute(_tool_call, **_opts)
|
|
57
|
-
now
|
|
58
|
-
|
|
57
|
+
now = Time.now
|
|
58
|
+
hour = now.hour
|
|
59
|
+
|
|
60
|
+
greeting = case hour
|
|
61
|
+
when 5..11 then 'Good morning'
|
|
62
|
+
when 12..17 then 'Good afternoon'
|
|
63
|
+
when 18..23 then 'Good evening'
|
|
64
|
+
else 'Good night'
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
message = "#{greeting}! It is currently #{now.strftime('%H:%M')} on #{now.strftime('%A')}."
|
|
68
|
+
|
|
69
|
+
{ time: now.iso8601, weekday: now.strftime('%A'), message: }.to_json
|
|
59
70
|
end
|
|
60
71
|
|
|
61
72
|
self
|
|
@@ -73,12 +73,15 @@ class OllamaChat::Tools::ReadFile
|
|
|
73
73
|
|
|
74
74
|
path = assert_valid_path(args.path, config.tools.functions.read_file.allowed?, check: :file)
|
|
75
75
|
content = extract_range(path.read, start_line, end_line)
|
|
76
|
+
es = OllamaChat::TokenEstimator.estimate(content)
|
|
77
|
+
message = "Read #{es.bytes_formatted} (#{es.tokens_formatted}) from #{path.to_s.inspect}."
|
|
76
78
|
|
|
77
79
|
{
|
|
78
80
|
path:,
|
|
79
81
|
content:,
|
|
80
82
|
start_line:,
|
|
81
83
|
end_line:,
|
|
84
|
+
message:,
|
|
82
85
|
}.to_json
|
|
83
86
|
rescue => e
|
|
84
87
|
{
|
|
@@ -123,15 +123,22 @@ class OllamaChat::Tools::RetrieveDocumentSnippets
|
|
|
123
123
|
records = rerank_records(chat, query, records)
|
|
124
124
|
end
|
|
125
125
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
126
|
+
collection_name = chat.documents.collection
|
|
127
|
+
message =
|
|
128
|
+
if records.any?
|
|
129
|
+
"Retrieved #{records.size} relevant snippets from collection #{collection_name.inspect} for query #{query.inspect}. See snippets below:\n\n" +
|
|
130
|
+
records.map { |record|
|
|
131
|
+
link = if record.source =~ %r(\Ahttps?://)
|
|
132
|
+
record.source
|
|
133
|
+
else
|
|
134
|
+
'file://%s' % File.expand_path(record.source)
|
|
135
|
+
end
|
|
136
|
+
link && record.tags.any? or next
|
|
137
|
+
[ link, ?# + record.tags.first ]
|
|
138
|
+
}.flat_map { |l, t| chat.hyperlink(l, t) }.join(' ')
|
|
139
|
+
else
|
|
140
|
+
"No relevant snippets found for query #{query.inspect} in collection #{collection_name.inspect}."
|
|
141
|
+
end
|
|
135
142
|
|
|
136
143
|
{
|
|
137
144
|
prompt: 'Consider these snippets generated from retrieval when formulating your response!',
|
|
@@ -181,7 +188,7 @@ class OllamaChat::Tools::RetrieveDocumentSnippets
|
|
|
181
188
|
begin
|
|
182
189
|
# We use the active chat model to perform the surgical precision
|
|
183
190
|
# filtering
|
|
184
|
-
if response = chat.generate(prompt:)
|
|
191
|
+
if response = chat.generate(prompt:).full?
|
|
185
192
|
indices = response.scan(/\d+/).map(&:to_i).select { |i| (0...records.size).include?(i) }
|
|
186
193
|
records = records.values_at(*indices) if indices.any?
|
|
187
194
|
end
|
|
@@ -208,7 +215,7 @@ class OllamaChat::Tools::RetrieveDocumentSnippets
|
|
|
208
215
|
prompt: chat.config.embedding.model.prompt?,
|
|
209
216
|
text_size: ,
|
|
210
217
|
text_count: ,
|
|
211
|
-
min_similarity:
|
|
218
|
+
min_similarity:
|
|
212
219
|
)
|
|
213
220
|
end
|
|
214
221
|
|