llmemory 0.2.6 → 0.2.7
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/README.md +22 -5
- data/app/controllers/llmemory/dashboard/application_controller.rb +12 -0
- data/app/views/llmemory/dashboard/short_term/show.html.erb +1 -1
- data/lib/llmemory/active_record_helpers.rb +21 -0
- data/lib/llmemory/cli/commands/mcp.rb +5 -3
- data/lib/llmemory/configuration.rb +41 -6
- data/lib/llmemory/crypto/field_helpers.rb +5 -0
- data/lib/llmemory/extractors/entity_relation_extractor.rb +9 -2
- data/lib/llmemory/extractors/fact_extractor.rb +67 -1
- data/lib/llmemory/forget_log.rb +7 -3
- data/lib/llmemory/llm/anthropic.rb +4 -6
- data/lib/llmemory/llm/http_client.rb +55 -0
- data/lib/llmemory/llm/openai.rb +5 -7
- data/lib/llmemory/llm/tracking_client.rb +22 -12
- data/lib/llmemory/llm/usage.rb +4 -0
- data/lib/llmemory/llm/usage_ledger.rb +25 -24
- data/lib/llmemory/long_term/episodic/memory.rb +5 -2
- data/lib/llmemory/long_term/episodic/storages/active_record_storage.rb +3 -1
- data/lib/llmemory/long_term/file_based/memory.rb +9 -4
- data/lib/llmemory/long_term/file_based/storage.rb +15 -2
- data/lib/llmemory/long_term/file_based/storages/active_record_storage.rb +3 -1
- data/lib/llmemory/long_term/graph_based/memory.rb +39 -5
- data/lib/llmemory/long_term/graph_based/storage.rb +15 -2
- data/lib/llmemory/long_term/procedural/memory.rb +5 -2
- data/lib/llmemory/long_term/procedural/storages/active_record_storage.rb +3 -1
- data/lib/llmemory/maintenance/consolidator.rb +6 -2
- data/lib/llmemory/mcp/authentication.rb +5 -3
- data/lib/llmemory/mcp/server.rb +21 -6
- data/lib/llmemory/mcp/store_helpers.rb +35 -0
- data/lib/llmemory/mcp/tools/memory_save.rb +15 -18
- data/lib/llmemory/mcp/tools/memory_search.rb +16 -19
- data/lib/llmemory/memory.rb +29 -11
- data/lib/llmemory/memory_module.rb +6 -2
- data/lib/llmemory/provenance.rb +23 -1
- data/lib/llmemory/retrieval/feedback_store.rb +11 -5
- data/lib/llmemory/retrieval/multi_source.rb +26 -0
- data/lib/llmemory/retrieval/temporal_ranker.rb +4 -2
- data/lib/llmemory/retrieval.rb +1 -0
- data/lib/llmemory/short_term/pruner.rb +16 -4
- data/lib/llmemory/short_term/session_lifecycle.rb +18 -9
- data/lib/llmemory/short_term/stores/active_record_store.rb +37 -8
- data/lib/llmemory/short_term/stores/base.rb +9 -0
- data/lib/llmemory/short_term/stores/key_codec.rb +33 -0
- data/lib/llmemory/short_term/stores/memory_store.rb +38 -7
- data/lib/llmemory/short_term/stores/postgres_store.rb +81 -33
- data/lib/llmemory/short_term/stores/redis_store.rb +85 -9
- data/lib/llmemory/short_term/stores.rb +17 -2
- data/lib/llmemory/vector_store/active_record_store.rb +23 -8
- data/lib/llmemory/vector_store/base.rb +4 -0
- data/lib/llmemory/vector_store/memory_store.rb +9 -0
- data/lib/llmemory/vector_store/openai_embeddings.rb +6 -7
- data/lib/llmemory/version.rb +1 -1
- data/lib/llmemory/working_memory.rb +10 -7
- metadata +6 -15
|
@@ -13,58 +13,103 @@ module Llmemory
|
|
|
13
13
|
@database_url = database_url || Llmemory.configuration.database_url
|
|
14
14
|
@connection = nil
|
|
15
15
|
@cipher = cipher || Llmemory.build_cipher
|
|
16
|
+
@table_ready = false
|
|
17
|
+
@conn_mutex = Mutex.new
|
|
16
18
|
end
|
|
17
19
|
|
|
18
20
|
def save(user_id, session_id, state)
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
21
|
+
with_conn do |connection|
|
|
22
|
+
ensure_table!(connection)
|
|
23
|
+
connection.exec_params(
|
|
24
|
+
<<~SQL,
|
|
25
|
+
INSERT INTO llmemory_checkpoints (user_id, session_id, state, updated_at)
|
|
26
|
+
VALUES ($1, $2, $3, $4)
|
|
27
|
+
ON CONFLICT (user_id, session_id)
|
|
28
|
+
DO UPDATE SET state = $3, updated_at = $4
|
|
29
|
+
SQL
|
|
30
|
+
[user_id, session_id, serialize(state), Time.now.utc.iso8601]
|
|
31
|
+
)
|
|
32
|
+
end
|
|
29
33
|
true
|
|
30
34
|
end
|
|
31
35
|
|
|
32
36
|
def load(user_id, session_id)
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
with_conn do |connection|
|
|
38
|
+
ensure_table!(connection)
|
|
39
|
+
result = connection.exec_params(
|
|
40
|
+
"SELECT state FROM llmemory_checkpoints WHERE user_id = $1 AND session_id = $2",
|
|
41
|
+
[user_id, session_id]
|
|
42
|
+
)
|
|
43
|
+
row = result.first
|
|
44
|
+
row ? deserialize(row["state"]) : nil
|
|
45
|
+
end
|
|
40
46
|
end
|
|
41
47
|
|
|
42
48
|
def delete(user_id, session_id)
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
49
|
+
with_conn do |connection|
|
|
50
|
+
ensure_table!(connection)
|
|
51
|
+
connection.exec_params(
|
|
52
|
+
"DELETE FROM llmemory_checkpoints WHERE user_id = $1 AND session_id = $2",
|
|
53
|
+
[user_id, session_id]
|
|
54
|
+
)
|
|
55
|
+
end
|
|
48
56
|
true
|
|
49
57
|
end
|
|
50
58
|
|
|
59
|
+
def update(user_id, session_id, &block)
|
|
60
|
+
with_conn do |connection|
|
|
61
|
+
ensure_table!(connection)
|
|
62
|
+
connection.transaction do
|
|
63
|
+
result = connection.exec_params(
|
|
64
|
+
"SELECT state FROM llmemory_checkpoints WHERE user_id = $1 AND session_id = $2 FOR UPDATE",
|
|
65
|
+
[user_id, session_id]
|
|
66
|
+
)
|
|
67
|
+
row = result.first
|
|
68
|
+
current = row ? deserialize(row["state"]) : nil
|
|
69
|
+
new_state = yield(current)
|
|
70
|
+
next new_state if new_state.nil?
|
|
71
|
+
|
|
72
|
+
connection.exec_params(
|
|
73
|
+
<<~SQL,
|
|
74
|
+
INSERT INTO llmemory_checkpoints (user_id, session_id, state, updated_at)
|
|
75
|
+
VALUES ($1, $2, $3, $4)
|
|
76
|
+
ON CONFLICT (user_id, session_id)
|
|
77
|
+
DO UPDATE SET state = $3, updated_at = $4
|
|
78
|
+
SQL
|
|
79
|
+
[user_id, session_id, serialize(new_state), Time.now.utc.iso8601]
|
|
80
|
+
)
|
|
81
|
+
new_state
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
51
86
|
def list_users
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
87
|
+
with_conn do |connection|
|
|
88
|
+
ensure_table!(connection)
|
|
89
|
+
result = connection.exec("SELECT DISTINCT user_id FROM llmemory_checkpoints")
|
|
90
|
+
result.map { |r| r["user_id"] }
|
|
91
|
+
end
|
|
55
92
|
end
|
|
56
93
|
|
|
57
94
|
def list_sessions(user_id:)
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
95
|
+
with_conn do |connection|
|
|
96
|
+
ensure_table!(connection)
|
|
97
|
+
result = connection.exec_params(
|
|
98
|
+
"SELECT session_id FROM llmemory_checkpoints WHERE user_id = $1",
|
|
99
|
+
[user_id]
|
|
100
|
+
)
|
|
101
|
+
result.map { |r| r["session_id"] }
|
|
102
|
+
end
|
|
64
103
|
end
|
|
65
104
|
|
|
66
105
|
private
|
|
67
106
|
|
|
107
|
+
def with_conn
|
|
108
|
+
@conn_mutex.synchronize do
|
|
109
|
+
yield(conn)
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
68
113
|
def conn
|
|
69
114
|
@connection ||= begin
|
|
70
115
|
require "pg"
|
|
@@ -72,8 +117,10 @@ module Llmemory
|
|
|
72
117
|
end
|
|
73
118
|
end
|
|
74
119
|
|
|
75
|
-
def ensure_table!
|
|
76
|
-
|
|
120
|
+
def ensure_table!(connection)
|
|
121
|
+
return if @table_ready
|
|
122
|
+
|
|
123
|
+
connection.exec(<<~SQL)
|
|
77
124
|
CREATE TABLE IF NOT EXISTS llmemory_checkpoints (
|
|
78
125
|
user_id TEXT NOT NULL,
|
|
79
126
|
session_id TEXT NOT NULL,
|
|
@@ -82,6 +129,7 @@ module Llmemory
|
|
|
82
129
|
PRIMARY KEY (user_id, session_id)
|
|
83
130
|
)
|
|
84
131
|
SQL
|
|
132
|
+
@table_ready = true
|
|
85
133
|
end
|
|
86
134
|
|
|
87
135
|
def serialize(state)
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative "base"
|
|
4
4
|
require_relative "../../crypto/field_helpers"
|
|
5
|
+
require_relative "key_codec"
|
|
6
|
+
require_relative "../session_lifecycle"
|
|
5
7
|
|
|
6
8
|
module Llmemory
|
|
7
9
|
module ShortTerm
|
|
@@ -9,39 +11,96 @@ module Llmemory
|
|
|
9
11
|
class RedisStore < Base
|
|
10
12
|
include Llmemory::Crypto::FieldHelpers
|
|
11
13
|
|
|
14
|
+
KEY_PREFIX = "llmemory:checkpoint"
|
|
15
|
+
MAX_CAS_RETRIES = 5
|
|
16
|
+
|
|
12
17
|
def initialize(redis_url: nil, cipher: nil)
|
|
13
18
|
@redis_url = redis_url || Llmemory.configuration.redis_url
|
|
14
19
|
@redis = nil
|
|
15
20
|
@cipher = cipher || Llmemory.build_cipher
|
|
21
|
+
@conn_mutex = Mutex.new
|
|
16
22
|
end
|
|
17
23
|
|
|
18
24
|
def save(user_id, session_id, state)
|
|
19
|
-
|
|
25
|
+
with_redis do |client|
|
|
26
|
+
key = key_for(user_id, session_id)
|
|
27
|
+
payload = serialize(state)
|
|
28
|
+
if pseudo_session?(session_id)
|
|
29
|
+
client.set(key, payload)
|
|
30
|
+
else
|
|
31
|
+
client.set(key, payload, ex: session_ttl_seconds)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
20
34
|
true
|
|
21
35
|
end
|
|
22
36
|
|
|
23
37
|
def load(user_id, session_id)
|
|
24
|
-
|
|
25
|
-
|
|
38
|
+
with_redis do |client|
|
|
39
|
+
data = client.get(key_for(user_id, session_id))
|
|
40
|
+
data ? deserialize(data) : nil
|
|
41
|
+
end
|
|
26
42
|
end
|
|
27
43
|
|
|
28
44
|
def delete(user_id, session_id)
|
|
29
|
-
|
|
45
|
+
with_redis { |client| client.del(key_for(user_id, session_id)) }
|
|
30
46
|
true
|
|
31
47
|
end
|
|
32
48
|
|
|
49
|
+
def update(user_id, session_id, &block)
|
|
50
|
+
key = key_for(user_id, session_id)
|
|
51
|
+
retries = 0
|
|
52
|
+
|
|
53
|
+
loop do
|
|
54
|
+
result = with_redis do |client|
|
|
55
|
+
client.watch(key) do
|
|
56
|
+
raw = client.get(key)
|
|
57
|
+
current = raw ? deserialize(raw) : nil
|
|
58
|
+
new_state = yield(current)
|
|
59
|
+
next new_state if new_state.nil?
|
|
60
|
+
|
|
61
|
+
payload = serialize(new_state)
|
|
62
|
+
if pseudo_session?(session_id)
|
|
63
|
+
client.multi { |multi| multi.set(key, payload) }
|
|
64
|
+
else
|
|
65
|
+
client.multi { |multi| multi.set(key, payload, ex: session_ttl_seconds) }
|
|
66
|
+
end
|
|
67
|
+
new_state
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
return result unless result.nil?
|
|
71
|
+
|
|
72
|
+
retries += 1
|
|
73
|
+
raise Llmemory::StoreError, "Redis CAS update failed after #{MAX_CAS_RETRIES} retries" if retries >= MAX_CAS_RETRIES
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
33
77
|
def list_users
|
|
34
|
-
|
|
35
|
-
|
|
78
|
+
scan_keys("#{KEY_PREFIX}:*").filter_map do |k|
|
|
79
|
+
parts = k.split(":", 4)
|
|
80
|
+
next unless parts.size >= 4
|
|
81
|
+
|
|
82
|
+
KeyCodec.decode(parts[2])
|
|
83
|
+
end.uniq
|
|
36
84
|
end
|
|
37
85
|
|
|
38
86
|
def list_sessions(user_id:)
|
|
39
|
-
|
|
40
|
-
|
|
87
|
+
encoded_user = KeyCodec.encode(user_id)
|
|
88
|
+
scan_keys("#{KEY_PREFIX}:#{encoded_user}:*").filter_map do |k|
|
|
89
|
+
parts = k.split(":", 4)
|
|
90
|
+
next unless parts.size >= 4
|
|
91
|
+
|
|
92
|
+
KeyCodec.decode(parts[3])
|
|
93
|
+
end
|
|
41
94
|
end
|
|
42
95
|
|
|
43
96
|
private
|
|
44
97
|
|
|
98
|
+
def with_redis
|
|
99
|
+
@conn_mutex.synchronize do
|
|
100
|
+
yield(redis)
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
45
104
|
def redis
|
|
46
105
|
@redis ||= begin
|
|
47
106
|
require "redis"
|
|
@@ -49,8 +108,16 @@ module Llmemory
|
|
|
49
108
|
end
|
|
50
109
|
end
|
|
51
110
|
|
|
111
|
+
def scan_keys(match)
|
|
112
|
+
keys = []
|
|
113
|
+
with_redis do |client|
|
|
114
|
+
client.scan_each(match: match) { |key| keys << key }
|
|
115
|
+
end
|
|
116
|
+
keys
|
|
117
|
+
end
|
|
118
|
+
|
|
52
119
|
def key_for(user_id, session_id)
|
|
53
|
-
"
|
|
120
|
+
"#{KEY_PREFIX}:#{KeyCodec.encode(user_id)}:#{KeyCodec.encode(session_id)}"
|
|
54
121
|
end
|
|
55
122
|
|
|
56
123
|
def serialize(state)
|
|
@@ -60,6 +127,15 @@ module Llmemory
|
|
|
60
127
|
def deserialize(data)
|
|
61
128
|
deserialize_state(data)
|
|
62
129
|
end
|
|
130
|
+
|
|
131
|
+
def pseudo_session?(session_id)
|
|
132
|
+
SessionLifecycle.pseudo_session?(session_id)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def session_ttl_seconds
|
|
136
|
+
ttl = Llmemory.configuration.redis_session_ttl_seconds
|
|
137
|
+
ttl.positive? ? ttl : 86_400 * 30
|
|
138
|
+
end
|
|
63
139
|
end
|
|
64
140
|
end
|
|
65
141
|
end
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "stores/key_codec"
|
|
3
4
|
require_relative "stores/base"
|
|
4
5
|
require_relative "stores/memory_store"
|
|
5
6
|
require_relative "stores/redis_store"
|
|
@@ -12,8 +13,14 @@ module Llmemory
|
|
|
12
13
|
# Shared by Checkpoint, SessionLifecycle and WorkingMemory.
|
|
13
14
|
def self.build(store_type = nil, cipher: nil)
|
|
14
15
|
resolved_cipher = cipher || Llmemory.build_cipher
|
|
15
|
-
|
|
16
|
-
|
|
16
|
+
type = (store_type || Llmemory.configuration.short_term_store).to_sym
|
|
17
|
+
case type
|
|
18
|
+
when :memory
|
|
19
|
+
if Llmemory.configuration.shared_memory_stores
|
|
20
|
+
shared_memory_store(resolved_cipher)
|
|
21
|
+
else
|
|
22
|
+
MemoryStore.new(cipher: resolved_cipher)
|
|
23
|
+
end
|
|
17
24
|
when :redis then RedisStore.new(cipher: resolved_cipher)
|
|
18
25
|
when :postgres then PostgresStore.new(cipher: resolved_cipher)
|
|
19
26
|
when :active_record, :activerecord
|
|
@@ -23,6 +30,14 @@ module Llmemory
|
|
|
23
30
|
MemoryStore.new(cipher: resolved_cipher)
|
|
24
31
|
end
|
|
25
32
|
end
|
|
33
|
+
|
|
34
|
+
def self.shared_memory_store(cipher)
|
|
35
|
+
@shared_memory_store ||= MemoryStore.new(cipher: cipher)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def self.reset_shared_singletons!
|
|
39
|
+
@shared_memory_store = nil
|
|
40
|
+
end
|
|
26
41
|
end
|
|
27
42
|
end
|
|
28
43
|
end
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "base"
|
|
4
|
+
require_relative "../../active_record_helpers"
|
|
4
5
|
|
|
5
6
|
module Llmemory
|
|
6
7
|
module VectorStore
|
|
7
8
|
# Persists embeddings in llmemory_embeddings (pgvector).
|
|
8
9
|
# Use when long_term_store is :active_record so hybrid search finds persisted embeddings.
|
|
9
10
|
class ActiveRecordStore < Base
|
|
11
|
+
include Llmemory::ActiveRecordHelpers
|
|
10
12
|
def initialize(embedding_provider: nil, source_type: "edge", cipher: nil)
|
|
11
13
|
self.class.load_model!
|
|
12
14
|
@embedding_provider = embedding_provider
|
|
@@ -35,14 +37,16 @@ module Llmemory
|
|
|
35
37
|
def store(id:, embedding:, metadata: {}, user_id: nil)
|
|
36
38
|
return id if user_id.nil? || user_id.to_s.empty?
|
|
37
39
|
text_content = (metadata || {}).dig("text") || (metadata || {}).dig(:text)
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
40
|
+
with_unique_retry do
|
|
41
|
+
rec = Llmemory::VectorStore::ActiveRecordEmbedding.find_or_initialize_by(
|
|
42
|
+
user_id: user_id.to_s,
|
|
43
|
+
source_type: @source_type,
|
|
44
|
+
source_id: id.to_s
|
|
45
|
+
)
|
|
46
|
+
rec.embedding = embedding.to_a.map(&:to_f)
|
|
47
|
+
rec.text_content = encrypt_text_content(text_content)
|
|
48
|
+
rec.save!
|
|
49
|
+
end
|
|
46
50
|
id
|
|
47
51
|
end
|
|
48
52
|
|
|
@@ -81,6 +85,17 @@ module Llmemory
|
|
|
81
85
|
search(query_embedding, top_k: top_k, user_id: user_id)
|
|
82
86
|
end
|
|
83
87
|
|
|
88
|
+
def delete(id:, user_id: nil)
|
|
89
|
+
return false if user_id.nil? || user_id.to_s.empty?
|
|
90
|
+
|
|
91
|
+
Llmemory::VectorStore::ActiveRecordEmbedding.where(
|
|
92
|
+
user_id: user_id.to_s,
|
|
93
|
+
source_type: @source_type,
|
|
94
|
+
source_id: id.to_s
|
|
95
|
+
).delete_all
|
|
96
|
+
true
|
|
97
|
+
end
|
|
98
|
+
|
|
84
99
|
private
|
|
85
100
|
|
|
86
101
|
def encrypt_text_content(text)
|
|
@@ -14,6 +14,10 @@ module Llmemory
|
|
|
14
14
|
def search(query_embedding, top_k: 10)
|
|
15
15
|
raise NotImplementedError, "#{self.class}#search must be implemented"
|
|
16
16
|
end
|
|
17
|
+
|
|
18
|
+
def delete(id:, user_id: nil)
|
|
19
|
+
raise NotImplementedError, "#{self.class}#delete must be implemented"
|
|
20
|
+
end
|
|
17
21
|
end
|
|
18
22
|
end
|
|
19
23
|
end
|
|
@@ -55,6 +55,15 @@ module Llmemory
|
|
|
55
55
|
search(query_embedding, top_k: top_k, user_id: user_id)
|
|
56
56
|
end
|
|
57
57
|
|
|
58
|
+
def delete(id:, user_id: nil)
|
|
59
|
+
if user_id
|
|
60
|
+
@entries.delete("#{user_id}:#{id}")
|
|
61
|
+
else
|
|
62
|
+
@entries.delete_if { |_key, data| data[:source_id].to_s == id.to_s }
|
|
63
|
+
end
|
|
64
|
+
true
|
|
65
|
+
end
|
|
66
|
+
|
|
58
67
|
private
|
|
59
68
|
|
|
60
69
|
def decrypt_metadata(meta)
|
|
@@ -5,18 +5,21 @@ require "json"
|
|
|
5
5
|
require "digest"
|
|
6
6
|
require_relative "base"
|
|
7
7
|
require_relative "../llm/usage"
|
|
8
|
+
require_relative "../llm/http_client"
|
|
8
9
|
|
|
9
10
|
module Llmemory
|
|
10
11
|
module VectorStore
|
|
11
12
|
class OpenAIEmbeddings < Base
|
|
13
|
+
include Llmemory::LLM::HttpClient
|
|
12
14
|
DEFAULT_MODEL = "text-embedding-3-small"
|
|
13
15
|
DEFAULT_DIMS = 1536
|
|
14
16
|
|
|
15
17
|
attr_reader :last_usage
|
|
16
18
|
|
|
17
|
-
def initialize(api_key: nil, model: nil)
|
|
19
|
+
def initialize(api_key: nil, model: nil, base_url: nil)
|
|
18
20
|
@api_key = api_key || Llmemory.configuration.llm_api_key
|
|
19
21
|
@model = model || DEFAULT_MODEL
|
|
22
|
+
@base_url = base_url || Llmemory.configuration.llm_base_url || "https://api.openai.com/v1"
|
|
20
23
|
@cache = {}
|
|
21
24
|
@cache_order = []
|
|
22
25
|
@last_usage = Llmemory::LLM::Usage.zero
|
|
@@ -64,7 +67,7 @@ module Llmemory
|
|
|
64
67
|
result = nil
|
|
65
68
|
payload = { provider: :openai, model: @model, text_chars: text.to_s.length }
|
|
66
69
|
Llmemory::Instrumentation.instrument(:llm_embed, payload) do
|
|
67
|
-
response = connection
|
|
70
|
+
response = post_with_resilience(connection, "embeddings") do |req|
|
|
68
71
|
req.headers["Authorization"] = "Bearer #{@api_key}"
|
|
69
72
|
req.headers["Content-Type"] = "application/json"
|
|
70
73
|
req.body = { input: text.to_s.strip, model: @model }.to_json
|
|
@@ -90,11 +93,7 @@ module Llmemory
|
|
|
90
93
|
end
|
|
91
94
|
|
|
92
95
|
def connection
|
|
93
|
-
@connection ||=
|
|
94
|
-
f.request :json
|
|
95
|
-
f.response :json
|
|
96
|
-
f.adapter Faraday.default_adapter
|
|
97
|
-
end
|
|
96
|
+
@connection ||= build_faraday_connection(@base_url)
|
|
98
97
|
end
|
|
99
98
|
|
|
100
99
|
def store(id:, embedding:, metadata: {})
|
data/lib/llmemory/version.rb
CHANGED
|
@@ -36,18 +36,21 @@ module Llmemory
|
|
|
36
36
|
end
|
|
37
37
|
|
|
38
38
|
def set(slot, value)
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
@store.update(@user_id, @store_key) do |state|
|
|
40
|
+
state = symbolize(state || {})
|
|
41
|
+
state[slot.to_sym] = value
|
|
42
|
+
state
|
|
43
|
+
end
|
|
42
44
|
value
|
|
43
45
|
end
|
|
44
46
|
|
|
45
47
|
# Bulk update in a single write.
|
|
46
48
|
def update(**slots)
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
49
|
+
@store.update(@user_id, @store_key) do |state|
|
|
50
|
+
state = symbolize(state || {})
|
|
51
|
+
slots.each { |k, v| state[k.to_sym] = v }
|
|
52
|
+
state
|
|
53
|
+
end
|
|
51
54
|
end
|
|
52
55
|
|
|
53
56
|
# Slots set by the caller beyond the predefined typed ones.
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: llmemory
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2.
|
|
4
|
+
version: 0.2.7
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- llmemory
|
|
@@ -65,20 +65,6 @@ dependencies:
|
|
|
65
65
|
- - "~>"
|
|
66
66
|
- !ruby/object:Gem::Version
|
|
67
67
|
version: '3.12'
|
|
68
|
-
- !ruby/object:Gem::Dependency
|
|
69
|
-
name: vcr
|
|
70
|
-
requirement: !ruby/object:Gem::Requirement
|
|
71
|
-
requirements:
|
|
72
|
-
- - "~>"
|
|
73
|
-
- !ruby/object:Gem::Version
|
|
74
|
-
version: '6.2'
|
|
75
|
-
type: :development
|
|
76
|
-
prerelease: false
|
|
77
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
78
|
-
requirements:
|
|
79
|
-
- - "~>"
|
|
80
|
-
- !ruby/object:Gem::Version
|
|
81
|
-
version: '6.2'
|
|
82
68
|
- !ruby/object:Gem::Dependency
|
|
83
69
|
name: webmock
|
|
84
70
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -141,6 +127,7 @@ files:
|
|
|
141
127
|
- lib/llmemory.rb
|
|
142
128
|
- lib/llmemory/actions.rb
|
|
143
129
|
- lib/llmemory/actions/reason.rb
|
|
130
|
+
- lib/llmemory/active_record_helpers.rb
|
|
144
131
|
- lib/llmemory/cli.rb
|
|
145
132
|
- lib/llmemory/cli/commands/base.rb
|
|
146
133
|
- lib/llmemory/cli/commands/episodic.rb
|
|
@@ -174,6 +161,7 @@ files:
|
|
|
174
161
|
- lib/llmemory/llm.rb
|
|
175
162
|
- lib/llmemory/llm/anthropic.rb
|
|
176
163
|
- lib/llmemory/llm/base.rb
|
|
164
|
+
- lib/llmemory/llm/http_client.rb
|
|
177
165
|
- lib/llmemory/llm/openai.rb
|
|
178
166
|
- lib/llmemory/llm/response.rb
|
|
179
167
|
- lib/llmemory/llm/tracking_client.rb
|
|
@@ -236,6 +224,7 @@ files:
|
|
|
236
224
|
- lib/llmemory/mcp.rb
|
|
237
225
|
- lib/llmemory/mcp/authentication.rb
|
|
238
226
|
- lib/llmemory/mcp/server.rb
|
|
227
|
+
- lib/llmemory/mcp/store_helpers.rb
|
|
239
228
|
- lib/llmemory/mcp/tools/memory_add_message.rb
|
|
240
229
|
- lib/llmemory/mcp/tools/memory_consolidate.rb
|
|
241
230
|
- lib/llmemory/mcp/tools/memory_episode_record.rb
|
|
@@ -265,6 +254,7 @@ files:
|
|
|
265
254
|
- lib/llmemory/retrieval/engine.rb
|
|
266
255
|
- lib/llmemory/retrieval/feedback_store.rb
|
|
267
256
|
- lib/llmemory/retrieval/mmr_reranker.rb
|
|
257
|
+
- lib/llmemory/retrieval/multi_source.rb
|
|
268
258
|
- lib/llmemory/retrieval/temporal_ranker.rb
|
|
269
259
|
- lib/llmemory/short_term.rb
|
|
270
260
|
- lib/llmemory/short_term/checkpoint.rb
|
|
@@ -275,6 +265,7 @@ files:
|
|
|
275
265
|
- lib/llmemory/short_term/stores/active_record_checkpoint.rb
|
|
276
266
|
- lib/llmemory/short_term/stores/active_record_store.rb
|
|
277
267
|
- lib/llmemory/short_term/stores/base.rb
|
|
268
|
+
- lib/llmemory/short_term/stores/key_codec.rb
|
|
278
269
|
- lib/llmemory/short_term/stores/memory_store.rb
|
|
279
270
|
- lib/llmemory/short_term/stores/postgres_store.rb
|
|
280
271
|
- lib/llmemory/short_term/stores/redis_store.rb
|