ollama_chat 0.0.124 → 0.0.125
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 +87 -0
- data/Rakefile +1 -1
- data/bin/ollama_chat_log +1 -1
- data/lib/ollama_chat/chat.rb +5 -13
- data/lib/ollama_chat/collection_search.rb +102 -6
- data/lib/ollama_chat/commands.rb +6 -3
- data/lib/ollama_chat/compaction.rb +6 -2
- data/lib/ollama_chat/database/migrations/011_add_trigger_to_sessions.rb +7 -0
- data/lib/ollama_chat/database/models/session.rb +11 -0
- data/lib/ollama_chat/database/session_locking.rb +3 -2
- data/lib/ollama_chat/information.rb +35 -3
- data/lib/ollama_chat/ollama_chat_config/default_config.yml +40 -8
- data/lib/ollama_chat/session_management.rb +80 -4
- data/lib/ollama_chat/source_fetching.rb +2 -2
- data/lib/ollama_chat/tools/search_knowledge.rb +10 -2
- data/lib/ollama_chat/utils/tag_resolver.rb +1 -1
- data/lib/ollama_chat/version.rb +1 -1
- data/lib/ollama_chat/web_searching.rb +3 -3
- data/ollama_chat.gemspec +6 -6
- data/spec/ollama_chat/chat_spec.rb +1 -1
- data/spec/ollama_chat/collection_search_spec.rb +134 -0
- data/spec/ollama_chat/command_concern_spec.rb +174 -0
- data/spec/ollama_chat/commands_spec.rb +330 -2
- data/spec/ollama_chat/compaction_spec.rb +1 -1
- data/spec/ollama_chat/parsing_spec.rb +5 -5
- data/spec/ollama_chat/session_management_spec.rb +66 -2
- data/spec/ollama_chat/source_fetching_spec.rb +2 -2
- data/spec/ollama_chat/tools/resolve_tag_spec.rb +5 -9
- data/spec/ollama_chat/tools/search_knowledge_spec.rb +16 -11
- metadata +9 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3de94c682281a4667e89feee589883bd33acf082165f37ce443072117a773167
|
|
4
|
+
data.tar.gz: 94cccbb92af48198a70879cf1028a0aebfe0cda49e25ccd82352cb35d9054bf7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d3bad30e34ad309fe6c2f2263802af2778eded9459affc6c6713c6383f58706fe54411e7a4b6f087c487718d0a6ea8e07e90dfed22ca1a64b563d207ce61287e
|
|
7
|
+
data.tar.gz: 8f9befaa4a6a519b8f7b4424f0d16380663b863e039f08142fc667b01ecb83aa989b9e635ea5373b22191c7ba8f568e97dd210d732d1d6d5099dd3e5cffb74ee
|
data/CHANGES.md
CHANGED
|
@@ -1,5 +1,92 @@
|
|
|
1
1
|
# Changes
|
|
2
2
|
|
|
3
|
+
## 2026-10-03 v0.0.125
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
* Session-scoped trigger injection mechanism:
|
|
8
|
+
* New migration `011_add_trigger_to_sessions` adding a `:text` JSON column to
|
|
9
|
+
`sessions`.
|
|
10
|
+
* `Session#trigger` attribute with `plugin :serialization, :json` and `nil`
|
|
11
|
+
default.
|
|
12
|
+
* `CollectionSearch#trigger_inject` dispatch logic including embedding
|
|
13
|
+
guards, collection switching, document finding, and reranking.
|
|
14
|
+
* New `/session trigger edit` command registered in `commands.rb`.
|
|
15
|
+
* `edit_session_trigger` method in `session_management.rb` supporting dual
|
|
16
|
+
chooser and prefill.
|
|
17
|
+
* Trigger configuration display (⚡ block) added to `info_rag` in
|
|
18
|
+
`information.rb`.
|
|
19
|
+
* `runtime_information_inject` extracted from `chat.rb` into
|
|
20
|
+
`information.rb`.
|
|
21
|
+
* New prompt contexts: `prompts.rerank` (`default`, `memory_rerank`,
|
|
22
|
+
`lorebook_rerank`), `prompts.prompt.snippets_retrieval`, and
|
|
23
|
+
`prompts.prompt.snippets_trigger`.
|
|
24
|
+
* `persona_name` line added to `prompts.system.persona`.
|
|
25
|
+
* `TagResult` changed from `Data.define` to `Struct.new` to allow mutable
|
|
26
|
+
fields.
|
|
27
|
+
* `∅` fallback for empty patterns in `collection_stats`.
|
|
28
|
+
* Info-level logging for embedding requests/responses and rerank survivors in
|
|
29
|
+
`collection_search.rb`.
|
|
30
|
+
* New spec file `collection_search_spec.rb` with 7 examples for
|
|
31
|
+
`#trigger_inject`.
|
|
32
|
+
|
|
33
|
+
### Changed
|
|
34
|
+
|
|
35
|
+
* `SessionLocking#lock?` now accepts a required `chat:` keyword argument to
|
|
36
|
+
decouple feedback delegation from the host object.
|
|
37
|
+
* `search_knowledge.rb` updated to use `snippets_retrieval` DB prompt,
|
|
38
|
+
`collection` block, and `database_collection?` guard.
|
|
39
|
+
* `@embedding` instance variable swept to `embedding` accessor in
|
|
40
|
+
`information.rb`, `source_fetching.rb`, and `web_searching.rb`.
|
|
41
|
+
* `Tins::Duration#format` used for compaction voice timing in
|
|
42
|
+
`report_compaction`, replacing `%.1f seconds`.
|
|
43
|
+
* `tins` dependency bumped from `~> **1.58**` to `~> **1.60**` to support
|
|
44
|
+
unpadded `%H`, `%M`, and `%u` directives.
|
|
45
|
+
* `resolve_tag_spec.rb` updated to replace an unreachable rescue-path test with
|
|
46
|
+
a "tagged file does not exist" test.
|
|
47
|
+
* `search_knowledge_spec.rb` updated for new prompt names, `collection` block,
|
|
48
|
+
and `tags_set` doubles.
|
|
49
|
+
* `compaction_spec.rb` regex updated to match the new duration format.
|
|
50
|
+
* `Tins::GO.go` flag string reordered in `bin/ollama_chat_log`.
|
|
51
|
+
|
|
52
|
+
### Fixed
|
|
53
|
+
|
|
54
|
+
* Spec expectations in `session_management_spec.rb`, `parsing_spec.rb`, and
|
|
55
|
+
`source_fetching_spec.rb` updated to use Ruby 3.1+ hash shorthand `chat:`.
|
|
56
|
+
|
|
57
|
+
### Tests
|
|
58
|
+
|
|
59
|
+
* Added specs for `CommandConcern` class methods:
|
|
60
|
+
* `help_message` via `OllamaChat::Commands`: table structure, category
|
|
61
|
+
headers, pattern filtering, subcommand expansion, and optional/mandatory
|
|
62
|
+
asterisks.
|
|
63
|
+
* `command_completions`: array shape, bare names, subcommand variants, and
|
|
64
|
+
sort order.
|
|
65
|
+
* `Command` class: `execute_if_match?` (match, captures, nil-regexp path,
|
|
66
|
+
no-context error), `completions` (product, optional bare, strict),
|
|
67
|
+
`command_names`, and `optional?`.
|
|
68
|
+
* Duplicate registration guard against the real `:copy` entry in
|
|
69
|
+
`OllamaChat::Commands`.
|
|
70
|
+
* Added specs for remaining command dispatch gaps:
|
|
71
|
+
* `/paste -i` stdin path via `paste_from_stdin`.
|
|
72
|
+
* `/last -v` voice output via `speak_last`.
|
|
73
|
+
* `/regenerate` success path (drop + return content).
|
|
74
|
+
* `/prompt delete` routes to `choose_and_delete_prompt`.
|
|
75
|
+
* `/web` without explicit count passes `nil`.
|
|
76
|
+
* `/character` JSON success paths: `info` (pager), `load` (raw content),
|
|
77
|
+
`import` (delegation).
|
|
78
|
+
* `/character` PNG `load` with `PNGMetadataExtractor`.
|
|
79
|
+
* Added specs for `SessionManagement#edit_session_trigger`:
|
|
80
|
+
* Setting trigger from `nil` to a configured hash with `enabled`,
|
|
81
|
+
`prompt_name`, and `text_count`.
|
|
82
|
+
* Updating config for an already-configured collection.
|
|
83
|
+
* Switching the trigger to a different collection name.
|
|
84
|
+
* Added specs for session model options and trigger edit:
|
|
85
|
+
* `/session model options` delegates to `edit_session_model_options` and
|
|
86
|
+
returns `:next`.
|
|
87
|
+
* `/session trigger edit` delegates to `edit_session_trigger` and returns
|
|
88
|
+
`:next`.
|
|
89
|
+
|
|
3
90
|
## 2026-10-02 v0.0.124
|
|
4
91
|
|
|
5
92
|
### Added
|
data/Rakefile
CHANGED
|
@@ -49,7 +49,7 @@ GemHadar do
|
|
|
49
49
|
dependency 'reverse_markdown', '~> 3.0'
|
|
50
50
|
dependency 'kramdown-ansi', '~> 0.5'
|
|
51
51
|
dependency 'complex_config', '~> 0.22', '>= 0.22.2'
|
|
52
|
-
dependency 'tins', '~> 1.
|
|
52
|
+
dependency 'tins', '~> 1.60'
|
|
53
53
|
dependency 'search_ui', '>= 0.2'
|
|
54
54
|
dependency 'amatch', '~> 0.4'
|
|
55
55
|
dependency 'pdf-reader', '~> 2.0'
|
data/bin/ollama_chat_log
CHANGED
data/lib/ollama_chat/chat.rb
CHANGED
|
@@ -314,10 +314,11 @@ class OllamaChat::Chat
|
|
|
314
314
|
# instance when a background thread was spawned (so callers can call
|
|
315
315
|
# `cancel_speaking`), or `nil` for inline playback or when voice is off
|
|
316
316
|
def speak(text, background: true)
|
|
317
|
+
session or return
|
|
317
318
|
voice.on? or return
|
|
318
|
-
|
|
319
|
+
current_voice = session.current_voice.full? or return
|
|
319
320
|
voice_handler.method_defined?(:speak) or return
|
|
320
|
-
voice_handler.new(chat: self, voice:).speak(text, background:)
|
|
321
|
+
voice_handler.new(chat: self, voice: current_voice).speak(text, background:)
|
|
321
322
|
end
|
|
322
323
|
|
|
323
324
|
# Returns a human-readable string representation of the Chat object,
|
|
@@ -553,17 +554,8 @@ class OllamaChat::Chat
|
|
|
553
554
|
group_uuid:
|
|
554
555
|
)
|
|
555
556
|
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
messages << OllamaChat::Message.new(
|
|
559
|
-
role: 'user',
|
|
560
|
-
tool_name: ,
|
|
561
|
-
sender_name: tool_name,
|
|
562
|
-
content: dynamic_runtime_information,
|
|
563
|
-
images: @images.dup,
|
|
564
|
-
group_uuid:
|
|
565
|
-
)
|
|
566
|
-
end
|
|
557
|
+
runtime_information_inject(group_uuid:)
|
|
558
|
+
trigger_inject(content, group_uuid:)
|
|
567
559
|
end
|
|
568
560
|
@images.clear
|
|
569
561
|
handler = OllamaChat::FollowChat.new(
|
|
@@ -24,14 +24,37 @@ module OllamaChat::CollectionSearch
|
|
|
24
24
|
tags = Documentrix::Utils::Tags.new(
|
|
25
25
|
tags, valid_tag: /\A#*([-\w.\]\[]+)/
|
|
26
26
|
)
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
effective_query = query.first(
|
|
28
|
+
config.embedding.model.context_length
|
|
29
|
+
)
|
|
30
|
+
embedding_prompt = config.embedding.model.prompt?
|
|
31
|
+
log(
|
|
32
|
+
:info, 'Embedding search request',
|
|
33
|
+
data: {
|
|
34
|
+
model: config.embedding.model.to_h,
|
|
35
|
+
collection: collection,
|
|
36
|
+
query: effective_query,
|
|
37
|
+
prompt: embedding_prompt,
|
|
38
|
+
}
|
|
39
|
+
)
|
|
40
|
+
records = @documents.find_where(
|
|
41
|
+
effective_query,
|
|
29
42
|
tags:,
|
|
30
|
-
prompt:
|
|
43
|
+
prompt: embedding_prompt,
|
|
31
44
|
text_size:,
|
|
32
45
|
text_count:,
|
|
33
46
|
min_similarity:
|
|
34
47
|
)
|
|
48
|
+
log(
|
|
49
|
+
:info, 'Embedding search response',
|
|
50
|
+
data: {
|
|
51
|
+
collection: collection,
|
|
52
|
+
query: effective_query,
|
|
53
|
+
result_count: records.size,
|
|
54
|
+
top: records.first&.similarity&.round(4),
|
|
55
|
+
}
|
|
56
|
+
)
|
|
57
|
+
records
|
|
35
58
|
end
|
|
36
59
|
|
|
37
60
|
# Reranks a set of records using the LLM with the `rerank` prompt.
|
|
@@ -43,11 +66,11 @@ module OllamaChat::CollectionSearch
|
|
|
43
66
|
# @return [Array<Documentrix::Utils::TagResult>] filtered records
|
|
44
67
|
def rerank_records(query, records, prompt_name: nil)
|
|
45
68
|
candidates = records.each_with_index.map { |r, i|
|
|
46
|
-
"[#{i}] #{truncate(r.text.
|
|
69
|
+
"[#{i}] #{r.tags_set.to_s(link: false)} #{truncate(r.text.squeeze, length: 300)}"
|
|
47
70
|
}.join("\n")
|
|
48
71
|
|
|
49
|
-
prompt_name ||= '
|
|
50
|
-
rerank_prompt = prompt(prompt_name) or
|
|
72
|
+
prompt_name ||= 'default'
|
|
73
|
+
rerank_prompt = prompt(prompt_name, context: 'rerank') or
|
|
51
74
|
raise 'missing prompt %s' % prompt_name.inspect
|
|
52
75
|
rerank_prompt = rerank_prompt.to_s
|
|
53
76
|
.named_placeholders_interpolate({ query:, candidates: })
|
|
@@ -63,4 +86,77 @@ module OllamaChat::CollectionSearch
|
|
|
63
86
|
end
|
|
64
87
|
records
|
|
65
88
|
end
|
|
89
|
+
|
|
90
|
+
# Injects relevant snippets from the session's trigger collection
|
|
91
|
+
# into the conversation as a tool message.
|
|
92
|
+
#
|
|
93
|
+
# Reads the first entry of the session's `trigger` hash, performs a
|
|
94
|
+
# vector search in the configured collection, reranks the candidates
|
|
95
|
+
# via the LLM, and appends a `trigger_inject` tool message containing
|
|
96
|
+
# the surviving snippets (text, similarity, tags). The message is
|
|
97
|
+
# grouped with the current user message via `group_uuid`.
|
|
98
|
+
#
|
|
99
|
+
# @param content [String] the parsed user message text used as the
|
|
100
|
+
# search query
|
|
101
|
+
# @param group_uuid [String, nil] the group UUID for message grouping
|
|
102
|
+
# (shared with the user message and runtime-info message)
|
|
103
|
+
#
|
|
104
|
+
# @return [self, nil] self if snippets were injected, nil if no
|
|
105
|
+
# trigger is configured, the trigger or embedding is disabled, the
|
|
106
|
+
# collection or prompt is missing, or no snippets survived reranking
|
|
107
|
+
def trigger_inject(content, group_uuid:)
|
|
108
|
+
embedding.on? or return
|
|
109
|
+
trigger = session.trigger&.first or return
|
|
110
|
+
collection, config = trigger
|
|
111
|
+
unless config['enabled']
|
|
112
|
+
log(:info, 'Trigger for collection %s disabled.' % collection)
|
|
113
|
+
return
|
|
114
|
+
end
|
|
115
|
+
unless col = database_collection?(collection)
|
|
116
|
+
log(:error, 'Unknown collection named %s' % collection)
|
|
117
|
+
return
|
|
118
|
+
end
|
|
119
|
+
prompt_name = config['prompt_name']
|
|
120
|
+
unless rerank_prompt = prompt(prompt_name, context: 'rerank').full?(:to_s)
|
|
121
|
+
log(
|
|
122
|
+
:error, 'Unknown rerank prompt named %s' % prompt_name,
|
|
123
|
+
data: { config: }
|
|
124
|
+
)
|
|
125
|
+
return
|
|
126
|
+
end
|
|
127
|
+
records = []
|
|
128
|
+
switch_collection(collection) do
|
|
129
|
+
records = find_document_records(content, text_count: config['text_count'])
|
|
130
|
+
records.empty? and return
|
|
131
|
+
pre_rerank = records.size
|
|
132
|
+
records = rerank_records(content, records, prompt_name:)
|
|
133
|
+
log(:info, 'Trigger: %d/%d passed rerank for %s' % [ records.size, pre_rerank, collection ],
|
|
134
|
+
data: { collection:, prompt_name: })
|
|
135
|
+
records.empty? and return
|
|
136
|
+
end
|
|
137
|
+
snippets = records.map { |record|
|
|
138
|
+
{
|
|
139
|
+
text: record.text,
|
|
140
|
+
similarity: record.similarity.to_f,
|
|
141
|
+
tags: record.tags_set.to_s(link: false),
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
message_content = {
|
|
145
|
+
prompt: prompt('snippets_trigger').to_s,
|
|
146
|
+
collection: {
|
|
147
|
+
name: collection,
|
|
148
|
+
description: col.description&.to_s,
|
|
149
|
+
},
|
|
150
|
+
snippets:
|
|
151
|
+
}.to_json
|
|
152
|
+
tool_name = 'trigger_inject'
|
|
153
|
+
messages << OllamaChat::Message.new(
|
|
154
|
+
role: 'user',
|
|
155
|
+
tool_name: ,
|
|
156
|
+
sender_name: tool_name,
|
|
157
|
+
content: message_content,
|
|
158
|
+
group_uuid:
|
|
159
|
+
)
|
|
160
|
+
self
|
|
161
|
+
end
|
|
66
162
|
end
|
data/lib/ollama_chat/commands.rb
CHANGED
|
@@ -113,15 +113,16 @@ module OllamaChat::Commands
|
|
|
113
113
|
|
|
114
114
|
command(
|
|
115
115
|
name: :session,
|
|
116
|
-
regexp: %r(^/session(?:\s+(change|previous|list|new|duplicate|rename|delete|model options change|model options))?((?:\s+-(?:p\s*\w+))*)(?:\s+([^-].*))?$),
|
|
117
|
-
complete: [ 'session', %w[ change previous list new duplicate rename delete model\ options\ change model\ options ] ],
|
|
116
|
+
regexp: %r(^/session(?:\s+(change|previous|list|new|duplicate|rename|delete|model options change|model options|trigger edit))?((?:\s+-(?:p\s*\w+))*)(?:\s+([^-].*))?$),
|
|
117
|
+
complete: [ 'session', %w[ change previous list new duplicate rename delete model\ options\ change model\ options trigger\ edit ] ],
|
|
118
118
|
optional: true,
|
|
119
119
|
options: "[-p profile]\n[name]",
|
|
120
120
|
help: <<~EOT
|
|
121
121
|
💬 Manage sessions:
|
|
122
122
|
- list/new/delete/rename/duplicate
|
|
123
123
|
- change [name]/previous
|
|
124
|
-
|
|
124
|
+
- model options/change
|
|
125
|
+
- trigger edit
|
|
125
126
|
EOT
|
|
126
127
|
) do |subcommand, opts, name|
|
|
127
128
|
case subcommand
|
|
@@ -152,6 +153,8 @@ module OllamaChat::Commands
|
|
|
152
153
|
else
|
|
153
154
|
feedback("No previous session defined.", type: :info)
|
|
154
155
|
end
|
|
156
|
+
when 'trigger edit'
|
|
157
|
+
edit_session_trigger
|
|
155
158
|
end
|
|
156
159
|
:next
|
|
157
160
|
end
|
|
@@ -295,11 +295,15 @@ module OllamaChat::Compaction
|
|
|
295
295
|
Context: #{result.context_before} → #{result.context_after}
|
|
296
296
|
Stored total: #{result.stored_total}
|
|
297
297
|
EOT
|
|
298
|
+
duration = Tins::Duration.new(duration)
|
|
299
|
+
duration_string = duration.format(
|
|
300
|
+
duration.minutes? ? '%M minutes and %u seconds' : '%u seconds'
|
|
301
|
+
)
|
|
298
302
|
speak(
|
|
299
|
-
'Compressed conversation context from %.f to %.f in
|
|
303
|
+
'Compressed conversation context from %.f to %.f in %s.' % [
|
|
300
304
|
100 * result.context_filled_before,
|
|
301
305
|
100 * result.context_filled_after,
|
|
302
|
-
|
|
306
|
+
duration_string,
|
|
303
307
|
]
|
|
304
308
|
)
|
|
305
309
|
end
|
|
@@ -12,6 +12,7 @@ class OllamaChat::Database::Models::Session < Sequel::Model(OllamaChat::DB)
|
|
|
12
12
|
plugin :touch
|
|
13
13
|
plugin :serialization, :json, :tools_default_enabled
|
|
14
14
|
plugin :serialization, :json, :model_options
|
|
15
|
+
plugin :serialization, :json, :trigger
|
|
15
16
|
plugin :validation_helpers
|
|
16
17
|
|
|
17
18
|
# Validates the session instance.
|
|
@@ -133,6 +134,15 @@ class OllamaChat::Database::Models::Session < Sequel::Model(OllamaChat::DB)
|
|
|
133
134
|
# @!attribute [v] model_options
|
|
134
135
|
# @return [String, nil] A JSON-serialized string containing model-specific options.
|
|
135
136
|
#
|
|
137
|
+
# @!attribute [v] trigger
|
|
138
|
+
# @return [Hash, nil] Maps collection names to search/rerank
|
|
139
|
+
# overrides, e.g.
|
|
140
|
+
# `{ 'memory-miyu' => { 'prompt_name' => 'memory_rerank',
|
|
141
|
+
# 'text_count' => 5 } }`.
|
|
142
|
+
# Recognized keys: `prompt_name`, `text_count`, `text_size`,
|
|
143
|
+
# `min_similarity`.
|
|
144
|
+
# `nil` when the trigger mechanism is disabled for this session.
|
|
145
|
+
#
|
|
136
146
|
# @!attribute [v] messages
|
|
137
147
|
# @return [String] The full conversation history, stored in JSONL format.
|
|
138
148
|
#
|
|
@@ -184,6 +194,7 @@ class OllamaChat::Database::Models::Session < Sequel::Model(OllamaChat::DB)
|
|
|
184
194
|
context_format: chat.config.context.format,
|
|
185
195
|
working_directory: Dir.pwd,
|
|
186
196
|
messages: '',
|
|
197
|
+
trigger: nil,
|
|
187
198
|
}
|
|
188
199
|
new(attributes)
|
|
189
200
|
end
|
|
@@ -25,10 +25,11 @@ module OllamaChat::Database::SessionLocking
|
|
|
25
25
|
|
|
26
26
|
# Attempts to acquire a lock on the session.
|
|
27
27
|
#
|
|
28
|
+
# @param chat [OllamaChat] the chat instance that receives feedback
|
|
28
29
|
# @return [Boolean] true if the lock was successfully acquired, false if already locked.
|
|
29
|
-
def lock?
|
|
30
|
+
def lock?(chat:)
|
|
30
31
|
if running_pid = locked?
|
|
31
|
-
feedback("session #{id} locked by running process #{running_pid}", type: :warn)
|
|
32
|
+
chat.feedback("session #{id} locked by running process #{running_pid}", type: :warn)
|
|
32
33
|
false
|
|
33
34
|
else
|
|
34
35
|
lock
|
|
@@ -81,7 +81,7 @@ module OllamaChat::Information
|
|
|
81
81
|
Current Collection
|
|
82
82
|
Name: #{bold{collection}}
|
|
83
83
|
Status: #{col&.enabled ? '✅' : '⛔'}
|
|
84
|
-
Patterns: #{italic{col&.patterns
|
|
84
|
+
Patterns: #{italic{col&.patterns.full?(:join, ' ') || '∅' }}
|
|
85
85
|
#Embeddings: #{@documents.size}
|
|
86
86
|
#Tags: #{@documents.tags.size}
|
|
87
87
|
Tags:
|
|
@@ -190,15 +190,23 @@ module OllamaChat::Information
|
|
|
190
190
|
#
|
|
191
191
|
# @param output [IO] the output stream to write the information to, defaults to STDOUT
|
|
192
192
|
def info_rag(output: STDOUT)
|
|
193
|
-
if
|
|
193
|
+
if embedding.on?
|
|
194
194
|
output.puts "🗄️ Current RAG model is #{bold{@embedding_model}}"
|
|
195
195
|
if @embedding_model_options.present?
|
|
196
196
|
output.puts " Options: #{JSON.pretty_generate(@embedding_model_options).gsub(/(?<!\A)^/, ' ')}"
|
|
197
197
|
end
|
|
198
198
|
output.puts "Text splitter is #{bold{config.embedding.splitter.name}}."
|
|
199
199
|
collection_stats(output:)
|
|
200
|
+
if session.trigger.full?
|
|
201
|
+
session.trigger.each do |col, cfg|
|
|
202
|
+
state = cfg['enabled'] ? '✅' : '⛔'
|
|
203
|
+
output.puts "\u26A1 Trigger: #{state} #{bold{col}}"
|
|
204
|
+
output.puts " Prompt: #{bold{cfg['prompt_name']}}"
|
|
205
|
+
output.puts " Text count: #{cfg['text_count'] || 'unlimited'}"
|
|
206
|
+
end
|
|
207
|
+
end
|
|
200
208
|
end
|
|
201
|
-
|
|
209
|
+
embedding.show(output:)
|
|
202
210
|
output.puts "📜 Document policy for parsing in user text: #{bold{document_policy}}"
|
|
203
211
|
end
|
|
204
212
|
|
|
@@ -418,6 +426,30 @@ module OllamaChat::Information
|
|
|
418
426
|
prompt(:dynamic_runtime_info).to_s % dynamic_runtime_information_values
|
|
419
427
|
end
|
|
420
428
|
|
|
429
|
+
# Injects the current dynamic runtime information (the "heartbeat")
|
|
430
|
+
# as a user-role tool message into the active message list.
|
|
431
|
+
#
|
|
432
|
+
# The injected message shares the caller's +group_uuid+ so it is
|
|
433
|
+
# grouped with the surrounding conversation exchange. The content
|
|
434
|
+
# is generated by +dynamic_runtime_information+, which interpolates
|
|
435
|
+
# live values (timestamp, git state, terminal size, context usage)
|
|
436
|
+
# into the configured prompt template.
|
|
437
|
+
#
|
|
438
|
+
# No-op when the +runtime_info+ switch is off.
|
|
439
|
+
#
|
|
440
|
+
# @param group_uuid [String] the UUID of the current message group
|
|
441
|
+
def runtime_information_inject(group_uuid:)
|
|
442
|
+
runtime_info.on? or return
|
|
443
|
+
tool_name = 'runtime_information'
|
|
444
|
+
messages << OllamaChat::Message.new(
|
|
445
|
+
role: 'user',
|
|
446
|
+
tool_name: ,
|
|
447
|
+
sender_name: tool_name,
|
|
448
|
+
content: dynamic_runtime_information,
|
|
449
|
+
group_uuid:
|
|
450
|
+
)
|
|
451
|
+
end
|
|
452
|
+
|
|
421
453
|
# Resolves the effective context window size for the current model.
|
|
422
454
|
#
|
|
423
455
|
# Prefers the session-level `num_ctx` override (set via `/model_options`
|
|
@@ -26,14 +26,14 @@ infobar:
|
|
|
26
26
|
prompts:
|
|
27
27
|
prompt:
|
|
28
28
|
embed: The source %{source} has been added to or updated in collection "%{collection}".
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
29
|
+
snippets_retrieval: |
|
|
30
|
+
Consider these snippets retrieved from the collection identified in the
|
|
31
|
+
`collection` field when formulating your response.
|
|
32
|
+
snippets_trigger: |
|
|
33
|
+
These snippets were automatically injected because a trigger fired on
|
|
34
|
+
this conversation turn. The source collection is identified in the
|
|
35
|
+
`collection` field. Use them as contextual background, not as search
|
|
36
|
+
results. Do not mention the injection to the user.
|
|
37
37
|
summarize: |
|
|
38
38
|
Generate an abstract summary of the content in this document using
|
|
39
39
|
%{words} words%{instruction}:
|
|
@@ -426,6 +426,8 @@ prompts:
|
|
|
426
426
|
system:
|
|
427
427
|
default: <%= OC::OLLAMA::CHAT::SYSTEM || "%{persona}\n\n%{runtime_info}".inspect %>
|
|
428
428
|
persona: |
|
|
429
|
+
`persona_name`: %{persona_name}
|
|
430
|
+
|
|
429
431
|
%{persona}
|
|
430
432
|
|
|
431
433
|
%{runtime_info}
|
|
@@ -444,6 +446,36 @@ prompts:
|
|
|
444
446
|
— no preamble, no postamble, no commentary,
|
|
445
447
|
no reference to yourself or the act of
|
|
446
448
|
writing.
|
|
449
|
+
rerank:
|
|
450
|
+
default: |
|
|
451
|
+
Query: %{query}
|
|
452
|
+
Candidates:
|
|
453
|
+
|
|
454
|
+
%{candidates}
|
|
455
|
+
|
|
456
|
+
Return only the indices of the most relevant snippets as a comma-separated
|
|
457
|
+
list (e.g., '0,2').
|
|
458
|
+
memory_rerank: |
|
|
459
|
+
Return memories the assistant should recall in this conversation context.
|
|
460
|
+
Ignore entries that are only tangentially related.
|
|
461
|
+
Query: %{query}
|
|
462
|
+
Candidates:
|
|
463
|
+
|
|
464
|
+
%{candidates}
|
|
465
|
+
|
|
466
|
+
Return only the indices of the most relevant memories as a comma-separated
|
|
467
|
+
list (e.g., '0,2').
|
|
468
|
+
lorebook_rerank: |
|
|
469
|
+
Return lore entries directly relevant to the region, character, or event
|
|
470
|
+
the user is currently discussing. Ignore entries that merely share a topic
|
|
471
|
+
but are not contextually needed.
|
|
472
|
+
Query: %{query}
|
|
473
|
+
Candidates:
|
|
474
|
+
|
|
475
|
+
%{candidates}
|
|
476
|
+
|
|
477
|
+
Return only the indices of the most relevant entries as a comma-separated
|
|
478
|
+
list (e.g., '0,2').
|
|
447
479
|
voice:
|
|
448
480
|
#handler: OllamaChat::Say
|
|
449
481
|
#enabled: false
|
|
@@ -197,7 +197,7 @@ module OllamaChat::SessionManagement
|
|
|
197
197
|
previous_session_id = @session.id
|
|
198
198
|
@session = new_session(name:)
|
|
199
199
|
set_previous_session_on_change(previous_session_id)
|
|
200
|
-
session.lock? or raise OllamaChat::OllamaChatError,
|
|
200
|
+
session.lock?(chat: self) or raise OllamaChat::OllamaChatError,
|
|
201
201
|
"Could not lock session #{session.id} #{session.errors.full?(:inspect)}"
|
|
202
202
|
session_apply
|
|
203
203
|
messages.clear
|
|
@@ -236,7 +236,9 @@ module OllamaChat::SessionManagement
|
|
|
236
236
|
use_model(_1)
|
|
237
237
|
copy_model_options_to_session
|
|
238
238
|
}
|
|
239
|
-
log(:info, "Session duplicated", data: {
|
|
239
|
+
log(:info, "Session duplicated", data: {
|
|
240
|
+
session_id: session.id, name: session.name, old_session_id: old_session.id
|
|
241
|
+
})
|
|
240
242
|
nil
|
|
241
243
|
end
|
|
242
244
|
|
|
@@ -253,7 +255,7 @@ module OllamaChat::SessionManagement
|
|
|
253
255
|
preferred_session
|
|
254
256
|
end
|
|
255
257
|
session or abort "No session named #{bold{session_name.inspect}} found."
|
|
256
|
-
if session.lock?
|
|
258
|
+
if session.lock?(chat: self)
|
|
257
259
|
messages.read_conversation_jsonl(session.messages.to_s)
|
|
258
260
|
session_apply
|
|
259
261
|
else
|
|
@@ -483,6 +485,80 @@ module OllamaChat::SessionManagement
|
|
|
483
485
|
end
|
|
484
486
|
end
|
|
485
487
|
|
|
488
|
+
# Interactively edits the trigger configuration for a collection.
|
|
489
|
+
#
|
|
490
|
+
# Presents a chooser for available rerank prompt names, then prompts
|
|
491
|
+
# for the collection, text_count, and enabled state. Existing values
|
|
492
|
+
# are prefilled when re-editing a known trigger so the user can simply
|
|
493
|
+
# accept defaults or toggle enabled off.
|
|
494
|
+
def edit_session_trigger
|
|
495
|
+
prompts = models::Prompt.where(context: 'rerank').map(&:name).sort
|
|
496
|
+
return feedback('No rerank prompts available.', type: :warn) \
|
|
497
|
+
unless prompts.any?
|
|
498
|
+
|
|
499
|
+
collections = all_collections.map(&:name).sort
|
|
500
|
+
return feedback('No collections available.', type: :warn) \
|
|
501
|
+
unless collections.any?
|
|
502
|
+
|
|
503
|
+
existing = session.trigger.to_h
|
|
504
|
+
|
|
505
|
+
collection = nil
|
|
506
|
+
choose_with_state do
|
|
507
|
+
# Prefill with the first configured collection as a starting
|
|
508
|
+
# point. Fine for the single-trigger case; with multiple
|
|
509
|
+
# configured, it's just an arbitrary default the user can
|
|
510
|
+
# override in the chooser.
|
|
511
|
+
if selected = existing.first&.first&.dup
|
|
512
|
+
self.current_search_state = SearchUI::Search::State.new(selected, 0)
|
|
513
|
+
end
|
|
514
|
+
collection = choose_entry(collections, prompt: 'Collection? %s')
|
|
515
|
+
return feedback('Exiting chooser.', type: :info) unless collection
|
|
516
|
+
end
|
|
517
|
+
|
|
518
|
+
prompt_name = nil
|
|
519
|
+
choose_with_state do
|
|
520
|
+
if selected = existing.dig(collection, 'prompt_name')
|
|
521
|
+
self.current_search_state = SearchUI::Search::State.new(selected, 0)
|
|
522
|
+
end
|
|
523
|
+
prompt_name = choose_entry(prompts, prompt: 'Rerank prompt? %s')
|
|
524
|
+
return feedback('Exiting chooser.', type: :info) unless prompt_name
|
|
525
|
+
end
|
|
526
|
+
|
|
527
|
+
prefill_tc = ''
|
|
528
|
+
prefill_enabled = 'y'
|
|
529
|
+
if cfg = existing[collection]
|
|
530
|
+
prefill_tc = cfg['text_count'].to_s
|
|
531
|
+
prefill_enabled = cfg['enabled'] ? 'y' : 'n'
|
|
532
|
+
end
|
|
533
|
+
|
|
534
|
+
tc_raw = ask?(
|
|
535
|
+
prompt: 'text_count (empty for no limit): ',
|
|
536
|
+
prefill: prefill_tc
|
|
537
|
+
)
|
|
538
|
+
return feedback('Cancelled.', type: :cancel) if tc_raw.nil?
|
|
539
|
+
text_count = tc_raw.strip.empty? ? nil : tc_raw.strip.to_i
|
|
540
|
+
|
|
541
|
+
enabled_raw = ask?(
|
|
542
|
+
prompt: 'Enabled? (y/n): ',
|
|
543
|
+
prefill: prefill_enabled
|
|
544
|
+
)
|
|
545
|
+
return feedback('Cancelled.', type: :cancel) if enabled_raw.nil?
|
|
546
|
+
enabled = enabled_raw.strip.match?(/\Ay/i)
|
|
547
|
+
|
|
548
|
+
cfg = {
|
|
549
|
+
'enabled' => enabled,
|
|
550
|
+
'prompt_name' => prompt_name,
|
|
551
|
+
}
|
|
552
|
+
cfg['text_count'] = text_count if text_count
|
|
553
|
+
|
|
554
|
+
session.update(trigger: { collection => cfg })
|
|
555
|
+
|
|
556
|
+
feedback(
|
|
557
|
+
"Trigger for #{collection.inspect} → #{cfg.inspect}.",
|
|
558
|
+
type: :info
|
|
559
|
+
)
|
|
560
|
+
end
|
|
561
|
+
|
|
486
562
|
# Derives a title for the session based on its content.
|
|
487
563
|
#
|
|
488
564
|
# @param length [Integer] the maximum length of the title (default: 128)
|
|
@@ -551,7 +627,7 @@ module OllamaChat::SessionManagement
|
|
|
551
627
|
session_close
|
|
552
628
|
previous_session = session
|
|
553
629
|
@session = chosen_session
|
|
554
|
-
if session.lock?
|
|
630
|
+
if session.lock?(chat: self)
|
|
555
631
|
messages.read_conversation_jsonl(session.messages.to_s)
|
|
556
632
|
if current_collection = session.current_collection.full? and
|
|
557
633
|
database_collection?(current_collection)
|
|
@@ -194,7 +194,7 @@ module OllamaChat::SourceFetching
|
|
|
194
194
|
# @return [Array, String, nil] The embedded chunks or processed content, or
|
|
195
195
|
# nil if embedding is disabled or fails
|
|
196
196
|
def embed_source(source_io, source, tags: [], count: nil)
|
|
197
|
-
|
|
197
|
+
embedding.on? or return parse_source(source_io)
|
|
198
198
|
unless @documents.source_modified?(source)
|
|
199
199
|
infobar.puts "Source #{source.to_s.inspect} already up-to-date. => Skipping."
|
|
200
200
|
log(:info, "Source up-to-date", data: { source: source.to_s })
|
|
@@ -272,7 +272,7 @@ module OllamaChat::SourceFetching
|
|
|
272
272
|
# nil if the operation fails
|
|
273
273
|
def embed(source, tags: [], prompt: prompt(:embed))
|
|
274
274
|
prompt = prompt.to_s
|
|
275
|
-
|
|
275
|
+
embedding.on? or return
|
|
276
276
|
fetch_source(source) do |source_io|
|
|
277
277
|
content = parse_source(source_io)
|
|
278
278
|
content.present? or return
|