@zerowidth/workbench-sdk 2.0.0-alpha.0 → 2.1.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/nodes/combine-document-chunks/combine-document-chunks.config.json +8 -0
- package/nodes/combine-document-chunks/combine-document-chunks.process.js +9 -2
- package/nodes/describe-tables/describe-tables.config.json +59 -0
- package/nodes/describe-tables/describe-tables.process.js +40 -0
- package/nodes/expand-chunk-context/expand-chunk-context.config.json +87 -0
- package/nodes/expand-chunk-context/expand-chunk-context.process.js +34 -0
- package/nodes/find-entity-path/find-entity-path.config.json +73 -0
- package/nodes/find-entity-path/find-entity-path.process.js +51 -0
- package/nodes/firecrawl-search/firecrawl-search.config.json +46 -0
- package/nodes/firecrawl-search/firecrawl-search.process.js +54 -0
- package/nodes/firecrawl-search/firecrawl-search.tests.json +9 -0
- package/nodes/get-chunk-by-index/get-chunk-by-index.config.json +8 -0
- package/nodes/get-chunk-by-index/get-chunk-by-index.process.js +9 -2
- package/nodes/get-entity-neighbors/get-entity-neighbors.config.json +81 -0
- package/nodes/get-entity-neighbors/get-entity-neighbors.process.js +51 -0
- package/nodes/keyword-search/keyword-search.config.json +67 -0
- package/nodes/keyword-search/keyword-search.process.js +45 -0
- package/nodes/knowledge-base/knowledge-base.config.json +32 -0
- package/nodes/knowledge-base/knowledge-base.process.js +15 -0
- package/nodes/list-documents/list-documents.config.json +60 -0
- package/nodes/list-documents/list-documents.process.js +25 -0
- package/nodes/list-entities/list-entities.config.json +74 -0
- package/nodes/list-entities/list-entities.process.js +25 -0
- package/nodes/query-knowledge-base/query-knowledge-base.config.json +14 -7
- package/nodes/query-knowledge-base/query-knowledge-base.process.js +25 -5
- package/nodes/read-chunks/read-chunks.config.json +79 -0
- package/nodes/read-chunks/read-chunks.process.js +49 -0
- package/nodes/remote-mcp-tool/remote-mcp-tool.config.json +1 -0
- package/nodes/search-internet/search-internet.config.json +7 -7
- package/nodes/semantic-search/semantic-search.config.json +9 -1
- package/nodes/semantic-search/semantic-search.process.js +41 -18
- package/nodes/tool/tool.config.json +1 -0
- package/package.json +12 -7
- package/src/index.js +81 -11
- package/src/integrations/firecrawl.js +71 -0
- package/src/integrations/knowledge-base-interface.js +49 -0
- package/src/integrations/sqlite.js +535 -83
- package/src/types/knowledge_base.json +9 -0
- package/src/utilities/loaders.js +67 -11
- package/src/utilities/typers.js +15 -17
- package/src/utilities/validators.js +16 -1
- package/types/knowledge_base.json +9 -0
|
@@ -1,44 +1,67 @@
|
|
|
1
1
|
export default async ({inputs, settings, config, nodeConfig}) => {
|
|
2
2
|
try {
|
|
3
|
-
//
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
// Resolve which knowledge base to search. Node-level binding: a
|
|
4
|
+
// `knowledge_base` handle wired in from a Knowledge Base node names a
|
|
5
|
+
// specific KB by uuid, resolved to a per-KB integration the host keyed
|
|
6
|
+
// as config.integrations.knowledgeBases[uuid]. Falls back to the
|
|
7
|
+
// flow-global KB (config.integrations.knowledgeBase / .sqlite) so
|
|
8
|
+
// existing single-KB flows keep working. See ADR 0023.
|
|
9
|
+
const kbRef = inputs.knowledge_base;
|
|
10
|
+
const kbByUuid =
|
|
11
|
+
kbRef && kbRef.uuid
|
|
12
|
+
? config.integrations?.[`knowledgeBase:${kbRef.uuid}`]
|
|
13
|
+
: null;
|
|
14
|
+
const knowledgeBase =
|
|
15
|
+
kbByUuid ||
|
|
16
|
+
config.integrations?.knowledgeBase ||
|
|
17
|
+
config.integrations?.sqlite;
|
|
18
|
+
const openrouter = config.integrations?.openrouter;
|
|
19
|
+
|
|
7
20
|
if (!knowledgeBase) {
|
|
8
21
|
throw new Error("Knowledge base integration not found. Make sure a knowledge database is available.");
|
|
9
22
|
}
|
|
10
|
-
|
|
11
|
-
if (!
|
|
12
|
-
throw new Error("
|
|
23
|
+
|
|
24
|
+
if (!openrouter) {
|
|
25
|
+
throw new Error("OpenRouter integration not found. Semantic search requires an OpenRouter API key for query embeddings.");
|
|
13
26
|
}
|
|
14
27
|
|
|
15
|
-
const {
|
|
16
|
-
query,
|
|
17
|
-
limit = 10,
|
|
18
|
-
|
|
19
|
-
|
|
28
|
+
const {
|
|
29
|
+
query,
|
|
30
|
+
limit = 10,
|
|
31
|
+
// Default 0 (matches the node config) — return matches ranked by
|
|
32
|
+
// similarity rather than silently filtering everything below 0.7.
|
|
33
|
+
similarity_threshold = 0,
|
|
34
|
+
document_id = null
|
|
20
35
|
} = inputs;
|
|
21
|
-
|
|
36
|
+
|
|
22
37
|
const { embedding_model = null } = settings;
|
|
23
38
|
|
|
24
39
|
if (!query || typeof query !== 'string') {
|
|
25
40
|
throw new Error("Query is required and must be a string");
|
|
26
41
|
}
|
|
27
42
|
|
|
28
|
-
//
|
|
43
|
+
// Embedding model: node-setting override, else the model the KB was
|
|
44
|
+
// indexed with (KB-owned — query + index MUST agree so semanticSearch
|
|
45
|
+
// can filter `embedding_model = ?`). OpenRouter model ids are
|
|
46
|
+
// namespaced, e.g. "openai/text-embedding-3-small".
|
|
29
47
|
let modelToUse = embedding_model;
|
|
30
48
|
if (!modelToUse) {
|
|
31
49
|
try {
|
|
32
50
|
modelToUse = await knowledgeBase.getEmbeddingModel();
|
|
33
51
|
} catch (error) {
|
|
34
52
|
console.warn('[WARN] Failed to get embedding model from knowledge base, using default:', error.message);
|
|
35
|
-
modelToUse = 'text-embedding-3-small';
|
|
53
|
+
modelToUse = 'openai/text-embedding-3-small';
|
|
36
54
|
}
|
|
37
55
|
}
|
|
38
56
|
|
|
39
|
-
//
|
|
40
|
-
|
|
41
|
-
const
|
|
57
|
+
// Embed the query via OpenRouter. Returns { embedding, embeddings,
|
|
58
|
+
// dimensions, model, usage, cost_total?, cost_itemized? }.
|
|
59
|
+
const embeddingResponse = await openrouter.createEmbedding(
|
|
60
|
+
{ model: modelToUse, input: query },
|
|
61
|
+
nodeConfig,
|
|
62
|
+
config
|
|
63
|
+
);
|
|
64
|
+
const queryEmbedding = embeddingResponse.embedding;
|
|
42
65
|
|
|
43
66
|
// Perform semantic search
|
|
44
67
|
const searchOptions = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zerowidth/workbench-sdk",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.1",
|
|
4
4
|
"dependencies": {
|
|
5
5
|
"adm-zip": "^0.5.16",
|
|
6
6
|
"ajv": "^8.17.1",
|
|
@@ -8,17 +8,21 @@
|
|
|
8
8
|
"js-tiktoken": "^1.0.21",
|
|
9
9
|
"openai": "^4.92.1",
|
|
10
10
|
"sqlite-vec": "^0.1.7-alpha.2",
|
|
11
|
-
"sqlite3": "^5.1.7",
|
|
12
11
|
"tiktoken": "^1.0.22",
|
|
13
12
|
"uuid": "^11.1.0"
|
|
14
13
|
},
|
|
15
14
|
"repository": "https://github.com/zerowidth-ai/workbench-sdk",
|
|
16
|
-
"description": "Workbench SDK
|
|
15
|
+
"description": "Workbench SDK \u2014 execute Workbench-authored flows in production. Successor to the zv1 package; see https://zerowidth.ai for the design surface.",
|
|
17
16
|
"main": "./src/index.js",
|
|
18
|
-
"files": [
|
|
17
|
+
"files": [
|
|
18
|
+
"src/",
|
|
19
|
+
"nodes/",
|
|
20
|
+
"types/",
|
|
21
|
+
"LICENSE",
|
|
22
|
+
"README.md"
|
|
23
|
+
],
|
|
19
24
|
"publishConfig": {
|
|
20
|
-
"access": "public"
|
|
21
|
-
"tag": "alpha"
|
|
25
|
+
"access": "public"
|
|
22
26
|
},
|
|
23
27
|
"engines": {
|
|
24
28
|
"node": ">=20.0.0"
|
|
@@ -29,7 +33,8 @@
|
|
|
29
33
|
"scripts": {
|
|
30
34
|
"test-nodes": "node tests/test.all-nodes.js",
|
|
31
35
|
"test-flows": "node tests/test.flows.js",
|
|
32
|
-
"test-flow": "node tests/test.flows.js"
|
|
36
|
+
"test-flow": "node tests/test.flows.js",
|
|
37
|
+
"test-kb": "node tests/test.kb-search.js && node tests/test.kb-graph.js"
|
|
33
38
|
},
|
|
34
39
|
"author": "Peter Binggeser @ ZeroWidth, LLC",
|
|
35
40
|
"license": "Apache-2.0",
|
package/src/index.js
CHANGED
|
@@ -364,6 +364,21 @@ export default class Workbench {
|
|
|
364
364
|
id: node.id
|
|
365
365
|
};
|
|
366
366
|
|
|
367
|
+
// Config-only nodes (remote-mcp-tool) have no process function —
|
|
368
|
+
// they exist for the plugin loop, not direct execution. If one
|
|
369
|
+
// lands in the queue anyway (e.g. a dangling MCP node with no
|
|
370
|
+
// plugin link), no-op instead of crashing the whole run.
|
|
371
|
+
if (typeof nodeDefinition.process !== 'function') {
|
|
372
|
+
this.logDebug(`Node [${node.id}] (${node.type}) is config-only; skipping direct execution.`);
|
|
373
|
+
const endDate = new Date();
|
|
374
|
+
timelineEntry.outputs = {};
|
|
375
|
+
timelineEntry.endTime = endDate.toISOString();
|
|
376
|
+
timelineEntry.durationMs = endDate - startDate;
|
|
377
|
+
timelineEntry.status = 'success';
|
|
378
|
+
this.timeline.push(timelineEntry);
|
|
379
|
+
return {};
|
|
380
|
+
}
|
|
381
|
+
|
|
367
382
|
const outputs = await this._raceAbort(
|
|
368
383
|
nodeDefinition.process({inputs, settings, config: this.config, nodeConfig})
|
|
369
384
|
);
|
|
@@ -1077,7 +1092,21 @@ export default class Workbench {
|
|
|
1077
1092
|
await this.config.integrations.sqlite.disconnect();
|
|
1078
1093
|
delete this.config.integrations.sqlite;
|
|
1079
1094
|
}
|
|
1080
|
-
|
|
1095
|
+
|
|
1096
|
+
// Clean up node-level knowledge base integrations (keyed knowledgeBase:<uuid>).
|
|
1097
|
+
if (this.config.integrations) {
|
|
1098
|
+
for (const key of Object.keys(this.config.integrations)) {
|
|
1099
|
+
if (!key.startsWith('knowledgeBase:')) continue;
|
|
1100
|
+
this.logDebug(`Cleaning up node-level knowledge base integration ${key}...`);
|
|
1101
|
+
try {
|
|
1102
|
+
await this.config.integrations[key].disconnect();
|
|
1103
|
+
} catch (err) {
|
|
1104
|
+
this.logDebug(`Failed to disconnect ${key}: ${err.message}`);
|
|
1105
|
+
}
|
|
1106
|
+
delete this.config.integrations[key];
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1081
1110
|
// Clean up any imported engines that were created
|
|
1082
1111
|
// These are stored in the cache when import nodes are processed
|
|
1083
1112
|
const rawStore = this.cache.getRawStore();
|
|
@@ -2337,10 +2366,13 @@ export default class Workbench {
|
|
|
2337
2366
|
}
|
|
2338
2367
|
}
|
|
2339
2368
|
}
|
|
2340
|
-
// Prepare the tool call message for the next LLM call
|
|
2369
|
+
// Prepare the tool call message for the next LLM call.
|
|
2370
|
+
// Preserve any text the model emitted alongside the tool calls —
|
|
2371
|
+
// nulling it here loses it from both the next round's context and
|
|
2372
|
+
// the final conversation output.
|
|
2341
2373
|
toolCallMessage = {
|
|
2342
2374
|
role: "assistant",
|
|
2343
|
-
content: null,
|
|
2375
|
+
content: llmResult.content ?? null,
|
|
2344
2376
|
tool_calls: llmResult.tool_calls
|
|
2345
2377
|
};
|
|
2346
2378
|
toolCallCount++;
|
|
@@ -2464,6 +2496,15 @@ export default class Workbench {
|
|
|
2464
2496
|
* @returns {boolean} True if the node is a plugin or macro
|
|
2465
2497
|
*/
|
|
2466
2498
|
isLocalNodePlugin(node) {
|
|
2499
|
+
// Remote MCP tool nodes and manual tool nodes carry is_plugin —
|
|
2500
|
+
// the flag drives shared plugin BEHAVIOR (entry-node exclusion,
|
|
2501
|
+
// propagation skip, the canvas's plugin drag-and-dock UX) — but
|
|
2502
|
+
// neither is a LOCAL plugin: MCP tools load their schemas from
|
|
2503
|
+
// the remote server (isRemoteMCPTool branch) and manual tools
|
|
2504
|
+
// are caller-executed pass-throughs (isManualToolNode branch).
|
|
2505
|
+
// Classifying either as local would misroute them in the plugin
|
|
2506
|
+
// loop (bogus single-tool schema / unwanted process execution).
|
|
2507
|
+
if (isRemoteMCPTool(node) || isManualToolNode(node)) return false;
|
|
2467
2508
|
const thisNodeConfig = this.nodes[node.type]?.config || {};
|
|
2468
2509
|
return thisNodeConfig.is_plugin || thisNodeConfig.is_macro;
|
|
2469
2510
|
}
|
|
@@ -2879,21 +2920,44 @@ export default class Workbench {
|
|
|
2879
2920
|
* @private
|
|
2880
2921
|
*/
|
|
2881
2922
|
_trackKnowledgeFiles() {
|
|
2882
|
-
//
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2923
|
+
// Host-provided knowledge db paths are HOST-OWNED: the engine must
|
|
2924
|
+
// never delete them unless the host explicitly opts in (hosted
|
|
2925
|
+
// runners that hand the engine per-run temp copies set
|
|
2926
|
+
// config.knowledgeBase.cleanupDbFiles = true). The engine's own
|
|
2927
|
+
// archive extractions live under ./.temp and are always cleaned.
|
|
2928
|
+
const optIn = this.config?.knowledgeBase?.cleanupDbFiles === true;
|
|
2929
|
+
const track = (filePath) => {
|
|
2930
|
+
if (!filePath) return;
|
|
2931
|
+
if (optIn || this._isEngineTempPath(filePath)) {
|
|
2932
|
+
this.trackKnowledgeFile(filePath);
|
|
2933
|
+
}
|
|
2934
|
+
};
|
|
2935
|
+
|
|
2936
|
+
track(this.flow.knowledgeDbPath);
|
|
2886
2937
|
|
|
2887
|
-
// Track import knowledge base files
|
|
2888
2938
|
if (this.flow.imports && Array.isArray(this.flow.imports)) {
|
|
2889
2939
|
for (const importDef of this.flow.imports) {
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2940
|
+
track(importDef.knowledgeDbPath);
|
|
2941
|
+
}
|
|
2942
|
+
}
|
|
2943
|
+
|
|
2944
|
+
if (this.flow.knowledgeDbPaths && typeof this.flow.knowledgeDbPaths === 'object') {
|
|
2945
|
+
for (const dbPath of Object.values(this.flow.knowledgeDbPaths)) {
|
|
2946
|
+
track(dbPath);
|
|
2893
2947
|
}
|
|
2894
2948
|
}
|
|
2895
2949
|
}
|
|
2896
2950
|
|
|
2951
|
+
/** Paths inside the engine's own ./.temp extraction dir. @private */
|
|
2952
|
+
_isEngineTempPath(filePath) {
|
|
2953
|
+
try {
|
|
2954
|
+
const tempDir = path.resolve(process.cwd(), '.temp') + path.sep;
|
|
2955
|
+
return path.resolve(filePath).startsWith(tempDir);
|
|
2956
|
+
} catch {
|
|
2957
|
+
return false;
|
|
2958
|
+
}
|
|
2959
|
+
}
|
|
2960
|
+
|
|
2897
2961
|
/**
|
|
2898
2962
|
* Clean up temporary knowledge base files for this specific engine instance
|
|
2899
2963
|
* @private
|
|
@@ -2996,3 +3060,9 @@ export default class Workbench {
|
|
|
2996
3060
|
}
|
|
2997
3061
|
}
|
|
2998
3062
|
}
|
|
3063
|
+
|
|
3064
|
+
// Base class for bring-your-own knowledge bases: implement its methods
|
|
3065
|
+
// over your own store (SQL database, vector store, HTTP service) and
|
|
3066
|
+
// pass an instance via `config.knowledgeBase.instance` (flow-global)
|
|
3067
|
+
// or `config.knowledgeBase.instances[kbUuid]` (per Knowledge Base node).
|
|
3068
|
+
export { KnowledgeBaseInterface } from './integrations/knowledge-base-interface.js';
|
|
@@ -82,6 +82,77 @@ export default class FirecrawlIntegration {
|
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
+
/**
|
|
86
|
+
* Search the web with the Firecrawl Search API
|
|
87
|
+
* @param {Object} params - Search parameters (query, limit, ...)
|
|
88
|
+
* @returns {Promise<Object>} Search response
|
|
89
|
+
*/
|
|
90
|
+
async search(params = {}) {
|
|
91
|
+
try {
|
|
92
|
+
const url = `${this.options.baseURL}/search`;
|
|
93
|
+
|
|
94
|
+
// Remove any params that are null, undefined, or empty strings
|
|
95
|
+
Object.keys(params).forEach(key => {
|
|
96
|
+
if (params[key] === '') {
|
|
97
|
+
delete params[key];
|
|
98
|
+
}
|
|
99
|
+
if (params[key] === null) {
|
|
100
|
+
delete params[key];
|
|
101
|
+
}
|
|
102
|
+
if (params[key] === undefined) {
|
|
103
|
+
delete params[key];
|
|
104
|
+
}
|
|
105
|
+
if (Array.isArray(params[key]) && params[key].length === 0) {
|
|
106
|
+
delete params[key];
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
const requestHeaders = { 'Content-Type': 'application/json', 'Authorization': `Bearer ${this.apiKey}` };
|
|
111
|
+
const startTime = Date.now();
|
|
112
|
+
|
|
113
|
+
const response = await axios({
|
|
114
|
+
url: url,
|
|
115
|
+
method: 'POST',
|
|
116
|
+
headers: requestHeaders,
|
|
117
|
+
data: params,
|
|
118
|
+
timeout: this.options.timeout
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
await emitAPICallEvent(this._engineConfig, {
|
|
122
|
+
timestamp: startTime, integration: 'firecrawl', nodeId: null, nodeType: null,
|
|
123
|
+
request: { method: 'POST', url, headers: requestHeaders, body: params },
|
|
124
|
+
response: { status: response.status, statusText: response.statusText },
|
|
125
|
+
duration: Date.now() - startTime, error: null
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
if (response.status >= 400) {
|
|
129
|
+
throw new Error(`Firecrawl API error: ${response.status} - ${response.data?.error || response.statusText}`);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return response.data;
|
|
133
|
+
|
|
134
|
+
} catch (error) {
|
|
135
|
+
// Note: emitAPICallEvent for error case is handled by the catch in the caller
|
|
136
|
+
// since errors from axios.post throw before we can capture the response
|
|
137
|
+
if (error.response) {
|
|
138
|
+
const status = error.response.status;
|
|
139
|
+
const statusText = error.response.statusText;
|
|
140
|
+
const responseData = error.response.data;
|
|
141
|
+
|
|
142
|
+
let errorMessage = `Firecrawl API Error (${status} ${statusText})`;
|
|
143
|
+
if (responseData?.error) {
|
|
144
|
+
errorMessage += `: ${responseData.error}`;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
throw new Error(errorMessage);
|
|
148
|
+
} else if (error.request) {
|
|
149
|
+
throw new Error('Firecrawl API Error: No response received');
|
|
150
|
+
} else {
|
|
151
|
+
throw new Error(`Firecrawl API Error: ${error.message}`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
85
156
|
/**
|
|
86
157
|
* Helper function to convert comma-separated string to array
|
|
87
158
|
* @param {string|Array} input - String or array input
|
|
@@ -116,4 +116,53 @@ export class KnowledgeBaseInterface {
|
|
|
116
116
|
async disconnect() {
|
|
117
117
|
throw new Error('disconnect() method must be implemented by knowledge base integration');
|
|
118
118
|
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Keyword (exact-match) search over chunk content.
|
|
122
|
+
* @param {string} query @param {Object} options - { limit, document_id }
|
|
123
|
+
* @returns {Promise<Array>} chunk-shaped rows
|
|
124
|
+
*/
|
|
125
|
+
async keywordSearch(query, options = {}) {
|
|
126
|
+
throw new Error('keywordSearch() method must be implemented by knowledge base integration');
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** List documents. @param {Object} options - { limit, offset } */
|
|
130
|
+
async listDocuments(options = {}) {
|
|
131
|
+
throw new Error('listDocuments() method must be implemented by knowledge base integration');
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** Page one document's chunks. @param {Object} options - { startIndex, limit } */
|
|
135
|
+
async getChunks(documentId, options = {}) {
|
|
136
|
+
throw new Error('getChunks() method must be implemented by knowledge base integration');
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** One chunk plus its neighbors. @param {Object} options - { before, after } */
|
|
140
|
+
async getChunkWindow(documentId, chunkIndex, options = {}) {
|
|
141
|
+
throw new Error('getChunkWindow() method must be implemented by knowledge base integration');
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** Tabular registry: table schemas + sample rows. @param {Object} options - { sampleLimit } */
|
|
145
|
+
async listTables(options = {}) {
|
|
146
|
+
throw new Error('listTables() method must be implemented by knowledge base integration');
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** Graph: entities, most-connected first. @param {Object} options - { limit, offset, type, search } */
|
|
150
|
+
async listEntities(options = {}) {
|
|
151
|
+
throw new Error('listEntities() method must be implemented by knowledge base integration');
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** Graph: resolve an entity by id or case-insensitive name. */
|
|
155
|
+
async getEntity(ref) {
|
|
156
|
+
throw new Error('getEntity() method must be implemented by knowledge base integration');
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** Graph: relations touching an entity. @param {Object} options - { direction, relation_type, limit } */
|
|
160
|
+
async getNeighbors(entityRef, options = {}) {
|
|
161
|
+
throw new Error('getNeighbors() method must be implemented by knowledge base integration');
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** Graph: shortest relation chain between two entities. @param {Object} options - { max_depth } */
|
|
165
|
+
async findPath(fromRef, toRef, options = {}) {
|
|
166
|
+
throw new Error('findPath() method must be implemented by knowledge base integration');
|
|
167
|
+
}
|
|
119
168
|
}
|