@indexnetwork/protocol 4.14.2-rc.356.1 → 4.16.0-rc.358.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 +2 -0
- package/dist/index.d.ts +13 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -2
- package/dist/index.js.map +1 -1
- package/dist/opportunity/discriminator/discriminator.env.d.ts +15 -0
- package/dist/opportunity/discriminator/discriminator.env.d.ts.map +1 -1
- package/dist/opportunity/discriminator/discriminator.env.js +15 -0
- package/dist/opportunity/discriminator/discriminator.env.js.map +1 -1
- package/dist/opportunity/discriminator/discriminator.push.d.ts +16 -0
- package/dist/opportunity/discriminator/discriminator.push.d.ts.map +1 -0
- package/dist/opportunity/discriminator/discriminator.push.js +26 -0
- package/dist/opportunity/discriminator/discriminator.push.js.map +1 -0
- package/dist/opportunity/opportunity.graph.d.ts +13 -0
- package/dist/opportunity/opportunity.graph.d.ts.map +1 -1
- package/dist/opportunity/opportunity.graph.js +5 -1
- package/dist/opportunity/opportunity.graph.js.map +1 -1
- package/dist/opportunity/opportunity.state.d.ts +3 -0
- package/dist/opportunity/opportunity.state.d.ts.map +1 -1
- package/dist/opportunity/opportunity.state.js.map +1 -1
- package/dist/shared/agent/model.config.d.ts +5 -0
- package/dist/shared/agent/model.config.d.ts.map +1 -1
- package/dist/shared/agent/model.config.js +1 -0
- package/dist/shared/agent/model.config.js.map +1 -1
- package/dist/shared/hyde/hyde.documents.d.ts +10 -0
- package/dist/shared/hyde/hyde.documents.d.ts.map +1 -0
- package/dist/shared/hyde/hyde.documents.js +50 -0
- package/dist/shared/hyde/hyde.documents.js.map +1 -0
- package/dist/shared/hyde/hyde.env.d.ts +9 -0
- package/dist/shared/hyde/hyde.env.d.ts.map +1 -0
- package/dist/shared/hyde/hyde.env.js +10 -0
- package/dist/shared/hyde/hyde.env.js.map +1 -0
- package/dist/shared/hyde/hyde.frame.d.ts +151 -0
- package/dist/shared/hyde/hyde.frame.d.ts.map +1 -0
- package/dist/shared/hyde/hyde.frame.js +108 -0
- package/dist/shared/hyde/hyde.frame.js.map +1 -0
- package/dist/shared/hyde/hyde.generator.d.ts +23 -11
- package/dist/shared/hyde/hyde.generator.d.ts.map +1 -1
- package/dist/shared/hyde/hyde.generator.js +50 -21
- package/dist/shared/hyde/hyde.generator.js.map +1 -1
- package/dist/shared/hyde/hyde.graph.d.ts +58 -17
- package/dist/shared/hyde/hyde.graph.d.ts.map +1 -1
- package/dist/shared/hyde/hyde.graph.js +333 -89
- package/dist/shared/hyde/hyde.graph.js.map +1 -1
- package/dist/shared/hyde/hyde.state.d.ts +19 -2
- package/dist/shared/hyde/hyde.state.d.ts.map +1 -1
- package/dist/shared/hyde/hyde.state.js +23 -3
- package/dist/shared/hyde/hyde.state.js.map +1 -1
- package/dist/shared/hyde/hyde.validator.d.ts +79 -0
- package/dist/shared/hyde/hyde.validator.d.ts.map +1 -0
- package/dist/shared/hyde/hyde.validator.js +49 -0
- package/dist/shared/hyde/hyde.validator.js.map +1 -0
- package/dist/shared/hyde/lens.inferrer.d.ts +190 -12
- package/dist/shared/hyde/lens.inferrer.d.ts.map +1 -1
- package/dist/shared/hyde/lens.inferrer.js +93 -38
- package/dist/shared/hyde/lens.inferrer.js.map +1 -1
- package/dist/shared/schemas/question.schema.d.ts +283 -2
- package/dist/shared/schemas/question.schema.d.ts.map +1 -1
- package/dist/shared/schemas/question.schema.js +87 -0
- package/dist/shared/schemas/question.schema.js.map +1 -1
- package/package.json +2 -1
|
@@ -1,18 +1,27 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* HyDE Graph: cache-aware hypothetical document generation with lens inference.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Legacy flow: infer_lenses → check_cache → generate_missing? → embed → cache_results.
|
|
5
|
+
* Frame-v1 flow adds one batch validate_generated node before embed.
|
|
6
6
|
*/
|
|
7
|
-
import { StateGraph, START, END } from '@langchain/langgraph';
|
|
8
7
|
import { createHash } from 'crypto';
|
|
8
|
+
import { END, START, StateGraph } from '@langchain/langgraph';
|
|
9
9
|
import { getAbortSignalConfig } from '../agent/model-signal.js';
|
|
10
|
-
import { HydeGraphState } from './hyde.state.js';
|
|
11
|
-
import { HYDE_DEFAULT_CACHE_TTL } from './hyde.strategies.js';
|
|
12
10
|
import { protocolLogger } from '../observability/protocol.logger.js';
|
|
13
11
|
import { timed } from '../observability/performance.js';
|
|
14
12
|
import { requestContext } from "../observability/request-context.js";
|
|
13
|
+
import { computeHydeSourceTextHash } from './hyde.documents.js';
|
|
14
|
+
import { getHydeGenerationMode, HYDE_FRAME_GENERATION_VERSION } from './hyde.env.js';
|
|
15
|
+
import { sanitizeHydeSourceFrame } from './hyde.frame.js';
|
|
16
|
+
import { HydeGraphState } from './hyde.state.js';
|
|
17
|
+
import { HYDE_DEFAULT_CACHE_TTL } from './hyde.strategies.js';
|
|
18
|
+
import { HydeValidator } from './hyde.validator.js';
|
|
15
19
|
const logger = protocolLogger("HyDEGraphFactory");
|
|
20
|
+
let lastGenerationTimestamp = 0;
|
|
21
|
+
function nextGenerationMarker() {
|
|
22
|
+
lastGenerationTimestamp = Math.max(Date.now(), lastGenerationTimestamp + 1);
|
|
23
|
+
return new Date(lastGenerationTimestamp).toISOString();
|
|
24
|
+
}
|
|
16
25
|
/** Hash a lens label (+ optional corpus) to a short key for cache/DB indexing. */
|
|
17
26
|
function lensHash(label, corpus) {
|
|
18
27
|
const input = corpus
|
|
@@ -20,149 +29,367 @@ function lensHash(label, corpus) {
|
|
|
20
29
|
: label.toLowerCase().trim();
|
|
21
30
|
return createHash('sha256').update(input).digest('hex').slice(0, 16);
|
|
22
31
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const entityKey = sourceId ?? `q:${createHash('sha256').update(sourceText).digest('hex').slice(0, 16)}`;
|
|
26
|
-
return `hyde:${sourceType}:${entityKey}:${lensHash(lens, corpus)}`;
|
|
32
|
+
function entityCacheKey(sourceId, sourceText) {
|
|
33
|
+
return sourceId ?? `q:${createHash('sha256').update(sourceText).digest('hex').slice(0, 16)}`;
|
|
27
34
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
35
|
+
function sortedFrame(frame) {
|
|
36
|
+
const sort = (items) => [...items].sort((left, right) => {
|
|
37
|
+
const leftJson = JSON.stringify(left);
|
|
38
|
+
const rightJson = JSON.stringify(right);
|
|
39
|
+
return leftJson < rightJson ? -1 : leftJson > rightJson ? 1 : 0;
|
|
40
|
+
});
|
|
41
|
+
return {
|
|
42
|
+
sourceRoles: sort(frame.sourceRoles),
|
|
43
|
+
counterpartRoles: sort(frame.counterpartRoles),
|
|
44
|
+
hardConstraints: sort(frame.hardConstraints),
|
|
45
|
+
namedEntities: sort(frame.namedEntities),
|
|
46
|
+
domainVocabulary: sort(frame.domainVocabulary),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/** Deterministic identity for the source content and sanitized frame. */
|
|
50
|
+
function computeHydeFrameFingerprint(sourceText, sourceFrame) {
|
|
51
|
+
return createHash('sha256')
|
|
52
|
+
.update(sourceText)
|
|
53
|
+
.update('\0')
|
|
54
|
+
.update(JSON.stringify(sortedFrame(sourceFrame)))
|
|
55
|
+
.digest('hex');
|
|
56
|
+
}
|
|
57
|
+
function requireFrameFingerprint(frameFingerprint) {
|
|
58
|
+
if (!frameFingerprint)
|
|
59
|
+
throw new Error('frame-v1 HyDE requires a frame fingerprint');
|
|
60
|
+
return frameFingerprint;
|
|
61
|
+
}
|
|
62
|
+
/** Preserve the exact legacy Redis key and isolate frame-v1 data by namespace. */
|
|
63
|
+
function cacheKey(mode, sourceType, sourceId, sourceText, lens, corpus, frameFingerprint) {
|
|
64
|
+
const entityKey = entityCacheKey(sourceId, sourceText);
|
|
65
|
+
if (mode === 'legacy')
|
|
66
|
+
return `hyde:${sourceType}:${entityKey}:${lensHash(lens, corpus)}`;
|
|
67
|
+
return `hyde:${HYDE_FRAME_GENERATION_VERSION}:${sourceType}:${entityKey}:${requireFrameFingerprint(frameFingerprint)}:${lensHash(lens, corpus)}`;
|
|
68
|
+
}
|
|
69
|
+
/** Preserve legacy identity and use a stable frame-v1 identity per lens/corpus. */
|
|
70
|
+
function dbStrategy(mode, label, corpus) {
|
|
71
|
+
const hash = lensHash(label, corpus);
|
|
72
|
+
return mode === 'legacy' ? hash : `${HYDE_FRAME_GENERATION_VERSION}:${hash}`;
|
|
73
|
+
}
|
|
74
|
+
function isValidGenerationMarker(value) {
|
|
75
|
+
return typeof value === 'string' && Number.isFinite(Date.parse(value));
|
|
76
|
+
}
|
|
77
|
+
function isFrameCacheDocument(doc, lensLabel, frameFingerprint, sourceTextHash) {
|
|
78
|
+
return doc.hydeGenerationVersion === HYDE_FRAME_GENERATION_VERSION
|
|
79
|
+
&& doc.validationStatus === 'valid'
|
|
80
|
+
&& doc.lens === lensLabel
|
|
81
|
+
&& doc.frameFingerprint === frameFingerprint
|
|
82
|
+
&& doc.sourceTextHash === sourceTextHash
|
|
83
|
+
&& isValidGenerationMarker(doc.generatedAt);
|
|
84
|
+
}
|
|
85
|
+
function isFrameDbContext(context, lensLabel, frameFingerprint, sourceTextHash) {
|
|
86
|
+
return context?.hydeGenerationVersion === HYDE_FRAME_GENERATION_VERSION
|
|
87
|
+
&& context.lensLabel === lensLabel
|
|
88
|
+
&& context.validationStatus === 'valid'
|
|
89
|
+
&& context.frameFingerprint === frameFingerprint
|
|
90
|
+
&& context.sourceTextHash === sourceTextHash
|
|
91
|
+
&& isValidGenerationMarker(context.generatedAt);
|
|
92
|
+
}
|
|
93
|
+
function emptyFrame() {
|
|
94
|
+
return {
|
|
95
|
+
sourceRoles: [],
|
|
96
|
+
counterpartRoles: [],
|
|
97
|
+
hardConstraints: [],
|
|
98
|
+
namedEntities: [],
|
|
99
|
+
domainVocabulary: [],
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
function opaqueDocumentKey(doc) {
|
|
103
|
+
return `d-${createHash('sha256')
|
|
104
|
+
.update(`${doc.lens}\0${doc.targetCorpus}\0${doc.hydeText}`)
|
|
105
|
+
.digest('hex')
|
|
106
|
+
.slice(0, 16)}`;
|
|
107
|
+
}
|
|
108
|
+
function isRuntimeVerdict(value) {
|
|
109
|
+
if (!value || typeof value !== 'object')
|
|
110
|
+
return false;
|
|
111
|
+
const verdict = value;
|
|
112
|
+
return typeof verdict.key === 'string'
|
|
113
|
+
&& typeof verdict.valid === 'boolean'
|
|
114
|
+
&& Array.isArray(verdict.unsupportedNamedEntities)
|
|
115
|
+
&& verdict.unsupportedNamedEntities.every((item) => typeof item === 'string')
|
|
116
|
+
&& Array.isArray(verdict.unsupportedHardConstraints)
|
|
117
|
+
&& verdict.unsupportedHardConstraints.every((item) => typeof item === 'string')
|
|
118
|
+
&& typeof verdict.reasoning === 'string';
|
|
119
|
+
}
|
|
120
|
+
/** Factory for the HyDE generation graph. Existing five-argument calls remain valid. */
|
|
32
121
|
export class HydeGraphFactory {
|
|
33
|
-
constructor(database, embedder, cache, inferrer, generator) {
|
|
122
|
+
constructor(database, embedder, cache, inferrer, generator, options = {}) {
|
|
34
123
|
this.database = database;
|
|
35
124
|
this.embedder = embedder;
|
|
36
125
|
this.cache = cache;
|
|
37
126
|
this.inferrer = inferrer;
|
|
38
127
|
this.generator = generator;
|
|
128
|
+
this.options = options;
|
|
39
129
|
}
|
|
40
130
|
createGraph() {
|
|
41
131
|
const self = this;
|
|
132
|
+
const mode = this.options.mode ?? getHydeGenerationMode();
|
|
133
|
+
const validator = mode === HYDE_FRAME_GENERATION_VERSION
|
|
134
|
+
? (this.options.validator ?? new HydeValidator())
|
|
135
|
+
: undefined;
|
|
42
136
|
/** Node 1: Infer lenses from source text + optional profile context. */
|
|
43
137
|
const inferLensesNode = async (state) => {
|
|
44
138
|
return timed("HydeGraph.inferLenses", async () => {
|
|
45
139
|
const { sourceText, profileContext, maxLenses } = state;
|
|
46
|
-
logger.verbose('Inferring lenses', { sourceTextLength: sourceText.length, hasProfileContext: !!profileContext });
|
|
47
140
|
const agentTimingsAccum = [];
|
|
48
141
|
try {
|
|
49
|
-
const
|
|
142
|
+
const traceEmitter = requestContext.getStore()?.traceEmitter;
|
|
50
143
|
const inferrerStart = Date.now();
|
|
51
|
-
|
|
144
|
+
traceEmitter?.({ type: "agent_start", name: "lens-inferrer" });
|
|
52
145
|
const result = await self.inferrer.infer({
|
|
53
146
|
sourceText,
|
|
54
147
|
profileContext,
|
|
55
148
|
maxLenses,
|
|
149
|
+
...(mode === HYDE_FRAME_GENERATION_VERSION ? { frameConstrained: true } : {}),
|
|
56
150
|
});
|
|
57
|
-
const
|
|
58
|
-
agentTimingsAccum.push({ name: 'lens.inferrer', durationMs
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
151
|
+
const durationMs = Date.now() - inferrerStart;
|
|
152
|
+
agentTimingsAccum.push({ name: 'lens.inferrer', durationMs });
|
|
153
|
+
traceEmitter?.({ type: "agent_end", name: "lens-inferrer", durationMs, summary: result.lenses.length > 0 ? `Inferred ${result.lenses.length} lens(es)` : "lens-inferrer completed" });
|
|
154
|
+
if (mode === HYDE_FRAME_GENERATION_VERSION) {
|
|
155
|
+
const sourceFrame = sanitizeHydeSourceFrame(sourceText, result.sourceFrame ?? emptyFrame());
|
|
156
|
+
return {
|
|
157
|
+
lenses: result.lenses,
|
|
158
|
+
sourceFrame,
|
|
159
|
+
frameFingerprint: computeHydeFrameFingerprint(sourceText, sourceFrame),
|
|
160
|
+
sourceTextHash: computeHydeSourceTextHash(sourceText),
|
|
161
|
+
generatedAt: nextGenerationMarker(),
|
|
162
|
+
agentTimings: agentTimingsAccum,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
64
165
|
return { lenses: result.lenses, agentTimings: agentTimingsAccum };
|
|
65
166
|
}
|
|
66
167
|
catch (error) {
|
|
67
168
|
logger.error('Lens inference failed in graph node', { error });
|
|
169
|
+
if (mode === HYDE_FRAME_GENERATION_VERSION) {
|
|
170
|
+
const sourceFrame = emptyFrame();
|
|
171
|
+
return {
|
|
172
|
+
lenses: [],
|
|
173
|
+
sourceFrame,
|
|
174
|
+
frameFingerprint: computeHydeFrameFingerprint(sourceText, sourceFrame),
|
|
175
|
+
sourceTextHash: computeHydeSourceTextHash(sourceText),
|
|
176
|
+
generatedAt: nextGenerationMarker(),
|
|
177
|
+
agentTimings: agentTimingsAccum,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
68
180
|
return { lenses: [], agentTimings: agentTimingsAccum };
|
|
69
181
|
}
|
|
70
182
|
});
|
|
71
183
|
};
|
|
72
|
-
/** Node 2: Check cache/DB for
|
|
184
|
+
/** Node 2: Check the mode-isolated cache/DB for matching documents. */
|
|
73
185
|
const checkCacheNode = async (state) => {
|
|
74
186
|
return timed("HydeGraph.checkCache", async () => {
|
|
75
187
|
const { sourceType, sourceId, sourceText, lenses, forceRegenerate } = state;
|
|
76
|
-
if (forceRegenerate)
|
|
77
|
-
logger.verbose('Force regenerate - skipping cache');
|
|
188
|
+
if (forceRegenerate)
|
|
78
189
|
return { hydeDocuments: {} };
|
|
79
|
-
|
|
190
|
+
const frameFingerprint = mode === HYDE_FRAME_GENERATION_VERSION
|
|
191
|
+
? requireFrameFingerprint(state.frameFingerprint)
|
|
192
|
+
: undefined;
|
|
193
|
+
const sourceTextHash = mode === HYDE_FRAME_GENERATION_VERSION
|
|
194
|
+
? state.sourceTextHash ?? computeHydeSourceTextHash(sourceText)
|
|
195
|
+
: undefined;
|
|
80
196
|
const cached = {};
|
|
81
197
|
for (const lens of lenses) {
|
|
82
|
-
const key = cacheKey(sourceType, sourceId ?? undefined, sourceText, lens.label, lens.corpus);
|
|
198
|
+
const key = cacheKey(mode, sourceType, sourceId ?? undefined, sourceText, lens.label, lens.corpus, frameFingerprint);
|
|
83
199
|
const fromCache = await self.cache.get(key);
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
200
|
+
const cacheAccepted = fromCache?.hydeText
|
|
201
|
+
&& fromCache.hydeEmbedding?.length
|
|
202
|
+
&& (mode === 'legacy' || isFrameCacheDocument(fromCache, lens.label, frameFingerprint, sourceTextHash));
|
|
203
|
+
if (cacheAccepted && fromCache) {
|
|
204
|
+
cached[lens.label] = {
|
|
205
|
+
...fromCache,
|
|
206
|
+
...(mode === HYDE_FRAME_GENERATION_VERSION ? { origin: 'cache' } : {}),
|
|
207
|
+
};
|
|
87
208
|
continue;
|
|
88
209
|
}
|
|
89
|
-
// For entity sources, check DB for persisted docs
|
|
90
210
|
if (sourceId) {
|
|
91
|
-
const fromDb = await self.database.getHydeDocument(sourceType, sourceId,
|
|
92
|
-
|
|
93
|
-
|
|
211
|
+
const fromDb = await self.database.getHydeDocument(sourceType, sourceId, dbStrategy(mode, lens.label, lens.corpus));
|
|
212
|
+
const frameDbContext = mode === HYDE_FRAME_GENERATION_VERSION && fromDb
|
|
213
|
+
? fromDb.context
|
|
214
|
+
: null;
|
|
215
|
+
if (fromDb && (mode === 'legacy' || isFrameDbContext(frameDbContext, lens.label, frameFingerprint, sourceTextHash))) {
|
|
94
216
|
cached[lens.label] = {
|
|
95
217
|
lens: lens.label,
|
|
96
218
|
targetCorpus: fromDb.targetCorpus,
|
|
97
219
|
hydeText: fromDb.hydeText,
|
|
98
220
|
hydeEmbedding: fromDb.hydeEmbedding,
|
|
221
|
+
...(mode === HYDE_FRAME_GENERATION_VERSION
|
|
222
|
+
? {
|
|
223
|
+
origin: 'db',
|
|
224
|
+
validationStatus: 'valid',
|
|
225
|
+
hydeGenerationVersion: HYDE_FRAME_GENERATION_VERSION,
|
|
226
|
+
frameFingerprint,
|
|
227
|
+
sourceTextHash,
|
|
228
|
+
generatedAt: frameDbContext.generatedAt,
|
|
229
|
+
}
|
|
230
|
+
: {}),
|
|
99
231
|
};
|
|
100
232
|
}
|
|
101
233
|
}
|
|
102
234
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
235
|
+
if (mode === HYDE_FRAME_GENERATION_VERSION) {
|
|
236
|
+
const newestTimestamp = Math.max(...Object.values(cached).map((doc) => Date.parse(doc.generatedAt ?? '')));
|
|
237
|
+
if (Number.isFinite(newestTimestamp)) {
|
|
238
|
+
return {
|
|
239
|
+
hydeDocuments: Object.fromEntries(Object.entries(cached).filter(([, doc]) => Date.parse(doc.generatedAt ?? '') === newestTimestamp)),
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
}
|
|
107
243
|
return { hydeDocuments: cached };
|
|
108
244
|
});
|
|
109
245
|
};
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
const { lenses, hydeDocuments } = state;
|
|
113
|
-
const missing = lenses.filter((l) => !hydeDocuments[l.label]);
|
|
114
|
-
if (missing.length > 0) {
|
|
115
|
-
logger.verbose('Need to generate', { missing: missing.map(l => l.label) });
|
|
116
|
-
return 'generate';
|
|
117
|
-
}
|
|
118
|
-
logger.verbose('All lenses cached, skipping generation');
|
|
119
|
-
return 'skip';
|
|
120
|
-
};
|
|
121
|
-
/** Node 3: Generate HyDE documents for lenses not in cache. */
|
|
246
|
+
const shouldGenerate = (state) => state.lenses.some((lens) => !state.hydeDocuments[lens.label]) ? 'generate' : 'skip';
|
|
247
|
+
/** Node 3: Generate all missing documents and return a complete snapshot. */
|
|
122
248
|
const generateMissingNode = async (state) => {
|
|
123
249
|
return timed("HydeGraph.generateMissing", async () => {
|
|
124
|
-
const { sourceText, lenses, hydeDocuments } = state;
|
|
125
|
-
const missing = lenses.filter((
|
|
126
|
-
logger.verbose('Generating HyDE documents', {
|
|
127
|
-
count: missing.length,
|
|
128
|
-
lenses: missing.map(l => l.label),
|
|
129
|
-
});
|
|
250
|
+
const { sourceText, sourceFrame, lenses, hydeDocuments } = state;
|
|
251
|
+
const missing = lenses.filter((lens) => !hydeDocuments[lens.label]);
|
|
130
252
|
const agentTimingsAccum = [];
|
|
131
253
|
const generated = {};
|
|
254
|
+
const sourceTextHash = mode === HYDE_FRAME_GENERATION_VERSION
|
|
255
|
+
? state.sourceTextHash ?? computeHydeSourceTextHash(sourceText)
|
|
256
|
+
: undefined;
|
|
257
|
+
const generatedAt = mode === HYDE_FRAME_GENERATION_VERSION
|
|
258
|
+
? state.generatedAt ?? nextGenerationMarker()
|
|
259
|
+
: undefined;
|
|
132
260
|
await Promise.all(missing.map(async (lens) => {
|
|
133
|
-
const
|
|
261
|
+
const traceEmitter = requestContext.getStore()?.traceEmitter;
|
|
134
262
|
const generatorStart = Date.now();
|
|
135
|
-
|
|
263
|
+
traceEmitter?.({ type: "agent_start", name: "hyde-generator" });
|
|
136
264
|
const out = await self.generator.generate({
|
|
137
265
|
sourceText,
|
|
138
266
|
lens: lens.label,
|
|
139
267
|
corpus: lens.corpus,
|
|
268
|
+
...(mode === HYDE_FRAME_GENERATION_VERSION && sourceFrame ? { sourceFrame } : {}),
|
|
140
269
|
});
|
|
141
|
-
const
|
|
142
|
-
agentTimingsAccum.push({ name: 'hyde.generator', durationMs
|
|
143
|
-
|
|
270
|
+
const durationMs = Date.now() - generatorStart;
|
|
271
|
+
agentTimingsAccum.push({ name: 'hyde.generator', durationMs });
|
|
272
|
+
traceEmitter?.({ type: "agent_end", name: "hyde-generator", durationMs, summary: `Generated: ${lens.label}` });
|
|
144
273
|
generated[lens.label] = {
|
|
145
274
|
lens: lens.label,
|
|
146
275
|
targetCorpus: lens.corpus,
|
|
147
276
|
hydeText: out.text,
|
|
148
277
|
hydeEmbedding: [],
|
|
278
|
+
...(mode === HYDE_FRAME_GENERATION_VERSION
|
|
279
|
+
? {
|
|
280
|
+
origin: 'generated',
|
|
281
|
+
frameFingerprint: requireFrameFingerprint(state.frameFingerprint),
|
|
282
|
+
sourceTextHash,
|
|
283
|
+
generatedAt,
|
|
284
|
+
}
|
|
285
|
+
: {}),
|
|
149
286
|
};
|
|
150
287
|
}));
|
|
151
|
-
|
|
288
|
+
const retained = mode === HYDE_FRAME_GENERATION_VERSION
|
|
289
|
+
? Object.fromEntries(Object.entries(hydeDocuments).map(([label, doc]) => [
|
|
290
|
+
label,
|
|
291
|
+
{
|
|
292
|
+
...doc,
|
|
293
|
+
frameFingerprint: requireFrameFingerprint(state.frameFingerprint),
|
|
294
|
+
sourceTextHash,
|
|
295
|
+
generatedAt,
|
|
296
|
+
},
|
|
297
|
+
]))
|
|
298
|
+
: hydeDocuments;
|
|
299
|
+
return { hydeDocuments: { ...retained, ...generated }, agentTimings: agentTimingsAccum };
|
|
152
300
|
});
|
|
153
301
|
};
|
|
154
|
-
/**
|
|
302
|
+
/** Frame-v1 only: validate newly generated docs in one batch. */
|
|
303
|
+
const validateGeneratedNode = async (state) => {
|
|
304
|
+
return timed("HydeGraph.validateGenerated", async () => {
|
|
305
|
+
const generated = Object.values(state.hydeDocuments).filter((doc) => doc.origin === 'generated');
|
|
306
|
+
if (generated.length === 0 || !validator)
|
|
307
|
+
return { hydeDocuments: state.hydeDocuments };
|
|
308
|
+
const frame = sanitizeHydeSourceFrame(state.sourceText, state.sourceFrame ?? emptyFrame());
|
|
309
|
+
const documents = {};
|
|
310
|
+
const lensByDocumentKey = new Map();
|
|
311
|
+
for (const doc of generated) {
|
|
312
|
+
const key = opaqueDocumentKey(doc);
|
|
313
|
+
documents[key] = { corpus: doc.targetCorpus, text: doc.hydeText };
|
|
314
|
+
lensByDocumentKey.set(key, doc.lens);
|
|
315
|
+
}
|
|
316
|
+
const updated = { ...state.hydeDocuments };
|
|
317
|
+
const agentTimingsAccum = [];
|
|
318
|
+
const traceEmitter = requestContext.getStore()?.traceEmitter;
|
|
319
|
+
const validatorStart = Date.now();
|
|
320
|
+
let validCount = 0;
|
|
321
|
+
let rejectedCount = 0;
|
|
322
|
+
let failedOpenCount = 0;
|
|
323
|
+
traceEmitter?.({ type: 'agent_start', name: 'hyde-validator' });
|
|
324
|
+
try {
|
|
325
|
+
const output = await validator.validate({
|
|
326
|
+
sourceText: state.sourceText,
|
|
327
|
+
sourceFrame: frame,
|
|
328
|
+
documents,
|
|
329
|
+
});
|
|
330
|
+
const rawVerdicts = Array.isArray(output.verdicts)
|
|
331
|
+
? output.verdicts
|
|
332
|
+
: [];
|
|
333
|
+
for (const key of Object.keys(documents)) {
|
|
334
|
+
const lensLabel = lensByDocumentKey.get(key);
|
|
335
|
+
if (!lensLabel)
|
|
336
|
+
continue;
|
|
337
|
+
const matching = rawVerdicts.filter((value) => !!value && typeof value === 'object' && value.key === key);
|
|
338
|
+
const doc = updated[lensLabel];
|
|
339
|
+
if (!doc)
|
|
340
|
+
continue;
|
|
341
|
+
if (matching.length !== 1 || !isRuntimeVerdict(matching[0])) {
|
|
342
|
+
failedOpenCount += 1;
|
|
343
|
+
updated[lensLabel] = { ...doc, validationStatus: 'failed_open', hydeGenerationVersion: HYDE_FRAME_GENERATION_VERSION };
|
|
344
|
+
continue;
|
|
345
|
+
}
|
|
346
|
+
const verdict = matching[0];
|
|
347
|
+
const hasUnsupportedGrounding = verdict.unsupportedNamedEntities.length > 0
|
|
348
|
+
|| verdict.unsupportedHardConstraints.length > 0;
|
|
349
|
+
const contradictory = verdict.valid === hasUnsupportedGrounding;
|
|
350
|
+
if (contradictory) {
|
|
351
|
+
failedOpenCount += 1;
|
|
352
|
+
updated[lensLabel] = { ...doc, validationStatus: 'failed_open', hydeGenerationVersion: HYDE_FRAME_GENERATION_VERSION };
|
|
353
|
+
}
|
|
354
|
+
else if (!verdict.valid) {
|
|
355
|
+
rejectedCount += 1;
|
|
356
|
+
delete updated[lensLabel];
|
|
357
|
+
}
|
|
358
|
+
else {
|
|
359
|
+
validCount += 1;
|
|
360
|
+
updated[lensLabel] = { ...doc, validationStatus: 'valid', hydeGenerationVersion: HYDE_FRAME_GENERATION_VERSION };
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
catch (error) {
|
|
365
|
+
logger.error('HyDE validation failed open', { error });
|
|
366
|
+
validCount = 0;
|
|
367
|
+
rejectedCount = 0;
|
|
368
|
+
failedOpenCount = generated.length;
|
|
369
|
+
for (const doc of generated) {
|
|
370
|
+
updated[doc.lens] = { ...doc, validationStatus: 'failed_open', hydeGenerationVersion: HYDE_FRAME_GENERATION_VERSION };
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
finally {
|
|
374
|
+
const durationMs = Date.now() - validatorStart;
|
|
375
|
+
agentTimingsAccum.push({ name: 'hyde.validator', durationMs });
|
|
376
|
+
traceEmitter?.({
|
|
377
|
+
type: 'agent_end',
|
|
378
|
+
name: 'hyde-validator',
|
|
379
|
+
durationMs,
|
|
380
|
+
summary: `${validCount} valid, ${rejectedCount} rejected, ${failedOpenCount} failed open`,
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
return { hydeDocuments: updated, agentTimings: agentTimingsAccum };
|
|
384
|
+
});
|
|
385
|
+
};
|
|
386
|
+
/** Embed all accepted/cached documents that do not have embeddings. */
|
|
155
387
|
const embedNode = async (state) => {
|
|
156
388
|
return timed("HydeGraph.embed", async () => {
|
|
157
|
-
const { hydeDocuments } = state;
|
|
158
|
-
const lensLabels = Object.keys(hydeDocuments);
|
|
159
389
|
const toEmbed = [];
|
|
160
390
|
const updated = {};
|
|
161
391
|
const hydeEmbeddings = {};
|
|
162
|
-
for (const label of
|
|
163
|
-
const doc = hydeDocuments[label];
|
|
164
|
-
if (!doc)
|
|
165
|
-
continue;
|
|
392
|
+
for (const [label, doc] of Object.entries(state.hydeDocuments)) {
|
|
166
393
|
if (doc.hydeEmbedding?.length) {
|
|
167
394
|
updated[label] = doc;
|
|
168
395
|
hydeEmbeddings[label] = doc.hydeEmbedding;
|
|
@@ -172,12 +399,8 @@ export class HydeGraphFactory {
|
|
|
172
399
|
}
|
|
173
400
|
}
|
|
174
401
|
if (toEmbed.length > 0) {
|
|
175
|
-
|
|
176
|
-
const
|
|
177
|
-
const embeddings = await self.embedder.generate(texts, undefined, getAbortSignalConfig());
|
|
178
|
-
const embeddingArray = Array.isArray(embeddings[0])
|
|
179
|
-
? embeddings
|
|
180
|
-
: [embeddings];
|
|
402
|
+
const embeddings = await self.embedder.generate(toEmbed.map((item) => item.doc.hydeText), undefined, getAbortSignalConfig());
|
|
403
|
+
const embeddingArray = Array.isArray(embeddings[0]) ? embeddings : [embeddings];
|
|
181
404
|
for (let i = 0; i < toEmbed.length; i++) {
|
|
182
405
|
const { label, doc } = toEmbed[i];
|
|
183
406
|
const embedding = embeddingArray[i] ?? [];
|
|
@@ -188,31 +411,43 @@ export class HydeGraphFactory {
|
|
|
188
411
|
return { hydeDocuments: updated, hydeEmbeddings };
|
|
189
412
|
});
|
|
190
413
|
};
|
|
191
|
-
/**
|
|
414
|
+
/** Cache/persist only legacy docs or successfully validated frame-v1 docs. */
|
|
192
415
|
const cacheResultsNode = async (state) => {
|
|
193
416
|
return timed("HydeGraph.cacheResults", async () => {
|
|
194
417
|
const { sourceType, sourceId, sourceText, hydeDocuments } = state;
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
418
|
+
const frameFingerprint = mode === HYDE_FRAME_GENERATION_VERSION
|
|
419
|
+
? requireFrameFingerprint(state.frameFingerprint)
|
|
420
|
+
: undefined;
|
|
421
|
+
const sourceTextHash = mode === HYDE_FRAME_GENERATION_VERSION
|
|
422
|
+
? state.sourceTextHash ?? computeHydeSourceTextHash(sourceText)
|
|
423
|
+
: undefined;
|
|
424
|
+
for (const [label, doc] of Object.entries(hydeDocuments)) {
|
|
425
|
+
if (mode === HYDE_FRAME_GENERATION_VERSION
|
|
426
|
+
&& !isFrameCacheDocument(doc, label, frameFingerprint, sourceTextHash))
|
|
198
427
|
continue;
|
|
199
|
-
const key = cacheKey(sourceType, sourceId ?? undefined, sourceText, label, doc.targetCorpus);
|
|
428
|
+
const key = cacheKey(mode, sourceType, sourceId ?? undefined, sourceText, label, doc.targetCorpus, frameFingerprint);
|
|
200
429
|
await self.cache.set(key, doc, { ttl: HYDE_DEFAULT_CACHE_TTL });
|
|
201
|
-
// Persist to DB for entity sources (intent/profile)
|
|
202
430
|
if (sourceId) {
|
|
203
431
|
await self.database.saveHydeDocument({
|
|
204
432
|
sourceType,
|
|
205
433
|
sourceId,
|
|
206
|
-
strategy:
|
|
434
|
+
strategy: dbStrategy(mode, label, doc.targetCorpus),
|
|
207
435
|
targetCorpus: doc.targetCorpus,
|
|
208
436
|
hydeText: doc.hydeText,
|
|
209
437
|
hydeEmbedding: doc.hydeEmbedding,
|
|
438
|
+
...(mode === HYDE_FRAME_GENERATION_VERSION ? {
|
|
439
|
+
context: {
|
|
440
|
+
hydeGenerationVersion: HYDE_FRAME_GENERATION_VERSION,
|
|
441
|
+
lensLabel: label,
|
|
442
|
+
validationStatus: 'valid',
|
|
443
|
+
frameFingerprint: doc.frameFingerprint,
|
|
444
|
+
sourceTextHash: doc.sourceTextHash,
|
|
445
|
+
generatedAt: doc.generatedAt,
|
|
446
|
+
},
|
|
447
|
+
} : {}),
|
|
210
448
|
});
|
|
211
449
|
}
|
|
212
450
|
}
|
|
213
|
-
logger.verbose('Cached results', {
|
|
214
|
-
count: Object.keys(hydeDocuments).length,
|
|
215
|
-
});
|
|
216
451
|
return {};
|
|
217
452
|
});
|
|
218
453
|
};
|
|
@@ -227,8 +462,17 @@ export class HydeGraphFactory {
|
|
|
227
462
|
.addConditionalEdges('check_cache', shouldGenerate, {
|
|
228
463
|
generate: 'generate_missing',
|
|
229
464
|
skip: 'embed',
|
|
230
|
-
})
|
|
231
|
-
|
|
465
|
+
});
|
|
466
|
+
if (mode === HYDE_FRAME_GENERATION_VERSION) {
|
|
467
|
+
workflow
|
|
468
|
+
.addNode('validate_generated', validateGeneratedNode)
|
|
469
|
+
.addEdge('generate_missing', 'validate_generated')
|
|
470
|
+
.addEdge('validate_generated', 'embed');
|
|
471
|
+
}
|
|
472
|
+
else {
|
|
473
|
+
workflow.addEdge('generate_missing', 'embed');
|
|
474
|
+
}
|
|
475
|
+
workflow
|
|
232
476
|
.addEdge('embed', 'cache_results')
|
|
233
477
|
.addEdge('cache_results', END);
|
|
234
478
|
return workflow.compile();
|