noteconnection 1.8.0 → 1.9.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 +257 -1803
- package/dist/src/agent_workspace.contract.parity.test.js +105 -421
- package/dist/src/agent_workspace.frontend.test.js +410 -11
- package/dist/src/android.knowledgebase.picker.contract.test.js +102 -0
- package/dist/src/appimage.patchelf.test.js +113 -0
- package/dist/src/appimage.portability.test.js +156 -0
- package/dist/src/backend/FileLoader.js +18 -4
- package/dist/src/backend/FileLoader.test.js +125 -0
- package/dist/src/backend/GraphBuilder.identity.test.js +86 -0
- package/dist/src/backend/GraphBuilder.js +66 -18
- package/dist/src/backend/GraphBuilder.matching.test.js +127 -0
- package/dist/src/backend/GraphMetrics.js +0 -3
- package/dist/src/backend/ResourceIdentity.js +155 -0
- package/dist/src/backend/ResourceIdentity.test.js +113 -0
- package/dist/src/backend/algorithms/CycleDetection.js +0 -1
- package/dist/src/backend/algorithms/HybridEngine.js +0 -3
- package/dist/src/backend/algorithms/LayoutEngine.js +0 -1
- package/dist/src/backend/algorithms/StatisticalAnalyzer.js +0 -4
- package/dist/src/backend/algorithms/TopologicalSort.js +0 -2
- package/dist/src/backend/build_dag.js +0 -1
- package/dist/src/capacitor.bridge.serialization.contract.test.js +3 -0
- package/dist/src/capacitor.runtime.contract.test.js +11 -2
- package/dist/src/core/Graph.js +138 -42
- package/dist/src/core/Graph.test.js +74 -0
- package/dist/src/core/NoteConnection.js +2 -2
- package/dist/src/core/PathBridge.js +265 -2
- package/dist/src/core/ResourceReference.js +17 -0
- package/dist/src/desktop.godot.pack.test.js +142 -0
- package/dist/src/desktop.runtime.graph.test.js +66 -0
- package/dist/src/export/WorkspaceExportBundle.js +44 -0
- package/dist/src/export/WorkspaceExportBundle.test.js +61 -0
- package/dist/src/foundation.release.evidence.contract.test.js +127 -9
- package/dist/src/foundation.sqlite.runtime.contract.test.js +6 -0
- package/dist/src/frontend/README.md +257 -1803
- package/dist/src/frontend/agent_workspace.js +665 -7
- package/dist/src/frontend/index.html +31 -2
- package/dist/src/frontend/knowledge_projection_contract.js +230 -0
- package/dist/src/frontend/knowledge_projection_store.js +214 -0
- package/dist/src/frontend/libs/path_core.js +158 -42
- package/dist/src/frontend/locales/en.json +32 -0
- package/dist/src/frontend/locales/zh.json +32 -0
- package/dist/src/frontend/mobile_budget_runtime.js +33 -0
- package/dist/src/frontend/mobile_exact_analyzer.js +393 -0
- package/dist/src/frontend/mobile_identity_contract.js +221 -0
- package/dist/src/frontend/mobile_semantic_comparator.js +264 -0
- package/dist/src/frontend/path.html +6 -2
- package/dist/src/frontend/simulationWorker.js +1 -1
- package/dist/src/frontend/source_manager.js +110 -4
- package/dist/src/frontend/storage_provider.js +661 -71
- package/dist/src/frontend/styles.css +107 -13
- package/dist/src/frontend/workspace_panes.js +111 -7
- package/dist/src/frontend.runtime.assets.contract.test.js +81 -0
- package/dist/src/graph.answer.operator.contract.test.js +72 -0
- package/dist/src/index.js +6 -2
- package/dist/src/index.knowledge-root.test.js +118 -0
- package/dist/src/indexing/IndexLifecycle.js +22 -0
- package/dist/src/indexing/IndexLifecycle.test.js +24 -0
- package/dist/src/knowledge.api.contract.test.js +4 -2
- package/dist/src/knowledge_projection.contract.test.js +134 -0
- package/dist/src/knowledge_projection_store.test.js +200 -0
- package/dist/src/learning/AnswerQualityEvaluation.js +153 -0
- package/dist/src/learning/AnswerQualityEvaluation.test.js +118 -0
- package/dist/src/learning/KnowledgeLearningPlatform.js +4687 -3892
- package/dist/src/learning/KnowledgeLearningPlatform.persistence.test.js +418 -0
- package/dist/src/learning/KnowledgeLearningPlatform.test.js +243 -6
- package/dist/src/learning/KnowledgeWorkspaceConversationRegression.js +25 -1
- package/dist/src/learning/KnowledgeWorkspaceConversationRegression.test.js +13 -5
- package/dist/src/learning/MeasurementEvidenceConflicts.test.js +78 -0
- package/dist/src/learning/PublicAnswerContract.test.js +85 -0
- package/dist/src/learning/PublicEvidenceSelection.test.js +94 -0
- package/dist/src/learning/PublicMeasurementDisclosure.test.js +54 -0
- package/dist/src/learning/agentConversationExecution.js +211 -0
- package/dist/src/learning/agentConversationExecution.test.js +209 -0
- package/dist/src/learning/agentConversationSerialization.js +266 -0
- package/dist/src/learning/agentConversationSerialization.test.js +165 -0
- package/dist/src/learning/agentResponseBudget.js +179 -0
- package/dist/src/learning/agentResponseBudget.test.js +97 -0
- package/dist/src/learning/agentWorkspaceCapabilityContracts.js +65 -0
- package/dist/src/learning/answerReleaseReview.js +1191 -250
- package/dist/src/learning/answerReleaseReview.test.js +1293 -7
- package/dist/src/learning/answerTaskPlan.js +473 -0
- package/dist/src/learning/conversationComposer.js +627 -386
- package/dist/src/learning/conversationComposer.test.js +1403 -0
- package/dist/src/learning/evidenceContextAssembler.js +312 -120
- package/dist/src/learning/graphAnswerCoverage.js +37 -0
- package/dist/src/learning/graphAnswerCoverage.test.js +93 -0
- package/dist/src/learning/graphAnswerCoverageCalibration.js +59 -0
- package/dist/src/learning/graphAnswerCoverageCalibration.test.js +46 -0
- package/dist/src/learning/graphAnswerFacts.js +94 -0
- package/dist/src/learning/graphAnswerFacts.test.js +39 -0
- package/dist/src/learning/graphAnswerPlan.js +908 -0
- package/dist/src/learning/graphAnswerPlan.test.js +907 -0
- package/dist/src/learning/graphAnswerQualityCalibration.js +255 -0
- package/dist/src/learning/graphAnswerQualityCalibration.test.js +72 -0
- package/dist/src/learning/graphAnswerQualityPolicy.js +30 -0
- package/dist/src/learning/graphClaimMatcher.js +104 -0
- package/dist/src/learning/graphConditionedContext.js +109 -0
- package/dist/src/learning/graphConditionedContext.test.js +99 -0
- package/dist/src/learning/graphContextAssembler.js +3 -13
- package/dist/src/learning/graphContextAssembler.test.js +2 -2
- package/dist/src/learning/graphExpansionPolicy.js +22 -0
- package/dist/src/learning/graphExpansionPolicy.test.js +33 -0
- package/dist/src/learning/identityMetadata.test.js +26 -0
- package/dist/src/learning/index.js +1 -0
- package/dist/src/learning/mobileAnswerProjection.js +104 -0
- package/dist/src/learning/mobileAnswerProjection.test.js +102 -0
- package/dist/src/learning/queryBackend.js +34 -10
- package/dist/src/learning/queryBackend.test.js +21 -0
- package/dist/src/learning/ragContextPack.js +115 -16
- package/dist/src/learning/ragContextPack.test.js +151 -0
- package/dist/src/learning/ragEvidenceQuality.js +96 -0
- package/dist/src/learning/ragEvidenceQuality.test.js +29 -0
- package/dist/src/learning/ragPublicText.js +67 -4
- package/dist/src/learning/ragPublicText.test.js +57 -0
- package/dist/src/learning/ragSufficiencyJudge.js +11 -1
- package/dist/src/learning/ragSufficiencyProviderJudge.js +16 -26
- package/dist/src/learning/ragSufficiencyProviderJudge.test.js +51 -0
- package/dist/src/learning/requestNormalization.js +106 -0
- package/dist/src/learning/requestNormalization.test.js +101 -0
- package/dist/src/learning/store.js +116 -12
- package/dist/src/learning/store.test.js +299 -0
- package/dist/src/learning/vectorAccelerationAdapter.js +22 -6
- package/dist/src/learning/vectorAccelerationAdapter.test.js +30 -0
- package/dist/src/learning/workspaceHydration.js +168 -0
- package/dist/src/learning/workspaceHydration.test.js +30 -0
- package/dist/src/license.policy.contract.test.js +9 -4
- package/dist/src/markdown/MarkdownGateway.js +4 -0
- package/dist/src/markdown/MarkdownGateway.test.js +59 -0
- package/dist/src/middleware/SseResponseWriter.js +122 -0
- package/dist/src/middleware/SseResponseWriter.test.js +54 -0
- package/dist/src/middleware/auth.js +28 -7
- package/dist/src/middleware/auth.test.js +33 -0
- package/dist/src/middleware/index.js +2 -1
- package/dist/src/mobile.android.evidence.contract.test.js +146 -0
- package/dist/src/mobile.android.signing.contract.test.js +66 -0
- package/dist/src/mobile.artifact.contract.test.js +144 -0
- package/dist/src/mobile.budget.contract.test.js +90 -0
- package/dist/src/mobile.canonical-id.readiness.test.js +54 -0
- package/dist/src/mobile.graph.parity.contract.test.js +207 -0
- package/dist/src/mobile.identity.contract.test.js +66 -0
- package/dist/src/mobile.identity.corpus.manifest.test.js +72 -0
- package/dist/src/mobile.identity.corpus.test.js +40 -0
- package/dist/src/mobile.native.recovery.contract.test.js +64 -0
- package/dist/src/mobile.pipeline.test.js +11 -1
- package/dist/src/mobile.profile.contract.test.js +225 -0
- package/dist/src/mobile.semantic.parity.test.js +92 -0
- package/dist/src/mobile_exact_analyzer.test.js +113 -0
- package/dist/src/notemd/ContentGenerator.js +4 -9
- package/dist/src/notemd/FileProcessor.js +4 -9
- package/dist/src/notemd/NotemdService.js +12 -17
- package/dist/src/notemd/Translator.js +4 -9
- package/dist/src/notemd/progressReporter.js +9 -0
- package/dist/src/notemd.server.integration.test.js +119 -4
- package/dist/src/notemd.workflow.pipeline.test.js +1 -1
- package/dist/src/pathbridge.handshake.contract.test.js +29 -0
- package/dist/src/pathbridge.host-adapter.contract.test.js +133 -0
- package/dist/src/pathcore.bundle.contract.test.js +8 -0
- package/dist/src/platform/ExportProfile.js +28 -0
- package/dist/src/platform/MobileBudget.js +11 -0
- package/dist/src/platform/PlatformCapabilities.js +14 -0
- package/dist/src/platform/PlatformCapabilities.test.js +6 -0
- package/dist/src/query_backend.external_http.integration.test.js +2 -2
- package/dist/src/release.godot.mirror.contract.test.js +28 -4
- package/dist/src/resources/ResourceRegistry.js +40 -0
- package/dist/src/resources/ResourceRegistry.test.js +36 -0
- package/dist/src/routes/index.js +0 -4
- package/dist/src/routes/knowledge.js +46 -33
- package/dist/src/routes/knowledgePayload.js +185 -0
- package/dist/src/routes/knowledgePayload.test.js +53 -0
- package/dist/src/routes/markdown.js +8 -0
- package/dist/src/routes/notemd.js +990 -234
- package/dist/src/routes/registry.contract.test.js +5 -2
- package/dist/src/routes/registry.shadow.contract.test.js +22 -0
- package/dist/src/routes/render.js +14 -51
- package/dist/src/runtime.capabilities.test.js +18 -2
- package/dist/src/server.js +566 -1090
- package/dist/src/server.migration.test.js +417 -15
- package/dist/src/sidecar.build.lock.contract.test.js +201 -0
- package/dist/src/sidecar.freshness.contract.test.js +184 -0
- package/dist/src/sidecar.replacement.boundary.contract.test.js +6 -3
- package/dist/src/sidecar.supply.readiness.contract.test.js +11 -0
- package/dist/src/simulation.worker.offline.test.js +104 -0
- package/dist/src/storage.provider.capacitor.content.contract.test.js +4 -1
- package/dist/src/storage.provider.contract.test.js +152 -1
- package/dist/src/tauri.frontend.build.contract.test.js +8 -0
- package/dist/src/tauri.window.evidence.execution.test.js +68 -0
- package/dist/src/utils/RuntimePaths.js +6 -0
- package/dist/src/utils/RuntimePaths.test.js +27 -0
- package/dist/src/windows.installer.build.test.js +109 -0
- package/dist/src/windows.installer.payload.test.js +76 -0
- package/dist/src/windows.installer.runtime.test.js +69 -0
- package/dist/src/workspace/WorkspaceRegistry.js +13 -0
- package/package.json +32 -10
- package/dist/src/routes/data.js +0 -267
- package/dist/src/routes/diagnostics.js +0 -51
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const graphConditionedContext_1 = require("./graphConditionedContext");
|
|
4
|
+
function makeFragment(overrides = {}) {
|
|
5
|
+
return {
|
|
6
|
+
fragmentId: 'fragment-default',
|
|
7
|
+
role: 'background',
|
|
8
|
+
text: 'evidence',
|
|
9
|
+
documentId: 'document-1',
|
|
10
|
+
sourcePath: 'notes/topic.md',
|
|
11
|
+
headingPath: [],
|
|
12
|
+
charCount: 8,
|
|
13
|
+
tokenEstimate: 2,
|
|
14
|
+
truncated: false,
|
|
15
|
+
citationIds: [],
|
|
16
|
+
relationEdgeIds: [],
|
|
17
|
+
score: 0.5,
|
|
18
|
+
sourceBoundary: 'direct_span_only',
|
|
19
|
+
...overrides,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
function makePlan() {
|
|
23
|
+
return {
|
|
24
|
+
intent: 'causal',
|
|
25
|
+
depth: 'standard',
|
|
26
|
+
anchorAtomId: 'atom-anchor',
|
|
27
|
+
leadClaimId: 'claim-required',
|
|
28
|
+
claims: [{
|
|
29
|
+
claimId: 'claim-required',
|
|
30
|
+
role: 'mechanism',
|
|
31
|
+
required: true,
|
|
32
|
+
priority: 100,
|
|
33
|
+
statement: 'The planned mechanism is supported by the graph.',
|
|
34
|
+
subjectAtomId: 'atom-anchor',
|
|
35
|
+
supportingAtomIds: ['atom-support'],
|
|
36
|
+
supportingEdgeIds: ['edge-support'],
|
|
37
|
+
evidenceRefs: [{
|
|
38
|
+
evidenceId: 'evidence-1',
|
|
39
|
+
atomId: 'atom-support',
|
|
40
|
+
sourcePath: 'notes/topic.md',
|
|
41
|
+
citationIds: ['citation-1'],
|
|
42
|
+
text: 'evidence',
|
|
43
|
+
}],
|
|
44
|
+
confidence: 0.95,
|
|
45
|
+
}],
|
|
46
|
+
requiredRoles: ['mechanism'],
|
|
47
|
+
omittedCandidates: [],
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
describe('conditionRagFragmentsByGraphPlan', () => {
|
|
51
|
+
test('prefers fragments matched by required graph atoms and relation edges', () => {
|
|
52
|
+
const fragments = [
|
|
53
|
+
makeFragment({
|
|
54
|
+
fragmentId: 'neighbor-unmatched',
|
|
55
|
+
role: 'graph_neighbor_support',
|
|
56
|
+
atomId: 'atom-other',
|
|
57
|
+
relationEdgeIds: ['edge-other'],
|
|
58
|
+
}),
|
|
59
|
+
makeFragment({
|
|
60
|
+
fragmentId: 'neighbor-matched',
|
|
61
|
+
role: 'graph_neighbor_support',
|
|
62
|
+
atomId: 'atom-support',
|
|
63
|
+
relationEdgeIds: ['edge-support'],
|
|
64
|
+
}),
|
|
65
|
+
makeFragment({
|
|
66
|
+
fragmentId: 'anchor-direct',
|
|
67
|
+
role: 'direct_support',
|
|
68
|
+
atomId: 'atom-anchor',
|
|
69
|
+
}),
|
|
70
|
+
];
|
|
71
|
+
const result = (0, graphConditionedContext_1.conditionRagFragmentsByGraphPlan)({
|
|
72
|
+
fragments,
|
|
73
|
+
graphAnswerPlan: makePlan(),
|
|
74
|
+
});
|
|
75
|
+
expect(result.fragmentOrder.indexOf('neighbor-matched'))
|
|
76
|
+
.toBeLessThan(result.fragmentOrder.indexOf('neighbor-unmatched'));
|
|
77
|
+
expect(result.trace.strategy).toBe('graph_answer_plan');
|
|
78
|
+
expect(result.trace.matchedClaimCount).toBe(1);
|
|
79
|
+
expect(result.trace.matchedFragmentCount).toBe(2);
|
|
80
|
+
expect(result.trace.selectedAtomIds).toEqual(['atom-anchor', 'atom-support']);
|
|
81
|
+
expect(result.trace.selectedEdgeIds).toEqual(['edge-support']);
|
|
82
|
+
});
|
|
83
|
+
test('keeps input order and exposes an explicit fallback when no plan exists', () => {
|
|
84
|
+
const fragments = [
|
|
85
|
+
makeFragment({ fragmentId: 'second' }),
|
|
86
|
+
makeFragment({ fragmentId: 'first' }),
|
|
87
|
+
];
|
|
88
|
+
const result = (0, graphConditionedContext_1.conditionRagFragmentsByGraphPlan)({ fragments });
|
|
89
|
+
expect(result.fragmentOrder).toEqual(['second', 'first']);
|
|
90
|
+
expect(result.trace).toEqual({
|
|
91
|
+
strategy: 'none',
|
|
92
|
+
matchedClaimCount: 0,
|
|
93
|
+
matchedFragmentCount: 0,
|
|
94
|
+
selectedAtomIds: [],
|
|
95
|
+
selectedEdgeIds: [],
|
|
96
|
+
fallbackReason: 'no_graph_answer_plan',
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
});
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.assembleAgentConversationGraphContext = assembleAgentConversationGraphContext;
|
|
4
4
|
exports.buildAgentConversationGraphContextFromKnowledgePoints = buildAgentConversationGraphContextFromKnowledgePoints;
|
|
5
5
|
const store_1 = require("./store");
|
|
6
|
+
const answerTaskPlan_1 = require("./answerTaskPlan");
|
|
6
7
|
function normalizeGraphComparableTitle(value) {
|
|
7
8
|
return normalizeWhitespace(String(value || '').toLowerCase());
|
|
8
9
|
}
|
|
@@ -105,21 +106,10 @@ function classifyConversationIntent(message) {
|
|
|
105
106
|
if (!normalized) {
|
|
106
107
|
return 'generic';
|
|
107
108
|
}
|
|
108
|
-
if (
|
|
109
|
-
|| normalized.includes('difference')
|
|
110
|
-
|| normalized.includes('vs')
|
|
111
|
-
|| normalized.includes('区分')
|
|
112
|
-
|| normalized.includes('对比')) {
|
|
109
|
+
if ((0, answerTaskPlan_1.isComparisonRequest)(normalized)) {
|
|
113
110
|
return 'compare';
|
|
114
111
|
}
|
|
115
|
-
if (
|
|
116
|
-
|| normalized.includes('how do')
|
|
117
|
-
|| normalized.includes('steps')
|
|
118
|
-
|| normalized.includes('plan')
|
|
119
|
-
|| normalized.includes('如何')
|
|
120
|
-
|| normalized.includes('怎么')
|
|
121
|
-
|| normalized.includes('步骤')
|
|
122
|
-
|| normalized.includes('方案')) {
|
|
112
|
+
if ((0, answerTaskPlan_1.isProcedureRequest)(normalized)) {
|
|
123
113
|
return 'how_to';
|
|
124
114
|
}
|
|
125
115
|
if (normalized.includes('what is')
|
|
@@ -722,7 +722,7 @@ describe('assembleAgentConversationGraphContext', () => {
|
|
|
722
722
|
outDegree: 2,
|
|
723
723
|
}));
|
|
724
724
|
});
|
|
725
|
-
test('prioritizes contrast successors over procedural sequence nodes
|
|
725
|
+
test.each(['compare water glass and plastic cup', '比较 water glass 和 plastic cup'])('prioritizes contrast successors over procedural sequence nodes: %s', async (message) => {
|
|
726
726
|
const atoms = [
|
|
727
727
|
createAtom({
|
|
728
728
|
id: 'atom_water_glass',
|
|
@@ -779,7 +779,7 @@ describe('assembleAgentConversationGraphContext', () => {
|
|
|
779
779
|
}),
|
|
780
780
|
];
|
|
781
781
|
const result = await (0, graphContextAssembler_1.assembleAgentConversationGraphContext)({
|
|
782
|
-
message
|
|
782
|
+
message,
|
|
783
783
|
usedScope: globalScope,
|
|
784
784
|
knowledgePoints,
|
|
785
785
|
store: new InMemoryOpsStore(atoms, edges),
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolveGraphExpansionPolicy = resolveGraphExpansionPolicy;
|
|
4
|
+
const EXPLICIT_DEPTH_PATTERN = /\b(?:deep|detailed|comprehensive|thorough|in-depth|in depth|in detail|fully|complete|research)\b|深入|深度|详细|详尽|完整|充分|展开|研究/u;
|
|
5
|
+
function resolveGraphExpansionPolicy(message) {
|
|
6
|
+
const explicitDepthRequested = EXPLICIT_DEPTH_PATTERN.test(String(message || '').toLowerCase());
|
|
7
|
+
return explicitDepthRequested
|
|
8
|
+
? {
|
|
9
|
+
enabled: true,
|
|
10
|
+
reason: 'explicit_depth_request',
|
|
11
|
+
maxSteps: 1,
|
|
12
|
+
maxNeighbors: 8,
|
|
13
|
+
maxPathDepth: 8,
|
|
14
|
+
}
|
|
15
|
+
: {
|
|
16
|
+
enabled: false,
|
|
17
|
+
reason: 'ordinary_query',
|
|
18
|
+
maxSteps: 0,
|
|
19
|
+
maxNeighbors: 6,
|
|
20
|
+
maxPathDepth: 6,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const graphExpansionPolicy_1 = require("./graphExpansionPolicy");
|
|
4
|
+
describe('resolveGraphExpansionPolicy', () => {
|
|
5
|
+
test.each([
|
|
6
|
+
'what is water glass',
|
|
7
|
+
'explain water glass',
|
|
8
|
+
'为什么杯壁会传热',
|
|
9
|
+
])('keeps ordinary questions on deterministic bounded retrieval: %s', (message) => {
|
|
10
|
+
expect((0, graphExpansionPolicy_1.resolveGraphExpansionPolicy)(message)).toEqual({
|
|
11
|
+
enabled: false,
|
|
12
|
+
reason: 'ordinary_query',
|
|
13
|
+
maxSteps: 0,
|
|
14
|
+
maxNeighbors: 6,
|
|
15
|
+
maxPathDepth: 6,
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
test.each([
|
|
19
|
+
'explain water glass in detail',
|
|
20
|
+
'perform a deep analysis of water glass',
|
|
21
|
+
'research the complete thermal mechanism',
|
|
22
|
+
'详细解释水杯的热交换机制',
|
|
23
|
+
'深入研究杯壁与环境的关系',
|
|
24
|
+
])('enables one replayable bounded expansion for explicit depth: %s', (message) => {
|
|
25
|
+
expect((0, graphExpansionPolicy_1.resolveGraphExpansionPolicy)(message)).toEqual({
|
|
26
|
+
enabled: true,
|
|
27
|
+
reason: 'explicit_depth_request',
|
|
28
|
+
maxSteps: 1,
|
|
29
|
+
maxNeighbors: 8,
|
|
30
|
+
maxPathDepth: 8,
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const KnowledgeLearningPlatform_1 = require("./KnowledgeLearningPlatform");
|
|
4
|
+
describe('knowledge document identity metadata', () => {
|
|
5
|
+
test('deletes a document through its portable source URI after ingest', async () => {
|
|
6
|
+
const platform = new KnowledgeLearningPlatform_1.KnowledgeLearningPlatform(() => new Date('2026-08-17T00:00:00.000Z'));
|
|
7
|
+
await platform.ingestKnowledge({
|
|
8
|
+
incremental: true,
|
|
9
|
+
documents: [{
|
|
10
|
+
documentId: 'legacy_doc',
|
|
11
|
+
sourcePath: 'Knowledge_Base/notes/identity.md',
|
|
12
|
+
sourceUri: 'note://workspace/v1/notes/identity.md',
|
|
13
|
+
revision: 'sha256:identity-revision',
|
|
14
|
+
identityAliases: ['identity', 'notes/identity.md'],
|
|
15
|
+
content: '# Identity\nPortable identity metadata stays additive.',
|
|
16
|
+
}],
|
|
17
|
+
});
|
|
18
|
+
const deleted = await platform.ingestKnowledge({
|
|
19
|
+
incremental: true,
|
|
20
|
+
deletedDocuments: [{ sourceUri: 'note://workspace/v1/notes/identity.md' }],
|
|
21
|
+
});
|
|
22
|
+
expect(deleted.summary.deletedDocuments).toBe(1);
|
|
23
|
+
expect(deleted.staleness[0]?.documentId).toBe('legacy_doc');
|
|
24
|
+
expect(deleted.staleness[0]?.status).toBe('deleted');
|
|
25
|
+
});
|
|
26
|
+
});
|
|
@@ -23,6 +23,7 @@ __exportStar(require("./queryBackend"), exports);
|
|
|
23
23
|
__exportStar(require("./vectorAccelerationAdapter"), exports);
|
|
24
24
|
__exportStar(require("./runtimeCapability"), exports);
|
|
25
25
|
__exportStar(require("./conversationComposer"), exports);
|
|
26
|
+
__exportStar(require("./agentConversationSerialization"), exports);
|
|
26
27
|
__exportStar(require("./ragSufficiencyProviderJudge"), exports);
|
|
27
28
|
__exportStar(require("./errors"), exports);
|
|
28
29
|
__exportStar(require("./domains"), exports);
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MOBILE_ANSWER_PROJECTION_SCHEMA_VERSION = void 0;
|
|
4
|
+
exports.projectAnswerForMobile = projectAnswerForMobile;
|
|
5
|
+
exports.MOBILE_ANSWER_PROJECTION_SCHEMA_VERSION = 1;
|
|
6
|
+
const MAX_MOBILE_DIRECT_ANSWER_CHARS = 2400;
|
|
7
|
+
const MAX_MOBILE_ROUTE_NODES = 8;
|
|
8
|
+
const MAX_MOBILE_CITATIONS = 4;
|
|
9
|
+
const MAX_MOBILE_TITLE_CHARS = 160;
|
|
10
|
+
const MAX_MOBILE_SOURCE_PATH_CHARS = 320;
|
|
11
|
+
function normalize(value) {
|
|
12
|
+
return String(value || '').replace(/\s+/gu, ' ').trim();
|
|
13
|
+
}
|
|
14
|
+
function normalizeMarkdown(value) {
|
|
15
|
+
return String(value || '').replace(/\r\n?/gu, '\n').trim();
|
|
16
|
+
}
|
|
17
|
+
function trimText(value, limit) {
|
|
18
|
+
const normalized = normalize(value);
|
|
19
|
+
return normalized.length <= limit ? normalized : `${normalized.slice(0, Math.max(0, limit - 1)).trim()}…`;
|
|
20
|
+
}
|
|
21
|
+
function countDisplayMathDelimiters(value) {
|
|
22
|
+
return (value.match(/(?<!\\)\$\$/gu) || []).length;
|
|
23
|
+
}
|
|
24
|
+
function trimMarkdown(value, limit) {
|
|
25
|
+
const normalized = normalizeMarkdown(value);
|
|
26
|
+
if (normalized.length <= limit) {
|
|
27
|
+
return normalized;
|
|
28
|
+
}
|
|
29
|
+
const suffix = '…';
|
|
30
|
+
let candidate = normalized.slice(0, Math.max(0, limit - suffix.length)).trimEnd();
|
|
31
|
+
// Do not publish an answer with an unmatched display-math fence. If a
|
|
32
|
+
// block crosses the mobile byte/character budget, omit that incomplete
|
|
33
|
+
// block rather than asking every client to repair malformed Markdown.
|
|
34
|
+
if (countDisplayMathDelimiters(candidate) % 2 !== 0) {
|
|
35
|
+
const openingIndex = candidate.lastIndexOf('$$');
|
|
36
|
+
candidate = openingIndex >= 0 ? candidate.slice(0, openingIndex).trimEnd() : '';
|
|
37
|
+
}
|
|
38
|
+
return `${candidate}${suffix}`.trim();
|
|
39
|
+
}
|
|
40
|
+
function stripRouteFromAnswer(answer) {
|
|
41
|
+
const normalized = normalizeMarkdown(answer);
|
|
42
|
+
const headingIndex = normalized.search(/(?:^|\n)#{1,6}\s+(?:建议学习路径|推荐学习路径|Suggested learning path)\s*$/imu);
|
|
43
|
+
const withoutHeadingRoute = headingIndex >= 0
|
|
44
|
+
? normalized.slice(0, headingIndex).trimEnd()
|
|
45
|
+
: normalized;
|
|
46
|
+
return withoutHeadingRoute
|
|
47
|
+
.replace(/(?:建议学习路径|推荐学习路径|Suggested learning path)\s*[::][^\n]*(?:\n[^\n]*)*$/iu, '')
|
|
48
|
+
.replace(/([。.!?])\1+/gu, '$1')
|
|
49
|
+
.trim();
|
|
50
|
+
}
|
|
51
|
+
function resolveTaskPlan(response) {
|
|
52
|
+
return response.answerTaskPlan
|
|
53
|
+
|| response.graphAnswerPlan?.answerTaskPlan
|
|
54
|
+
|| response.trace?.answerTaskPlan
|
|
55
|
+
|| response.knowledgeRun?.answerTaskPlan;
|
|
56
|
+
}
|
|
57
|
+
function projectCitation(citation) {
|
|
58
|
+
const citationId = normalize(citation.citationId);
|
|
59
|
+
const title = trimText(citation.title, MAX_MOBILE_TITLE_CHARS);
|
|
60
|
+
const sourcePath = trimText(citation.sourcePath, MAX_MOBILE_SOURCE_PATH_CHARS);
|
|
61
|
+
if (!citationId || !title || !sourcePath) {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
citationId,
|
|
66
|
+
title,
|
|
67
|
+
sourcePath,
|
|
68
|
+
...(Number.isFinite(Number(citation.startLine)) ? { startLine: Number(citation.startLine) } : {}),
|
|
69
|
+
...(Number.isFinite(Number(citation.endLine)) ? { endLine: Number(citation.endLine) } : {}),
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
function projectAnswerForMobile(response) {
|
|
73
|
+
const taskPlan = resolveTaskPlan(response);
|
|
74
|
+
const structuredAnswerBlock = (response.assistantBlocks || [])
|
|
75
|
+
.find((block) => block && block.type === 'structured_answer');
|
|
76
|
+
const directAnswer = trimMarkdown(stripRouteFromAnswer(String(response.answer
|
|
77
|
+
|| response.assistantMessage
|
|
78
|
+
|| (structuredAnswerBlock && 'directAnswer' in structuredAnswerBlock
|
|
79
|
+
? structuredAnswerBlock.directAnswer
|
|
80
|
+
: '')
|
|
81
|
+
|| '')), MAX_MOBILE_DIRECT_ANSWER_CHARS);
|
|
82
|
+
const route = (taskPlan?.learningRoute || [])
|
|
83
|
+
.slice()
|
|
84
|
+
.sort((left, right) => left.order - right.order)
|
|
85
|
+
.slice(0, MAX_MOBILE_ROUTE_NODES)
|
|
86
|
+
.map((node) => ({
|
|
87
|
+
nodeId: trimText(node.nodeId, MAX_MOBILE_TITLE_CHARS),
|
|
88
|
+
title: trimText(node.title, MAX_MOBILE_TITLE_CHARS),
|
|
89
|
+
role: node.role,
|
|
90
|
+
order: Math.max(1, Math.floor(Number(node.order) || 1)),
|
|
91
|
+
}))
|
|
92
|
+
.filter((node) => Boolean(node.nodeId && node.title));
|
|
93
|
+
const citations = (response.citations || [])
|
|
94
|
+
.slice(0, MAX_MOBILE_CITATIONS)
|
|
95
|
+
.map(projectCitation)
|
|
96
|
+
.filter((citation) => Boolean(citation));
|
|
97
|
+
return {
|
|
98
|
+
schemaVersion: exports.MOBILE_ANSWER_PROJECTION_SCHEMA_VERSION,
|
|
99
|
+
primarySubject: trimText(taskPlan?.primarySubject || '', MAX_MOBILE_TITLE_CHARS),
|
|
100
|
+
directAnswer,
|
|
101
|
+
route,
|
|
102
|
+
citations,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const mobileAnswerProjection_1 = require("./mobileAnswerProjection");
|
|
4
|
+
describe('mobile answer projection', () => {
|
|
5
|
+
test('keeps a bounded task result and drops internal RAG payloads', () => {
|
|
6
|
+
const response = {
|
|
7
|
+
answer: '非晶冰是水的一种缺乏长程有序的固态形式。建议学习路径:1. 水分子与氢键网络 -> 2. 非晶冰 -> 3. 径向分布函数 g(r)。',
|
|
8
|
+
assistantMessage: 'same',
|
|
9
|
+
answerTaskPlan: {
|
|
10
|
+
schemaVersion: '1',
|
|
11
|
+
primarySubject: '非晶冰',
|
|
12
|
+
requestedDepth: 'standard',
|
|
13
|
+
subtasks: [
|
|
14
|
+
{ subtaskId: 'definition', kind: 'definition', subject: '非晶冰', required: true, expectedOutput: 'direct_answer' },
|
|
15
|
+
{ subtaskId: 'learning_route', kind: 'learning_route', subject: '非晶冰', required: true, expectedOutput: 'ordered_nodes' },
|
|
16
|
+
],
|
|
17
|
+
learningRoute: [
|
|
18
|
+
{ nodeId: 'water', title: '水分子与氢键网络', role: 'prerequisite', order: 1, orderingBasis: 'source_order', evidenceRefs: ['e1'], reason: '基础' },
|
|
19
|
+
{ nodeId: 'ice', title: '非晶冰', role: 'core', order: 2, orderingBasis: 'source_order', evidenceRefs: ['e2'], reason: '核心' },
|
|
20
|
+
{ nodeId: 'rdf', title: '径向分布函数 g(r)', role: 'mechanism', order: 3, orderingBasis: 'source_order', evidenceRefs: ['e3'], reason: '机制' },
|
|
21
|
+
],
|
|
22
|
+
},
|
|
23
|
+
citations: [
|
|
24
|
+
{
|
|
25
|
+
citationId: 'e1', atomId: 'water', documentId: 'doc', sourcePath: 'Knowledge_Base/waterglass/water.md', title: '水分子', snippet: 'full source text should not be copied', startLine: 2, endLine: 3, score: 0.9,
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
trace: {
|
|
29
|
+
ragContextPack: { fragments: [{ text: 'large internal source body' }] },
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
const projection = (0, mobileAnswerProjection_1.projectAnswerForMobile)(response);
|
|
33
|
+
expect(projection).toEqual(expect.objectContaining({
|
|
34
|
+
schemaVersion: 1,
|
|
35
|
+
primarySubject: '非晶冰',
|
|
36
|
+
directAnswer: '非晶冰是水的一种缺乏长程有序的固态形式。',
|
|
37
|
+
route: [
|
|
38
|
+
expect.objectContaining({ title: '水分子与氢键网络', role: 'prerequisite', order: 1 }),
|
|
39
|
+
expect.objectContaining({ title: '非晶冰', role: 'core', order: 2 }),
|
|
40
|
+
expect.objectContaining({ title: '径向分布函数 g(r)', role: 'mechanism', order: 3 }),
|
|
41
|
+
],
|
|
42
|
+
citations: [expect.objectContaining({ title: '水分子', sourcePath: 'Knowledge_Base/waterglass/water.md' })],
|
|
43
|
+
}));
|
|
44
|
+
expect(JSON.stringify(projection)).not.toContain('large internal source body');
|
|
45
|
+
expect(JSON.stringify(projection)).not.toContain('full source text should not be copied');
|
|
46
|
+
});
|
|
47
|
+
test('preserves display-math Markdown while removing the appended learning route', () => {
|
|
48
|
+
const response = {
|
|
49
|
+
answer: [
|
|
50
|
+
'非晶冰是缺乏长程有序的水的固态形式。',
|
|
51
|
+
'',
|
|
52
|
+
'$$',
|
|
53
|
+
'g(r) = \\frac{1}{4\\pi r^2\\rho} \\frac{dN}{dr}',
|
|
54
|
+
'$$',
|
|
55
|
+
'',
|
|
56
|
+
'### 建议学习路径',
|
|
57
|
+
'',
|
|
58
|
+
'1. **水分子**(前置)',
|
|
59
|
+
].join('\n'),
|
|
60
|
+
assistantMessage: 'fallback',
|
|
61
|
+
citations: [],
|
|
62
|
+
answerTaskPlan: {
|
|
63
|
+
schemaVersion: '1',
|
|
64
|
+
primarySubject: '非晶冰',
|
|
65
|
+
requestedDepth: 'standard',
|
|
66
|
+
subtasks: [
|
|
67
|
+
{ subtaskId: 'definition', kind: 'definition', subject: '非晶冰', required: true, expectedOutput: 'direct_answer' },
|
|
68
|
+
{ subtaskId: 'learning_route', kind: 'learning_route', subject: '非晶冰', required: true, expectedOutput: 'ordered_nodes' },
|
|
69
|
+
],
|
|
70
|
+
learningRoute: [],
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
const projection = (0, mobileAnswerProjection_1.projectAnswerForMobile)(response);
|
|
74
|
+
expect(projection.directAnswer).toContain('$$\ng(r) = \\frac{1}{4\\pi r^2\\rho} \\frac{dN}{dr}\n$$');
|
|
75
|
+
expect(projection.directAnswer).not.toContain('建议学习路径');
|
|
76
|
+
expect((projection.directAnswer.match(/(?<!\\)\$\$/gu) || []).length % 2).toBe(0);
|
|
77
|
+
});
|
|
78
|
+
test('does not leave an unmatched display-math fence when the mobile budget truncates an answer', () => {
|
|
79
|
+
const longPrefix = '说明。'.repeat(500);
|
|
80
|
+
const response = {
|
|
81
|
+
answer: `${longPrefix}\n\n$$\nE = mc^2\n$$`,
|
|
82
|
+
assistantMessage: '',
|
|
83
|
+
citations: [],
|
|
84
|
+
};
|
|
85
|
+
const projection = (0, mobileAnswerProjection_1.projectAnswerForMobile)(response);
|
|
86
|
+
expect(projection.directAnswer.length).toBeLessThanOrEqual(2400);
|
|
87
|
+
expect((projection.directAnswer.match(/(?<!\\)\$\$/gu) || []).length % 2).toBe(0);
|
|
88
|
+
});
|
|
89
|
+
test('uses the response-level answer as the canonical release over a stale structured block', () => {
|
|
90
|
+
const response = {
|
|
91
|
+
answer: 'Canonical released answer for the subject.',
|
|
92
|
+
assistantMessage: 'Canonical released answer for the subject.',
|
|
93
|
+
assistantBlocks: [{
|
|
94
|
+
blockId: 'stale',
|
|
95
|
+
type: 'structured_answer',
|
|
96
|
+
directAnswer: 'Stale block answer that must not win.',
|
|
97
|
+
}],
|
|
98
|
+
citations: [],
|
|
99
|
+
};
|
|
100
|
+
expect((0, mobileAnswerProjection_1.projectAnswerForMobile)(response).directAnswer).toBe('Canonical released answer for the subject.');
|
|
101
|
+
});
|
|
102
|
+
});
|
|
@@ -42,6 +42,7 @@ exports.listGraphQueryBackendCatalog = listGraphQueryBackendCatalog;
|
|
|
42
42
|
const crypto_1 = require("crypto");
|
|
43
43
|
const fs = __importStar(require("fs"));
|
|
44
44
|
const path = __importStar(require("path"));
|
|
45
|
+
const graphClaimMatcher_1 = require("./graphClaimMatcher");
|
|
45
46
|
function evaluateAnnRunbookHealth(params) {
|
|
46
47
|
const checks = [];
|
|
47
48
|
const threshold = params.failureThreshold ?? 3;
|
|
@@ -295,7 +296,9 @@ function buildAtomSemanticTokens(atom) {
|
|
|
295
296
|
.slice(0, SEMANTIC_KEYWORD_TOKEN_LIMIT));
|
|
296
297
|
const titleTokens = extractSemanticTokens(atom.title, SEMANTIC_TITLE_TOKEN_LIMIT);
|
|
297
298
|
const contentTokens = extractSemanticTokens(atom.content, SEMANTIC_CONTENT_TOKEN_LIMIT);
|
|
298
|
-
|
|
299
|
+
const conceptTokens = (0, graphClaimMatcher_1.semanticFeatures)(`${atom.title} ${atom.content}`)
|
|
300
|
+
.filter((feature) => feature.startsWith('concept:'));
|
|
301
|
+
return uniqueTokens([...keywordTokens, ...titleTokens, ...contentTokens, ...conceptTokens]);
|
|
299
302
|
}
|
|
300
303
|
function buildQuerySemanticTokens(context) {
|
|
301
304
|
const directTokens = uniqueTokens((context.queryTokens || [])
|
|
@@ -303,7 +306,9 @@ function buildQuerySemanticTokens(context) {
|
|
|
303
306
|
.filter((token) => token.length > 0));
|
|
304
307
|
const variantTokens = buildQueryTextVariants(context).flatMap((variant) => (extractSemanticTokens(variant, SEMANTIC_QUERY_TOKEN_LIMIT)));
|
|
305
308
|
const fallbackTokens = extractSemanticTokens(context.query, SEMANTIC_QUERY_TOKEN_LIMIT);
|
|
306
|
-
|
|
309
|
+
const conceptTokens = (0, graphClaimMatcher_1.semanticFeatures)(buildQueryTextVariants(context).join(' '))
|
|
310
|
+
.filter((feature) => feature.startsWith('concept:'));
|
|
311
|
+
return uniqueTokens([...directTokens, ...variantTokens, ...fallbackTokens, ...conceptTokens]).slice(0, SEMANTIC_QUERY_TOKEN_LIMIT);
|
|
307
312
|
}
|
|
308
313
|
function computeJaccard(left, right) {
|
|
309
314
|
if (!left.length || !right.length) {
|
|
@@ -986,8 +991,10 @@ const DEFAULT_LOCAL_VECTOR_ACCELERATION_ADAPTER = {
|
|
|
986
991
|
async function selectLocalVectorAnnCandidates(params) {
|
|
987
992
|
const adapterId = String(params.accelerationAdapter?.id || '').trim() || LOCAL_VECTOR_ACCELERATION_ADAPTER_ID;
|
|
988
993
|
try {
|
|
994
|
+
params.signal?.throwIfAborted();
|
|
989
995
|
if (typeof params.accelerationAdapter.syncIndex === 'function') {
|
|
990
996
|
await params.accelerationAdapter.syncIndex({
|
|
997
|
+
signal: params.signal,
|
|
991
998
|
atomCount: params.index.atomCount,
|
|
992
999
|
tokenToAtomIds: params.index.tokenToAtomIds,
|
|
993
1000
|
signatureBuckets: params.index.signatureBuckets,
|
|
@@ -997,7 +1004,9 @@ async function selectLocalVectorAnnCandidates(params) {
|
|
|
997
1004
|
indexSignature: params.index.signature,
|
|
998
1005
|
});
|
|
999
1006
|
}
|
|
1007
|
+
params.signal?.throwIfAborted();
|
|
1000
1008
|
const rawSelection = await params.accelerationAdapter.selectCandidates({
|
|
1009
|
+
signal: params.signal,
|
|
1001
1010
|
atomCount: params.index.atomCount,
|
|
1002
1011
|
queryTokens: [...params.queryTokens],
|
|
1003
1012
|
queryWeights: params.queryWeights,
|
|
@@ -1010,6 +1019,7 @@ async function selectLocalVectorAnnCandidates(params) {
|
|
|
1010
1019
|
embeddingDimension: params.index.documentFrequency.size,
|
|
1011
1020
|
indexSignature: params.index.signature,
|
|
1012
1021
|
});
|
|
1022
|
+
params.signal?.throwIfAborted();
|
|
1013
1023
|
const mode = (rawSelection?.mode === 'token_prefilter'
|
|
1014
1024
|
|| rawSelection?.mode === 'token_signature_prefilter'
|
|
1015
1025
|
|| rawSelection?.mode === 'full_scan') ? rawSelection.mode : 'full_scan';
|
|
@@ -1029,6 +1039,7 @@ async function selectLocalVectorAnnCandidates(params) {
|
|
|
1029
1039
|
};
|
|
1030
1040
|
}
|
|
1031
1041
|
catch (error) {
|
|
1042
|
+
params.signal?.throwIfAborted();
|
|
1032
1043
|
const normalizedError = String(error?.message || error || 'unknown_error')
|
|
1033
1044
|
.replace(/\s+/g, ' ')
|
|
1034
1045
|
.trim()
|
|
@@ -1054,7 +1065,11 @@ class LocalHybridGraphQueryBackend {
|
|
|
1054
1065
|
const querySemanticTokens = buildQuerySemanticTokens(context);
|
|
1055
1066
|
const graphFeatureIndex = buildGraphFeatureIndex(context, querySemanticTokens);
|
|
1056
1067
|
const candidates = [];
|
|
1057
|
-
context.atoms.
|
|
1068
|
+
for (const [candidateIndex, atom] of context.atoms.entries()) {
|
|
1069
|
+
if (context.signal && candidateIndex % 128 === 0) {
|
|
1070
|
+
await new Promise(resolve => setImmediate(resolve));
|
|
1071
|
+
context.signal.throwIfAborted();
|
|
1072
|
+
}
|
|
1058
1073
|
const titleLower = atom.title.toLowerCase();
|
|
1059
1074
|
const contentLower = atom.content.toLowerCase();
|
|
1060
1075
|
const keywordMatches = context.queryTokens.filter((token) => atom.keywords.includes(token)).length;
|
|
@@ -1071,7 +1086,7 @@ class LocalHybridGraphQueryBackend {
|
|
|
1071
1086
|
score: Number(clamp(score, 0, 100).toFixed(4)),
|
|
1072
1087
|
});
|
|
1073
1088
|
}
|
|
1074
|
-
}
|
|
1089
|
+
}
|
|
1075
1090
|
candidates.sort((left, right) => right.score - left.score);
|
|
1076
1091
|
const maxCandidates = Math.max(context.topK, Math.min(context.atoms.length, context.topK * 4));
|
|
1077
1092
|
return {
|
|
@@ -1102,7 +1117,11 @@ class KeywordOnlyGraphQueryBackend {
|
|
|
1102
1117
|
async query(context) {
|
|
1103
1118
|
const queryVariants = buildQueryTextVariants(context);
|
|
1104
1119
|
const candidates = [];
|
|
1105
|
-
context.atoms.
|
|
1120
|
+
for (const [candidateIndex, atom] of context.atoms.entries()) {
|
|
1121
|
+
if (context.signal && candidateIndex % 128 === 0) {
|
|
1122
|
+
await new Promise(resolve => setImmediate(resolve));
|
|
1123
|
+
context.signal.throwIfAborted();
|
|
1124
|
+
}
|
|
1106
1125
|
const titleLower = atom.title.toLowerCase();
|
|
1107
1126
|
const contentLower = atom.content.toLowerCase();
|
|
1108
1127
|
const keywordMatches = context.queryTokens.filter((token) => atom.keywords.includes(token)).length;
|
|
@@ -1115,7 +1134,7 @@ class KeywordOnlyGraphQueryBackend {
|
|
|
1115
1134
|
score: Number(clamp(score, 0, 100).toFixed(4)),
|
|
1116
1135
|
});
|
|
1117
1136
|
}
|
|
1118
|
-
}
|
|
1137
|
+
}
|
|
1119
1138
|
candidates.sort((left, right) => right.score - left.score);
|
|
1120
1139
|
const maxCandidates = Math.max(context.topK, Math.min(context.atoms.length, context.topK * 4));
|
|
1121
1140
|
return {
|
|
@@ -1190,6 +1209,7 @@ class LocalVectorGraphQueryBackend {
|
|
|
1190
1209
|
let annCandidateSelection;
|
|
1191
1210
|
try {
|
|
1192
1211
|
annCandidateSelection = await selectLocalVectorAnnCandidates({
|
|
1212
|
+
signal: context.signal,
|
|
1193
1213
|
index,
|
|
1194
1214
|
queryTokens: querySemanticTokens,
|
|
1195
1215
|
queryWeights,
|
|
@@ -1265,10 +1285,14 @@ class LocalVectorGraphQueryBackend {
|
|
|
1265
1285
|
representationStrictMode: this.localVectorAccelerationRepresentationStrict,
|
|
1266
1286
|
};
|
|
1267
1287
|
const candidates = [];
|
|
1268
|
-
atomIdsToScore.
|
|
1288
|
+
for (const [candidateIndex, atomId] of atomIdsToScore.entries()) {
|
|
1289
|
+
if (context.signal && candidateIndex % 128 === 0) {
|
|
1290
|
+
await new Promise(resolve => setImmediate(resolve));
|
|
1291
|
+
context.signal.throwIfAborted();
|
|
1292
|
+
}
|
|
1269
1293
|
const entry = index.entriesByAtomId.get(atomId);
|
|
1270
1294
|
if (!entry) {
|
|
1271
|
-
|
|
1295
|
+
continue;
|
|
1272
1296
|
}
|
|
1273
1297
|
const cosineSimilarity = computeCosineSimilarity(queryWeights, entry.weights);
|
|
1274
1298
|
const semanticOverlap = computeJaccard(querySemanticTokens, entry.tokens);
|
|
@@ -1278,13 +1302,13 @@ class LocalVectorGraphQueryBackend {
|
|
|
1278
1302
|
: 0;
|
|
1279
1303
|
const score = (cosineSimilarity * 4.2) + (semanticOverlap * 1.1) + graphFeatureBonus + signatureBonus;
|
|
1280
1304
|
if (score <= 0) {
|
|
1281
|
-
|
|
1305
|
+
continue;
|
|
1282
1306
|
}
|
|
1283
1307
|
candidates.push({
|
|
1284
1308
|
atomId,
|
|
1285
1309
|
score: Number(clamp(score, 0, 100).toFixed(4)),
|
|
1286
1310
|
});
|
|
1287
|
-
}
|
|
1311
|
+
}
|
|
1288
1312
|
candidates.sort((left, right) => right.score - left.score);
|
|
1289
1313
|
const maxCandidates = Math.max(context.topK, Math.min(context.atoms.length, context.topK * 4));
|
|
1290
1314
|
return {
|
|
@@ -135,6 +135,27 @@ describe('query backend factory', () => {
|
|
|
135
135
|
expect(result.trace?.retrievalModes).toContain('semantic_similarity');
|
|
136
136
|
expect(result.trace?.modeWeights?.semantic).toBeGreaterThan(0);
|
|
137
137
|
});
|
|
138
|
+
test('local hybrid backend reuses multilingual graph concepts for comparison retrieval', async () => {
|
|
139
|
+
const backend = (0, queryBackend_1.createGraphQueryBackend)({ backend: 'local_hybrid' });
|
|
140
|
+
const atoms = [
|
|
141
|
+
makeAtom('water_glass_definition', '水杯', '水杯是透明玻璃容器。', ['水杯', '玻璃'], 'zh'),
|
|
142
|
+
makeAtom('container_material_comparison', '材料比较', '塑料杯与玻璃水杯相比更轻、更有韧性,但刚度和透明度较低。', ['塑料杯', '玻璃水杯', '比较'], 'zh'),
|
|
143
|
+
];
|
|
144
|
+
const result = await backend.query({
|
|
145
|
+
request: {
|
|
146
|
+
query: 'compare water glass and plastic cup',
|
|
147
|
+
topK: 2,
|
|
148
|
+
},
|
|
149
|
+
query: 'compare water glass and plastic cup',
|
|
150
|
+
queryTokens: ['compare', 'water', 'glass', 'plastic', 'cup'],
|
|
151
|
+
queryVariants: ['water glass', 'plastic cup'],
|
|
152
|
+
asOf: '2026-01-01T00:00:00.000Z',
|
|
153
|
+
topK: 2,
|
|
154
|
+
atoms,
|
|
155
|
+
activeEdges: [],
|
|
156
|
+
});
|
|
157
|
+
expect(result.candidates.map((candidate) => candidate.atomId)).toContain('container_material_comparison');
|
|
158
|
+
});
|
|
138
159
|
test('local vector backend includes vector similarity mode', async () => {
|
|
139
160
|
const backend = (0, queryBackend_1.createGraphQueryBackend)({ backend: 'local_vector' });
|
|
140
161
|
const atoms = [
|