rails_console_ai 0.35.0 → 0.38.0
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/CHANGELOG.md +25 -0
- data/README.md +102 -10
- data/app/controllers/rails_console_ai/artifacts_controller.rb +67 -0
- data/app/helpers/rails_console_ai/sessions_helper.rb +11 -11
- data/app/models/rails_console_ai/artifact.rb +231 -0
- data/app/views/layouts/rails_console_ai/application.html.erb +1 -0
- data/app/views/rails_console_ai/artifacts/index.html.erb +60 -0
- data/app/views/rails_console_ai/artifacts/show.html.erb +119 -0
- data/app/views/rails_console_ai/sessions/show.html.erb +6 -0
- data/config/routes.rb +8 -0
- data/lib/generators/rails_console_ai/templates/initializer.rb +12 -1
- data/lib/rails_console_ai/channel/console.rb +132 -18
- data/lib/rails_console_ai/channel/slack.rb +1 -1
- data/lib/rails_console_ai/configuration.rb +114 -15
- data/lib/rails_console_ai/context_builder.rb +48 -18
- data/lib/rails_console_ai/conversation_engine.rb +231 -61
- data/lib/rails_console_ai/line_editor.rb +142 -0
- data/lib/rails_console_ai/providers/anthropic.rb +3 -1
- data/lib/rails_console_ai/providers/base.rb +26 -1
- data/lib/rails_console_ai/providers/bedrock.rb +5 -2
- data/lib/rails_console_ai/providers/local.rb +17 -37
- data/lib/rails_console_ai/providers/openai.rb +31 -10
- data/lib/rails_console_ai/providers/openrouter.rb +108 -0
- data/lib/rails_console_ai/slack_bot.rb +58 -23
- data/lib/rails_console_ai/slash_commands.rb +150 -0
- data/lib/rails_console_ai/sub_agent.rb +35 -2
- data/lib/rails_console_ai/tools/registry.rb +233 -3
- data/lib/rails_console_ai/version.rb +1 -1
- data/lib/rails_console_ai.rb +223 -0
- metadata +8 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
module RailsConsoleAi
|
|
2
2
|
class Configuration
|
|
3
|
-
PROVIDERS = %i[anthropic openai local bedrock].freeze
|
|
3
|
+
PROVIDERS = %i[anthropic openai openrouter local bedrock].freeze
|
|
4
4
|
|
|
5
5
|
# Per-family model attributes, matched by substring so one entry covers every
|
|
6
6
|
# ID variant of a family: bare Anthropic IDs (claude-sonnet-5), dated
|
|
@@ -8,19 +8,23 @@ module RailsConsoleAi
|
|
|
8
8
|
# (us.anthropic.claude-sonnet-5, global.anthropic.claude-opus-4-6-v1).
|
|
9
9
|
#
|
|
10
10
|
# input/output are $ per MTok (converted to per-token in .pricing_for).
|
|
11
|
-
# Cache pricing is derived: read = 0.1x input, write = 1.25x input
|
|
11
|
+
# Cache pricing is derived: read = 0.1x input, write = 1.25x input, unless a
|
|
12
|
+
# family sets cache_read ($ per MTok) explicitly (opus-5-5 reads at 0.05x).
|
|
12
13
|
# temperature: false marks families that reject the `temperature` parameter
|
|
13
|
-
# (removed on opus-4-7+, sonnet-5
|
|
14
|
+
# (removed on opus-4-7+, sonnet-5+, and fable-5).
|
|
15
|
+
# effort: false marks families that reject `output_config.effort` (haiku-4-5).
|
|
14
16
|
# max_tokens is the OUTPUT cap we request; context is the total window.
|
|
15
17
|
MODEL_FAMILIES = {
|
|
16
18
|
'claude-fable-5' => { input: 10.0, output: 50.0, max_tokens: 16_000, context: 1_000_000, temperature: false },
|
|
19
|
+
'claude-opus-5-5' => { input: 4.0, output: 20.0, cache_read: 0.20, max_tokens: 16_000, context: 1_000_000, temperature: false },
|
|
17
20
|
'claude-opus-5' => { input: 5.0, output: 25.0, max_tokens: 16_000, context: 1_000_000, temperature: false },
|
|
18
21
|
'claude-opus-4-8' => { input: 5.0, output: 25.0, max_tokens: 16_000, context: 1_000_000, temperature: false },
|
|
19
22
|
'claude-opus-4-7' => { input: 5.0, output: 25.0, max_tokens: 16_000, context: 1_000_000, temperature: false },
|
|
20
23
|
'claude-opus-4-6' => { input: 5.0, output: 25.0, max_tokens: 16_000, context: 1_000_000, temperature: true },
|
|
24
|
+
'claude-sonnet-5-5' => { input: 2.0, output: 10.0, max_tokens: 16_000, context: 1_000_000, temperature: false },
|
|
21
25
|
'claude-sonnet-5' => { input: 2.0, output: 10.0, max_tokens: 16_000, context: 1_000_000, temperature: false },
|
|
22
26
|
'claude-sonnet-4-6' => { input: 3.0, output: 15.0, max_tokens: 16_000, context: 1_000_000, temperature: true },
|
|
23
|
-
'claude-haiku-4-5' => { input: 1.0, output: 5.0, max_tokens: 16_000, context: 200_000, temperature: true },
|
|
27
|
+
'claude-haiku-4-5' => { input: 1.0, output: 5.0, max_tokens: 16_000, context: 200_000, temperature: true, effort: false },
|
|
24
28
|
}.freeze
|
|
25
29
|
|
|
26
30
|
# Assumed context window for models with no family entry — local models and
|
|
@@ -28,18 +32,28 @@ module RailsConsoleAi
|
|
|
28
32
|
# little early, over-guessing means no warning before the request is rejected.
|
|
29
33
|
DEFAULT_CONTEXT_WINDOW = 200_000
|
|
30
34
|
|
|
35
|
+
EFFORT_LEVELS = %w[low medium high xhigh max].freeze
|
|
36
|
+
|
|
31
37
|
# Family keys sorted longest-first so a more specific family always wins
|
|
32
|
-
#
|
|
33
|
-
# before 'claude-sonnet-5').
|
|
38
|
+
# when keys overlap (e.g. 'claude-sonnet-5-5' matches before 'claude-sonnet-5').
|
|
34
39
|
MODEL_FAMILY_KEYS = MODEL_FAMILIES.keys.sort_by { |k| -k.length }.freeze
|
|
35
40
|
|
|
36
41
|
# Returns the family attributes for a model ID, or nil for unknown models.
|
|
37
42
|
def self.model_family(model_id)
|
|
38
43
|
return nil unless model_id
|
|
39
|
-
|
|
44
|
+
normalized = model_id.gsub(/(?<=\d)\.(?=\d)/, '-')
|
|
45
|
+
key = MODEL_FAMILY_KEYS.find { |k| normalized.include?(k) }
|
|
40
46
|
key && MODEL_FAMILIES[key]
|
|
41
47
|
end
|
|
42
48
|
|
|
49
|
+
# The effort a model would be sent, or nil when it takes none: unknown models
|
|
50
|
+
# (OpenAI, local) and families marked effort: false.
|
|
51
|
+
def self.effort_for(model_id, effort)
|
|
52
|
+
return nil if effort.nil? || effort.to_s.empty?
|
|
53
|
+
family = model_family(model_id)
|
|
54
|
+
family && family[:effort] != false ? effort.to_s : nil
|
|
55
|
+
end
|
|
56
|
+
|
|
43
57
|
# Per-token pricing for a model ID, matched by family. Returns
|
|
44
58
|
# { input:, output:, cache_read:, cache_write: } or nil for unknown models.
|
|
45
59
|
# Cache reads bill at 0.1x the base input rate; cache writes at 1.25x for the
|
|
@@ -51,11 +65,27 @@ module RailsConsoleAi
|
|
|
51
65
|
{
|
|
52
66
|
input: input,
|
|
53
67
|
output: family[:output] / 1_000_000,
|
|
54
|
-
cache_read: input * 0.1,
|
|
68
|
+
cache_read: family[:cache_read] ? family[:cache_read] / 1_000_000 : input * 0.1,
|
|
55
69
|
cache_write: input * (cache_ttl.to_s == '1h' ? 2.0 : 1.25),
|
|
56
70
|
}
|
|
57
71
|
end
|
|
58
72
|
|
|
73
|
+
# The four usage buckets each bill at their own rate. The API's `input_tokens`
|
|
74
|
+
# is the UNCACHED remainder — cached tokens are reported separately and are not
|
|
75
|
+
# part of it (total prompt size is input + cache_read + cache_write), so
|
|
76
|
+
# discounting cache_read out of input double-counts and can drive a cost
|
|
77
|
+
# negative. Every estimated cost readout goes through here. Providers that
|
|
78
|
+
# report real dollars (OpenRouter) are preferred over this estimate.
|
|
79
|
+
def self.estimate_cost(model, input:, output:, cache_read: 0, cache_write: 0, cache_ttl: nil)
|
|
80
|
+
pricing = pricing_for(model, cache_ttl: cache_ttl)
|
|
81
|
+
return nil unless pricing
|
|
82
|
+
|
|
83
|
+
((input || 0) * pricing[:input]) +
|
|
84
|
+
((output || 0) * pricing[:output]) +
|
|
85
|
+
((cache_read || 0) * pricing[:cache_read]) +
|
|
86
|
+
((cache_write || 0) * pricing[:cache_write])
|
|
87
|
+
end
|
|
88
|
+
|
|
59
89
|
# Total context window for a model ID, matched by family.
|
|
60
90
|
def self.context_window_for(model_id)
|
|
61
91
|
family = model_family(model_id)
|
|
@@ -81,7 +111,8 @@ module RailsConsoleAi
|
|
|
81
111
|
}
|
|
82
112
|
].freeze
|
|
83
113
|
|
|
84
|
-
attr_accessor :provider, :api_key, :model, :thinking_model, :max_tokens,
|
|
114
|
+
attr_accessor :provider, :api_key, :model, :thinking_model, :cheap_model, :max_tokens,
|
|
115
|
+
:effort, :thinking_effort,
|
|
85
116
|
:auto_execute, :temperature,
|
|
86
117
|
:timeout, :open_timeout, :max_retries, :debug, :max_tool_rounds,
|
|
87
118
|
:error_hints,
|
|
@@ -93,19 +124,29 @@ module RailsConsoleAi
|
|
|
93
124
|
:authenticate,
|
|
94
125
|
:slack_bot_token, :slack_app_token, :slack_channel_ids, :slack_allowed_usernames,
|
|
95
126
|
:local_url, :local_model, :local_api_key,
|
|
127
|
+
:openrouter_url, :openrouter_app_name, :openrouter_site_url,
|
|
96
128
|
:bedrock_region,
|
|
97
129
|
:code_search_paths,
|
|
98
130
|
:channels,
|
|
99
131
|
:bypass_guards_for_methods,
|
|
100
132
|
:user_extra_info,
|
|
101
133
|
:sub_agent_max_rounds,
|
|
102
|
-
:sub_agent_model
|
|
134
|
+
:sub_agent_model,
|
|
135
|
+
:line_editor,
|
|
136
|
+
:base_url,
|
|
137
|
+
:artifact_date_prefix
|
|
103
138
|
|
|
104
139
|
def initialize
|
|
105
140
|
@provider = :anthropic
|
|
106
141
|
@api_key = nil
|
|
107
142
|
@model = nil
|
|
108
143
|
@thinking_model = nil
|
|
144
|
+
@cheap_model = nil
|
|
145
|
+
# The default and thinking tiers run the same model by default and differ
|
|
146
|
+
# only in how hard it thinks. Sent as output_config.effort, and only to
|
|
147
|
+
# model families that accept it. The cheap tier sends no effort.
|
|
148
|
+
@effort = 'medium'
|
|
149
|
+
@thinking_effort = 'high'
|
|
109
150
|
@max_tokens = nil
|
|
110
151
|
@auto_execute = false
|
|
111
152
|
@temperature = 0.2
|
|
@@ -147,6 +188,9 @@ module RailsConsoleAi
|
|
|
147
188
|
@local_url = 'http://localhost:11434'
|
|
148
189
|
@local_model = 'qwen2.5:7b'
|
|
149
190
|
@local_api_key = nil
|
|
191
|
+
@openrouter_url = nil
|
|
192
|
+
@openrouter_app_name = nil
|
|
193
|
+
@openrouter_site_url = nil
|
|
150
194
|
@bedrock_region = nil
|
|
151
195
|
@code_search_paths = %w[app]
|
|
152
196
|
@channels = {}
|
|
@@ -154,6 +198,18 @@ module RailsConsoleAi
|
|
|
154
198
|
@user_extra_info = {}
|
|
155
199
|
@sub_agent_max_rounds = 15
|
|
156
200
|
@sub_agent_model = nil
|
|
201
|
+
# Interactive line editor: :auto prefers Reline (live "/" completion menu)
|
|
202
|
+
# and falls back to Readline. Force one with :reline or :readline.
|
|
203
|
+
@line_editor = :auto
|
|
204
|
+
# Public base URL of the host app, e.g. "https://secure.apptoto.com". Used
|
|
205
|
+
# to build links the gem hands out — an artifact URL is read in a console,
|
|
206
|
+
# pasted into Slack or opened on a phone, where a bare path is useless.
|
|
207
|
+
# Falls back to the app's default_url_options, then to a relative path.
|
|
208
|
+
@base_url = nil
|
|
209
|
+
# Stamp new artifact names with the day they were made ("20260911_Churn
|
|
210
|
+
# report"), so a list of them reads chronologically. Updates keep the name
|
|
211
|
+
# and date they were created with.
|
|
212
|
+
@artifact_date_prefix = true
|
|
157
213
|
end
|
|
158
214
|
|
|
159
215
|
def resolve_user_extra_info(username)
|
|
@@ -240,6 +296,8 @@ module RailsConsoleAi
|
|
|
240
296
|
ENV['ANTHROPIC_API_KEY']
|
|
241
297
|
when :openai
|
|
242
298
|
ENV['OPENAI_API_KEY']
|
|
299
|
+
when :openrouter
|
|
300
|
+
ENV['OPENROUTER_API_KEY']
|
|
243
301
|
when :local
|
|
244
302
|
@local_api_key || 'no-key'
|
|
245
303
|
when :bedrock
|
|
@@ -250,11 +308,32 @@ module RailsConsoleAi
|
|
|
250
308
|
def resolved_model
|
|
251
309
|
return @model if @model && !@model.empty?
|
|
252
310
|
|
|
311
|
+
case @provider
|
|
312
|
+
when :anthropic
|
|
313
|
+
'claude-opus-5-5'
|
|
314
|
+
when :openai
|
|
315
|
+
'gpt-5.3-codex'
|
|
316
|
+
when :openrouter
|
|
317
|
+
'anthropic/claude-opus-5.5'
|
|
318
|
+
when :local
|
|
319
|
+
@local_model
|
|
320
|
+
when :bedrock
|
|
321
|
+
'us.anthropic.claude-opus-5-5'
|
|
322
|
+
end
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
# The model /cheap switches to: faster and cheaper than the default, for
|
|
326
|
+
# routine lookups where the default's depth isn't needed.
|
|
327
|
+
def resolved_cheap_model
|
|
328
|
+
return @cheap_model if @cheap_model && !@cheap_model.empty?
|
|
329
|
+
|
|
253
330
|
case @provider
|
|
254
331
|
when :anthropic
|
|
255
332
|
'claude-sonnet-5'
|
|
256
333
|
when :openai
|
|
257
334
|
'gpt-5.3-codex'
|
|
335
|
+
when :openrouter
|
|
336
|
+
'anthropic/claude-sonnet-5'
|
|
258
337
|
when :local
|
|
259
338
|
@local_model
|
|
260
339
|
when :bedrock
|
|
@@ -266,17 +345,24 @@ module RailsConsoleAi
|
|
|
266
345
|
return @max_tokens if @max_tokens
|
|
267
346
|
|
|
268
347
|
family = self.class.model_family(resolved_model)
|
|
269
|
-
|
|
348
|
+
return family[:max_tokens] if family
|
|
349
|
+
|
|
350
|
+
@provider == :openrouter ? 16_000 : 4096
|
|
270
351
|
end
|
|
271
352
|
|
|
272
353
|
# Returns nil for model families that reject the `temperature` parameter
|
|
273
|
-
# (opus-4-7+, sonnet-5
|
|
354
|
+
# (opus-4-7+, sonnet-5+, fable-5) so providers omit the field from the request.
|
|
274
355
|
def resolved_temperature
|
|
275
356
|
family = self.class.model_family(resolved_model)
|
|
276
357
|
return nil if family && family[:temperature] == false
|
|
277
358
|
@temperature
|
|
278
359
|
end
|
|
279
360
|
|
|
361
|
+
# Returns the effort to send for the configured model, or nil to omit it.
|
|
362
|
+
def resolved_effort
|
|
363
|
+
self.class.effort_for(resolved_model, @effort)
|
|
364
|
+
end
|
|
365
|
+
|
|
280
366
|
# Returns '1h' when the 1-hour prompt cache is requested, else nil (the
|
|
281
367
|
# 5-minute default). Providers that offer only one cache duration ignore it.
|
|
282
368
|
def resolved_cache_ttl
|
|
@@ -289,13 +375,15 @@ module RailsConsoleAi
|
|
|
289
375
|
|
|
290
376
|
case @provider
|
|
291
377
|
when :anthropic
|
|
292
|
-
'claude-opus-5'
|
|
378
|
+
'claude-opus-5-5'
|
|
293
379
|
when :openai
|
|
294
380
|
'gpt-5.3-codex'
|
|
381
|
+
when :openrouter
|
|
382
|
+
'anthropic/claude-opus-5.5'
|
|
295
383
|
when :local
|
|
296
384
|
@local_model
|
|
297
385
|
when :bedrock
|
|
298
|
-
'us.anthropic.claude-opus-5'
|
|
386
|
+
'us.anthropic.claude-opus-5-5'
|
|
299
387
|
end
|
|
300
388
|
end
|
|
301
389
|
|
|
@@ -303,11 +391,22 @@ module RailsConsoleAi
|
|
|
303
391
|
@provider == :local ? [@timeout, 300].max : @timeout
|
|
304
392
|
end
|
|
305
393
|
|
|
394
|
+
ENV_KEYS = {
|
|
395
|
+
anthropic: 'ANTHROPIC_API_KEY',
|
|
396
|
+
openai: 'OPENAI_API_KEY',
|
|
397
|
+
openrouter: 'OPENROUTER_API_KEY'
|
|
398
|
+
}.freeze
|
|
399
|
+
|
|
306
400
|
def validate!
|
|
307
401
|
unless PROVIDERS.include?(@provider)
|
|
308
402
|
raise ConfigurationError, "Unknown provider: #{@provider}. Valid: #{PROVIDERS.join(', ')}"
|
|
309
403
|
end
|
|
310
404
|
|
|
405
|
+
{ effort: @effort, thinking_effort: @thinking_effort }.each do |name, value|
|
|
406
|
+
next if value.nil? || EFFORT_LEVELS.include?(value.to_s)
|
|
407
|
+
raise ConfigurationError, "Invalid #{name}: #{value.inspect}. Valid: #{EFFORT_LEVELS.join(', ')}"
|
|
408
|
+
end
|
|
409
|
+
|
|
311
410
|
if @provider == :local
|
|
312
411
|
raise ConfigurationError, "No local_url configured for :local provider." unless @local_url && !@local_url.empty?
|
|
313
412
|
elsif @provider == :bedrock
|
|
@@ -319,7 +418,7 @@ module RailsConsoleAi
|
|
|
319
418
|
end
|
|
320
419
|
else
|
|
321
420
|
unless resolved_api_key
|
|
322
|
-
env_var = @provider
|
|
421
|
+
env_var = ENV_KEYS[@provider] || 'API_KEY'
|
|
323
422
|
raise ConfigurationError, "No API key. Set config.api_key or #{env_var} env var."
|
|
324
423
|
end
|
|
325
424
|
end
|
|
@@ -48,6 +48,26 @@ module RailsConsoleAi
|
|
|
48
48
|
lines.join("\n")
|
|
49
49
|
end
|
|
50
50
|
|
|
51
|
+
# The index of memory names the model can recall, with the instruction to
|
|
52
|
+
# look before answering. Public because SubAgent builds its own prompt and
|
|
53
|
+
# shares this section, so a sub-agent knows the same memories exist.
|
|
54
|
+
def memory_context
|
|
55
|
+
return nil unless @config.memories_enabled
|
|
56
|
+
|
|
57
|
+
require 'rails_console_ai/tools/memory_tools'
|
|
58
|
+
summaries = Tools::MemoryTools.new.memory_summaries
|
|
59
|
+
return nil if summaries.nil? || summaries.empty?
|
|
60
|
+
|
|
61
|
+
lines = ["## Memories"]
|
|
62
|
+
lines.concat(summaries)
|
|
63
|
+
lines << ""
|
|
64
|
+
lines << "Call recall_memory (by name) or recall_memories (by keyword) to get details before answering. Do NOT guess from the name alone."
|
|
65
|
+
lines.join("\n")
|
|
66
|
+
rescue => e
|
|
67
|
+
RailsConsoleAi.logger.debug("RailsConsoleAi: memory context failed: #{e.message}")
|
|
68
|
+
nil
|
|
69
|
+
end
|
|
70
|
+
|
|
51
71
|
private
|
|
52
72
|
|
|
53
73
|
def smart_system_instructions
|
|
@@ -102,6 +122,34 @@ module RailsConsoleAi
|
|
|
102
122
|
- This keeps your conversation context small and efficient.
|
|
103
123
|
- If a custom agent is available for the task (see Agents section), specify it by name.
|
|
104
124
|
|
|
125
|
+
You have artifact tools for output that is a document rather than an answer:
|
|
126
|
+
- When the user asks for a report, summary, export, table or page, build it with
|
|
127
|
+
save_artifact instead of printing it into the console. Reply with the URL it returns,
|
|
128
|
+
and tell them the handle so they can ask for it again later.
|
|
129
|
+
- New artifacts are named with today's date: "Q3 churn summary" is stored as
|
|
130
|
+
"20260911_Q3 churn summary" and addressed as 20260911-q3-churn-summary. Do not add the
|
|
131
|
+
date yourself — pass the plain title and it is stamped for you.
|
|
132
|
+
- Saving under a name that already exists replaces that artifact and bumps its revision.
|
|
133
|
+
To revise one from an earlier day, pass its exact handle as the name; passing the plain
|
|
134
|
+
title again makes a fresh artifact under today's date.
|
|
135
|
+
- When the user refers to an earlier artifact ("update the churn report", "add last month
|
|
136
|
+
to that table"), use list_artifacts to find its handle, read_artifact to get what is
|
|
137
|
+
already there, then save_artifact under the SAME name to replace it. Do not start a new
|
|
138
|
+
one unless they want a new one.
|
|
139
|
+
- delete_artifact removes one, only when asked.
|
|
140
|
+
- When the content already exists as a value in the console — a file you read from S3,
|
|
141
|
+
a string you built in code — do NOT retype it into save_artifact. Your response has an
|
|
142
|
+
output-token limit, and a large file will be cut off mid-argument and the call thrown
|
|
143
|
+
away. Save it from Ruby instead, inside execute_code:
|
|
144
|
+
html = S3.read_file("report.html")
|
|
145
|
+
RailsConsoleAi.save_artifact(name: "Mathnasium migration", content: html)
|
|
146
|
+
That is also the only way to store something verbatim: anything you retype is your
|
|
147
|
+
recollection of the bytes, not the bytes. Use save_artifact for content you are
|
|
148
|
+
composing yourself.
|
|
149
|
+
- HTML artifacts render in a sandboxed frame: inline CSS works, but scripts never run and
|
|
150
|
+
nothing loads over the network. Inline every style and embed the data directly.
|
|
151
|
+
- Use text/csv or application/json when the user wants data to take elsewhere.
|
|
152
|
+
|
|
105
153
|
RULES:
|
|
106
154
|
- Give ONE concise answer. Do not offer multiple alternatives or variations.
|
|
107
155
|
- For multi-step tasks, use execute_plan to break the work into small, clear steps.
|
|
@@ -195,23 +243,5 @@ module RailsConsoleAi
|
|
|
195
243
|
RailsConsoleAi.logger.debug("RailsConsoleAi: pinned memory context failed: #{e.message}")
|
|
196
244
|
nil
|
|
197
245
|
end
|
|
198
|
-
|
|
199
|
-
def memory_context
|
|
200
|
-
return nil unless @config.memories_enabled
|
|
201
|
-
|
|
202
|
-
require 'rails_console_ai/tools/memory_tools'
|
|
203
|
-
summaries = Tools::MemoryTools.new.memory_summaries
|
|
204
|
-
return nil if summaries.nil? || summaries.empty?
|
|
205
|
-
|
|
206
|
-
lines = ["## Memories"]
|
|
207
|
-
lines.concat(summaries)
|
|
208
|
-
lines << ""
|
|
209
|
-
lines << "Call recall_memory (by name) or recall_memories (by keyword) to get details before answering. Do NOT guess from the name alone."
|
|
210
|
-
lines.join("\n")
|
|
211
|
-
rescue => e
|
|
212
|
-
RailsConsoleAi.logger.debug("RailsConsoleAi: memory context failed: #{e.message}")
|
|
213
|
-
nil
|
|
214
|
-
end
|
|
215
|
-
|
|
216
246
|
end
|
|
217
247
|
end
|