ollama_chat 0.0.95 → 0.0.96
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 +51 -0
- data/Rakefile +1 -1
- data/lib/ollama_chat/chat.rb +1 -0
- data/lib/ollama_chat/commands.rb +1 -6
- data/lib/ollama_chat/database/models/prompt.rb +1 -3
- data/lib/ollama_chat/follow_chat.rb +4 -5
- data/lib/ollama_chat/message_list.rb +2 -4
- data/lib/ollama_chat/ollama_chat_config/default_config.yml +1 -0
- data/lib/ollama_chat/parsing.rb +10 -6
- data/lib/ollama_chat/personae_management.rb +24 -10
- data/lib/ollama_chat/session_management.rb +22 -28
- data/lib/ollama_chat/source_fetching.rb +1 -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/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 +17 -10
- 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/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 +36 -1
- 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 +9 -5
- data/lib/ollama_chat/utils/token_estimator.rb +0 -15
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 54aa30ed02a44f43f0f197247d4e7f5377007802025ddc55632f60965e6d0548
|
|
4
|
+
data.tar.gz: aa6e058dd8135e265fbfbc848620ece0730182ff6c11e4b0e9c87a8b68e97590
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 96d53286b971b4863f688a8613e9ca3fe3e0ee65b9c5f18b042ea3a910ea8ef8cdb4b2e2b26750c08aae6bcbbdd43371e8f0fd9598f24c494256f58f42ddd8ef
|
|
7
|
+
data.tar.gz: 1d5aa2eadaa9723f6a20f1019f6965249e9d526d38bd6de5d5e86ed1b76ac82e86bf0e59915330f35a2a48f0ce99c6a4109a50c94d38e30965f4d658be768ce3
|
data/CHANGES.md
CHANGED
|
@@ -1,5 +1,56 @@
|
|
|
1
1
|
# Changes
|
|
2
2
|
|
|
3
|
+
## 2026-07-03 v0.0.96
|
|
4
|
+
|
|
5
|
+
### New Features
|
|
6
|
+
|
|
7
|
+
- Added reroll capability to the `roll_dice` tool, including a new `reroll`
|
|
8
|
+
configuration option in `default_config.yml` and implementation of a reroll
|
|
9
|
+
loop using `chat.confirm?`.
|
|
10
|
+
- Enhanced the `roll_dice` tool output to include `min` and `max` bounds in the
|
|
11
|
+
JSON response and result range in the summary message.
|
|
12
|
+
- Improved character importing by implementing `import_persona_from_json` in
|
|
13
|
+
`PersonaeManagement`, removing temporary file dependencies.
|
|
14
|
+
- Added the ability to offer new sessions during session selection via
|
|
15
|
+
`offer_new_session: true` in `SessionManagement`.
|
|
16
|
+
|
|
17
|
+
### Improvements
|
|
18
|
+
|
|
19
|
+
- **Tool Messaging**: Added or enhanced human-readable summary `message` fields
|
|
20
|
+
for several tools to improve user experience:
|
|
21
|
+
- `GetTime`: Now includes time-of-day greetings and formatted weekdays.
|
|
22
|
+
- `GetCurrentWeather`: Now incorporates temperature, conditions, and daily
|
|
23
|
+
forecasts.
|
|
24
|
+
- `ReadFile` & `WriteFile`: Integrated `OllamaChat::TokenEstimator` for
|
|
25
|
+
formatted byte and token counts.
|
|
26
|
+
- `GeneratePassword`: Added summaries covering various alphabet types and
|
|
27
|
+
errors.
|
|
28
|
+
- `RetrieveDocumentSnippets`: Added counts and collection details.
|
|
29
|
+
- `ExecuteGrep`: Added logic to distinguish between found matches and blank
|
|
30
|
+
results.
|
|
31
|
+
- `RunTests`: Added emoji-based result summaries and removed reliance on
|
|
32
|
+
the global `$?` variable.
|
|
33
|
+
- `ComputeBMI`: Enhanced summary messages and simplified calculation logic.
|
|
34
|
+
- **Character Personalization**:
|
|
35
|
+
- Updated `convert_json_character_to_markdown` to replace `{{char}}`
|
|
36
|
+
placeholders with actual names.
|
|
37
|
+
- Enhanced `personalize_character_profile` using case-insensitive regexes
|
|
38
|
+
for `{{user}} and {{char}}`.
|
|
39
|
+
- Refactored `PNGMetadataExtractor.decode_character` to return a tuple of
|
|
40
|
+
the decoded string and parsed JSON data.
|
|
41
|
+
- **Token Estimation**:
|
|
42
|
+
- Relocated `TokenEstimator` to a primary domain module and introduced an
|
|
43
|
+
`Estimate` struct for formatted bytes and tokens.
|
|
44
|
+
- Implemented `OllamaChat::TokenEstimator::Crude` as the estimation engine.
|
|
45
|
+
|
|
46
|
+
### Refactoring & Maintenance
|
|
47
|
+
|
|
48
|
+
- Updated Ruby syntax across the project to use shorthand hash syntax (e.g., in
|
|
49
|
+
`.where`, object initialization, and method calls).
|
|
50
|
+
- Improved compatibility by replacing the `it` parameter with `_1` in the
|
|
51
|
+
`tools` method within `ToolCalling`.
|
|
52
|
+
- Bumped `context_spook` dependency from **1.5** to **1.6**.
|
|
53
|
+
|
|
3
54
|
## 2026-06-29 v0.0.95
|
|
4
55
|
|
|
5
56
|
### New Features & Enhancements
|
data/Rakefile
CHANGED
|
@@ -56,7 +56,7 @@ GemHadar do
|
|
|
56
56
|
dependency 'bigdecimal', '~> 3.1'
|
|
57
57
|
dependency 'csv', '~> 3.0'
|
|
58
58
|
dependency 'const_conf', '~> 0.3'
|
|
59
|
-
dependency 'context_spook', '~> 1.
|
|
59
|
+
dependency 'context_spook', '~> 1.6'
|
|
60
60
|
dependency 'infobar', '>= 0.13.1'
|
|
61
61
|
dependency 'rubyzip', '~> 3.0'
|
|
62
62
|
dependency 'sequel', '~> 5.0'
|
data/lib/ollama_chat/chat.rb
CHANGED
data/lib/ollama_chat/commands.rb
CHANGED
|
@@ -724,12 +724,7 @@ module OllamaChat::Commands
|
|
|
724
724
|
disable_content_parsing
|
|
725
725
|
data
|
|
726
726
|
when 'import'
|
|
727
|
-
|
|
728
|
-
Tempfile.create('character.md') do |tmp|
|
|
729
|
-
tmp.puts markdown
|
|
730
|
-
tmp.flush
|
|
731
|
-
import_persona(Pathname.new(tmp.path))
|
|
732
|
-
end
|
|
727
|
+
import_persona_from_json(data)
|
|
733
728
|
:next
|
|
734
729
|
end
|
|
735
730
|
end
|
|
@@ -52,9 +52,7 @@ class OllamaChat::Database::Models::Prompt < Sequel::Model(OllamaChat::DB)
|
|
|
52
52
|
# database when the underlying prompt is removed.
|
|
53
53
|
def after_destroy
|
|
54
54
|
super
|
|
55
|
-
OllamaChat::Database::Models::Favourite.
|
|
56
|
-
where(context: context, name: name).
|
|
57
|
-
destroy
|
|
55
|
+
OllamaChat::Database::Models::Favourite.where(context:, name:).destroy
|
|
58
56
|
end
|
|
59
57
|
|
|
60
58
|
# Seeds the prompt table from the provided chat configuration.
|
|
@@ -165,7 +165,7 @@ class OllamaChat::FollowChat
|
|
|
165
165
|
STDOUT.printf(
|
|
166
166
|
"\n%s Execution of tool %s confirmed.\n\n", symbol, bold { name }
|
|
167
167
|
)
|
|
168
|
-
result = OllamaChat::Tools.registered[name].execute(tool_call, chat:
|
|
168
|
+
result = OllamaChat::Tools.registered[name].execute(tool_call, chat:)
|
|
169
169
|
if confirmed == :explicit
|
|
170
170
|
chat.log(:info, "Execution of tool %s was explicitly confirmed." % name)
|
|
171
171
|
else
|
|
@@ -191,14 +191,13 @@ class OllamaChat::FollowChat
|
|
|
191
191
|
false
|
|
192
192
|
end
|
|
193
193
|
|
|
194
|
-
|
|
195
|
-
tokens = OllamaChat::Utils::TokenEstimator.estimate(size_bytes)
|
|
194
|
+
es = OllamaChat::TokenEstimator.estimate(result.to_s)
|
|
196
195
|
|
|
197
196
|
tools_used[name] = {
|
|
198
197
|
message:,
|
|
199
198
|
warn: ,
|
|
200
|
-
size:
|
|
201
|
-
tokens:
|
|
199
|
+
size: es.bytes_formatted,
|
|
200
|
+
tokens: es.tokens_formatted,
|
|
202
201
|
duration: Tins::Duration.new(Time.now - start).to_s,
|
|
203
202
|
}
|
|
204
203
|
end
|
|
@@ -380,9 +380,7 @@ class OllamaChat::MessageList
|
|
|
380
380
|
def show_system_prompt
|
|
381
381
|
current_system = system.to_s
|
|
382
382
|
size_bytes = current_system.size
|
|
383
|
-
|
|
384
|
-
tokens = OllamaChat::Utils::TokenEstimator.estimate(size_bytes)
|
|
385
|
-
tokens_size = format_tokens(tokens)
|
|
383
|
+
es = OllamaChat::TokenEstimator.estimate(size_bytes)
|
|
386
384
|
system_prompt = @chat.kramdown_ansi_parse(current_system).
|
|
387
385
|
gsub(/\n+\z/, '').full?
|
|
388
386
|
if system_prompt.blank?
|
|
@@ -398,7 +396,7 @@ class OllamaChat::MessageList
|
|
|
398
396
|
#{system_prompt}
|
|
399
397
|
|
|
400
398
|
System prompt name: #{bold{system_name}}
|
|
401
|
-
System prompt length: 👾#{
|
|
399
|
+
System prompt length: 👾#{es.bytes_formatted} 🧩#{es.tokens_formatted}
|
|
402
400
|
EOT
|
|
403
401
|
end
|
|
404
402
|
self
|
data/lib/ollama_chat/parsing.rb
CHANGED
|
@@ -76,9 +76,10 @@ module OllamaChat::Parsing
|
|
|
76
76
|
results = []
|
|
77
77
|
|
|
78
78
|
if data = metadata.delete('chara') and
|
|
79
|
-
char = OllamaChat::Utils::PNGMetadataExtractor.decode_character(data)
|
|
79
|
+
(char, char_data = OllamaChat::Utils::PNGMetadataExtractor.decode_character(data))
|
|
80
80
|
then
|
|
81
|
-
|
|
81
|
+
name = char_data.dig('data', 'name').full? || Pathname.new(source_io.path).basename.sub_ext('')
|
|
82
|
+
results << "Character Profile:\n\n#{personalize_character_profile(char, name:)}"
|
|
82
83
|
end
|
|
83
84
|
|
|
84
85
|
if data = metadata.delete('parameters') and
|
|
@@ -222,13 +223,16 @@ module OllamaChat::Parsing
|
|
|
222
223
|
)
|
|
223
224
|
end
|
|
224
225
|
|
|
225
|
-
# Personalizes a character profile by replacing
|
|
226
|
+
# Personalizes a character profile by replacing placeholders with actual names.
|
|
226
227
|
#
|
|
227
228
|
# @param char [String] The raw character JSON string.
|
|
229
|
+
# @param name [String] The name of the character to replace {{char}} with.
|
|
228
230
|
# @return [String] The personalized character profile.
|
|
229
|
-
def personalize_character_profile(char)
|
|
230
|
-
|
|
231
|
-
char.gsub(
|
|
231
|
+
def personalize_character_profile(char, name:)
|
|
232
|
+
my_user_name = user_name || 'the user'
|
|
233
|
+
char = char.gsub(/{{user}}/i, my_user_name)
|
|
234
|
+
name.present? and char = char.gsub(/{{char}}/i, name)
|
|
235
|
+
char
|
|
232
236
|
end
|
|
233
237
|
|
|
234
238
|
# Regular expression to scan content for url/file references
|
|
@@ -351,14 +351,11 @@ module OllamaChat::PersonaeManagement
|
|
|
351
351
|
[ pathname, pathname.size ]
|
|
352
352
|
end.compact.sort_by(&:last).reverse_each do |pathname, size_bytes|
|
|
353
353
|
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)
|
|
354
|
+
es = OllamaChat::TokenEstimator.estimate(size_bytes)
|
|
358
355
|
is_default = default_persona_name == persona_name
|
|
359
356
|
display_name = prefix_favourite(is_default ? bold { persona_name } : persona_name, favs[persona_name])
|
|
360
357
|
|
|
361
|
-
table << [ display_name,
|
|
358
|
+
table << [ display_name, es.bytes_formatted, es.tokens_formatted, ]
|
|
362
359
|
end
|
|
363
360
|
|
|
364
361
|
table.align_column 1, :right
|
|
@@ -554,24 +551,41 @@ module OllamaChat::PersonaeManagement
|
|
|
554
551
|
persona_name
|
|
555
552
|
end
|
|
556
553
|
|
|
554
|
+
# Imports a character persona from JSON data, prompts for a name,
|
|
555
|
+
# and saves the resulting Markdown profile to disk.
|
|
556
|
+
#
|
|
557
|
+
# @param json_data [String] The raw character data in JSON format.
|
|
558
|
+
# @return [String, nil] The name of the created persona if successful,
|
|
559
|
+
# or nil if the process was cancelled or failed.
|
|
560
|
+
def import_persona_from_json(json_data)
|
|
561
|
+
persona_name = determine_valid_new_name_for_persona('to import from JSON/PNG') or return
|
|
562
|
+
markdown = convert_json_character_to_markdown(json_data, persona_name)
|
|
563
|
+
persona_pathname = persona_name_to_pathname(persona_name)
|
|
564
|
+
persona_pathname.write(markdown)
|
|
565
|
+
persona_name
|
|
566
|
+
end
|
|
567
|
+
|
|
557
568
|
# Transforms raw character data (JSON or YAML) into a high-fidelity,
|
|
558
569
|
# structured Markdown persona profile using the persona architect prompt and
|
|
559
570
|
# the current persona template.
|
|
560
571
|
#
|
|
561
572
|
# This method leverages the LLM to interpret raw attributes and expand them
|
|
562
573
|
# into evocative prose, ensuring the final output conforms to the system's
|
|
563
|
-
# standard persona structure. It also normalizes
|
|
564
|
-
#
|
|
574
|
+
# standard persona structure. It also normalizes placeholders: {{user}} is
|
|
575
|
+
# converted to %{user} for runtime personalization, and {{char}} is replaced
|
|
576
|
+
# with the actual character name provided.
|
|
565
577
|
#
|
|
566
578
|
# @param character [String] the raw character data in JSON format
|
|
579
|
+
# @param persona_name [String] the name of the character to replace {{char}} with
|
|
567
580
|
# @return [String] the resulting structured Markdown persona profile
|
|
568
|
-
def convert_json_character_to_markdown(character)
|
|
569
|
-
generate(
|
|
581
|
+
def convert_json_character_to_markdown(character, persona_name)
|
|
582
|
+
response = generate(
|
|
570
583
|
prompt: prompt(:persona_architect).to_s % {
|
|
571
584
|
character:,
|
|
572
585
|
persona_template: prompt(:persona).to_s
|
|
573
586
|
}
|
|
574
|
-
).response
|
|
587
|
+
).response
|
|
588
|
+
response.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.
|
|
@@ -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
|
|
@@ -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,
|
|
@@ -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
|
|
@@ -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!',
|
|
@@ -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
|
|