mnueron 0.3.0 → 0.4.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 +123 -1
- package/dashboard/index.html +38 -0
- package/dist/cli.js +1187 -1
- package/dist/cli.js.map +1 -1
- package/dist/dashboard/server.js +95 -0
- package/dist/dashboard/server.js.map +1 -1
- package/dist/detectors/claude_desktop.js +79 -22
- package/dist/detectors/claude_desktop.js.map +1 -1
- package/dist/import/claude_cowork.js +359 -0
- package/dist/import/claude_cowork.js.map +1 -0
- package/dist/import/claude_desktop.js +196 -0
- package/dist/import/claude_desktop.js.map +1 -0
- package/dist/store/consolidator.js +168 -0
- package/dist/store/consolidator.js.map +1 -0
- package/dist/store/entity-extractor.js +283 -0
- package/dist/store/entity-extractor.js.map +1 -0
- package/dist/store/entity-resolver.js +378 -0
- package/dist/store/entity-resolver.js.map +1 -0
- package/dist/store/local.js +522 -14
- package/dist/store/local.js.map +1 -1
- package/dist/store/procedural.js +328 -0
- package/dist/store/procedural.js.map +1 -0
- package/dist/store/relation-extractor.js +292 -0
- package/dist/store/relation-extractor.js.map +1 -0
- package/dist/store/remote.js +182 -20
- package/dist/store/remote.js.map +1 -1
- package/dist/tools.js +84 -0
- package/dist/tools.js.map +1 -1
- package/dist/watch/cowork.js +137 -0
- package/dist/watch/cowork.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
2
|
+
// P5 — Self-revising memory loop (phase 5a: detection only).
|
|
3
|
+
//
|
|
4
|
+
// Background scan that finds likely-duplicate memories and surfaces them as
|
|
5
|
+
// proposals the user can act on. NO automatic writes — phase 5a is purely
|
|
6
|
+
// observational. Phase 5b (LLM-proposed merges) and 5c (auto-merge on
|
|
7
|
+
// high-confidence cases) layer on top later.
|
|
8
|
+
//
|
|
9
|
+
// Detection method: embedding cosine similarity between every pair of
|
|
10
|
+
// memories within a sliding window. We don't N² across the whole store —
|
|
11
|
+
// that would melt. Instead:
|
|
12
|
+
//
|
|
13
|
+
// 1. Walk memories sorted by created_at DESC.
|
|
14
|
+
// 2. For each memory M, vector-search the top-K nearest neighbors.
|
|
15
|
+
// 3. Filter to candidates with cosine >= DUPLICATE_THRESHOLD.
|
|
16
|
+
// 4. Emit one `ConsolidationProposal` per (M, neighbor) pair the
|
|
17
|
+
// reviewer hasn't already seen or actioned.
|
|
18
|
+
//
|
|
19
|
+
// State persists in `consolidation_proposals`:
|
|
20
|
+
// - status: 'pending' | 'approved' | 'rejected'
|
|
21
|
+
// - kind: 'duplicate' (5a) | 'contradiction' (5b) | 'stale' (5b)
|
|
22
|
+
// - score: similarity that triggered the proposal
|
|
23
|
+
// Action history per proposal is its own table so we can audit who/when
|
|
24
|
+
// in a multi-user (hosted) context. The local store keeps it simple.
|
|
25
|
+
//
|
|
26
|
+
// Safety: phase 5a never deletes or mutates memories. The only mutation
|
|
27
|
+
// is INSERTs into consolidation_proposals.
|
|
28
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
29
|
+
import { randomUUID } from 'node:crypto';
|
|
30
|
+
/** Cosine threshold for emitting a duplicate proposal. Tuned conservative —
|
|
31
|
+
* we'd rather miss some duplicates than swamp the user with false positives. */
|
|
32
|
+
const DUPLICATE_THRESHOLD = 0.92;
|
|
33
|
+
/** Top-K neighbors examined per memory. Larger = catches more duplicates,
|
|
34
|
+
* smaller = faster scan. K=5 covers ~all real-world duplicates per sampling. */
|
|
35
|
+
const NEIGHBORS_PER_MEMORY = 5;
|
|
36
|
+
/** Default cap on how many memories to scan per invocation. */
|
|
37
|
+
const DEFAULT_SCAN_LIMIT = 200;
|
|
38
|
+
/** Schema bootstrap. Idempotent. Called from local.ts migrate(). */
|
|
39
|
+
export function ensureConsolidationSchema(db) {
|
|
40
|
+
db.exec(`
|
|
41
|
+
CREATE TABLE IF NOT EXISTS consolidation_proposals (
|
|
42
|
+
id TEXT PRIMARY KEY,
|
|
43
|
+
kind TEXT NOT NULL,
|
|
44
|
+
memory_a_id TEXT NOT NULL,
|
|
45
|
+
memory_b_id TEXT NOT NULL,
|
|
46
|
+
score REAL NOT NULL,
|
|
47
|
+
status TEXT NOT NULL DEFAULT 'pending',
|
|
48
|
+
note TEXT,
|
|
49
|
+
proposed_at INTEGER NOT NULL,
|
|
50
|
+
reviewed_at INTEGER
|
|
51
|
+
);
|
|
52
|
+
CREATE INDEX IF NOT EXISTS idx_proposals_status
|
|
53
|
+
ON consolidation_proposals(status, proposed_at DESC);
|
|
54
|
+
CREATE INDEX IF NOT EXISTS idx_proposals_memory_a
|
|
55
|
+
ON consolidation_proposals(memory_a_id);
|
|
56
|
+
CREATE INDEX IF NOT EXISTS idx_proposals_memory_b
|
|
57
|
+
ON consolidation_proposals(memory_b_id);
|
|
58
|
+
-- Stop a duplicate proposal from being re-created on every scan.
|
|
59
|
+
CREATE UNIQUE INDEX IF NOT EXISTS uq_proposals_pair_kind
|
|
60
|
+
ON consolidation_proposals(memory_a_id, memory_b_id, kind);
|
|
61
|
+
`);
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Phase 5a — pure detection pass. Walks memories in reverse chronological
|
|
65
|
+
* order, vector-searches top-K neighbors per memory, and inserts
|
|
66
|
+
* 'duplicate' proposals for any pair with similarity ≥ threshold.
|
|
67
|
+
*
|
|
68
|
+
* Idempotent via the (memory_a_id, memory_b_id, kind) unique index — if a
|
|
69
|
+
* proposal already exists it's silently retained (counted as
|
|
70
|
+
* `proposalsAlreadyKnown`).
|
|
71
|
+
*
|
|
72
|
+
* Returns counts so the CLI can print a one-line summary.
|
|
73
|
+
*/
|
|
74
|
+
export async function detectDuplicates(db, vecAvailable, opts = {}) {
|
|
75
|
+
if (!vecAvailable) {
|
|
76
|
+
// Without vectors we have no similarity signal — bail.
|
|
77
|
+
return { scanned: 0, proposalsCreated: 0, proposalsAlreadyKnown: 0 };
|
|
78
|
+
}
|
|
79
|
+
const limit = Math.max(1, Math.min(opts.limit ?? DEFAULT_SCAN_LIMIT, 5000));
|
|
80
|
+
const threshold = clamp01(opts.threshold ?? DUPLICATE_THRESHOLD);
|
|
81
|
+
// Pull seed memories. We need their stored embedding bytes from the vec
|
|
82
|
+
// table so we can use them as the search probe.
|
|
83
|
+
const seedRows = (opts.namespace
|
|
84
|
+
? db.prepare(`SELECT m.id, m.namespace
|
|
85
|
+
FROM memories m
|
|
86
|
+
WHERE m.namespace = ?
|
|
87
|
+
ORDER BY m.created_at DESC
|
|
88
|
+
LIMIT ?`).all(opts.namespace, limit)
|
|
89
|
+
: db.prepare(`SELECT m.id, m.namespace
|
|
90
|
+
FROM memories m
|
|
91
|
+
ORDER BY m.created_at DESC
|
|
92
|
+
LIMIT ?`).all(limit));
|
|
93
|
+
const insert = db.prepare(`INSERT INTO consolidation_proposals
|
|
94
|
+
(id, kind, memory_a_id, memory_b_id, score, status, note, proposed_at)
|
|
95
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
96
|
+
ON CONFLICT(memory_a_id, memory_b_id, kind) DO NOTHING`);
|
|
97
|
+
const now = Date.now();
|
|
98
|
+
let scanned = 0;
|
|
99
|
+
let proposalsCreated = 0;
|
|
100
|
+
let proposalsAlreadyKnown = 0;
|
|
101
|
+
for (const seed of seedRows) {
|
|
102
|
+
scanned += 1;
|
|
103
|
+
// Read this seed's embedding from memories_vec.
|
|
104
|
+
const row = db
|
|
105
|
+
.prepare(`SELECT embedding FROM memories_vec WHERE memory_id = ?`)
|
|
106
|
+
.get(seed.id);
|
|
107
|
+
if (!row?.embedding)
|
|
108
|
+
continue;
|
|
109
|
+
// Top-K nearest neighbors (excluding the seed itself).
|
|
110
|
+
const neighbors = db
|
|
111
|
+
.prepare(`SELECT memory_id, distance
|
|
112
|
+
FROM memories_vec
|
|
113
|
+
WHERE embedding MATCH ?
|
|
114
|
+
AND k = ?
|
|
115
|
+
ORDER BY distance ASC`)
|
|
116
|
+
.all(row.embedding, NEIGHBORS_PER_MEMORY + 1);
|
|
117
|
+
for (const n of neighbors) {
|
|
118
|
+
if (n.memory_id === seed.id)
|
|
119
|
+
continue; // skip self
|
|
120
|
+
const sim = clamp01(1 - (n.distance * n.distance) / 2);
|
|
121
|
+
if (sim < threshold)
|
|
122
|
+
continue;
|
|
123
|
+
// Canonicalize the pair: lower id first, so (A, B) and (B, A) collapse.
|
|
124
|
+
const [a, b] = seed.id < n.memory_id ? [seed.id, n.memory_id] : [n.memory_id, seed.id];
|
|
125
|
+
const before = db
|
|
126
|
+
.prepare(`SELECT 1 FROM consolidation_proposals
|
|
127
|
+
WHERE memory_a_id = ? AND memory_b_id = ? AND kind = 'duplicate' LIMIT 1`)
|
|
128
|
+
.get(a, b);
|
|
129
|
+
if (before) {
|
|
130
|
+
proposalsAlreadyKnown += 1;
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
insert.run(randomUUID(), 'duplicate', a, b, sim, 'pending', null, now);
|
|
134
|
+
proposalsCreated += 1;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return { scanned, proposalsCreated, proposalsAlreadyKnown };
|
|
138
|
+
}
|
|
139
|
+
export function listProposals(db, opts = {}) {
|
|
140
|
+
const parts = ['1=1'];
|
|
141
|
+
const params = [];
|
|
142
|
+
if (opts.status) {
|
|
143
|
+
parts.push('status = ?');
|
|
144
|
+
params.push(opts.status);
|
|
145
|
+
}
|
|
146
|
+
if (opts.kind) {
|
|
147
|
+
parts.push('kind = ?');
|
|
148
|
+
params.push(opts.kind);
|
|
149
|
+
}
|
|
150
|
+
const limit = Math.max(1, Math.min(opts.limit ?? 100, 1000));
|
|
151
|
+
const offset = Math.max(0, opts.offset ?? 0);
|
|
152
|
+
return db
|
|
153
|
+
.prepare(`SELECT * FROM consolidation_proposals
|
|
154
|
+
WHERE ${parts.join(' AND ')}
|
|
155
|
+
ORDER BY proposed_at DESC
|
|
156
|
+
LIMIT ? OFFSET ?`)
|
|
157
|
+
.all(...params, limit, offset);
|
|
158
|
+
}
|
|
159
|
+
export function reviewProposal(db, id, decision) {
|
|
160
|
+
db.prepare(`UPDATE consolidation_proposals SET status = ?, reviewed_at = ? WHERE id = ?`).run(decision, Date.now(), id);
|
|
161
|
+
return db
|
|
162
|
+
.prepare(`SELECT * FROM consolidation_proposals WHERE id = ?`)
|
|
163
|
+
.get(id) ?? null;
|
|
164
|
+
}
|
|
165
|
+
function clamp01(n) {
|
|
166
|
+
return n < 0 ? 0 : n > 1 ? 1 : n;
|
|
167
|
+
}
|
|
168
|
+
//# sourceMappingURL=consolidator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"consolidator.js","sourceRoot":"","sources":["../../src/store/consolidator.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* P1 — Entity extraction for the local CLI / SQLite provider.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors the hosted-backend entity-extractor (see ai-boilerplate-pro/src/lib/
|
|
5
|
+
* entity-extractor.ts) but uses raw fetch for both providers to avoid pulling
|
|
6
|
+
* the Anthropic SDK into the CLI's dependency tree. The CLI ships to npm and
|
|
7
|
+
* gets installed globally by users, so we keep deps lean.
|
|
8
|
+
*
|
|
9
|
+
* Provider precedence (per-call BYOK keys never persisted):
|
|
10
|
+
* 1. metadata.byok_anthropic_key -> Claude Haiku via raw fetch
|
|
11
|
+
* 2. metadata.byok_openai_key -> gpt-4o-mini via raw fetch
|
|
12
|
+
* 3. ANTHROPIC_API_KEY env var -> Claude Haiku
|
|
13
|
+
* 4. OPENAI_API_KEY env var -> gpt-4o-mini
|
|
14
|
+
* 5. none -> return [], save proceeds with no entities (fail-open)
|
|
15
|
+
*
|
|
16
|
+
* Gating: opt-in. set `metadata.extract_entities: true` per-call, or
|
|
17
|
+
* the `MNUERON_ENABLE_ENTITY_EXTRACTION` env var globally.
|
|
18
|
+
*
|
|
19
|
+
* Fail-open across the board. Save never blocks on extraction.
|
|
20
|
+
*/
|
|
21
|
+
const MIN_LENGTH_CHARS = 200;
|
|
22
|
+
const ANTHROPIC_MODEL = 'claude-haiku-4-5';
|
|
23
|
+
const OPENAI_MODEL = 'gpt-4o-mini';
|
|
24
|
+
const MAX_CONTENT_CHARS = 12000;
|
|
25
|
+
const MAX_ENTITIES = 25;
|
|
26
|
+
const TIMEOUT_MS = 30000;
|
|
27
|
+
export const ENTITY_EXTRACTION_ENABLED = (process.env.MNUERON_ENABLE_ENTITY_EXTRACTION ?? '').toLowerCase() === 'true';
|
|
28
|
+
/**
|
|
29
|
+
* Per-call gate. Same shape as hosted-side `shouldExtractEntities`.
|
|
30
|
+
*
|
|
31
|
+
* Explicit per-call opt-in (metadata.extract_entities: true OR BYOK key)
|
|
32
|
+
* always runs, even on short content. The length floor only applies to
|
|
33
|
+
* the env-var default path, to keep that from burning money on noise.
|
|
34
|
+
*/
|
|
35
|
+
export function shouldExtractEntities(contentLen, metadata, minChars = MIN_LENGTH_CHARS) {
|
|
36
|
+
if (metadata?.extract_entities === true)
|
|
37
|
+
return true;
|
|
38
|
+
const a = metadata?.byok_anthropic_key;
|
|
39
|
+
if (typeof a === 'string' && a.length > 0)
|
|
40
|
+
return true;
|
|
41
|
+
const o = metadata?.byok_openai_key;
|
|
42
|
+
if (typeof o === 'string' && o.length > 0)
|
|
43
|
+
return true;
|
|
44
|
+
if (ENTITY_EXTRACTION_ENABLED && contentLen >= minChars)
|
|
45
|
+
return true;
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Pull a structured entity list from `content`. Tries each provider in
|
|
50
|
+
* order until one returns results. Returns `[]` on every failure.
|
|
51
|
+
*/
|
|
52
|
+
export async function extractEntities(content, opts = {}) {
|
|
53
|
+
const min = opts.minChars ?? MIN_LENGTH_CHARS;
|
|
54
|
+
if (!content || content.length < min)
|
|
55
|
+
return [];
|
|
56
|
+
const trimmed = content.slice(0, MAX_CONTENT_CHARS);
|
|
57
|
+
try {
|
|
58
|
+
if (opts.anthropicKey) {
|
|
59
|
+
const out = await extractViaAnthropic(trimmed, opts.anthropicKey);
|
|
60
|
+
if (out.length > 0)
|
|
61
|
+
return cap(out);
|
|
62
|
+
}
|
|
63
|
+
if (opts.openaiKey) {
|
|
64
|
+
const out = await extractViaOpenAI(trimmed, opts.openaiKey);
|
|
65
|
+
if (out.length > 0)
|
|
66
|
+
return cap(out);
|
|
67
|
+
}
|
|
68
|
+
if (process.env.ANTHROPIC_API_KEY) {
|
|
69
|
+
const out = await extractViaAnthropic(trimmed, process.env.ANTHROPIC_API_KEY);
|
|
70
|
+
if (out.length > 0)
|
|
71
|
+
return cap(out);
|
|
72
|
+
}
|
|
73
|
+
if (process.env.OPENAI_API_KEY) {
|
|
74
|
+
const out = await extractViaOpenAI(trimmed, process.env.OPENAI_API_KEY);
|
|
75
|
+
if (out.length > 0)
|
|
76
|
+
return cap(out);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
catch (e) {
|
|
80
|
+
console.warn('[mnueron/entity-extractor]', e instanceof Error ? e.message : e);
|
|
81
|
+
}
|
|
82
|
+
return [];
|
|
83
|
+
}
|
|
84
|
+
const SYSTEM_PROMPT = [
|
|
85
|
+
'You extract structured entities from memory text that will be stored',
|
|
86
|
+
"alongside an AI agent's long-term memory. The goal is a list of",
|
|
87
|
+
'recurring, identifiable, *referenceable* things — not a list of every',
|
|
88
|
+
'noun in the text.',
|
|
89
|
+
'',
|
|
90
|
+
'For each entity found, output:',
|
|
91
|
+
'',
|
|
92
|
+
' - name: the canonical display form (proper noun preferred)',
|
|
93
|
+
' - type: one of "person", "organization", "project", "technology",',
|
|
94
|
+
' "place", "decision", "event", "concept", "other"',
|
|
95
|
+
' - context: a short phrase (<= 80 chars) from the source that disambiguates',
|
|
96
|
+
' this entity from others with the same name. Optional.',
|
|
97
|
+
'',
|
|
98
|
+
'STRONG inclusion criteria — only emit an entity if it would survive these:',
|
|
99
|
+
' - Is it a proper noun, a named system, or a clearly bounded concept?',
|
|
100
|
+
' (YES: "Stripe", "Q3 roadmap", "PostgreSQL", "deprecate v1 API decision")',
|
|
101
|
+
' (NO: "Step 1 view file", "DB-backed settings", "server-side validation")',
|
|
102
|
+
' - Will another memory plausibly reference this same thing again?',
|
|
103
|
+
' If you can\'t imagine a second memory mentioning it, skip it.',
|
|
104
|
+
' - Is it identifiable on its own? "the API" is not — "Stripe API v2" is.',
|
|
105
|
+
'',
|
|
106
|
+
'EXPLICIT EXCLUSIONS — never emit any of these:',
|
|
107
|
+
' - Action phrases / implementation steps ("Step 1", "set up X", "fix Y",',
|
|
108
|
+
' "DB-backed", "server-side validation", "role-based auth").',
|
|
109
|
+
' - Generic technical terms used as common nouns ("the database",',
|
|
110
|
+
' "the wizard", "the controller", "the API call").',
|
|
111
|
+
' - File paths or hostnames as "place" — those are "technology".',
|
|
112
|
+
' - Verbs or verb phrases as concepts.',
|
|
113
|
+
' - Single-letter or all-lowercase one-word concepts ("PROD" can stay if',
|
|
114
|
+
' it\'s clearly an environment label, but "qa" or "dev" alone — skip).',
|
|
115
|
+
' - Anything mentioned only once in passing with no further context.',
|
|
116
|
+
'',
|
|
117
|
+
'Type guidance:',
|
|
118
|
+
' - "place" ONLY for real geographic locations (cities, offices,',
|
|
119
|
+
' countries). Hostnames, URLs, file paths → "technology".',
|
|
120
|
+
' - "decision" for explicit choices made ("we decided to use X").',
|
|
121
|
+
' NOT for implementation steps or recommendations in passing.',
|
|
122
|
+
' - "project" for named multi-task efforts ("Q3 roadmap", "Auth Rewrite").',
|
|
123
|
+
' NOT for one-off files or single UI screens.',
|
|
124
|
+
' - "concept" sparingly — only for abstract ideas referenced multiple times.',
|
|
125
|
+
'',
|
|
126
|
+
'Cap output at 25 entities; if more exist, pick the most important.',
|
|
127
|
+
'If fewer than 2 high-quality entities exist, return [] — better to return',
|
|
128
|
+
'nothing than to fill the list with noise.',
|
|
129
|
+
'',
|
|
130
|
+
'Respond with STRICT JSON. No prose, no preamble, no markdown.',
|
|
131
|
+
'Schema:',
|
|
132
|
+
' { "entities": [ { "name": "...", "type": "...", "context": "..." }, ... ] }',
|
|
133
|
+
].join('\n');
|
|
134
|
+
async function extractViaAnthropic(content, apiKey) {
|
|
135
|
+
const ctrl = new AbortController();
|
|
136
|
+
const timer = setTimeout(() => ctrl.abort(), TIMEOUT_MS);
|
|
137
|
+
try {
|
|
138
|
+
const resp = await fetch('https://api.anthropic.com/v1/messages', {
|
|
139
|
+
method: 'POST',
|
|
140
|
+
headers: {
|
|
141
|
+
'x-api-key': apiKey,
|
|
142
|
+
'anthropic-version': '2023-06-01',
|
|
143
|
+
'Content-Type': 'application/json',
|
|
144
|
+
},
|
|
145
|
+
body: JSON.stringify({
|
|
146
|
+
model: ANTHROPIC_MODEL,
|
|
147
|
+
max_tokens: 1500,
|
|
148
|
+
temperature: 0.1,
|
|
149
|
+
system: SYSTEM_PROMPT,
|
|
150
|
+
messages: [{ role: 'user', content }],
|
|
151
|
+
}),
|
|
152
|
+
signal: ctrl.signal,
|
|
153
|
+
});
|
|
154
|
+
if (!resp.ok) {
|
|
155
|
+
const body = await resp.text();
|
|
156
|
+
console.warn('[mnueron/entity-extractor/anthropic] HTTP ' + resp.status + ': ' + body.slice(0, 200));
|
|
157
|
+
return [];
|
|
158
|
+
}
|
|
159
|
+
const data = (await resp.json());
|
|
160
|
+
const text = (data.content ?? [])
|
|
161
|
+
.filter((b) => b.type === 'text')
|
|
162
|
+
.map((b) => b.text ?? '')
|
|
163
|
+
.join('');
|
|
164
|
+
return parseEntities(text);
|
|
165
|
+
}
|
|
166
|
+
catch (e) {
|
|
167
|
+
console.warn('[mnueron/entity-extractor/anthropic]', e instanceof Error ? e.message : e);
|
|
168
|
+
return [];
|
|
169
|
+
}
|
|
170
|
+
finally {
|
|
171
|
+
clearTimeout(timer);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
async function extractViaOpenAI(content, apiKey) {
|
|
175
|
+
const ctrl = new AbortController();
|
|
176
|
+
const timer = setTimeout(() => ctrl.abort(), TIMEOUT_MS);
|
|
177
|
+
try {
|
|
178
|
+
const resp = await fetch('https://api.openai.com/v1/chat/completions', {
|
|
179
|
+
method: 'POST',
|
|
180
|
+
headers: {
|
|
181
|
+
Authorization: 'Bearer ' + apiKey,
|
|
182
|
+
'Content-Type': 'application/json',
|
|
183
|
+
},
|
|
184
|
+
body: JSON.stringify({
|
|
185
|
+
model: OPENAI_MODEL,
|
|
186
|
+
max_tokens: 1500,
|
|
187
|
+
temperature: 0.1,
|
|
188
|
+
response_format: { type: 'json_object' },
|
|
189
|
+
messages: [
|
|
190
|
+
{ role: 'system', content: SYSTEM_PROMPT },
|
|
191
|
+
{ role: 'user', content },
|
|
192
|
+
],
|
|
193
|
+
}),
|
|
194
|
+
signal: ctrl.signal,
|
|
195
|
+
});
|
|
196
|
+
if (!resp.ok) {
|
|
197
|
+
const body = await resp.text();
|
|
198
|
+
console.warn('[mnueron/entity-extractor/openai] HTTP ' + resp.status + ': ' + body.slice(0, 200));
|
|
199
|
+
return [];
|
|
200
|
+
}
|
|
201
|
+
const data = (await resp.json());
|
|
202
|
+
return parseEntities(data.choices?.[0]?.message?.content ?? '');
|
|
203
|
+
}
|
|
204
|
+
catch (e) {
|
|
205
|
+
console.warn('[mnueron/entity-extractor/openai]', e instanceof Error ? e.message : e);
|
|
206
|
+
return [];
|
|
207
|
+
}
|
|
208
|
+
finally {
|
|
209
|
+
clearTimeout(timer);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Resilient JSON parse:
|
|
214
|
+
* - strips ```json ... ``` fences
|
|
215
|
+
* - skips leading prose ("Here are the entities:\n{...}")
|
|
216
|
+
* - accepts entities as bare array OR under `entities`/`results`/`items`
|
|
217
|
+
*/
|
|
218
|
+
function parseEntities(raw) {
|
|
219
|
+
if (!raw)
|
|
220
|
+
return [];
|
|
221
|
+
let s = raw.trim();
|
|
222
|
+
if (s.startsWith('```')) {
|
|
223
|
+
s = s.replace(/^```(?:json)?\s*/i, '').replace(/\s*```$/i, '');
|
|
224
|
+
}
|
|
225
|
+
const start = s.search(/[\[{]/);
|
|
226
|
+
if (start > 0)
|
|
227
|
+
s = s.slice(start);
|
|
228
|
+
let parsed;
|
|
229
|
+
try {
|
|
230
|
+
parsed = JSON.parse(s);
|
|
231
|
+
}
|
|
232
|
+
catch {
|
|
233
|
+
return [];
|
|
234
|
+
}
|
|
235
|
+
const arr = pickEntitiesArray(parsed);
|
|
236
|
+
if (!Array.isArray(arr))
|
|
237
|
+
return [];
|
|
238
|
+
const out = [];
|
|
239
|
+
for (const raw of arr) {
|
|
240
|
+
if (!raw || typeof raw !== 'object')
|
|
241
|
+
continue;
|
|
242
|
+
const r = raw;
|
|
243
|
+
const name = typeof r.name === 'string' ? r.name.trim() : '';
|
|
244
|
+
if (!name)
|
|
245
|
+
continue;
|
|
246
|
+
const type = typeof r.type === 'string' ? r.type.trim() : 'other';
|
|
247
|
+
const context = typeof r.context === 'string' ? r.context.trim().slice(0, 80) : undefined;
|
|
248
|
+
out.push({ name, type, context, canonical_id: null });
|
|
249
|
+
}
|
|
250
|
+
return dedupe(out);
|
|
251
|
+
}
|
|
252
|
+
function pickEntitiesArray(parsed) {
|
|
253
|
+
if (Array.isArray(parsed))
|
|
254
|
+
return parsed;
|
|
255
|
+
if (parsed && typeof parsed === 'object') {
|
|
256
|
+
const p = parsed;
|
|
257
|
+
if (Array.isArray(p.entities))
|
|
258
|
+
return p.entities;
|
|
259
|
+
if (Array.isArray(p.results))
|
|
260
|
+
return p.results;
|
|
261
|
+
if (Array.isArray(p.items))
|
|
262
|
+
return p.items;
|
|
263
|
+
if (Array.isArray(p.extracted))
|
|
264
|
+
return p.extracted;
|
|
265
|
+
}
|
|
266
|
+
return null;
|
|
267
|
+
}
|
|
268
|
+
function dedupe(items) {
|
|
269
|
+
const seen = new Set();
|
|
270
|
+
const out = [];
|
|
271
|
+
for (const item of items) {
|
|
272
|
+
const key = item.name.toLowerCase();
|
|
273
|
+
if (seen.has(key))
|
|
274
|
+
continue;
|
|
275
|
+
seen.add(key);
|
|
276
|
+
out.push(item);
|
|
277
|
+
}
|
|
278
|
+
return out;
|
|
279
|
+
}
|
|
280
|
+
function cap(items) {
|
|
281
|
+
return items.length > MAX_ENTITIES ? items.slice(0, MAX_ENTITIES) : items;
|
|
282
|
+
}
|
|
283
|
+
//# sourceMappingURL=entity-extractor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entity-extractor.js","sourceRoot":"","sources":["../../src/store/entity-extractor.ts"],"names":[],"mappings":"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"}
|