@equationalapplications/expo-llm-wiki 2.5.0 → 3.0.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.
- package/README.md +191 -0
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -2,11 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
Expo/React Native adapter for @equationalapplications/core-llm-wiki, powered by `expo-sqlite`.
|
|
4
4
|
|
|
5
|
+
[](https://www.npmjs.com/package/@equationalapplications/expo-llm-wiki)
|
|
6
|
+
[](https://www.npmjs.com/package/@equationalapplications/expo-llm-wiki)
|
|
7
|
+
[](https://bundlephobia.com/package/@equationalapplications/expo-llm-wiki)
|
|
8
|
+
[](https://www.typescriptlang.org/)
|
|
9
|
+
[](LICENSE)
|
|
10
|
+
|
|
11
|
+
> Inspired by [Andrej Karpathy's LLM Wiki memory spec](https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f).
|
|
12
|
+
|
|
5
13
|
## Features
|
|
6
14
|
|
|
7
15
|
- **Expo-ready** — Pre-configured for React Native + Expo
|
|
8
16
|
- **Built on `expo-sqlite`** — Stable, well-supported SQLite driver
|
|
17
|
+
- **Semantic search** — Vector embeddings via `embed` function, with MiniSearch fallback
|
|
18
|
+
- **Retrieval tuning** — Per-call overrides for search behavior (pre-filter, hybrid blend)
|
|
9
19
|
- **React hooks** — `WikiProvider`, `useMemoryRead`, and all other hooks are re-exported directly from `@equationalapplications/expo-llm-wiki`
|
|
20
|
+
- **Full-featured memory** — Facts, tasks, events, maintenance jobs (librarian, heal, reembed, prune)
|
|
10
21
|
|
|
11
22
|
## Installation
|
|
12
23
|
|
|
@@ -15,6 +26,113 @@ npx expo install expo-sqlite
|
|
|
15
26
|
npm install @equationalapplications/expo-llm-wiki
|
|
16
27
|
```
|
|
17
28
|
|
|
29
|
+
## Semantic Search
|
|
30
|
+
|
|
31
|
+
Enable vector-based retrieval by providing an `embed` function:
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
import { createWiki } from '@equationalapplications/expo-llm-wiki';
|
|
35
|
+
import { openDatabaseSync } from 'expo-sqlite';
|
|
36
|
+
|
|
37
|
+
const db = openDatabaseSync('wiki.db');
|
|
38
|
+
|
|
39
|
+
const wiki = createWiki(db, {
|
|
40
|
+
config: {
|
|
41
|
+
// Optimize retrieval for large memory stores
|
|
42
|
+
preFilterLimit: 50, // Limit cosine scoring to top-50 keyword matches
|
|
43
|
+
hybridWeight: 0.7, // Blend semantic (0.7) + keyword (0.3)
|
|
44
|
+
},
|
|
45
|
+
llmProvider: {
|
|
46
|
+
generateText: async ({ systemPrompt, userPrompt }) => {
|
|
47
|
+
// Your LLM call — must return the model output as a string
|
|
48
|
+
return 'Model output';
|
|
49
|
+
},
|
|
50
|
+
embed: async (text: string) => {
|
|
51
|
+
// Your embedding service (e.g., OpenAI, Cohere)
|
|
52
|
+
// Use an absolute URL — React Native / Expo apps do not have a browser
|
|
53
|
+
// origin to resolve relative URLs against on device or simulator.
|
|
54
|
+
const response = await fetch('https://your-api.example.com/api/embed', {
|
|
55
|
+
method: 'POST',
|
|
56
|
+
body: JSON.stringify({ text })
|
|
57
|
+
});
|
|
58
|
+
const { embedding } = await response.json();
|
|
59
|
+
return embedding; // number[]
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
onRetrievalFallback: (error) => {
|
|
63
|
+
console.warn('Embedding unavailable, using keyword search:', error);
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
await wiki.setup();
|
|
68
|
+
|
|
69
|
+
// Semantic query
|
|
70
|
+
const memory = await wiki.read('user-123', 'what activities should I do this weekend?');
|
|
71
|
+
// Matches facts like "Saturday hiking trip" even with no lexical overlap
|
|
72
|
+
|
|
73
|
+
// Per-call overrides
|
|
74
|
+
const fasterSearch = await wiki.read('user-123', 'activities', {
|
|
75
|
+
maxResults: 5,
|
|
76
|
+
preFilterLimit: 20, // Tighter pre-filter for speed
|
|
77
|
+
hybridWeight: 0.5, // More keyword weight
|
|
78
|
+
});
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Configuration
|
|
82
|
+
|
|
83
|
+
All `WikiConfig` fields are optional:
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
const wiki = createWiki(db, {
|
|
87
|
+
llmProvider: { /* ... */ },
|
|
88
|
+
config: {
|
|
89
|
+
tablePrefix: 'llm_wiki_', // default: 'llm_wiki_'
|
|
90
|
+
maxResults: 10, // default: 10
|
|
91
|
+
autoLibrarianThreshold: 20, // default: 20 — events before librarian auto-runs
|
|
92
|
+
autoHealThreshold: 100, // default: 100 — events before heal auto-runs
|
|
93
|
+
maxChunkLength: 12000, // default: 12000 (char count per ingestDocument chunk)
|
|
94
|
+
chunkOverlap: 400, // default: 400 (overlap between chunks in characters)
|
|
95
|
+
chunkConcurrency: 1, // default: 1 (parallel LLM calls per ingestDocument)
|
|
96
|
+
pruneRetainSoftDeletedFor: 7, // default: 7 (days before hard-deleting soft-deleted facts)
|
|
97
|
+
pruneEventsAfter: 30, // default: 30 (days before hard-deleting old events)
|
|
98
|
+
orphanAfterDays: 30, // default: 30 (days before runHeal flags sourceless facts; null to disable)
|
|
99
|
+
staleInferredAfterDays: 60, // default: 60 (days before runHeal downgrades inferred facts; null to disable)
|
|
100
|
+
preFilterLimit: 50, // default: undefined — MiniSearch pre-filter before cosine scan; recommended for >500 facts
|
|
101
|
+
hybridWeight: 0.7, // default: undefined — blend semantic (1.0) ↔ keyword (0.0); pure semantic when unset
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Retrieval Tuning
|
|
107
|
+
|
|
108
|
+
Optimize `read()` performance and blend retrieval strategies:
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
const config = {
|
|
112
|
+
// Limit cosine similarity scoring to top-K MiniSearch keyword candidates
|
|
113
|
+
preFilterLimit: 50,
|
|
114
|
+
|
|
115
|
+
// Blend semantic and keyword scores (0.0 = pure keyword, 1.0 = pure semantic)
|
|
116
|
+
hybridWeight: 0.7,
|
|
117
|
+
|
|
118
|
+
// Max results returned per read
|
|
119
|
+
maxResults: 10,
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const wiki = createWiki(db, {
|
|
123
|
+
config,
|
|
124
|
+
llmProvider: { /* ... */ },
|
|
125
|
+
});
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
**Hybrid scoring blends:**
|
|
129
|
+
- `hybridWeight: 1.0` → pure semantic ranking among the candidates being scored; if `preFilterLimit` is set, semantic scoring is still limited to the top-K MiniSearch matches
|
|
130
|
+
- `hybridWeight: 0.5` → balanced semantic + keyword (50/50 blend)
|
|
131
|
+
- `hybridWeight: 0.0` → pure keyword ranking, skips `embed()` entirely (no LLM API cost)
|
|
132
|
+
|
|
133
|
+
**Pre-filtering optimization:**
|
|
134
|
+
When `preFilterLimit: 50` is set with 1000 facts, cosine similarity is computed only for the top 50 MiniSearch keyword matches, reducing O(N) scoring to O(50).
|
|
135
|
+
|
|
18
136
|
## Usage
|
|
19
137
|
|
|
20
138
|
```typescript
|
|
@@ -64,6 +182,79 @@ export function UserProfile({ userId }: { userId: string }) {
|
|
|
64
182
|
}
|
|
65
183
|
```
|
|
66
184
|
|
|
185
|
+
## Component Lifecycle
|
|
186
|
+
|
|
187
|
+
```mermaid
|
|
188
|
+
flowchart TD
|
|
189
|
+
A["<WikiProvider wiki={wiki}>"] --> B["App Components"]
|
|
190
|
+
B --> C{"Use Hook?"}
|
|
191
|
+
C -->|"useMemoryRead(entityId, query, options?)"| D["[Read Memory]"]
|
|
192
|
+
C -->|"useWikiWrite()"| E["[Write Memory]"]
|
|
193
|
+
C -->|"useWikiIngest()"| F["[Ingest Document]"]
|
|
194
|
+
C -->|"useWikiForget()"| G["[Delete Memory]"]
|
|
195
|
+
C -->|"useWikiMaintenance()"| H["[Run Jobs]"]
|
|
196
|
+
D --> I{"entityId, query, wiki,<br/>or ReadOptions changed?"}
|
|
197
|
+
I -->|"Yes"| J["Auto-refetch"]
|
|
198
|
+
I -->|"No"| K["Return cached data"]
|
|
199
|
+
J --> L["Trigger read()"]
|
|
200
|
+
L --> M["Embed query<br/>if embed available"]
|
|
201
|
+
M --> N["Phase 1: Score facts<br/>Phase 2: Fetch winners"]
|
|
202
|
+
N --> O["Update component state"]
|
|
203
|
+
O --> P["Re-render with data"]
|
|
204
|
+
E --> Q["Execute write()"]
|
|
205
|
+
F --> Q
|
|
206
|
+
G --> Q
|
|
207
|
+
H --> Q
|
|
208
|
+
Q --> R["Write completes"]
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
**Data flow:**
|
|
212
|
+
1. **Wrap app** with `<WikiProvider wiki={wiki}>` — provides wiki context
|
|
213
|
+
2. **Use hooks** in components — access memory reactively
|
|
214
|
+
3. **Read operations** auto-refetch when `entityId`, `query`, `wiki`, or `ReadOptions` values change; call `refetch()` to refresh manually
|
|
215
|
+
4. **Write operations** (write, ingest, forget, maintenance) do not automatically re-trigger `useMemoryRead`; call `refetch()` after a write to refresh read results
|
|
216
|
+
5. **Re-render** with new data flowing back to UI
|
|
217
|
+
|
|
218
|
+
## Retrieval Engine Internals
|
|
219
|
+
|
|
220
|
+
```mermaid
|
|
221
|
+
flowchart TD
|
|
222
|
+
A["read(entityId, query)"] --> B{hybridWeight = 0?}
|
|
223
|
+
B -->|Yes| C["MiniSearch only<br/>(skip embed)"]
|
|
224
|
+
B -->|No| D{embed available?}
|
|
225
|
+
D -->|No| C
|
|
226
|
+
D -->|Yes| F["Embed query"]
|
|
227
|
+
F -->|throws| E["onRetrievalFallback<br/>callback"]
|
|
228
|
+
E --> C
|
|
229
|
+
F -->|succeeds| G{preFilterLimit<br/>active?}
|
|
230
|
+
G -->|Yes| H["MiniSearch pre-filter<br/>top K candidates"]
|
|
231
|
+
H --> I["Phase 1: Cosine score<br/>top K candidates"]
|
|
232
|
+
G -->|No| J["Phase 1: Cosine score<br/>all facts"]
|
|
233
|
+
J --> K["Cache vectors<br/>in-memory<br/>(full scan only)"]
|
|
234
|
+
K --> L{hybridWeight = 1?}
|
|
235
|
+
I --> L
|
|
236
|
+
L -->|Yes| M["Pure semantic<br/>ranking"]
|
|
237
|
+
L -->|No| N["Hybrid blend:<br/>semantic + keyword<br/>via MiniSearch"]
|
|
238
|
+
M --> O["Phase 2: Fetch full rows<br/>top maxResults"]
|
|
239
|
+
N --> O
|
|
240
|
+
C --> P["MiniSearch ranking"]
|
|
241
|
+
P --> O
|
|
242
|
+
O --> R["Track access"]
|
|
243
|
+
R --> Q["Return MemoryBundle"]
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
The flowchart shows:
|
|
247
|
+
1. **Fast-path** when `hybridWeight = 0` (pure keyword, no embed cost)
|
|
248
|
+
2. **Fallback chain** when embed unavailable (MiniSearch silently) or throws (`onRetrievalFallback` callback, then MiniSearch)
|
|
249
|
+
3. **Pre-filtering** to limit cosine scoring to top-K keyword matches (O(N) → O(K))
|
|
250
|
+
4. **Two-phase SELECT**: phase 1 scores all/filtered facts with minimal columns, phase 2 fetches full rows for winners
|
|
251
|
+
5. **Hybrid scoring** to blend semantic and keyword rankings
|
|
252
|
+
6. **Vector caching** on full scans only; reads with `preFilterLimit` active skip cache population
|
|
253
|
+
|
|
67
254
|
## License
|
|
68
255
|
|
|
69
256
|
MIT
|
|
257
|
+
|
|
258
|
+
---
|
|
259
|
+
|
|
260
|
+
Made with ❤️ by Equational Applications LLC. [https://equationalapplications.com/](https://equationalapplications.com/)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@equationalapplications/expo-llm-wiki",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "Expo/React Native adapter for @equationalapplications/core-llm-wiki.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -28,8 +28,8 @@
|
|
|
28
28
|
"registry": "https://registry.npmjs.org"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@equationalapplications/core-llm-wiki": "
|
|
32
|
-
"@equationalapplications/react-llm-wiki": "
|
|
31
|
+
"@equationalapplications/core-llm-wiki": "3.0.0",
|
|
32
|
+
"@equationalapplications/react-llm-wiki": "3.0.0"
|
|
33
33
|
},
|
|
34
34
|
"peerDependencies": {
|
|
35
35
|
"expo-sqlite": "^14.0.0 || ^15.0.0 || ^55.0.0",
|