ollama_chat 0.0.125 → 0.0.126
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 +90 -0
- data/Rakefile +1 -1
- data/lib/ollama_chat/commands.rb +23 -16
- data/lib/ollama_chat/compaction.rb +1 -1
- data/lib/ollama_chat/follow_chat.rb +1 -1
- data/lib/ollama_chat/information.rb +6 -20
- data/lib/ollama_chat/ollama_chat_config/default_config.yml +5 -3
- data/lib/ollama_chat/pager.rb +5 -2
- data/lib/ollama_chat/rag_handling.rb +211 -22
- data/lib/ollama_chat/session_management.rb +24 -4
- data/lib/ollama_chat/tool_calling.rb +3 -16
- data/lib/ollama_chat/tools/browse.rb +0 -1
- data/lib/ollama_chat/tools/compute_bmi.rb +1 -2
- data/lib/ollama_chat/tools/concern.rb +26 -0
- data/lib/ollama_chat/tools/copy_to_clipboard.rb +1 -3
- data/lib/ollama_chat/tools/delete_file.rb +4 -3
- data/lib/ollama_chat/tools/directory_structure.rb +0 -1
- data/lib/ollama_chat/tools/eval_ruby.rb +0 -1
- data/lib/ollama_chat/tools/execute_grep.rb +0 -2
- data/lib/ollama_chat/tools/execute_jira_twg.rb +0 -2
- data/lib/ollama_chat/tools/execute_ri.rb +0 -1
- data/lib/ollama_chat/tools/execute_shell.rb +4 -6
- data/lib/ollama_chat/tools/file_context.rb +0 -2
- data/lib/ollama_chat/tools/forget.rb +20 -8
- data/lib/ollama_chat/tools/gem_path_lookup.rb +0 -1
- data/lib/ollama_chat/tools/generate_image.rb +6 -8
- data/lib/ollama_chat/tools/generate_password.rb +0 -2
- data/lib/ollama_chat/tools/get_current_weather.rb +2 -4
- data/lib/ollama_chat/tools/get_cve.rb +0 -2
- data/lib/ollama_chat/tools/get_endoflife.rb +0 -2
- data/lib/ollama_chat/tools/get_ghr.rb +2 -4
- data/lib/ollama_chat/tools/get_jira_issue.rb +0 -2
- data/lib/ollama_chat/tools/get_location.rb +0 -2
- data/lib/ollama_chat/tools/get_rfc.rb +0 -2
- data/lib/ollama_chat/tools/get_time.rb +2 -3
- data/lib/ollama_chat/tools/get_url.rb +0 -2
- data/lib/ollama_chat/tools/lookup_group.rb +0 -2
- data/lib/ollama_chat/tools/memorize.rb +38 -13
- data/lib/ollama_chat/tools/move_file.rb +8 -4
- data/lib/ollama_chat/tools/open_file_in_editor.rb +0 -2
- data/lib/ollama_chat/tools/paste_from_clipboard.rb +1 -4
- data/lib/ollama_chat/tools/paste_into_editor.rb +3 -5
- data/lib/ollama_chat/tools/patch_file.rb +10 -7
- data/lib/ollama_chat/tools/read_file.rb +3 -5
- data/lib/ollama_chat/tools/resolve_tag.rb +1 -2
- data/lib/ollama_chat/tools/roll_dice.rb +0 -2
- data/lib/ollama_chat/tools/run_tests.rb +2 -2
- data/lib/ollama_chat/tools/search_knowledge.rb +4 -3
- data/lib/ollama_chat/tools/search_web.rb +0 -3
- data/lib/ollama_chat/tools/write_file.rb +4 -3
- data/lib/ollama_chat/tools.rb +50 -4
- data/lib/ollama_chat/version.rb +1 -1
- data/ollama_chat.gemspec +4 -4
- data/spec/ollama_chat/commands_spec.rb +23 -9
- data/spec/ollama_chat/rag_handling_spec.rb +191 -18
- data/spec/ollama_chat/session_management_spec.rb +6 -2
- data/spec/ollama_chat/tool_calling_spec.rb +0 -11
- data/spec/ollama_chat/tools/browse_spec.rb +11 -7
- data/spec/ollama_chat/tools/compute_bmi_spec.rb +8 -8
- data/spec/ollama_chat/tools/concern_spec.rb +7 -1
- data/spec/ollama_chat/tools/copy_to_clipboard_spec.rb +8 -8
- data/spec/ollama_chat/tools/delete_file_spec.rb +7 -7
- data/spec/ollama_chat/tools/directory_structure_spec.rb +7 -7
- data/spec/ollama_chat/tools/execute_grep_spec.rb +10 -10
- data/spec/ollama_chat/tools/execute_jira_twg_spec.rb +8 -8
- data/spec/ollama_chat/tools/execute_ri_spec.rb +6 -6
- data/spec/ollama_chat/tools/execute_shell_spec.rb +1 -1
- data/spec/ollama_chat/tools/file_context_spec.rb +4 -4
- data/spec/ollama_chat/tools/forget_spec.rb +25 -15
- data/spec/ollama_chat/tools/gem_path_lookup_spec.rb +5 -5
- data/spec/ollama_chat/tools/generate_image_spec.rb +10 -10
- data/spec/ollama_chat/tools/generate_password_spec.rb +8 -8
- data/spec/ollama_chat/tools/get_current_weather_spec.rb +6 -6
- data/spec/ollama_chat/tools/get_cve_spec.rb +5 -5
- data/spec/ollama_chat/tools/get_endoflife_spec.rb +5 -5
- data/spec/ollama_chat/tools/get_ghr_spec.rb +11 -11
- data/spec/ollama_chat/tools/get_jira_issue_spec.rb +6 -6
- data/spec/ollama_chat/tools/get_location_spec.rb +6 -6
- data/spec/ollama_chat/tools/get_rfc_spec.rb +5 -5
- data/spec/ollama_chat/tools/get_time_spec.rb +4 -4
- data/spec/ollama_chat/tools/get_url_spec.rb +17 -17
- data/spec/ollama_chat/tools/lookup_group_spec.rb +5 -5
- data/spec/ollama_chat/tools/memorize_spec.rb +67 -11
- data/spec/ollama_chat/tools/move_file_spec.rb +8 -8
- data/spec/ollama_chat/tools/open_file_in_editor_spec.rb +6 -6
- data/spec/ollama_chat/tools/paste_from_clipboard_spec.rb +7 -7
- data/spec/ollama_chat/tools/paste_into_editor_spec.rb +6 -6
- data/spec/ollama_chat/tools/patch_file_spec.rb +9 -9
- data/spec/ollama_chat/tools/read_file_spec.rb +12 -12
- data/spec/ollama_chat/tools/resolve_tag_spec.rb +6 -6
- data/spec/ollama_chat/tools/roll_dice_spec.rb +6 -6
- data/spec/ollama_chat/tools/run_tests_spec.rb +13 -13
- data/spec/ollama_chat/tools/search_knowledge_spec.rb +17 -17
- data/spec/ollama_chat/tools/search_web_spec.rb +6 -6
- data/spec/ollama_chat/tools/write_file_spec.rb +15 -15
- metadata +4 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 93612bbed949bff6bad6cf89f581b324165c0aa672f6f92b675b63a6a2dce7e0
|
|
4
|
+
data.tar.gz: 79aac9f7dada8df87035af5bdfd2913d16012af44a781b7a20c44a457163b313
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2eb715b50338ddb686e6aab7debd264591e1018216adef57146237a862a5f37f217a31bb481a724b4bb8fcc99144c1a40679e3048c64cb88f0eaaabd546661fc
|
|
7
|
+
data.tar.gz: 8fa30b91ba125939cd506078bcaf6c9549bb43aa6323f9186e0acae218c7814fd8add54aa75b39f907d4affc78889c52a45b2479ec57c4218d76e61e67843ed3
|
data/CHANGES.md
CHANGED
|
@@ -1,5 +1,95 @@
|
|
|
1
1
|
# Changes
|
|
2
2
|
|
|
3
|
+
## 2026-10-05 v0.0.126
|
|
4
|
+
|
|
5
|
+
* **Tool Registry Refactor**: Refactored the tool registry to store tool
|
|
6
|
+
classes instead of pre-built instances.
|
|
7
|
+
* `registered_class(name)` now returns the class for `summary_template`
|
|
8
|
+
access.
|
|
9
|
+
* `registered_tool(name, chat:)` instantiates a fresh instance bound to
|
|
10
|
+
the current session.
|
|
11
|
+
* `Concern#initialize(chat)` injects the chat reference; all tools now
|
|
12
|
+
access config via `self.chat` instead of `opts[:chat]`.
|
|
13
|
+
* Helper methods in `execute_shell`, `generate_image`,
|
|
14
|
+
`get_current_weather`, `get_ghr`, and `patch_file` now use `self.chat`
|
|
15
|
+
directly.
|
|
16
|
+
* Migrated all tool specs to use `described_class.new(chat)` and
|
|
17
|
+
`.execute(tool_call)`.
|
|
18
|
+
|
|
19
|
+
* **Path Allowlists**: Moved path allowlists into tool parameter
|
|
20
|
+
descriptions.
|
|
21
|
+
* Added `allowed_paths` to `tools/concern.rb` to return expanded,
|
|
22
|
+
existing paths for the calling tool's `allowed` config.
|
|
23
|
+
* Removed `tool_paths_allowed` from `tool_calling.rb` and its spec.
|
|
24
|
+
* Injected `allowed_paths.map(&:inspect).join(', ')` into the `path`,
|
|
25
|
+
`source`, and `destination` parameter descriptions of `delete_file`,
|
|
26
|
+
`move_file`, `patch_file`, `read_file`, and `write_file`.
|
|
27
|
+
|
|
28
|
+
* **RAG Handling & Runtime Info**: Relocated `collection_descriptions` from
|
|
29
|
+
`information.rb` to `rag_handling.rb`.
|
|
30
|
+
* Removed `collections` key from `static_runtime_information_values` and
|
|
31
|
+
`default_config.yml`.
|
|
32
|
+
* Migrated `static_runtime_information` and `dynamic_runtime_information`
|
|
33
|
+
from `String#%` to `named_placeholders_interpolate`.
|
|
34
|
+
|
|
35
|
+
* **Collection Commands**: Reworked collection clearing into three distinct
|
|
36
|
+
commands.
|
|
37
|
+
* Split monolithic `clear_collection` into `clear_whole_collection`,
|
|
38
|
+
`clear_collection_tags`, and `clear_collection_sources`.
|
|
39
|
+
* `clear_collection_tags` implements a two-level tag-to-source browser.
|
|
40
|
+
* `clear_collection_sources` implements a source-to-tags browser.
|
|
41
|
+
* Added `use_pager(force:)` keyword in `pager.rb` to bypass size checks
|
|
42
|
+
using `Float::INFINITY`.
|
|
43
|
+
* Added `Kramdown::ANSI::Width.wrap(..., percentage: 90)` to pager views
|
|
44
|
+
for terminal-width wrapping.
|
|
45
|
+
* Bumped `documentrix` minimum to **0.8.0** in `Rakefile` and
|
|
46
|
+
`ollama_chat.gemspec` for the new `records(tags:, sources:)` API.
|
|
47
|
+
* Extended `/collection` regexp and completion for `clear sources`.
|
|
48
|
+
|
|
49
|
+
* **Memory Tools**: Enhanced memory management tools.
|
|
50
|
+
* `memorize` now accepts an optional `update` kwarg (ISO timestamp tag)
|
|
51
|
+
to replace an existing memory entry, returning `replaced_timestamp` and
|
|
52
|
+
`replaced_text`.
|
|
53
|
+
* `forget` now returns `forgotten_text` in the JSON response and includes
|
|
54
|
+
an 80-character snippet in the human-readable `message`.
|
|
55
|
+
* Renamed `set_current_collection` to `set_documents_collection` in
|
|
56
|
+
`rag_handling.rb`, `session_management.rb`, and specs.
|
|
57
|
+
* Made `current_collection` public in `RAGHandling`.
|
|
58
|
+
* Enriched `search_knowledge` tool `collection` param with allowed
|
|
59
|
+
collections from `collection_descriptions` and current default.
|
|
60
|
+
|
|
61
|
+
* **Session Management**:
|
|
62
|
+
* Derive memory trigger from persona at session apply by adding a
|
|
63
|
+
`trigger` block to `default_config.yml`.
|
|
64
|
+
* Extracted `create_memory_collection` into `RAGHandling` as the single
|
|
65
|
+
canonical entry point.
|
|
66
|
+
* In `SessionManagement#session_apply`, auto-derive the session trigger
|
|
67
|
+
from `initial_persona_name` and `config.trigger` when no explicit
|
|
68
|
+
trigger is set.
|
|
69
|
+
* Advertise `C-u` random shortcut in the session prompt to clear prefill
|
|
70
|
+
and obtain a random session name.
|
|
71
|
+
|
|
72
|
+
* **Commands & UX**:
|
|
73
|
+
* Added `-r` flag to `/compose` to quote the last assistant reply as an
|
|
74
|
+
`edit_text` pre-fill.
|
|
75
|
+
* Removed the `confirm?` prompt before `compact_with_retry` in
|
|
76
|
+
`/conversation compact`; compaction now runs immediately.
|
|
77
|
+
* Split `/collection clear` into `clear tags` subcommand and bare `clear`
|
|
78
|
+
routing to `clear_whole_collection`.
|
|
79
|
+
|
|
80
|
+
* **Tool Descriptions & Logging**:
|
|
81
|
+
* Made `paste_into_editor` description imperative.
|
|
82
|
+
* Documented bottom-up edit order in `patch_file` to prevent models from
|
|
83
|
+
incorrectly compensating for line shifts.
|
|
84
|
+
* Log full `edits` array in `patch_file` tool info log payload instead of
|
|
85
|
+
just `edits_count`.
|
|
86
|
+
* Strip ANSI codes from `run_tests` captured output using
|
|
87
|
+
`OllamaChat::Utils::StripANSI`.
|
|
88
|
+
|
|
89
|
+
* **Compatibility & Fixes**:
|
|
90
|
+
* Include `persona_name` in the `default` system prompt template in
|
|
91
|
+
`default_config.yml`.
|
|
92
|
+
|
|
3
93
|
## 2026-10-03 v0.0.125
|
|
4
94
|
|
|
5
95
|
### Added
|
data/Rakefile
CHANGED
|
@@ -40,7 +40,7 @@ GemHadar do
|
|
|
40
40
|
|
|
41
41
|
dependency 'excon', '~> 1.0'
|
|
42
42
|
dependency 'ollama-ruby', '~> 1.23'
|
|
43
|
-
dependency 'documentrix', '>= 0.
|
|
43
|
+
dependency 'documentrix', '>= 0.8.0'
|
|
44
44
|
dependency 'unix_socks', '>= 0.4'
|
|
45
45
|
dependency 'rss', '~> 0.3'
|
|
46
46
|
dependency 'term-ansicolor', '~> 1.11'
|
data/lib/ollama_chat/commands.rb
CHANGED
|
@@ -427,15 +427,7 @@ module OllamaChat::Commands
|
|
|
427
427
|
feedback("Denied.", type: :denied)
|
|
428
428
|
end
|
|
429
429
|
when 'compact'
|
|
430
|
-
|
|
431
|
-
prompt: '🔔 Compact conversation? Old messages will be summarized. (y/n) ',
|
|
432
|
-
yes: /\Ay/i
|
|
433
|
-
)
|
|
434
|
-
then
|
|
435
|
-
compact_with_retry
|
|
436
|
-
else
|
|
437
|
-
feedback("Denied.", type: :denied)
|
|
438
|
-
end
|
|
430
|
+
compact_with_retry
|
|
439
431
|
when 'summarize'
|
|
440
432
|
opts = go_command('s', opts)
|
|
441
433
|
summarize_conversation(sentence: opts[?s])
|
|
@@ -648,12 +640,12 @@ module OllamaChat::Commands
|
|
|
648
640
|
|
|
649
641
|
command(
|
|
650
642
|
name: :collection,
|
|
651
|
-
regexp: %r(^/collection(?:\s+(change|clear|list|rename|update(?: all)?|new|edit|delete|query))?((?:\s+-[er])*)?$),
|
|
652
|
-
complete: [ 'collection', %w[ change clear list rename update update\ all new edit delete query ] ],
|
|
643
|
+
regexp: %r(^/collection(?:\s+(change|clear(?: (?:tags|sources))?|list|rename|update(?: all)?|new|edit|delete|query))?((?:\s+-[er])*)?$),
|
|
644
|
+
complete: [ 'collection', %w[ change clear\ tags clear\ sources clear list rename update update\ all new edit delete query ] ],
|
|
653
645
|
optional: true,
|
|
654
646
|
help: <<~EOT
|
|
655
647
|
📚 Manage RAG collections:
|
|
656
|
-
|
|
648
|
+
- change/clear/clear tags/clear sources/list/rename
|
|
657
649
|
- update: Re-index modified docs
|
|
658
650
|
- update all: Re-index all collections
|
|
659
651
|
- new: Interactively create a new collection
|
|
@@ -665,7 +657,11 @@ module OllamaChat::Commands
|
|
|
665
657
|
) do |subcommand, opts|
|
|
666
658
|
case subcommand
|
|
667
659
|
when 'clear'
|
|
668
|
-
|
|
660
|
+
clear_whole_collection
|
|
661
|
+
when 'clear tags'
|
|
662
|
+
clear_collection_tags
|
|
663
|
+
when 'clear sources'
|
|
664
|
+
clear_collection_sources
|
|
669
665
|
when 'change'
|
|
670
666
|
choose_collection(collection)
|
|
671
667
|
when 'list'
|
|
@@ -829,12 +825,23 @@ module OllamaChat::Commands
|
|
|
829
825
|
|
|
830
826
|
command(
|
|
831
827
|
name: :compose,
|
|
832
|
-
regexp: %r(^/compose
|
|
828
|
+
regexp: %r(^/compose(\s+-r)?\s*$),
|
|
829
|
+
options: '[-r]',
|
|
833
830
|
help: <<~EOT
|
|
834
831
|
\u270D Compose message in external editor
|
|
832
|
+
Options: -r pre-fill with quoted last assistant reply
|
|
835
833
|
EOT
|
|
836
|
-
) do
|
|
837
|
-
|
|
834
|
+
) do |opts|
|
|
835
|
+
opts = go_command('r', opts)
|
|
836
|
+
prefill = if opts[?r]
|
|
837
|
+
if msg = messages.find_last(content: true) { !_1.tool? && _1.role == 'assistant' }
|
|
838
|
+
msg.content.to_s.lines.map { "> #{_1}" }.join + "\n\n"
|
|
839
|
+
else
|
|
840
|
+
feedback('No assistant message to quote.', type: :warn)
|
|
841
|
+
next :next
|
|
842
|
+
end
|
|
843
|
+
end
|
|
844
|
+
edit_text(prefill).full? or :next
|
|
838
845
|
end
|
|
839
846
|
|
|
840
847
|
command(
|
|
@@ -21,7 +21,7 @@ module OllamaChat::Compaction
|
|
|
21
21
|
# @return [String] a short natural-language description of the call.
|
|
22
22
|
def tool_summary_line(tool_name, result)
|
|
23
23
|
default_summary = "was called."
|
|
24
|
-
klass = OllamaChat::Tools.
|
|
24
|
+
klass = OllamaChat::Tools.registered_class(tool_name)
|
|
25
25
|
klass&.summary_template(result:) || default_summary
|
|
26
26
|
rescue StandardError
|
|
27
27
|
default_summary
|
|
@@ -180,7 +180,7 @@ class OllamaChat::FollowChat
|
|
|
180
180
|
feedback(
|
|
181
181
|
("\n%s Execution of tool %s confirmed.\n\n" % [ symbol, bold { name } ])
|
|
182
182
|
)
|
|
183
|
-
result = OllamaChat::Tools.
|
|
183
|
+
result = OllamaChat::Tools.registered_tool(name, chat:).execute(tool_call)
|
|
184
184
|
chat.log(:info, "Tool execution confirmed", data: { tool: name, confirmed: })
|
|
185
185
|
end
|
|
186
186
|
|
|
@@ -327,22 +327,6 @@ module OllamaChat::Information
|
|
|
327
327
|
config.infobar.message.to_h
|
|
328
328
|
end
|
|
329
329
|
|
|
330
|
-
# Retrieves a hash of collection names and their descriptions from the
|
|
331
|
-
# database.
|
|
332
|
-
#
|
|
333
|
-
# This is used to provide context to the AI model about available RAG
|
|
334
|
-
# collections.
|
|
335
|
-
#
|
|
336
|
-
# @return [Hash{String => String}] a hash mapping collection names to their
|
|
337
|
-
# descriptions
|
|
338
|
-
def collection_descriptions
|
|
339
|
-
cols = models::Collection.where(enabled: true)
|
|
340
|
-
.select(:name, :description).order(:name)
|
|
341
|
-
cols.each_with_object({}) do |c, hash|
|
|
342
|
-
hash[c.name] = c.description
|
|
343
|
-
end
|
|
344
|
-
end
|
|
345
|
-
|
|
346
330
|
# Generates a hash containing static runtime information.
|
|
347
331
|
#
|
|
348
332
|
# This method collects session-level constants including the user,
|
|
@@ -353,11 +337,9 @@ module OllamaChat::Information
|
|
|
353
337
|
def static_runtime_information_values
|
|
354
338
|
{
|
|
355
339
|
client: ,
|
|
356
|
-
collections: JSON.pretty_generate(collection_descriptions),
|
|
357
340
|
current_directory: Pathname.pwd.expand_path.to_path,
|
|
358
341
|
languages: config.languages * ', ',
|
|
359
342
|
location: location.on?.full? { location_description } || 'n/a',
|
|
360
|
-
tool_paths_allowed: JSON.pretty_generate(tool_paths_allowed),
|
|
361
343
|
user: ,
|
|
362
344
|
}
|
|
363
345
|
end
|
|
@@ -369,7 +351,9 @@ module OllamaChat::Information
|
|
|
369
351
|
#
|
|
370
352
|
# @return [String] a formatted static runtime information string.
|
|
371
353
|
def static_runtime_information
|
|
372
|
-
prompt(:static_runtime_info).to_s
|
|
354
|
+
prompt(:static_runtime_info).to_s.named_placeholders_interpolate(
|
|
355
|
+
static_runtime_information_values
|
|
356
|
+
)
|
|
373
357
|
end
|
|
374
358
|
|
|
375
359
|
# Returns a formatted string representing the estimated text length of
|
|
@@ -423,7 +407,9 @@ module OllamaChat::Information
|
|
|
423
407
|
#
|
|
424
408
|
# @return [String] the formatted dynamic runtime information string.
|
|
425
409
|
def dynamic_runtime_information
|
|
426
|
-
prompt(:dynamic_runtime_info).to_s
|
|
410
|
+
prompt(:dynamic_runtime_info).to_s.named_placeholders_interpolate(
|
|
411
|
+
dynamic_runtime_information_values
|
|
412
|
+
)
|
|
427
413
|
end
|
|
428
414
|
|
|
429
415
|
# Injects the current dynamic runtime information (the "heartbeat")
|
|
@@ -56,8 +56,6 @@ prompts:
|
|
|
56
56
|
- Location: %{location}
|
|
57
57
|
- Current directory is "%{current_directory}"
|
|
58
58
|
- Client is %{client}.
|
|
59
|
-
- These tools have access to these allowed paths: %{tool_paths_allowed}
|
|
60
|
-
- Search in these collections with `search_knowledge`: %{collections}
|
|
61
59
|
dynamic_runtime_info: |
|
|
62
60
|
There is usually no reason to mention this information to the user unless
|
|
63
61
|
asked about it.
|
|
@@ -424,7 +422,7 @@ prompts:
|
|
|
424
422
|
To see the full output of any tool call above, use lookup_group
|
|
425
423
|
with the group UUID in parentheses.
|
|
426
424
|
system:
|
|
427
|
-
default: <%= OC::OLLAMA::CHAT::SYSTEM || "%{persona}\n\n%{runtime_info}".inspect %>
|
|
425
|
+
default: <%= OC::OLLAMA::CHAT::SYSTEM || "`persona_name`: %{persona_name}\n\n%{persona}\n\n%{runtime_info}".inspect %>
|
|
428
426
|
persona: |
|
|
429
427
|
`persona_name`: %{persona_name}
|
|
430
428
|
|
|
@@ -497,6 +495,10 @@ document_policy: importing
|
|
|
497
495
|
persona:
|
|
498
496
|
# Basename (without .md) of the default persona file in the personae directory.
|
|
499
497
|
default_name: personal_assistant
|
|
498
|
+
trigger:
|
|
499
|
+
enabled: true
|
|
500
|
+
prompt_name: memory_rerank
|
|
501
|
+
text_count: 3
|
|
500
502
|
parsing:
|
|
501
503
|
directory_structure_max_depth: 1
|
|
502
504
|
think:
|
data/lib/ollama_chat/pager.rb
CHANGED
|
@@ -12,14 +12,17 @@ module OllamaChat::Pager
|
|
|
12
12
|
# If the output would exceed the terminal's line capacity, it pipes the content
|
|
13
13
|
# through an appropriate pager command (like 'less' or 'more').
|
|
14
14
|
#
|
|
15
|
+
# @param force [Boolean] bypass the line-count threshold and always
|
|
16
|
+
# invoke the pager, regardless of output length.
|
|
15
17
|
# @yield A block that yields an IO object to write output to
|
|
16
18
|
# @yieldparam [IO] the IO object to write to
|
|
17
|
-
def use_pager
|
|
19
|
+
def use_pager(force: false)
|
|
18
20
|
command = determine_pager_command
|
|
19
21
|
output_buffer = StringIO.new
|
|
20
22
|
yield output_buffer
|
|
21
23
|
buffer = output_buffer.string
|
|
22
|
-
|
|
24
|
+
lines = force ? Float::INFINITY : buffer.count(?\n)
|
|
25
|
+
Kramdown::ANSI::Pager.pager(command:, lines:) do |output|
|
|
23
26
|
output.puts buffer
|
|
24
27
|
end
|
|
25
28
|
end
|
|
@@ -32,7 +32,32 @@ module OllamaChat::RAGHandling
|
|
|
32
32
|
models::Collection[name: collection.to_s]
|
|
33
33
|
end
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
# Creates (or reuses) the persistent memory collection for a persona.
|
|
36
|
+
#
|
|
37
|
+
# The collection is named "memory-#{persona_name}" and is registered in
|
|
38
|
+
# the local database with an empty pattern list. If a collection with
|
|
39
|
+
# that name already exists, it is returned as-is without modification.
|
|
40
|
+
#
|
|
41
|
+
# This is the single canonical entry point for ensuring a persona's
|
|
42
|
+
# memory collection exists before it is referenced by the trigger
|
|
43
|
+
# mechanism, the `memorize`/`forget` tools, or any other consumer.
|
|
44
|
+
#
|
|
45
|
+
# @param persona_name [String] the persona stem (e.g. "miyu_pairing")
|
|
46
|
+
# whose memory collection should be available.
|
|
47
|
+
# @return [String] the collection name (e.g. "memory-miyu_pairing").
|
|
48
|
+
def create_memory_collection(persona_name)
|
|
49
|
+
collection = "memory-#{persona_name}"
|
|
50
|
+
|
|
51
|
+
unless chat.database_collection?(collection)
|
|
52
|
+
models::Collection.create(
|
|
53
|
+
name: collection,
|
|
54
|
+
description: "Memory for persona #{persona_name}",
|
|
55
|
+
patterns: [],
|
|
56
|
+
)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
collection
|
|
60
|
+
end
|
|
36
61
|
|
|
37
62
|
# Returns the name of the currently active document collection.
|
|
38
63
|
#
|
|
@@ -41,45 +66,209 @@ module OllamaChat::RAGHandling
|
|
|
41
66
|
@documents.collection
|
|
42
67
|
end
|
|
43
68
|
|
|
44
|
-
#
|
|
69
|
+
# Retrieves a hash of collection names and their descriptions from the
|
|
70
|
+
# database.
|
|
45
71
|
#
|
|
46
|
-
# This
|
|
47
|
-
#
|
|
48
|
-
#
|
|
49
|
-
#
|
|
50
|
-
|
|
72
|
+
# This is used to provide context to the AI model about available RAG
|
|
73
|
+
# collections.
|
|
74
|
+
#
|
|
75
|
+
# @return [Hash{String => String}] a hash mapping collection names to their
|
|
76
|
+
# descriptions
|
|
77
|
+
def collection_descriptions
|
|
78
|
+
cols = models::Collection.where(enabled: true)
|
|
79
|
+
.select(:name, :description).order(:name)
|
|
80
|
+
cols.each_with_object({}) do |c, hash|
|
|
81
|
+
hash[c.name] = c.description
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
private
|
|
86
|
+
|
|
87
|
+
# The clear_whole_collection method confirms user intent to delete the entire
|
|
88
|
+
# collection, then clears all documents and logs the action, returning self
|
|
89
|
+
# on success.
|
|
90
|
+
#
|
|
91
|
+
# @return [ nil, OllamaChat::Chat ] when the user declines the confirmation
|
|
92
|
+
# prompt, self otherwise.
|
|
93
|
+
def clear_whole_collection
|
|
94
|
+
if confirm?(prompt: '🔔 Really delete the whole collection? Are you sure? (y/n) ', yes: /\Ay/i)
|
|
95
|
+
@documents.clear
|
|
96
|
+
log(:info, "Collection cleared", data: { collection: })
|
|
97
|
+
feedback("Cleared collection #{bold{collection}}.", type: :info)
|
|
98
|
+
self
|
|
99
|
+
else
|
|
100
|
+
feedback('Denied.', type: :denied)
|
|
101
|
+
nil
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Clears documents from the collection through an interactive
|
|
106
|
+
# two-level tag browser.
|
|
107
|
+
#
|
|
108
|
+
# Level 1: choose a tag from the collection.
|
|
109
|
+
# Level 2: inspect records grouped by source, with preview (v) and
|
|
110
|
+
# surgical clear (c) options.
|
|
111
|
+
def clear_collection_tags
|
|
51
112
|
choose_with_state do
|
|
52
113
|
loop do
|
|
53
|
-
|
|
114
|
+
display = @documents.tags.map(&:to_s).sort.unshift('[EXIT]')
|
|
54
115
|
tag = choose_entry(
|
|
55
|
-
|
|
56
|
-
prompt: '
|
|
116
|
+
display,
|
|
117
|
+
prompt: 'Which tag to inspect? %s'
|
|
57
118
|
)
|
|
58
119
|
case tag
|
|
120
|
+
when nil, '[EXIT]'
|
|
121
|
+
feedback('Exiting chooser.')
|
|
122
|
+
break
|
|
123
|
+
else
|
|
124
|
+
records = @documents.records(tags: tag)
|
|
125
|
+
groups = records.group_by { |r| r.source.full? || '(nil)' }
|
|
126
|
+
|
|
127
|
+
if groups.size == 1 && groups.values.first.size == 1
|
|
128
|
+
record = groups.values.first.first
|
|
129
|
+
source = record.source
|
|
130
|
+
snippet = record.text.strip[0, 72].inspect
|
|
131
|
+
label = source ? "#{source} #{snippet}" : snippet
|
|
132
|
+
feedback(label, type: :info)
|
|
133
|
+
view_tag_records(tag, [record])
|
|
134
|
+
ask?(prompt: " [c]lear / other to go back: ") =~ /\Ac/i or next
|
|
135
|
+
clear_tag_records(tag, source)
|
|
136
|
+
else
|
|
137
|
+
group_lines = []
|
|
138
|
+
group_map = {}
|
|
139
|
+
groups.sort_by { |src, _| src }.each do |src, recs|
|
|
140
|
+
if recs.size == 1
|
|
141
|
+
snippet = recs.first.text.strip[0, 72].inspect
|
|
142
|
+
line = src == '(nil)' ? snippet : "#{src} #{snippet}"
|
|
143
|
+
else
|
|
144
|
+
line = "#{src} (#{recs.size} records)"
|
|
145
|
+
end
|
|
146
|
+
group_lines << line
|
|
147
|
+
group_map[line] = [src, recs]
|
|
148
|
+
end
|
|
149
|
+
group_lines.unshift('[back]')
|
|
150
|
+
chosen = choose_entry(
|
|
151
|
+
group_lines,
|
|
152
|
+
prompt: "Tag #{tag.to_s.gsub(?%, '%%')} — which source group? %s"
|
|
153
|
+
)
|
|
154
|
+
next if chosen.nil? || chosen == '[back]'
|
|
155
|
+
src, recs = group_map[chosen]
|
|
156
|
+
action = ask?(prompt: " [v]iew / [c]lear / other to go back: ")
|
|
157
|
+
case action
|
|
158
|
+
when /\Av/i
|
|
159
|
+
view_tag_records(tag, recs)
|
|
160
|
+
when /\Ac/i
|
|
161
|
+
clear_tag_records(tag, src == '(nil)' ? nil : src)
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# Renders tag-filtered records in the pager.
|
|
170
|
+
def view_tag_records(tag, records)
|
|
171
|
+
use_pager(force: true) do |output|
|
|
172
|
+
output.puts "#{bold{tag}} (#{records.size} records)"
|
|
173
|
+
output.puts
|
|
174
|
+
records.each do |record|
|
|
175
|
+
output.puts kramdown_ansi_parse(<<~EOT)
|
|
176
|
+
**#{record.source}**
|
|
177
|
+
```
|
|
178
|
+
#{Kramdown::ANSI::Width.wrap(record.text.sub(/\n+\z/, ''), percentage: 90)}
|
|
179
|
+
```
|
|
180
|
+
---
|
|
181
|
+
EOT
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# Clears records for a tag, using source_remove when a source is
|
|
187
|
+
# present, or clear(tags:) for memory entries (no source).
|
|
188
|
+
def clear_tag_records(tag, source)
|
|
189
|
+
if source
|
|
190
|
+
@documents.source_remove(source)
|
|
191
|
+
else
|
|
192
|
+
@documents.clear(tags: [ tag ])
|
|
193
|
+
end
|
|
194
|
+
log(:info, 'Tag cleared from collection',
|
|
195
|
+
data: { collection:, tag:, source: })
|
|
196
|
+
feedback(
|
|
197
|
+
"Cleared tag #{tag} from collection #{bold{collection}}.",
|
|
198
|
+
type: :info
|
|
199
|
+
)
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
# Removes source documents from the collection through an interactive
|
|
203
|
+
# user interface.
|
|
204
|
+
#
|
|
205
|
+
# Each source is displayed with its associated tags for context.
|
|
206
|
+
# Users can preview a source's records in a pager before confirming
|
|
207
|
+
# removal.
|
|
208
|
+
def clear_collection_sources
|
|
209
|
+
choose_with_state do
|
|
210
|
+
loop do
|
|
211
|
+
sources = Set.new
|
|
212
|
+
source_tags = Hash.new { |h, k| h[k] = Set.new }
|
|
213
|
+
@documents.records.each do |record|
|
|
214
|
+
source = record.source.full? or next
|
|
215
|
+
sources.add(source)
|
|
216
|
+
source_tags[source].merge(record.tags)
|
|
217
|
+
end
|
|
218
|
+
display_map = {}
|
|
219
|
+
sources.each do |s|
|
|
220
|
+
tags = source_tags[s].map(&:to_s)
|
|
221
|
+
line = tags.any? ? "#{s} [#{tags.join(', ')}]" : s
|
|
222
|
+
display_map[line] = s
|
|
223
|
+
end
|
|
224
|
+
display = display_map.keys
|
|
225
|
+
display.unshift('[EXIT]')
|
|
226
|
+
chosen = choose_entry(
|
|
227
|
+
display,
|
|
228
|
+
prompt: 'Which source to remove from the collection? %s'
|
|
229
|
+
)
|
|
230
|
+
case chosen
|
|
59
231
|
when nil, '[EXIT]'
|
|
60
232
|
feedback("Exiting chooser.")
|
|
61
233
|
break
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
234
|
+
else
|
|
235
|
+
source = display_map[chosen]
|
|
236
|
+
action = ask?(prompt: " [v]iew / [c]lear / other to go back: ")
|
|
237
|
+
case action
|
|
238
|
+
when /\Av/i
|
|
239
|
+
records = @documents.records(sources: [ source ])
|
|
240
|
+
use_pager do |output|
|
|
241
|
+
output.puts "#{bold{source}} (#{records.size} records)"
|
|
242
|
+
output.puts
|
|
243
|
+
records.each do |record|
|
|
244
|
+
output.puts kramdown_ansi_parse(<<~EOT)
|
|
245
|
+
**[#{record.tags.join(', ')}]**
|
|
246
|
+
```
|
|
247
|
+
#{Kramdown::ANSI::Width.wrap(record.text.sub(/\n+\z/, ''), percentage: 90)}
|
|
248
|
+
```
|
|
249
|
+
---
|
|
250
|
+
EOT
|
|
251
|
+
end
|
|
252
|
+
end
|
|
253
|
+
when /\Ac/i
|
|
254
|
+
@documents.source_remove(source)
|
|
255
|
+
log(:info, "Source removed from collection",
|
|
256
|
+
data: { collection:, source: })
|
|
257
|
+
feedback(
|
|
258
|
+
"Removed source #{source} from collection #{bold{collection}}.",
|
|
259
|
+
type: :info
|
|
260
|
+
)
|
|
68
261
|
end
|
|
69
|
-
when /./
|
|
70
|
-
@documents.clear(tags: [ tag ])
|
|
71
|
-
log(:info, "Tag cleared from collection", data: { collection:, tag: })
|
|
72
|
-
feedback("Cleared tag #{tag} from collection #{bold{collection}}.", type: :info)
|
|
73
262
|
end
|
|
74
263
|
end
|
|
75
264
|
end
|
|
76
265
|
end
|
|
77
266
|
|
|
78
|
-
# Sets the
|
|
267
|
+
# Sets the documents collection to collection.
|
|
79
268
|
#
|
|
80
269
|
# @param collection [String, Symbol] the name of the collection to set
|
|
81
270
|
# @return [String, Symbol] the newly set collection name
|
|
82
|
-
def
|
|
271
|
+
def set_documents_collection(collection)
|
|
83
272
|
@documents.collection = collection
|
|
84
273
|
end
|
|
85
274
|
|
|
@@ -163,7 +163,7 @@ module OllamaChat::SessionManagement
|
|
|
163
163
|
session_name = nil
|
|
164
164
|
loop do
|
|
165
165
|
session_name = ask?(
|
|
166
|
-
prompt: "❓ Enter new session name #{action}, C-c ⇒ cancel: ",
|
|
166
|
+
prompt: "❓ Enter new session name #{action}, C-u ⇒ random, C-c ⇒ cancel: ",
|
|
167
167
|
prefill: default_name
|
|
168
168
|
)
|
|
169
169
|
if session_name.nil?
|
|
@@ -264,7 +264,26 @@ module OllamaChat::SessionManagement
|
|
|
264
264
|
end
|
|
265
265
|
end
|
|
266
266
|
|
|
267
|
+
# Applies the loaded session to the chat context.
|
|
268
|
+
#
|
|
269
|
+
# Performs the post-lock setup sequence:
|
|
270
|
+
# 1. Updates the session's working directory to `Dir.pwd`.
|
|
271
|
+
# 2. Initializes the in-memory message history from the session
|
|
272
|
+
# payload.
|
|
273
|
+
# 3. Repairs any orphaned group UUIDs in the message list.
|
|
274
|
+
# 4. Ensures the persistent memory collection for the session's
|
|
275
|
+
# persona exists in the database (no-op if already present).
|
|
276
|
+
#
|
|
277
|
+
# Called from {#setup_session} after the session has been locked.
|
|
278
|
+
#
|
|
279
|
+
# @return [OllamaChat::Database::Models::Session] the applied session
|
|
280
|
+
# instance.
|
|
267
281
|
def session_apply
|
|
282
|
+
initial_persona_name.full? do |persona_name|
|
|
283
|
+
collection = create_memory_collection(persona_name)
|
|
284
|
+
session.trigger.present? or
|
|
285
|
+
session.trigger = { collection => chat.config.trigger.to_h }
|
|
286
|
+
end
|
|
268
287
|
session.update(working_directory: Dir.pwd)
|
|
269
288
|
init_history
|
|
270
289
|
repair_group_uuids
|
|
@@ -503,6 +522,7 @@ module OllamaChat::SessionManagement
|
|
|
503
522
|
existing = session.trigger.to_h
|
|
504
523
|
|
|
505
524
|
collection = nil
|
|
525
|
+
|
|
506
526
|
choose_with_state do
|
|
507
527
|
# Prefill with the first configured collection as a starting
|
|
508
528
|
# point. Fine for the single-trigger case; with multiple
|
|
@@ -630,11 +650,11 @@ module OllamaChat::SessionManagement
|
|
|
630
650
|
if session.lock?(chat: self)
|
|
631
651
|
messages.read_conversation_jsonl(session.messages.to_s)
|
|
632
652
|
if current_collection = session.current_collection.full? and
|
|
633
|
-
|
|
653
|
+
database_collection?(current_collection)
|
|
634
654
|
then
|
|
635
|
-
|
|
655
|
+
set_documents_collection(current_collection)
|
|
636
656
|
else
|
|
637
|
-
|
|
657
|
+
set_documents_collection(:default)
|
|
638
658
|
end
|
|
639
659
|
session.current_model.full? { use_model(_1) }
|
|
640
660
|
set_default_persona_name(session.default_persona_name.full? || :none)
|
|
@@ -55,7 +55,9 @@ 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 {
|
|
58
|
+
enabled_tools.filter_map {
|
|
59
|
+
OllamaChat::Tools.registered_tool(_1, chat: self)&.to_hash
|
|
60
|
+
}
|
|
59
61
|
end
|
|
60
62
|
|
|
61
63
|
# The default_enabled_tools method returns an array of tool names that are
|
|
@@ -181,21 +183,6 @@ module OllamaChat::ToolCalling
|
|
|
181
183
|
end
|
|
182
184
|
end
|
|
183
185
|
|
|
184
|
-
# The tool_paths_allowed method returns a hash mapping each enabled tool name
|
|
185
|
-
# to its list of allowed paths or patterns. @return [Hash] a hash where keys
|
|
186
|
-
# are tool names and values are the allowed path lists
|
|
187
|
-
def tool_paths_allowed
|
|
188
|
-
config.tools.functions.to_h.
|
|
189
|
-
select { |name, value| tool_enabled?(name) && value[:allowed].present? }.
|
|
190
|
-
sort_by(&:first).
|
|
191
|
-
each_with_object({}) do |(name, value), hash|
|
|
192
|
-
hash[name.to_s] = value[:allowed].filter_map do
|
|
193
|
-
pathname = Pathname.new(_1).expand_path
|
|
194
|
-
pathname.exist?.full? { pathname.to_path }
|
|
195
|
-
end
|
|
196
|
-
end
|
|
197
|
-
end
|
|
198
|
-
|
|
199
186
|
private
|
|
200
187
|
|
|
201
188
|
def handle_tool_call_results?(&block)
|