@batonfx/memory 0.3.3 → 0.3.5

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.
@@ -1,5 +1,5 @@
1
1
  $ bun build ./src/index.ts --outdir dist --target bun --format esm
2
- Bundled 176 modules in 529ms
2
+ Bundled 176 modules in 390ms
3
3
 
4
4
  index.js 1.1 MB (entry point)
5
5
 
package/dist/index.js CHANGED
@@ -32662,11 +32662,13 @@ class Memory extends exports_Context.Service()("@batonfx/core/Memory") {
32662
32662
  }
32663
32663
  var noop = {
32664
32664
  recall: () => exports_Effect.succeed([]),
32665
- remember: () => exports_Effect.void
32665
+ remember: () => exports_Effect.void,
32666
+ forget: () => exports_Effect.void
32666
32667
  };
32667
32668
  var merge6 = (first, second) => ({
32668
32669
  recall: (input) => exports_Effect.all([first.recall(input), second.recall(input)]).pipe(exports_Effect.map(([firstItems, secondItems]) => [...firstItems, ...secondItems])),
32669
- remember: (input) => exports_Effect.all([first.remember(input), second.remember(input)], { discard: true })
32670
+ remember: (input) => exports_Effect.all([first.remember(input), second.remember(input)], { discard: true }),
32671
+ forget: (input) => exports_Effect.all([first.forget(input), second.forget(input)], { discard: true })
32670
32672
  });
32671
32673
  var noopLayer = exports_Layer.succeed(Memory, Memory.of(noop));
32672
32674
  var testLayer = (implementation) => exports_Layer.succeed(Memory, Memory.of(implementation));
@@ -32862,7 +32864,12 @@ var make37 = exports_Ref.make(exports_HashMap.empty()).pipe(exports_Effect.map((
32862
32864
  matches.push({ document, score });
32863
32865
  }
32864
32866
  return matches.toSorted((left, right) => right.score - left.score).slice(0, input.limit);
32865
- })
32867
+ }),
32868
+ delete: (input) => exports_Ref.update(documents, (current) => exports_HashMap.filter(current, (document) => {
32869
+ if (!sameKey(document.key, input.key))
32870
+ return true;
32871
+ return input.id === undefined ? false : document.id !== input.id;
32872
+ }))
32866
32873
  })));
32867
32874
  var memoryLayer2 = exports_Layer.effect(VectorStore, make37.pipe(exports_Effect.map(VectorStore.of)));
32868
32875
  var testLayer2 = (implementation) => exports_Layer.succeed(VectorStore, VectorStore.of(implementation));
@@ -32939,7 +32946,8 @@ var make38 = (options = {}) => exports_Effect.gen(function* () {
32939
32946
  embedding: embedding.vector
32940
32947
  }
32941
32948
  ])))), exports_Effect.mapError(memoryError));
32942
- }
32949
+ },
32950
+ forget: (input) => store.delete(input).pipe(exports_Effect.mapError(memoryError))
32943
32951
  };
32944
32952
  });
32945
32953
  var makeSemanticRecall = make38;
@@ -33064,7 +33072,25 @@ var make39 = (options = {}) => exports_Ref.make(exports_HashMap.empty()).pipe(ex
33064
33072
  };
33065
33073
  yield* exports_Ref.update(states, exports_HashMap.set(id2, nextState));
33066
33074
  });
33067
- }
33075
+ },
33076
+ forget: (input) => exports_Ref.update(states, (current) => {
33077
+ const id2 = keyId(input.key);
33078
+ if (input.id === undefined)
33079
+ return exports_HashMap.remove(current, id2);
33080
+ const existing = exports_HashMap.get(current, id2);
33081
+ if (existing._tag === "None")
33082
+ return current;
33083
+ const summary = input.id === "working-summary" ? undefined : existing.value.summary;
33084
+ const recent = existing.value.recent.filter((item) => item.id !== input.id);
33085
+ if (summary === undefined && recent.length === 0)
33086
+ return exports_HashMap.remove(current, id2);
33087
+ const nextState = {
33088
+ recent,
33089
+ counter: existing.value.counter,
33090
+ ...summary === undefined ? {} : { summary }
33091
+ };
33092
+ return exports_HashMap.set(current, id2, nextState);
33093
+ })
33068
33094
  };
33069
33095
  }));
33070
33096
  var makeWorkingMemory = make39;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
3
  "name": "@batonfx/memory",
4
- "version": "0.3.3",
4
+ "version": "0.3.5",
5
5
  "private": false,
6
6
  "type": "module",
7
7
  "exports": {
@@ -14,7 +14,7 @@
14
14
  "typecheck": "bun tsc --noEmit"
15
15
  },
16
16
  "dependencies": {
17
- "@batonfx/core": "0.3.3",
17
+ "@batonfx/core": "0.3.5",
18
18
  "effect": "4.0.0-beta.93"
19
19
  },
