@objectstack/knowledge-memory 17.0.0-rc.0 → 17.0.0-rc.1
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.
- package/CHANGELOG.md +211 -0
- package/package.json +11 -5
- package/.turbo/turbo-build.log +0 -22
- package/src/__tests__/memory-adapter.test.ts +0 -105
- package/src/index.ts +0 -235
- package/tsconfig.json +0 -20
- package/vitest.config.ts +0 -25
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,216 @@
|
|
|
1
1
|
# @objectstack/knowledge-memory
|
|
2
2
|
|
|
3
|
+
## 17.0.0-rc.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 2e836de: chore(packaging): CHANGELOG.md ships in every npm tarball (#4261)
|
|
8
|
+
|
|
9
|
+
The AGENTS.md post-task checklist requires breaking changesets to carry their
|
|
10
|
+
FROM → TO migration because "this text ships to consumers as `CHANGELOG.md`
|
|
11
|
+
inside the npm package and is what an upgrading agent greps after the tombstone
|
|
12
|
+
error." That delivery path was severed for 68 of the 69 publishable packages:
|
|
13
|
+
npm packs `package.json` / `README*` / `LICENSE*` unconditionally but — unlike
|
|
14
|
+
older npm versions — not `CHANGELOG.md`, and the canonical
|
|
15
|
+
`"files": ["dist", "README.md"]` whitelist never named it. Measured on npm
|
|
16
|
+
10.9.7: `npm pack --dry-run` on `@objectstack/types` shipped 3 files while its
|
|
17
|
+
70KB `CHANGELOG.md` stayed behind. Only `@objectstack/spec` listed it
|
|
18
|
+
explicitly.
|
|
19
|
+
|
|
20
|
+
The tombstone-error scenario is precisely the one where the repo is out of
|
|
21
|
+
reach — the upgrading agent has `node_modules` and nothing else — so the
|
|
22
|
+
migration text has to ride in the tarball. Every publishable package now
|
|
23
|
+
declares `CHANGELOG.md` in `files`, and the canonical whitelist is
|
|
24
|
+
`["dist", "README.md", "CHANGELOG.md"]`.
|
|
25
|
+
|
|
26
|
+
The other half is the gate: `check:published-files` gains a fifth invariant,
|
|
27
|
+
COMPLETE — a whitelist that fails to cover `CHANGELOG.md` fails the
|
|
28
|
+
always-required lint job, so the next package cannot silently sever the path
|
|
29
|
+
again. `@objectstack/spec`'s per-package EXTRA_ENTRIES exemption dissolves
|
|
30
|
+
into the canonical set.
|
|
31
|
+
|
|
32
|
+
Consumer-visible change: one more file per install (the package's changelog,
|
|
33
|
+
e.g. 70.8KB for `@objectstack/types`), and `grep -r "removed key"
|
|
34
|
+
node_modules/@objectstack/*/CHANGELOG.md` now finds the migration it was
|
|
35
|
+
promised.
|
|
36
|
+
|
|
37
|
+
- cc2de0e: chore(packaging): 20 packages stop publishing their sources, tests and build tooling (#4248)
|
|
38
|
+
|
|
39
|
+
These 20 packages declared no `files` field, so npm fell back to packing the
|
|
40
|
+
whole package directory. `npm pack --dry-run` on `@objectstack/plugin-webhooks`
|
|
41
|
+
listed **21 files** — 15 under `src/`, three of them unit tests
|
|
42
|
+
(`auto-enqueuer.test.ts`, `bootstrap-declared-webhooks.test.ts`, …), plus the
|
|
43
|
+
build-time `scripts/i18n-extract.config.ts`. `dist/` lands on top of that at
|
|
44
|
+
publish time rather than instead of it, so consumers were installing the
|
|
45
|
+
TypeScript sources and the test suite alongside the artifact they asked for.
|
|
46
|
+
|
|
47
|
+
Each now declares `"files": ["dist", "README.md"]`, matching the 29 packages
|
|
48
|
+
that already did. Nothing a consumer imports moves: every `main` / `types` /
|
|
49
|
+
`exports` target in all 20 already resolved inside `dist/`, which the new
|
|
50
|
+
`check:published-files` guard verifies rather than assumes. The visible change
|
|
51
|
+
is a smaller install and a smaller dependency-scanning surface — `npm pack` on
|
|
52
|
+
`@objectstack/plugin-webhooks` now yields 2 files plus `dist/`.
|
|
53
|
+
|
|
54
|
+
The other half of the fix is the gate. Half the packages declaring `files` and
|
|
55
|
+
half not was the #3786 shape — a hand-copied convention with nothing enforcing
|
|
56
|
+
it, where whoever forgets the line gets no signal at all. `check:published-files`
|
|
57
|
+
(new, wired into the always-required `lint` job) holds every non-private
|
|
58
|
+
workspace package to four invariants: `files` is **declared**; it is
|
|
59
|
+
**sufficient** (covers every entry point, so tightening a whitelist cannot ship
|
|
60
|
+
a package that fails to resolve); it is **minimal** (admits no test, test-harness
|
|
61
|
+
config or build script); and anything beyond `dist` + `README.md` is
|
|
62
|
+
**registered** with a reason, reconciled in both directions so a stale exemption
|
|
63
|
+
is an error rather than dead text. `@objectstack/spec` is the one package with
|
|
64
|
+
registered extras — its `.zod.ts` sources, JSON Schemas, liveness ledgers and
|
|
65
|
+
`CHANGELOG.md` are product, not build input.
|
|
66
|
+
|
|
67
|
+
This also closes an assumption #4206 was resting on. Excluding `<pkg>/scripts/**`
|
|
68
|
+
from the docs-drift implementation test is sound only while no package publishes
|
|
69
|
+
`scripts/` as runtime code; that held, but it held because someone read all three
|
|
70
|
+
offenders by hand. It is now checked on every PR.
|
|
71
|
+
|
|
72
|
+
- Updated dependencies [6a67d7a]
|
|
73
|
+
- Updated dependencies [0ecc656]
|
|
74
|
+
- Updated dependencies [06772eb]
|
|
75
|
+
- Updated dependencies [270650f]
|
|
76
|
+
- Updated dependencies [3aef718]
|
|
77
|
+
- Updated dependencies [1ea6bce]
|
|
78
|
+
- Updated dependencies [c1dcacd]
|
|
79
|
+
- Updated dependencies [ad303ed]
|
|
80
|
+
- Updated dependencies [32ccb23]
|
|
81
|
+
- Updated dependencies [f5a4ef0]
|
|
82
|
+
- Updated dependencies [2d3e255]
|
|
83
|
+
- Updated dependencies [7d7521f]
|
|
84
|
+
- Updated dependencies [5dc4d02]
|
|
85
|
+
- Updated dependencies [05154a1]
|
|
86
|
+
- Updated dependencies [9b6fe7c]
|
|
87
|
+
- Updated dependencies [8c711fb]
|
|
88
|
+
- Updated dependencies [09e4547]
|
|
89
|
+
- Updated dependencies [91f4c78]
|
|
90
|
+
- Updated dependencies [820eff9]
|
|
91
|
+
- Updated dependencies [8d895ff]
|
|
92
|
+
- Updated dependencies [f6472d7]
|
|
93
|
+
- Updated dependencies [78caf51]
|
|
94
|
+
- Updated dependencies [62a789b]
|
|
95
|
+
- Updated dependencies [789ad63]
|
|
96
|
+
- Updated dependencies [2af1988]
|
|
97
|
+
- Updated dependencies [0af50a3]
|
|
98
|
+
- Updated dependencies [2e836de]
|
|
99
|
+
- Updated dependencies [12a19a8]
|
|
100
|
+
- Updated dependencies [41dcda3]
|
|
101
|
+
- Updated dependencies [c8124e5]
|
|
102
|
+
- Updated dependencies [a1a4140]
|
|
103
|
+
- Updated dependencies [217e2e6]
|
|
104
|
+
- Updated dependencies [86a71d1]
|
|
105
|
+
- Updated dependencies [d5c75e2]
|
|
106
|
+
- Updated dependencies [03d26f7]
|
|
107
|
+
- Updated dependencies [4384921]
|
|
108
|
+
- Updated dependencies [3c628ce]
|
|
109
|
+
- Updated dependencies [7cb922e]
|
|
110
|
+
- Updated dependencies [1d22114]
|
|
111
|
+
- Updated dependencies [b5f9397]
|
|
112
|
+
- Updated dependencies [ed77493]
|
|
113
|
+
- Updated dependencies [58a03d2]
|
|
114
|
+
- Updated dependencies [dc530b4]
|
|
115
|
+
- Updated dependencies [e59786e]
|
|
116
|
+
- Updated dependencies [bcf1112]
|
|
117
|
+
- Updated dependencies [9774b78]
|
|
118
|
+
- Updated dependencies [b07d829]
|
|
119
|
+
- Updated dependencies [a648e96]
|
|
120
|
+
- Updated dependencies [a47ac06]
|
|
121
|
+
- Updated dependencies [e4c61a7]
|
|
122
|
+
- Updated dependencies [cc60165]
|
|
123
|
+
- Updated dependencies [081aa6f]
|
|
124
|
+
- Updated dependencies [91f4c78]
|
|
125
|
+
- Updated dependencies [e8d0c21]
|
|
126
|
+
- Updated dependencies [45dc446]
|
|
127
|
+
- Updated dependencies [c1d44f7]
|
|
128
|
+
- Updated dependencies [ab9fb5c]
|
|
129
|
+
- Updated dependencies [f985b3f]
|
|
130
|
+
- Updated dependencies [9a4932a]
|
|
131
|
+
- Updated dependencies [f9fc874]
|
|
132
|
+
- Updated dependencies [011b386]
|
|
133
|
+
- Updated dependencies [7777e8f]
|
|
134
|
+
- Updated dependencies [507b92a]
|
|
135
|
+
- Updated dependencies [7309c81]
|
|
136
|
+
- Updated dependencies [20bc1ec]
|
|
137
|
+
- Updated dependencies [90c2b15]
|
|
138
|
+
- Updated dependencies [42eeb7d]
|
|
139
|
+
- Updated dependencies [01e124d]
|
|
140
|
+
- Updated dependencies [7ce02eb]
|
|
141
|
+
- Updated dependencies [a13827e]
|
|
142
|
+
- Updated dependencies [7733604]
|
|
143
|
+
- Updated dependencies [40e420f]
|
|
144
|
+
- Updated dependencies [d13004a]
|
|
145
|
+
- Updated dependencies [be7360c]
|
|
146
|
+
- Updated dependencies [5b47ab5]
|
|
147
|
+
- Updated dependencies [b09d8d9]
|
|
148
|
+
- Updated dependencies [b09d8d9]
|
|
149
|
+
- Updated dependencies [8675db6]
|
|
150
|
+
- Updated dependencies [b09d8d9]
|
|
151
|
+
- Updated dependencies [3eb1b2b]
|
|
152
|
+
- Updated dependencies [59b85c0]
|
|
153
|
+
- Updated dependencies [6e357ed]
|
|
154
|
+
- Updated dependencies [d6938bf]
|
|
155
|
+
- Updated dependencies [31e0be9]
|
|
156
|
+
- Updated dependencies [4bfd455]
|
|
157
|
+
- Updated dependencies [ffd2ce2]
|
|
158
|
+
- Updated dependencies [62f8017]
|
|
159
|
+
- Updated dependencies [a831df1]
|
|
160
|
+
- Updated dependencies [f752ee3]
|
|
161
|
+
- Updated dependencies [a1b61e0]
|
|
162
|
+
- Updated dependencies [cd6b9f2]
|
|
163
|
+
- Updated dependencies [2cb6d3c]
|
|
164
|
+
- Updated dependencies [af2a095]
|
|
165
|
+
- Updated dependencies [ec796d5]
|
|
166
|
+
- Updated dependencies [e87fea1]
|
|
167
|
+
- Updated dependencies [c65e529]
|
|
168
|
+
- Updated dependencies [3ca34c1]
|
|
169
|
+
- Updated dependencies [239c3a3]
|
|
170
|
+
- Updated dependencies [94a0bbc]
|
|
171
|
+
- Updated dependencies [d6bfb3d]
|
|
172
|
+
- Updated dependencies [a2266a6]
|
|
173
|
+
- Updated dependencies [d25a0ec]
|
|
174
|
+
- Updated dependencies [667b83e]
|
|
175
|
+
- Updated dependencies [627b188]
|
|
176
|
+
- Updated dependencies [8d4eae7]
|
|
177
|
+
- Updated dependencies [857a6cf]
|
|
178
|
+
- Updated dependencies [65a3a84]
|
|
179
|
+
- Updated dependencies [ccd9397]
|
|
180
|
+
- Updated dependencies [bca935b]
|
|
181
|
+
- Updated dependencies [d92c72d]
|
|
182
|
+
- Updated dependencies [c54c822]
|
|
183
|
+
- Updated dependencies [8dcc0f5]
|
|
184
|
+
- Updated dependencies [75b9e51]
|
|
185
|
+
- Updated dependencies [0a2f233]
|
|
186
|
+
- Updated dependencies [8621cdd]
|
|
187
|
+
- Updated dependencies [6f23667]
|
|
188
|
+
- Updated dependencies [5d21a48]
|
|
189
|
+
- Updated dependencies [19365b7]
|
|
190
|
+
- Updated dependencies [b7ed26d]
|
|
191
|
+
- Updated dependencies [b3a3d83]
|
|
192
|
+
- Updated dependencies [7a55913]
|
|
193
|
+
- Updated dependencies [35accbf]
|
|
194
|
+
- Updated dependencies [6038de7]
|
|
195
|
+
- Updated dependencies [eb95d97]
|
|
196
|
+
- Updated dependencies [e4c2dc8]
|
|
197
|
+
- Updated dependencies [1bd2795]
|
|
198
|
+
- Updated dependencies [8186a70]
|
|
199
|
+
- Updated dependencies [a329cca]
|
|
200
|
+
- Updated dependencies [6eec18c]
|
|
201
|
+
- Updated dependencies [4d7bebf]
|
|
202
|
+
- Updated dependencies [821ac7a]
|
|
203
|
+
- Updated dependencies [8f81731]
|
|
204
|
+
- Updated dependencies [8b50cb3]
|
|
205
|
+
- Updated dependencies [8c2db68]
|
|
206
|
+
- Updated dependencies [22b5e54]
|
|
207
|
+
- Updated dependencies [0166bd5]
|
|
208
|
+
- Updated dependencies [9b702dc]
|
|
209
|
+
- Updated dependencies [ab16331]
|
|
210
|
+
- @objectstack/spec@17.0.0-rc.1
|
|
211
|
+
- @objectstack/core@17.0.0-rc.1
|
|
212
|
+
- @objectstack/service-knowledge@17.0.0-rc.1
|
|
213
|
+
|
|
3
214
|
## 17.0.0-rc.0
|
|
4
215
|
|
|
5
216
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@objectstack/knowledge-memory",
|
|
3
|
-
"version": "17.0.0-rc.
|
|
3
|
+
"version": "17.0.0-rc.1",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"description": "In-memory knowledge adapter for ObjectStack (dev / test reference implementation).",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -13,9 +13,9 @@
|
|
|
13
13
|
}
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"@objectstack/core": "17.0.0-rc.
|
|
17
|
-
"@objectstack/service-knowledge": "17.0.0-rc.
|
|
18
|
-
"@objectstack/spec": "17.0.0-rc.
|
|
16
|
+
"@objectstack/core": "17.0.0-rc.1",
|
|
17
|
+
"@objectstack/service-knowledge": "17.0.0-rc.1",
|
|
18
|
+
"@objectstack/spec": "17.0.0-rc.1"
|
|
19
19
|
},
|
|
20
20
|
"devDependencies": {
|
|
21
21
|
"@types/node": "^26.1.1",
|
|
@@ -29,9 +29,15 @@
|
|
|
29
29
|
"memory",
|
|
30
30
|
"in-memory"
|
|
31
31
|
],
|
|
32
|
+
"files": [
|
|
33
|
+
"dist",
|
|
34
|
+
"README.md",
|
|
35
|
+
"CHANGELOG.md"
|
|
36
|
+
],
|
|
32
37
|
"scripts": {
|
|
33
38
|
"build": "tsup --config ../../../tsup.config.ts",
|
|
34
39
|
"dev": "tsc -w",
|
|
35
|
-
"test": "vitest run"
|
|
40
|
+
"test": "vitest run",
|
|
41
|
+
"typecheck": "tsc --noEmit"
|
|
36
42
|
}
|
|
37
43
|
}
|
package/.turbo/turbo-build.log
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
> @objectstack/knowledge-memory@17.0.0-rc.0 build /home/runner/work/objectstack/objectstack/packages/plugins/knowledge-memory
|
|
3
|
-
> tsup --config ../../../tsup.config.ts
|
|
4
|
-
|
|
5
|
-
[34mCLI[39m Building entry: src/index.ts
|
|
6
|
-
[34mCLI[39m Using tsconfig: tsconfig.json
|
|
7
|
-
[34mCLI[39m tsup v8.5.1
|
|
8
|
-
[34mCLI[39m Using tsup config: /home/runner/work/objectstack/objectstack/tsup.config.ts
|
|
9
|
-
[34mCLI[39m Target: es2020
|
|
10
|
-
[34mCLI[39m Cleaning output folder
|
|
11
|
-
[34mESM[39m Build start
|
|
12
|
-
[34mCJS[39m Build start
|
|
13
|
-
[32mCJS[39m [1mdist/index.js [22m[32m5.63 KB[39m
|
|
14
|
-
[32mCJS[39m [1mdist/index.js.map [22m[32m10.83 KB[39m
|
|
15
|
-
[32mCJS[39m ⚡️ Build success in 97ms
|
|
16
|
-
[32mESM[39m [1mdist/index.mjs [22m[32m4.55 KB[39m
|
|
17
|
-
[32mESM[39m [1mdist/index.mjs.map [22m[32m10.79 KB[39m
|
|
18
|
-
[32mESM[39m ⚡️ Build success in 132ms
|
|
19
|
-
[34mDTS[39m Build start
|
|
20
|
-
[32mDTS[39m ⚡️ Build success in 13512ms
|
|
21
|
-
[32mDTS[39m [1mdist/index.d.mts [22m[32m1.69 KB[39m
|
|
22
|
-
[32mDTS[39m [1mdist/index.d.ts [22m[32m1.69 KB[39m
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
|
|
2
|
-
|
|
3
|
-
import { describe, it, expect } from 'vitest';
|
|
4
|
-
import { KnowledgeMemoryAdapter } from '../index';
|
|
5
|
-
import type { KnowledgeDocument } from '@objectstack/spec/ai';
|
|
6
|
-
import type { KnowledgeSource } from '@objectstack/spec/ai';
|
|
7
|
-
|
|
8
|
-
const source: KnowledgeSource = {
|
|
9
|
-
id: 'src1',
|
|
10
|
-
label: 'Test source',
|
|
11
|
-
adapter: 'memory',
|
|
12
|
-
source: { kind: 'http', urls: ['https://example.com'] } as KnowledgeSource['source'],
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
const docs: KnowledgeDocument[] = [
|
|
16
|
-
{
|
|
17
|
-
id: 'd1',
|
|
18
|
-
sourceId: 'src1',
|
|
19
|
-
content: 'The quick brown fox jumps over the lazy dog. Foxes are clever animals.',
|
|
20
|
-
title: 'Fox facts',
|
|
21
|
-
metadata: { topic: 'animals' },
|
|
22
|
-
},
|
|
23
|
-
{
|
|
24
|
-
id: 'd2',
|
|
25
|
-
sourceId: 'src1',
|
|
26
|
-
content: 'Refunds require a receipt and must be processed within 30 days of purchase.',
|
|
27
|
-
title: 'Refund policy',
|
|
28
|
-
metadata: { topic: 'policy' },
|
|
29
|
-
},
|
|
30
|
-
{
|
|
31
|
-
id: 'd3',
|
|
32
|
-
sourceId: 'src1',
|
|
33
|
-
content: 'Customer support is available 24/7 by email at help@example.com.',
|
|
34
|
-
title: 'Support',
|
|
35
|
-
metadata: { topic: 'policy' },
|
|
36
|
-
},
|
|
37
|
-
];
|
|
38
|
-
|
|
39
|
-
describe('KnowledgeMemoryAdapter', () => {
|
|
40
|
-
it('upsert + search returns the semantically closest doc on top', async () => {
|
|
41
|
-
const a = new KnowledgeMemoryAdapter();
|
|
42
|
-
await a.upsert(docs, { source });
|
|
43
|
-
const hits = await a.search('how do refunds work', { source, topK: 3 });
|
|
44
|
-
expect(hits.length).toBeGreaterThan(0);
|
|
45
|
-
expect(hits[0].documentId).toBe('d2');
|
|
46
|
-
expect(hits[0].score).toBeGreaterThan(0);
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
it('respects topK', async () => {
|
|
50
|
-
const a = new KnowledgeMemoryAdapter();
|
|
51
|
-
await a.upsert(docs, { source });
|
|
52
|
-
const hits = await a.search('refund support fox', { source, topK: 1 });
|
|
53
|
-
expect(hits.length).toBe(1);
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
it('returns empty array when no token overlap', async () => {
|
|
57
|
-
const a = new KnowledgeMemoryAdapter();
|
|
58
|
-
await a.upsert(docs, { source });
|
|
59
|
-
const hits = await a.search('xyzzy plugh', { source, topK: 5 });
|
|
60
|
-
expect(hits).toEqual([]);
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
it('filters by metadata', async () => {
|
|
64
|
-
const a = new KnowledgeMemoryAdapter();
|
|
65
|
-
await a.upsert(docs, { source });
|
|
66
|
-
const hits = await a.search('fox', { source, topK: 5, filter: { topic: 'policy' } });
|
|
67
|
-
expect(hits.find((h) => h.documentId === 'd1')).toBeUndefined();
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
it('delete removes a document', async () => {
|
|
71
|
-
const a = new KnowledgeMemoryAdapter();
|
|
72
|
-
await a.upsert(docs, { source });
|
|
73
|
-
await a.delete(['d2'], { source });
|
|
74
|
-
const hits = await a.search('refund', { source, topK: 5 });
|
|
75
|
-
expect(hits.find((h) => h.documentId === 'd2')).toBeUndefined();
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
it('upsert replaces existing chunks for the same doc', async () => {
|
|
79
|
-
const a = new KnowledgeMemoryAdapter();
|
|
80
|
-
await a.upsert(docs, { source });
|
|
81
|
-
await a.upsert(
|
|
82
|
-
[{ id: 'd2', sourceId: 'src1', content: 'Pineapples are tropical fruit.', title: 'New' }],
|
|
83
|
-
{ source },
|
|
84
|
-
);
|
|
85
|
-
const hits = await a.search('refund', { source, topK: 5 });
|
|
86
|
-
expect(hits.find((h) => h.documentId === 'd2')).toBeUndefined();
|
|
87
|
-
const tropical = await a.search('pineapple tropical', { source, topK: 5 });
|
|
88
|
-
expect(tropical[0]?.documentId).toBe('d2');
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
it('chunks long documents (snippet bounded)', async () => {
|
|
92
|
-
const a = new KnowledgeMemoryAdapter();
|
|
93
|
-
const long = 'lorem ipsum dolor sit amet '.repeat(200);
|
|
94
|
-
await a.upsert([{ id: 'long', sourceId: 'src1', content: long }], { source });
|
|
95
|
-
const hits = await a.search('lorem', { source, topK: 10 });
|
|
96
|
-
expect(hits.length).toBeGreaterThan(1);
|
|
97
|
-
for (const h of hits) expect(h.snippet.length).toBeLessThanOrEqual(900);
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
it('healthCheck returns ok', async () => {
|
|
101
|
-
const a = new KnowledgeMemoryAdapter();
|
|
102
|
-
const h = await a.healthCheck();
|
|
103
|
-
expect(h.ok).toBe(true);
|
|
104
|
-
});
|
|
105
|
-
});
|
package/src/index.ts
DELETED
|
@@ -1,235 +0,0 @@
|
|
|
1
|
-
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
|
|
2
|
-
|
|
3
|
-
import type {
|
|
4
|
-
IKnowledgeAdapter,
|
|
5
|
-
IKnowledgeService,
|
|
6
|
-
AdapterContext,
|
|
7
|
-
AdapterSearchOptions,
|
|
8
|
-
} from '@objectstack/spec/contracts';
|
|
9
|
-
import type {
|
|
10
|
-
KnowledgeDocument,
|
|
11
|
-
KnowledgeHit,
|
|
12
|
-
} from '@objectstack/spec/ai';
|
|
13
|
-
import { KNOWLEDGE_SERVICE } from '@objectstack/spec/contracts';
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* In-memory knowledge adapter. Stores per-source document maps,
|
|
17
|
-
* chunks documents naively on paragraph boundaries (with a soft 800-char
|
|
18
|
-
* cap), embeds each chunk as a sparse token-frequency vector, and
|
|
19
|
-
* answers `search()` via brute-force cosine similarity.
|
|
20
|
-
*
|
|
21
|
-
* Deterministic — same input always produces the same hits — so it's
|
|
22
|
-
* great for tests and reference implementations. **Don't use in prod.**
|
|
23
|
-
*/
|
|
24
|
-
export class KnowledgeMemoryAdapter implements IKnowledgeAdapter {
|
|
25
|
-
readonly id: string;
|
|
26
|
-
private readonly store = new Map<string, ChunkRecord[]>();
|
|
27
|
-
|
|
28
|
-
constructor(id = 'memory') {
|
|
29
|
-
this.id = id;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
async upsert(docs: KnowledgeDocument[], ctx: AdapterContext): Promise<void> {
|
|
33
|
-
const sourceId = ctx.source.id;
|
|
34
|
-
const existing = this.store.get(sourceId) ?? [];
|
|
35
|
-
const byDoc = new Map<string, ChunkRecord[]>();
|
|
36
|
-
for (const c of existing) {
|
|
37
|
-
const arr = byDoc.get(c.documentId) ?? [];
|
|
38
|
-
arr.push(c);
|
|
39
|
-
byDoc.set(c.documentId, arr);
|
|
40
|
-
}
|
|
41
|
-
for (const doc of docs) {
|
|
42
|
-
byDoc.set(doc.id, chunkAndEmbed(doc));
|
|
43
|
-
}
|
|
44
|
-
this.store.set(sourceId, Array.from(byDoc.values()).flat());
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
async delete(documentIds: string[], ctx: AdapterContext): Promise<void> {
|
|
48
|
-
const sourceId = ctx.source.id;
|
|
49
|
-
const chunks = this.store.get(sourceId);
|
|
50
|
-
if (!chunks) return;
|
|
51
|
-
const dropSet = new Set(documentIds);
|
|
52
|
-
this.store.set(
|
|
53
|
-
sourceId,
|
|
54
|
-
chunks.filter((c) => !dropSet.has(c.documentId)),
|
|
55
|
-
);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
async search(query: string, opts: AdapterSearchOptions): Promise<KnowledgeHit[]> {
|
|
59
|
-
const chunks = this.store.get(opts.source.id) ?? [];
|
|
60
|
-
if (chunks.length === 0) return [];
|
|
61
|
-
const qVec = embedTokens(tokenize(query));
|
|
62
|
-
const qNorm = vectorNorm(qVec);
|
|
63
|
-
if (qNorm === 0) return [];
|
|
64
|
-
|
|
65
|
-
const filter = opts.filter ?? {};
|
|
66
|
-
const filtered = Object.keys(filter).length
|
|
67
|
-
? chunks.filter((c) => matchesFilter(c.metadata, filter))
|
|
68
|
-
: chunks;
|
|
69
|
-
|
|
70
|
-
const scored = filtered.map((c) => ({
|
|
71
|
-
chunk: c,
|
|
72
|
-
score: cosineSimilarity(qVec, c.vector, qNorm, c.vectorNorm),
|
|
73
|
-
}));
|
|
74
|
-
scored.sort((a, b) => b.score - a.score);
|
|
75
|
-
const top = scored.slice(0, opts.topK).filter((s) => s.score > 0);
|
|
76
|
-
return top.map<KnowledgeHit>(({ chunk, score }) => ({
|
|
77
|
-
chunkId: chunk.chunkId,
|
|
78
|
-
documentId: chunk.documentId,
|
|
79
|
-
sourceId: opts.source.id,
|
|
80
|
-
sourceRecordId: chunk.sourceRecordId,
|
|
81
|
-
score,
|
|
82
|
-
snippet: chunk.content,
|
|
83
|
-
title: chunk.title,
|
|
84
|
-
metadata: chunk.metadata,
|
|
85
|
-
}));
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
async healthCheck(): Promise<{ ok: boolean; message?: string }> {
|
|
89
|
-
return { ok: true, message: `memory adapter (${this.store.size} sources)` };
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
interface ChunkRecord {
|
|
94
|
-
chunkId: string;
|
|
95
|
-
documentId: string;
|
|
96
|
-
sourceRecordId?: string;
|
|
97
|
-
content: string;
|
|
98
|
-
title?: string;
|
|
99
|
-
metadata: Record<string, unknown>;
|
|
100
|
-
vector: Map<string, number>;
|
|
101
|
-
vectorNorm: number;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
const CHUNK_TARGET = 800;
|
|
105
|
-
|
|
106
|
-
function chunkAndEmbed(doc: KnowledgeDocument): ChunkRecord[] {
|
|
107
|
-
const paragraphs = doc.content
|
|
108
|
-
.split(/\n{2,}/)
|
|
109
|
-
.map((p) => p.trim())
|
|
110
|
-
.filter(Boolean);
|
|
111
|
-
const chunks: string[] = [];
|
|
112
|
-
let buf = '';
|
|
113
|
-
for (const p of paragraphs.length ? paragraphs : [doc.content]) {
|
|
114
|
-
if (buf && buf.length + p.length + 2 > CHUNK_TARGET) {
|
|
115
|
-
chunks.push(buf);
|
|
116
|
-
buf = '';
|
|
117
|
-
}
|
|
118
|
-
if (p.length > CHUNK_TARGET) {
|
|
119
|
-
if (buf) {
|
|
120
|
-
chunks.push(buf);
|
|
121
|
-
buf = '';
|
|
122
|
-
}
|
|
123
|
-
for (let i = 0; i < p.length; i += CHUNK_TARGET) {
|
|
124
|
-
chunks.push(p.slice(i, i + CHUNK_TARGET));
|
|
125
|
-
}
|
|
126
|
-
} else {
|
|
127
|
-
buf = buf ? `${buf}\n\n${p}` : p;
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
if (buf) chunks.push(buf);
|
|
131
|
-
return chunks.map((content, idx) => {
|
|
132
|
-
const vec = embedTokens(tokenize(content));
|
|
133
|
-
return {
|
|
134
|
-
chunkId: `${doc.id}#${idx}`,
|
|
135
|
-
documentId: doc.id,
|
|
136
|
-
sourceRecordId: doc.sourceRecordId,
|
|
137
|
-
content,
|
|
138
|
-
title: doc.title,
|
|
139
|
-
metadata: doc.metadata ?? {},
|
|
140
|
-
vector: vec,
|
|
141
|
-
vectorNorm: vectorNorm(vec),
|
|
142
|
-
};
|
|
143
|
-
});
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
function tokenize(text: string): string[] {
|
|
147
|
-
return text
|
|
148
|
-
.toLowerCase()
|
|
149
|
-
.split(/[^a-z0-9\u4e00-\u9fff]+/i)
|
|
150
|
-
.filter((t) => t.length > 1);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
function embedTokens(tokens: string[]): Map<string, number> {
|
|
154
|
-
const vec = new Map<string, number>();
|
|
155
|
-
for (const t of tokens) vec.set(t, (vec.get(t) ?? 0) + 1);
|
|
156
|
-
return vec;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
function vectorNorm(v: Map<string, number>): number {
|
|
160
|
-
let s = 0;
|
|
161
|
-
for (const x of v.values()) s += x * x;
|
|
162
|
-
return Math.sqrt(s);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
function cosineSimilarity(
|
|
166
|
-
a: Map<string, number>,
|
|
167
|
-
b: Map<string, number>,
|
|
168
|
-
aNorm = vectorNorm(a),
|
|
169
|
-
bNorm = vectorNorm(b),
|
|
170
|
-
): number {
|
|
171
|
-
if (aNorm === 0 || bNorm === 0) return 0;
|
|
172
|
-
// iterate smaller map
|
|
173
|
-
const [small, big] = a.size < b.size ? [a, b] : [b, a];
|
|
174
|
-
let dot = 0;
|
|
175
|
-
for (const [k, v] of small) {
|
|
176
|
-
const w = big.get(k);
|
|
177
|
-
if (w) dot += v * w;
|
|
178
|
-
}
|
|
179
|
-
return dot / (aNorm * bNorm);
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
function matchesFilter(
|
|
183
|
-
metadata: Record<string, unknown>,
|
|
184
|
-
filter: Record<string, unknown>,
|
|
185
|
-
): boolean {
|
|
186
|
-
for (const [k, v] of Object.entries(filter)) {
|
|
187
|
-
if (metadata[k] !== v) return false;
|
|
188
|
-
}
|
|
189
|
-
return true;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
/* ---------------------------------------------------------------- */
|
|
193
|
-
/* Kernel plugin glue */
|
|
194
|
-
/* ---------------------------------------------------------------- */
|
|
195
|
-
|
|
196
|
-
import type { Plugin, PluginContext } from '@objectstack/core';
|
|
197
|
-
|
|
198
|
-
export interface KnowledgeMemoryPluginOptions {
|
|
199
|
-
/** Adapter id. @default 'memory' */
|
|
200
|
-
id?: string;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
/**
|
|
204
|
-
* `KnowledgeMemoryPlugin` — registers a `KnowledgeMemoryAdapter` with
|
|
205
|
-
* the host's `IKnowledgeService` during `start()`.
|
|
206
|
-
*/
|
|
207
|
-
export class KnowledgeMemoryPlugin implements Plugin {
|
|
208
|
-
name = 'com.objectstack.plugin.knowledge-memory';
|
|
209
|
-
version = '0.1.0';
|
|
210
|
-
type = 'standard';
|
|
211
|
-
|
|
212
|
-
private readonly adapter: KnowledgeMemoryAdapter;
|
|
213
|
-
|
|
214
|
-
constructor(opts: KnowledgeMemoryPluginOptions = {}) {
|
|
215
|
-
this.adapter = new KnowledgeMemoryAdapter(opts.id ?? 'memory');
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
async init(_ctx: PluginContext): Promise<void> {
|
|
219
|
-
// No-op: registration happens in start() once IKnowledgeService is up.
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
async start(ctx: PluginContext): Promise<void> {
|
|
223
|
-
let svc: IKnowledgeService | undefined;
|
|
224
|
-
try {
|
|
225
|
-
svc = ctx.getService<IKnowledgeService>(KNOWLEDGE_SERVICE);
|
|
226
|
-
} catch {
|
|
227
|
-
ctx.logger.warn?.(
|
|
228
|
-
'KnowledgeMemoryPlugin: IKnowledgeService not registered — install KnowledgeServicePlugin first.',
|
|
229
|
-
);
|
|
230
|
-
return;
|
|
231
|
-
}
|
|
232
|
-
svc.registerAdapter(this.adapter.id, this.adapter);
|
|
233
|
-
ctx.logger.info?.(`KnowledgeMemoryPlugin: adapter '${this.adapter.id}' registered.`);
|
|
234
|
-
}
|
|
235
|
-
}
|
package/tsconfig.json
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"extends": "../../../tsconfig.json",
|
|
3
|
-
"compilerOptions": {
|
|
4
|
-
"target": "ES2020",
|
|
5
|
-
"module": "ES2020",
|
|
6
|
-
"moduleResolution": "bundler",
|
|
7
|
-
"declaration": true,
|
|
8
|
-
"outDir": "./dist",
|
|
9
|
-
"strict": true,
|
|
10
|
-
"esModuleInterop": true,
|
|
11
|
-
"skipLibCheck": true,
|
|
12
|
-
"noUnusedLocals": false,
|
|
13
|
-
"noUnusedParameters": false,
|
|
14
|
-
"forceConsistentCasingInFileNames": true,
|
|
15
|
-
"types": ["node"],
|
|
16
|
-
"rootDir": "./src"
|
|
17
|
-
},
|
|
18
|
-
"include": ["src/**/*"],
|
|
19
|
-
"exclude": ["node_modules", "dist"]
|
|
20
|
-
}
|
package/vitest.config.ts
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
|
|
2
|
-
|
|
3
|
-
import { defineConfig } from 'vitest/config';
|
|
4
|
-
import path from 'path';
|
|
5
|
-
|
|
6
|
-
export default defineConfig({
|
|
7
|
-
test: {
|
|
8
|
-
globals: true,
|
|
9
|
-
environment: 'node',
|
|
10
|
-
},
|
|
11
|
-
resolve: {
|
|
12
|
-
alias: {
|
|
13
|
-
'@objectstack/core': path.resolve(__dirname, '../../core/src/index.ts'),
|
|
14
|
-
'@objectstack/service-knowledge': path.resolve(__dirname, '../../services/service-knowledge/src/index.ts'),
|
|
15
|
-
'@objectstack/spec/ai': path.resolve(__dirname, '../../spec/src/ai/index.ts'),
|
|
16
|
-
'@objectstack/spec/contracts': path.resolve(__dirname, '../../spec/src/contracts/index.ts'),
|
|
17
|
-
'@objectstack/spec/data': path.resolve(__dirname, '../../spec/src/data/index.ts'),
|
|
18
|
-
'@objectstack/spec/kernel': path.resolve(__dirname, '../../spec/src/kernel/index.ts'),
|
|
19
|
-
'@objectstack/spec/system': path.resolve(__dirname, '../../spec/src/system/index.ts'),
|
|
20
|
-
// [ADR-0105 D1] Reached transitively via `@objectstack/types` (tenancy posture).
|
|
21
|
-
'@objectstack/spec/security': path.resolve(__dirname, '../../spec/src/security/index.ts'),
|
|
22
|
-
'@objectstack/spec': path.resolve(__dirname, '../../spec/src/index.ts'),
|
|
23
|
-
},
|
|
24
|
-
},
|
|
25
|
-
});
|