@pellux/goodvibes-sdk 0.27.5 → 0.27.6

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.
@@ -3,7 +3,7 @@
3
3
  "product": {
4
4
  "id": "goodvibes",
5
5
  "surface": "operator",
6
- "version": "0.27.5"
6
+ "version": "0.27.6"
7
7
  },
8
8
  "auth": {
9
9
  "modes": [
@@ -1,6 +1,6 @@
1
1
  export declare const FOUNDATION_METADATA: {
2
2
  readonly productId: "goodvibes";
3
- readonly productVersion: "0.27.5";
3
+ readonly productVersion: "0.27.6";
4
4
  readonly operatorMethodCount: 256;
5
5
  readonly operatorEventCount: 30;
6
6
  readonly peerEndpointCount: 6;
@@ -1,7 +1,7 @@
1
1
  // Synced from packages/contracts/src/generated/foundation-metadata.ts
2
2
  export const FOUNDATION_METADATA = {
3
3
  "productId": "goodvibes",
4
- "productVersion": "0.27.5",
4
+ "productVersion": "0.27.6",
5
5
  "operatorMethodCount": 256,
6
6
  "operatorEventCount": 30,
7
7
  "peerEndpointCount": 6
@@ -3,7 +3,7 @@ export const OPERATOR_CONTRACT = {
3
3
  "product": {
4
4
  "id": "goodvibes",
5
5
  "surface": "operator",
6
- "version": "0.27.5"
6
+ "version": "0.27.6"
7
7
  },
8
8
  "auth": {
9
9
  "modes": [
@@ -1 +1 @@
1
- {"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../../../../src/_internal/platform/knowledge/home-graph/search.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,KAAK,EACV,mBAAmB,EACnB,yBAAyB,EACzB,mBAAmB,EACnB,qBAAqB,EACtB,MAAM,aAAa,CAAC;AAGrB,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAqGxD,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,SAAS,qBAAqB,EAAE,CAAC;IACnD,QAAQ,CAAC,KAAK,EAAE,SAAS,mBAAmB,EAAE,CAAC;IAC/C,QAAQ,CAAC,KAAK,EAAE,SAAS,mBAAmB,EAAE,CAAC;IAC/C,QAAQ,CAAC,oBAAoB,EAAE,WAAW,CAAC,MAAM,EAAE,yBAAyB,CAAC,CAAC;CAC/E;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,GAAG,oBAAoB,CAuBrG;AAED,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,SAAS,qBAAqB,EAAE,EACzC,KAAK,EAAE,SAAS,mBAAmB,EAAE,EACrC,KAAK,EAAE,SAAS,mBAAmB,EAAE,EACrC,oBAAoB,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,yBAAyB,GAAG,IAAI,GAAG,SAAS,EACxF,KAAK,EAAE,MAAM,GACZ,qBAAqB,EAAE,CAmFzB;AAED,wBAAgB,yCAAyC,CACvD,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,SAAS,qBAAqB,EAAE,EACzC,KAAK,EAAE,SAAS,mBAAmB,EAAE,EACrC,KAAK,EAAE,SAAS,mBAAmB,EAAE,EACrC,oBAAoB,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,yBAAyB,GAAG,IAAI,GAAG,SAAS,EACxF,KAAK,EAAE,MAAM,GACZ,qBAAqB,EAAE,CAoCzB;AAwMD,wBAAgB,8BAA8B,CAAC,UAAU,EAAE,yBAAyB,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAWhH"}
1
+ {"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../../../../src/_internal/platform/knowledge/home-graph/search.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,KAAK,EACV,mBAAmB,EACnB,yBAAyB,EACzB,mBAAmB,EACnB,qBAAqB,EACtB,MAAM,aAAa,CAAC;AAGrB,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAoIxD,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,SAAS,qBAAqB,EAAE,CAAC;IACnD,QAAQ,CAAC,KAAK,EAAE,SAAS,mBAAmB,EAAE,CAAC;IAC/C,QAAQ,CAAC,KAAK,EAAE,SAAS,mBAAmB,EAAE,CAAC;IAC/C,QAAQ,CAAC,oBAAoB,EAAE,WAAW,CAAC,MAAM,EAAE,yBAAyB,CAAC,CAAC;CAC/E;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,GAAG,oBAAoB,CAuBrG;AAED,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,SAAS,qBAAqB,EAAE,EACzC,KAAK,EAAE,SAAS,mBAAmB,EAAE,EACrC,KAAK,EAAE,SAAS,mBAAmB,EAAE,EACrC,oBAAoB,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,yBAAyB,GAAG,IAAI,GAAG,SAAS,EACxF,KAAK,EAAE,MAAM,GACZ,qBAAqB,EAAE,CAoFzB;AAED,wBAAgB,yCAAyC,CACvD,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,SAAS,qBAAqB,EAAE,EACzC,KAAK,EAAE,SAAS,mBAAmB,EAAE,EACrC,KAAK,EAAE,SAAS,mBAAmB,EAAE,EACrC,oBAAoB,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,yBAAyB,GAAG,IAAI,GAAG,SAAS,EACxF,KAAK,EAAE,MAAM,GACZ,qBAAqB,EAAE,CAoCzB;AAmND,wBAAgB,8BAA8B,CAAC,UAAU,EAAE,yBAAyB,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAWhH"}
@@ -91,10 +91,39 @@ const SOURCE_EVIDENCE_TOKENS = new Set([
91
91
  'supports',
92
92
  'warranty',
93
93
  ]);
94
+ const ANCHOR_INTENT_TOKENS = new Set([
95
+ ...SOURCE_EVIDENCE_TOKENS,
96
+ 'function',
97
+ 'functions',
98
+ 'mode',
99
+ 'modes',
100
+ 'setup',
101
+ 'install',
102
+ 'configure',
103
+ 'documentation',
104
+ 'manual',
105
+ ]);
106
+ const HOME_GRAPH_ANCHOR_KINDS = new Set([
107
+ 'ha_home',
108
+ 'ha_entity',
109
+ 'ha_device',
110
+ 'ha_area',
111
+ 'ha_automation',
112
+ 'ha_script',
113
+ 'ha_scene',
114
+ 'ha_label',
115
+ 'ha_integration',
116
+ 'ha_room',
117
+ 'ha_device_passport',
118
+ 'ha_maintenance_item',
119
+ 'ha_troubleshooting_case',
120
+ 'ha_purchase',
121
+ 'ha_network_node',
122
+ ]);
94
123
  const INTEGRATION_QUERY_TOKENS = new Set(['automation', 'automations', 'homeassistant', 'integration', 'integrations', 'webostv']);
95
124
  export function readHomeGraphSearchState(store, spaceId) {
96
125
  const sources = store.listSources(10_000).filter((source) => (belongsToSpace(source, spaceId) && !isGeneratedPageSource(source)));
97
- const nodes = store.listNodes(10_000).filter((node) => belongsToSpace(node, spaceId));
126
+ const nodes = store.listNodes(10_000).filter((node) => belongsToSpace(node, spaceId) && node.status !== 'stale');
98
127
  const sourceIds = new Set(sources.map((source) => source.id));
99
128
  const nodeIds = new Set(nodes.map((node) => node.id));
100
129
  const edges = store.listEdges().filter((edge) => (edgeIsActive(edge)
@@ -118,8 +147,9 @@ export function scoreHomeGraphResults(query, sources, nodes, edges, extractionBy
118
147
  if (tokens.length === 0)
119
148
  return [];
120
149
  const expandedTokens = expandTokens(tokens);
121
- const anchors = selectAnchorNodes(tokens, nodes);
122
- const sourceAnchors = selectSourceAnchors(tokens, anchors.map((anchor) => anchor.node));
150
+ const anchorTokens = selectAnchorQueryTokens(tokens);
151
+ const anchors = selectAnchorNodes(anchorTokens, nodes);
152
+ const sourceAnchors = selectSourceAnchors(anchorTokens, anchors.map((anchor) => anchor.node));
123
153
  const anchorIds = new Set(sourceAnchors.map((node) => node.id));
124
154
  const anchorIdentityTokens = collectAnchorIdentityTokens(sourceAnchors);
125
155
  const sourceLinks = buildSourceLinkIndex(edges);
@@ -319,7 +349,11 @@ function sourceAnchorIdentityScore(anchorTokens, source, extraction) {
319
349
  ]);
320
350
  }
321
351
  function selectAnchorNodes(tokens, nodes) {
352
+ if (tokens.length === 0)
353
+ return [];
322
354
  return nodes.map((node) => {
355
+ if (!isHomeGraphAnchorNode(node))
356
+ return { node, score: 0 };
323
357
  const baseScore = scoreFields(tokens, nodeIdentityFields(node));
324
358
  const intentBoost = sourceAnchorIntentBoost(tokens, node);
325
359
  return {
@@ -331,6 +365,14 @@ function selectAnchorNodes(tokens, nodes) {
331
365
  .sort((a, b) => b.score - a.score || a.node.id.localeCompare(b.node.id))
332
366
  .slice(0, 12);
333
367
  }
368
+ function selectAnchorQueryTokens(tokens) {
369
+ return tokens.filter((token) => !ANCHOR_INTENT_TOKENS.has(token));
370
+ }
371
+ function isHomeGraphAnchorNode(node) {
372
+ if (typeof node.metadata.semanticKind === 'string')
373
+ return false;
374
+ return HOME_GRAPH_ANCHOR_KINDS.has(node.kind);
375
+ }
334
376
  function selectSourceAnchors(tokens, nodes) {
335
377
  const preferred = nodes.filter((node) => sourceAnchorIntentBoost(tokens, node) >= 0);
336
378
  if (preferred.length > 0)
@@ -1,7 +1,7 @@
1
1
  import { belongsToSpace, edgeIsActive, readRecord } from './helpers.js';
2
2
  export function readHomeGraphState(store, spaceId) {
3
3
  const sources = store.listSources(10_000).filter((source) => belongsToSpace(source, spaceId));
4
- const nodes = store.listNodes(10_000).filter((node) => belongsToSpace(node, spaceId));
4
+ const nodes = store.listNodes(10_000).filter((node) => belongsToSpace(node, spaceId) && node.status !== 'stale');
5
5
  const sourceIds = new Set(sources.map((source) => source.id));
6
6
  const nodeIds = new Set(nodes.map((node) => node.id));
7
7
  const edges = store.listEdges().filter((edge) => (edgeIsActive(edge)
@@ -1 +1 @@
1
- {"version":3,"file":"answer.d.ts","sourceRoot":"","sources":["../../../../../src/_internal/platform/knowledge/semantic/answer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAOlD,OAAO,KAAK,EACV,4BAA4B,EAC5B,6BAA6B,EAE7B,oBAAoB,EAErB,MAAM,YAAY,CAAC;AAepB,UAAU,sBAAsB;IAC9B,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC;IAC/B,QAAQ,CAAC,GAAG,CAAC,EAAE,oBAAoB,GAAG,IAAI,CAAC;CAC5C;AAaD,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,sBAAsB,EAC/B,KAAK,EAAE,4BAA4B,GAClC,OAAO,CAAC,6BAA6B,CAAC,CAiDxC"}
1
+ {"version":3,"file":"answer.d.ts","sourceRoot":"","sources":["../../../../../src/_internal/platform/knowledge/semantic/answer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAOlD,OAAO,KAAK,EACV,4BAA4B,EAC5B,6BAA6B,EAE7B,oBAAoB,EAErB,MAAM,YAAY,CAAC;AAepB,UAAU,sBAAsB;IAC9B,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC;IAC/B,QAAQ,CAAC,GAAG,CAAC,EAAE,oBAAoB,GAAG,IAAI,CAAC;CAC5C;AAsCD,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,sBAAsB,EAC/B,KAAK,EAAE,4BAA4B,GAClC,OAAO,CAAC,6BAA6B,CAAC,CAiDxC"}
@@ -1,5 +1,29 @@
1
1
  import { getKnowledgeSpaceId, isInKnowledgeSpace, normalizeKnowledgeSpaceId } from '../spaces.js';
2
2
  import { MAX_ANSWER_EVIDENCE_CHARS, clampText, normalizeWhitespace, readRecord, readString, scoreSemanticText, semanticHash, semanticMetadata, sourceSemanticText, tokenizeSemanticQuery, uniqueStrings, } from './utils.js';
3
+ const GENERIC_ANSWER_INTENT_TOKENS = new Set([
4
+ 'capabilities',
5
+ 'capability',
6
+ 'configuration',
7
+ 'configure',
8
+ 'feature',
9
+ 'features',
10
+ 'function',
11
+ 'functions',
12
+ 'install',
13
+ 'mode',
14
+ 'modes',
15
+ 'procedure',
16
+ 'setting',
17
+ 'settings',
18
+ 'setup',
19
+ 'spec',
20
+ 'specification',
21
+ 'specifications',
22
+ 'specs',
23
+ 'support',
24
+ 'supported',
25
+ 'supports',
26
+ ]);
3
27
  export async function answerKnowledgeQuery(context, input) {
4
28
  const spaceId = normalizeKnowledgeSpaceId(input.knowledgeSpaceId);
5
29
  const mode = input.mode ?? 'standard';
@@ -53,6 +77,7 @@ function collectAnswerEvidence(store, input, spaceId, limit) {
53
77
  const tokens = expandQueryTokens(tokenizeSemanticQuery(input.query));
54
78
  if (tokens.length === 0)
55
79
  return [];
80
+ const subjectTokens = tokens.filter((token) => !GENERIC_ANSWER_INTENT_TOKENS.has(token));
56
81
  const candidateSourceIds = new Set(input.candidateSourceIds ?? []);
57
82
  const candidateNodeIds = new Set(input.candidateNodeIds ?? []);
58
83
  const linkedObjectIds = new Set((input.linkedObjects ?? []).map((node) => node.id));
@@ -66,17 +91,28 @@ function collectAnswerEvidence(store, input, spaceId, limit) {
66
91
  const extraction = store.getExtractionBySourceId(source.id);
67
92
  const facts = filterFactsForQuery(input.query, sourceFacts.get(source.id) ?? []);
68
93
  const text = sourceSemanticText(source, extraction);
69
- const baseScore = scoreSemanticText([
94
+ const scoringText = [
70
95
  source.title,
71
96
  source.summary,
72
97
  source.description,
73
98
  source.tags.join(' '),
74
99
  text,
75
100
  facts.map(renderFactForScoring).join(' '),
76
- ].join('\n'), tokens);
101
+ ].join('\n');
102
+ const baseScore = scoreSemanticText(scoringText, tokens);
103
+ const subjectScore = subjectTokens.length > 0 ? scoreSemanticText(scoringText, subjectTokens) : 0;
77
104
  const candidateBoost = candidateSourceIds.has(source.id) ? 220 : 0;
78
105
  const linkedBoost = linkedSourceIds.has(source.id) ? 160 : 0;
79
- const score = baseScore + candidateBoost + linkedBoost + Math.min(60, facts.length * 6);
106
+ const genericOnly = subjectTokens.length > 0 && subjectScore === 0;
107
+ const weakStrictCandidate = strictCandidates
108
+ && candidateSourceIds.size > 1
109
+ && candidateSourceIds.has(source.id)
110
+ && !linkedSourceIds.has(source.id)
111
+ && genericOnly;
112
+ const weakBroadMatch = !strictCandidates && genericOnly;
113
+ const score = weakStrictCandidate || weakBroadMatch
114
+ ? 0
115
+ : baseScore + candidateBoost + linkedBoost + Math.min(60, facts.length * 6);
80
116
  return {
81
117
  kind: 'source',
82
118
  id: source.id,
@@ -88,18 +124,25 @@ function collectAnswerEvidence(store, input, spaceId, limit) {
88
124
  };
89
125
  });
90
126
  const nodeItems = store.listNodes(10_000)
91
- .filter((node) => belongsToAnswerSpace(node, spaceId))
127
+ .filter((node) => belongsToAnswerSpace(node, spaceId) && node.status !== 'stale')
92
128
  .filter((node) => !strictCandidates
93
129
  || candidateNodeIds.has(node.id)
94
130
  || linkedObjectIds.has(node.id)
95
131
  || (typeof node.sourceId === 'string' && candidateSourceIds.has(node.sourceId)))
96
132
  .map((node) => {
97
- const score = scoreSemanticText([
133
+ const scoringText = [
98
134
  node.title,
99
135
  node.summary,
100
136
  node.aliases.join(' '),
101
137
  JSON.stringify(node.metadata),
102
- ].join('\n'), tokens) + (candidateNodeIds.has(node.id) || linkedObjectIds.has(node.id) ? 120 : 0) + semanticKindBoost(node);
138
+ ].join('\n');
139
+ const baseScore = scoreSemanticText(scoringText, tokens);
140
+ const subjectScore = subjectTokens.length > 0 ? scoreSemanticText(scoringText, subjectTokens) : 0;
141
+ const genericOnly = subjectTokens.length > 0 && subjectScore === 0;
142
+ const candidateOrLinked = candidateNodeIds.has(node.id) || linkedObjectIds.has(node.id);
143
+ const score = genericOnly && !candidateOrLinked
144
+ ? 0
145
+ : baseScore + (candidateOrLinked ? 120 : 0) + semanticKindBoost(node);
103
146
  return {
104
147
  kind: 'node',
105
148
  id: node.id,
@@ -184,10 +227,14 @@ function renderFallbackAnswer(query, mode, evidence) {
184
227
  return lines.length > 0 ? lines.join('\n') : `No knowledge matched "${query}".`;
185
228
  }
186
229
  function filterFactsForQuery(query, facts) {
187
- const intent = factIntent(tokenizeSemanticQuery(query));
188
- if (!intent)
189
- return [...facts];
190
- return facts.filter((fact) => intent.has(readString(fact.metadata.factKind) ?? 'note'));
230
+ const tokens = tokenizeSemanticQuery(query);
231
+ const intent = factIntent(tokens);
232
+ const matching = intent
233
+ ? facts.filter((fact) => intent.has(readString(fact.metadata.factKind) ?? 'note'))
234
+ : [...facts];
235
+ return matching
236
+ .filter((fact) => fact.status !== 'stale' && !isLowValueFactForQuery(tokens, intent, fact))
237
+ .sort(compareFactQuality);
191
238
  }
192
239
  function factIntent(tokens) {
193
240
  const tokenSet = new Set(tokens);
@@ -207,6 +254,49 @@ function factIntent(tokens) {
207
254
  function hasAny(values, candidates) {
208
255
  return candidates.some((candidate) => values.has(candidate));
209
256
  }
257
+ function isLowValueFactForQuery(tokens, intent, fact) {
258
+ if (!intent || !hasFeatureIntent(intent))
259
+ return false;
260
+ const kind = readString(fact.metadata.factKind) ?? 'note';
261
+ if (!['feature', 'capability', 'specification', 'compatibility', 'configuration', 'identity'].includes(kind))
262
+ return false;
263
+ const text = [
264
+ fact.title,
265
+ fact.summary,
266
+ readString(fact.metadata.value),
267
+ readString(fact.metadata.evidence),
268
+ Array.isArray(fact.metadata.labels) ? fact.metadata.labels.join(' ') : '',
269
+ ].filter(Boolean).join(' ').toLowerCase();
270
+ if (isManualBoilerplateFeatureText(text))
271
+ return true;
272
+ const extractor = readString(fact.metadata.extractor);
273
+ const confidence = typeof fact.confidence === 'number' ? fact.confidence : 0;
274
+ if (extractor !== 'deterministic' || confidence > 60)
275
+ return false;
276
+ const subjectTokens = tokens.filter((token) => !GENERIC_ANSWER_INTENT_TOKENS.has(token));
277
+ return subjectTokens.length > 0 && !hasConcreteFeatureSignal(text);
278
+ }
279
+ function hasFeatureIntent(intent) {
280
+ return intent.has('feature') || intent.has('capability') || intent.has('specification') || intent.has('compatibility');
281
+ }
282
+ function isManualBoilerplateFeatureText(text) {
283
+ return /\b(items? supplied|accessories supplied|contents? of (this )?manual|may vary|may be changed|without prior notice|depending (upon|on) (the )?model|available menus? and options?|product specifications?|power cord|electric shock|fire hazard|certified cable|do not|warning|caution|risk|hazard|clean only|near water|ventilation|antenna grounding)\b/.test(text);
284
+ }
285
+ function hasConcreteFeatureSignal(text) {
286
+ return /\b(hdmi|usb|hdr|hdr10|dolby|vision|earc|arc|bluetooth|wi-?fi|ethernet|voice|remote|game|filmmaker|airplay|chromecast|resolution|4k|8k|refresh|ports?|speakers?|audio|display|screen|apps?|streaming|matter|energy monitoring|scheduling|sensor|battery|z-?wave|zigbee|thread|motion|temperature|humidity|camera|recording|lock|garage|local control|api|automation)\b/.test(text);
287
+ }
288
+ function compareFactQuality(left, right) {
289
+ return factQuality(right) - factQuality(left) || left.title.localeCompare(right.title);
290
+ }
291
+ function factQuality(fact) {
292
+ const extractor = readString(fact.metadata.extractor);
293
+ const kind = readString(fact.metadata.factKind);
294
+ const value = readString(fact.metadata.value);
295
+ return (extractor === 'llm' ? 40 : 0)
296
+ + (value ? 12 : 0)
297
+ + (kind === 'capability' || kind === 'feature' ? 8 : kind === 'specification' ? 6 : 0)
298
+ + Math.round(fact.confidence / 10);
299
+ }
210
300
  async function persistAnswerGap(store, spaceId, query, reason) {
211
301
  const node = await store.upsertNode({
212
302
  id: `sem-answer-gap-${semanticHash(spaceId, query)}`,
@@ -245,7 +335,7 @@ async function persistAnswerGaps(store, spaceId, query, gaps) {
245
335
  return nodes;
246
336
  }
247
337
  function buildSourceFactIndex(store, spaceId) {
248
- const facts = store.listNodes(10_000).filter((node) => (node.metadata.semanticKind === 'fact' && belongsToAnswerSpace(node, spaceId)));
338
+ const facts = store.listNodes(10_000).filter((node) => (node.status !== 'stale' && node.metadata.semanticKind === 'fact' && belongsToAnswerSpace(node, spaceId)));
249
339
  const bySource = new Map();
250
340
  for (const fact of facts) {
251
341
  const sourceId = readString(fact.metadata.sourceId) ?? fact.sourceId;
@@ -1 +1 @@
1
- {"version":3,"file":"enrichment.d.ts","sourceRoot":"","sources":["../../../../../src/_internal/platform/knowledge/semantic/enrichment.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,KAAK,EAEV,mBAAmB,EACnB,qBAAqB,EACtB,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAKV,oBAAoB,EAErB,MAAM,YAAY,CAAC;AAkBpB,MAAM,WAAW,kCAAkC;IACjD,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC;IAC/B,QAAQ,CAAC,GAAG,CAAC,EAAE,oBAAoB,GAAG,IAAI,CAAC;CAC5C;AAED,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;CACpC;AAED,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,CAAC,MAAM,EAAE,qBAAqB,CAAC;IACvC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,CAAC,EAAE,KAAK,GAAG,eAAe,CAAC;IAC7C,QAAQ,CAAC,KAAK,EAAE,SAAS,mBAAmB,EAAE,CAAC;IAC/C,QAAQ,CAAC,QAAQ,EAAE,SAAS,mBAAmB,EAAE,CAAC;IAClD,QAAQ,CAAC,QAAQ,CAAC,EAAE,mBAAmB,CAAC;IACxC,QAAQ,CAAC,IAAI,EAAE,SAAS,mBAAmB,EAAE,CAAC;CAC/C;AAED,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,kCAAkC,EAC3C,MAAM,EAAE,qBAAqB,EAC7B,OAAO,GAAE,4BAAiC,GACzC,OAAO,CAAC,2BAA2B,CAAC,CA6BtC"}
1
+ {"version":3,"file":"enrichment.d.ts","sourceRoot":"","sources":["../../../../../src/_internal/platform/knowledge/semantic/enrichment.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,KAAK,EAEV,mBAAmB,EACnB,qBAAqB,EACtB,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAKV,oBAAoB,EAErB,MAAM,YAAY,CAAC;AAkBpB,MAAM,WAAW,kCAAkC;IACjD,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC;IAC/B,QAAQ,CAAC,GAAG,CAAC,EAAE,oBAAoB,GAAG,IAAI,CAAC;CAC5C;AAED,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;CACpC;AAED,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,CAAC,MAAM,EAAE,qBAAqB,CAAC;IACvC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,CAAC,EAAE,KAAK,GAAG,eAAe,CAAC;IAC7C,QAAQ,CAAC,KAAK,EAAE,SAAS,mBAAmB,EAAE,CAAC;IAC/C,QAAQ,CAAC,QAAQ,EAAE,SAAS,mBAAmB,EAAE,CAAC;IAClD,QAAQ,CAAC,QAAQ,CAAC,EAAE,mBAAmB,CAAC;IACxC,QAAQ,CAAC,IAAI,EAAE,SAAS,mBAAmB,EAAE,CAAC;CAC/C;AAED,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,kCAAkC,EAC3C,MAAM,EAAE,qBAAqB,EAC7B,OAAO,GAAE,4BAAiC,GACzC,OAAO,CAAC,2BAA2B,CAAC,CA+BtC"}
@@ -4,7 +4,9 @@ export async function enrichKnowledgeSource(context, source, options = {}) {
4
4
  const text = sourceSemanticText(source, extraction);
5
5
  const textHash = sourceSemanticHash(source, extraction);
6
6
  const existingSemantic = readRecord(source.metadata.semanticEnrichment);
7
- if (!options.force && existingSemantic.textHash === textHash) {
7
+ const currentExtractor = readString(existingSemantic.extractor);
8
+ const shouldUpgradeDeterministic = Boolean(context.llm && existingSemantic.textHash === textHash && currentExtractor !== 'llm');
9
+ if (!options.force && existingSemantic.textHash === textHash && !shouldUpgradeDeterministic) {
8
10
  return emptyResult(source, true, 'semantic enrichment is current');
9
11
  }
10
12
  if (text.length < 40) {
@@ -257,8 +259,39 @@ async function persistSemanticExtraction(store, source, extraction, semantic, op
257
259
  });
258
260
  }
259
261
  const wikiPage = await persistWikiPage(store, source, semantic, spaceId, options.textHash);
262
+ await markPreviousSemanticNodesStale(store, source.id, spaceId, new Set([
263
+ ...entities.map((node) => node.id),
264
+ ...facts.map((node) => node.id),
265
+ ...gaps.map((node) => node.id),
266
+ ...(wikiPage ? [wikiPage.id] : []),
267
+ ]));
260
268
  return { source, skipped: false, extractor: semantic.extractor, facts, entities, gaps, ...(wikiPage ? { wikiPage } : {}) };
261
269
  }
270
+ async function markPreviousSemanticNodesStale(store, sourceId, spaceId, activeIds) {
271
+ const supersededAt = Date.now();
272
+ const semanticNodes = store.listNodes(10_000).filter((node) => (node.sourceId === sourceId
273
+ && typeof node.metadata.semanticKind === 'string'
274
+ && node.status !== 'stale'
275
+ && !activeIds.has(node.id)));
276
+ for (const node of semanticNodes) {
277
+ await store.upsertNode({
278
+ id: node.id,
279
+ kind: node.kind,
280
+ slug: node.slug,
281
+ title: node.title,
282
+ summary: node.summary,
283
+ aliases: node.aliases,
284
+ status: 'stale',
285
+ confidence: node.confidence,
286
+ ...(node.sourceId ? { sourceId: node.sourceId } : {}),
287
+ metadata: {
288
+ ...node.metadata,
289
+ supersededAt,
290
+ supersededInSpaceId: spaceId,
291
+ },
292
+ });
293
+ }
294
+ }
262
295
  async function persistWikiPage(store, source, semantic, spaceId, textHash) {
263
296
  const markdown = semantic.wikiPage?.markdown ?? renderDeterministicWikiPage(source, semantic.facts);
264
297
  if (!markdown.trim())
@@ -1 +1 @@
1
- {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../../../../src/_internal/platform/knowledge/semantic/service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAGzD,OAAO,KAAK,EACV,4BAA4B,EAC5B,6BAA6B,EAC7B,iCAAiC,EACjC,oBAAoB,EACrB,MAAM,YAAY,CAAC;AAEpB,MAAM,WAAW,+BAA+B;IAC9C,QAAQ,CAAC,GAAG,CAAC,EAAE,oBAAoB,GAAG,IAAI,CAAC;IAC3C,QAAQ,CAAC,uBAAuB,CAAC,EAAE,MAAM,CAAC;CAC3C;AAED,qBAAa,wBAAwB;IAEjC,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,OAAO;gBADP,KAAK,EAAE,cAAc,EACrB,OAAO,GAAE,+BAAoC;IAG1D,YAAY,CAChB,QAAQ,EAAE,MAAM,EAChB,KAAK,GAAE;QAAE,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAA;KAAO,GAC3E,OAAO,CAAC,iCAAiC,GAAG,IAAI,CAAC;IAO9C,aAAa,CACjB,OAAO,EAAE,SAAS,qBAAqB,EAAE,EACzC,KAAK,GAAE;QAAE,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAO,GACpG,OAAO,CAAC,SAAS,iCAAiC,EAAE,CAAC;IASlD,OAAO,CAAC,KAAK,GAAE;QACnB,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;QACvC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QACxB,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;QACzB,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;KAC/B,GAAG,OAAO,CAAC;QACf,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;QACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;QAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;QACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;QACxB,QAAQ,CAAC,MAAM,EAAE,SAAS;YAAE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;YAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;SAAE,EAAE,CAAC;KACnF,CAAC;IA2BI,MAAM,CAAC,KAAK,EAAE,4BAA4B,GAAG,OAAO,CAAC,6BAA6B,CAAC;CAI1F"}
1
+ {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../../../../src/_internal/platform/knowledge/semantic/service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAGzD,OAAO,KAAK,EACV,4BAA4B,EAC5B,6BAA6B,EAC7B,iCAAiC,EACjC,oBAAoB,EACrB,MAAM,YAAY,CAAC;AAGpB,MAAM,WAAW,+BAA+B;IAC9C,QAAQ,CAAC,GAAG,CAAC,EAAE,oBAAoB,GAAG,IAAI,CAAC;IAC3C,QAAQ,CAAC,uBAAuB,CAAC,EAAE,MAAM,CAAC;CAC3C;AAED,qBAAa,wBAAwB;IAEjC,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,OAAO;gBADP,KAAK,EAAE,cAAc,EACrB,OAAO,GAAE,+BAAoC;IAG1D,YAAY,CAChB,QAAQ,EAAE,MAAM,EAChB,KAAK,GAAE;QAAE,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAA;KAAO,GAC3E,OAAO,CAAC,iCAAiC,GAAG,IAAI,CAAC;IAO9C,aAAa,CACjB,OAAO,EAAE,SAAS,qBAAqB,EAAE,EACzC,KAAK,GAAE;QAAE,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAO,GACpG,OAAO,CAAC,SAAS,iCAAiC,EAAE,CAAC;IASlD,OAAO,CAAC,KAAK,GAAE;QACnB,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;QACvC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QACxB,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;QACzB,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;KAC/B,GAAG,OAAO,CAAC;QACf,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;QACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;QAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;QACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;QACxB,QAAQ,CAAC,MAAM,EAAE,SAAS;YAAE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;YAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;SAAE,EAAE,CAAC;KACnF,CAAC;IA6BI,MAAM,CAAC,KAAK,EAAE,4BAA4B,GAAG,OAAO,CAAC,6BAA6B,CAAC;CAI1F"}
@@ -1,5 +1,6 @@
1
1
  import { answerKnowledgeQuery } from './answer.js';
2
2
  import { enrichKnowledgeSource } from './enrichment.js';
3
+ import { readRecord, readString, sourceSemanticHash, sourceSemanticText } from './utils.js';
3
4
  export class KnowledgeSemanticService {
4
5
  store;
5
6
  options;
@@ -37,7 +38,9 @@ export class KnowledgeSemanticService {
37
38
  const errors = [];
38
39
  for (const source of sources) {
39
40
  try {
40
- const llm = this.options.llm && llmAttempts < maxLlmSources ? this.options.llm : null;
41
+ const llm = this.options.llm && llmAttempts < maxLlmSources && sourceCanUseLlmUpgrade(this.store, source)
42
+ ? this.options.llm
43
+ : null;
41
44
  if (llm)
42
45
  llmAttempts += 1;
43
46
  const result = await enrichKnowledgeSource({ store: this.store, llm }, source, input);
@@ -58,3 +61,12 @@ export class KnowledgeSemanticService {
58
61
  return answerKnowledgeQuery({ store: this.store, llm: this.options.llm }, input);
59
62
  }
60
63
  }
64
+ function sourceCanUseLlmUpgrade(store, source) {
65
+ const extraction = store.getExtractionBySourceId(source.id);
66
+ const text = sourceSemanticText(source, extraction);
67
+ if (text.length < 40)
68
+ return false;
69
+ const existingSemantic = readRecord(source.metadata.semanticEnrichment);
70
+ return existingSemantic.textHash !== sourceSemanticHash(source, extraction)
71
+ || readString(existingSemantic.extractor) !== 'llm';
72
+ }
@@ -1,6 +1,6 @@
1
1
  import { readFileSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
- let version = '0.27.5';
3
+ let version = '0.27.6';
4
4
  try {
5
5
  const pkg = JSON.parse(readFileSync(join(import.meta.dir, '..', '..', 'package.json'), 'utf-8'));
6
6
  version = pkg.version ?? version;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pellux/goodvibes-sdk",
3
- "version": "0.27.5",
3
+ "version": "0.27.6",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/mgd34msu/goodvibes-sdk.git"