20
20
  "devDependencies": {
@@ -109,6 +109,7 @@ export const make = (
109
109
  Effect.mapError(memoryError),
110
110
  )
111
111
  },
112
+ forget: (input) => store.delete(input).pipe(Effect.mapError(memoryError)),
112
113
  }
113
114
  })
114
115
 
@@ -28,6 +28,12 @@ export interface Query {
28
28
  readonly minScore?: number
29
29
  }
30
30
 
31
+ /** @experimental */
32
+ export interface DeleteInput {
33
+ readonly key: Memory.Key
34
+ readonly id?: string | undefined
35
+ }
36
+
31
37
  /** @experimental */
32
38
  export class VectorStoreError extends Schema.TaggedErrorClass<VectorStoreError>()("@batonfx/memory/VectorStoreError", {
33
39
  message: Schema.String,
@@ -37,6 +43,7 @@ export class VectorStoreError extends Schema.TaggedErrorClass<VectorStoreError>(
37
43
  export interface Interface {
38
44
  readonly upsert: (documents: ReadonlyArray<Embedded>) => Effect.Effect<void, VectorStoreError>
39
45
  readonly query: (query: Query) => Effect.Effect<ReadonlyArray<Match>, VectorStoreError>
46
+ readonly delete: (input: DeleteInput) => Effect.Effect<void, VectorStoreError>
40
47
  }
41
48
 
42
49
  /** @experimental */
@@ -106,6 +113,13 @@ const make = Ref.make(HashMap.empty<string, Embedded>()).pipe(
106
113
  }
107
114
  return matches.toSorted((left, right) => right.score - left.score).slice(0, input.limit)
108
115
  }),
116
+ delete: (input) =>
117
+ Ref.update(documents, (current) =>
118
+ HashMap.filter(current, (document) => {
119
+ if (!sameKey(document.key, input.key)) return true
120
+ return input.id === undefined ? false : document.id !== input.id
121
+ }),
122
+ ),
109
123
  }),
110
124
  ),
111
125
  )
@@ -180,6 +180,22 @@ export const make = (options: Options = {}): Effect.Effect<Memory.Interface> =>
180
180
  yield* Ref.update(states, HashMap.set(id, nextState))
181
181
  })
182
182
  },
183
+ forget: (input) =>
184
+ Ref.update(states, (current) => {
185
+ const id = keyId(input.key)
186
+ if (input.id === undefined) return HashMap.remove(current, id)
187
+ const existing = HashMap.get(current, id)
188
+ if (existing._tag === "None") return current
189
+ const summary = input.id === "working-summary" ? undefined : existing.value.summary
190
+ const recent = existing.value.recent.filter((item) => item.id !== input.id)
191
+ if (summary === undefined && recent.length === 0) return HashMap.remove(current, id)
192
+ const nextState: KeyState = {
193
+ recent,
194
+ counter: existing.value.counter,
195
+ ...(summary === undefined ? {} : { summary }),
196
+ }
197
+ return HashMap.set(current, id, nextState)
198
+ }),
183
199
  }
184
200
  }),
185
201
  )
@@ -91,6 +91,39 @@ describe("SemanticRecall", () => {
91
91
  }).pipe(Effect.provide(memoryLayer)),
92
92
  )
93
93
 
94
+ it.effect("forgets one semantic memory id within the exact memory key", () =>
95
+ Effect.gen(function* () {
96
+ const memory = yield* Memory.Memory
97
+
98
+ yield* memory.remember({
99
+ key,
100
+ turn: 0,
101
+ terminal: true,
102
+ transcript: prompt(user("What color is the sky?"), assistant("blue")),
103
+ })
104
+ yield* memory.remember({
105
+ key,
106
+ turn: 1,
107
+ terminal: true,
108
+ transcript: prompt(user("What color is the ocean?"), assistant("blue")),
109
+ })
110
+ yield* memory.remember({
111
+ key: otherKey,
112
+ turn: 0,
113
+ terminal: true,
114
+ transcript: prompt(user("What color is the door?"), assistant("blue")),
115
+ })
116
+
117
+ yield* memory.forget({ key, id: "semantic-1" })
118
+
119
+ const retained = yield* memory.recall({ key, turn: 0, prompt: prompt(user("color")) })
120
+ const otherRetained = yield* memory.recall({ key: otherKey, turn: 0, prompt: prompt(user("color")) })
121
+
122
+ expect(retained.map(itemText)).toEqual(["User: What color is the ocean?\nAssistant: blue"])
123
+ expect(otherRetained.map(itemText)).toEqual(["User: What color is the door?\nAssistant: blue"])
124
+ }).pipe(Effect.provide(memoryLayer)),
125
+ )
126
+
94
127
  it.effect("maps embedding failures to MemoryError", () => {
95
128
  const embeddingError = AiError.make({
96
129
  module: "SemanticRecallTest",
@@ -79,4 +79,43 @@ describe("VectorStore", () => {
79
79
  expect(failure.message).toContain("dimension")
80
80
  }).pipe(Effect.provide(VectorStore.memoryLayer)),
81
81
  )
82
+
83
+ it.effect("deletes documents for the exact memory key", () =>
84
+ Effect.gen(function* () {
85
+ const store = yield* VectorStore.VectorStore
86
+
87
+ yield* store.upsert([
88
+ document("same-id", key, "visible", [1, 0]),
89
+ document("same-id", otherSubject, "other subject", [1, 0]),
90
+ ])
91
+
92
+ yield* store.delete({ key })
93
+
94
+ const deletedMatches = yield* store.query({ key, embedding: [1, 0], limit: 10 })
95
+ const remainingMatches = yield* store.query({ key: otherSubject, embedding: [1, 0], limit: 10 })
96
+
97
+ expect(deletedMatches).toEqual([])
98
+ expect(remainingMatches.map((match) => match.document.text)).toEqual(["other subject"])
99
+ }).pipe(Effect.provide(VectorStore.memoryLayer)),
100
+ )
101
+
102
+ it.effect("deletes one document id within the exact memory key", () =>
103
+ Effect.gen(function* () {
104
+ const store = yield* VectorStore.VectorStore
105
+
106
+ yield* store.upsert([
107
+ document("first", key, "first", [1, 0]),
108
+ document("second", key, "second", [0, 1]),
109
+ document("first", otherSubject, "other subject", [1, 0]),
110
+ ])
111
+
112
+ yield* store.delete({ key, id: "first" })
113
+
114
+ const deletedKeyMatches = yield* store.query({ key, embedding: [1, 0], limit: 10 })
115
+ const otherSubjectMatches = yield* store.query({ key: otherSubject, embedding: [1, 0], limit: 10 })
116
+
117
+ expect(deletedKeyMatches.map((match) => match.document.text)).toEqual(["second"])
118
+ expect(otherSubjectMatches.map((match) => match.document.text)).toEqual(["other subject"])
119
+ }).pipe(Effect.provide(VectorStore.memoryLayer)),
120
+ )
82
121
  })
@@ -98,4 +98,58 @@ describe("WorkingMemory", () => {
98
98
  expect(recalled).toEqual([])
99
99
  }).pipe(Effect.provide(WorkingMemory.layer({ maxMessages: 2 }))),
100
100
  )
101
+
102
+ it.effect("forgets the exact memory key", () =>
103
+ Effect.gen(function* () {
104
+ const memory = yield* Memory.Memory
105
+
106
+ yield* memory.remember({
107
+ key,
108
+ turn: 0,
109
+ terminal: true,
110
+ transcript: prompt(user("one"), assistant("two")),
111
+ })
112
+ yield* memory.remember({
113
+ key: otherKey,
114
+ turn: 0,
115
+ terminal: true,
116
+ transcript: prompt(user("three"), assistant("four")),
117
+ })
118
+
119
+ yield* memory.forget({ key })
120
+
121
+ const forgotten = yield* memory.recall({ key, turn: 0, prompt: prompt(user("current")) })
122
+ const retained = yield* memory.recall({ key: otherKey, turn: 0, prompt: prompt(user("current")) })
123
+
124
+ expect(forgotten).toEqual([])
125
+ expect(retained.map(itemText)).toEqual(["User: three", "Assistant: four"])
126
+ }).pipe(Effect.provide(WorkingMemory.layer({ maxMessages: 2 }))),
127
+ )
128
+
129
+ it.effect("forgets one recalled item id within the exact memory key", () =>
130
+ Effect.gen(function* () {
131
+ const memory = yield* Memory.Memory
132
+
133
+ yield* memory.remember({
134
+ key,
135
+ turn: 0,
136
+ terminal: true,
137
+ transcript: prompt(user("one"), assistant("two")),
138
+ })
139
+ yield* memory.remember({
140
+ key: otherKey,
141
+ turn: 0,
142
+ terminal: true,
143
+ transcript: prompt(user("three"), assistant("four")),
144
+ })
145
+
146
+ yield* memory.forget({ key, id: "working-1" })
147
+
148
+ const retained = yield* memory.recall({ key, turn: 0, prompt: prompt(user("current")) })
149
+ const otherRetained = yield* memory.recall({ key: otherKey, turn: 0, prompt: prompt(user("current")) })
150
+
151
+ expect(retained.map(itemText)).toEqual(["Assistant: two"])
152
+ expect(otherRetained.map(itemText)).toEqual(["User: three", "Assistant: four"])
153
+ }).pipe(Effect.provide(WorkingMemory.layer({ maxMessages: 2 }))),
154
+ )
101
155
  })