@zerowidth/workbench-sdk 2.0.0-alpha.0 → 2.0.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/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/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/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 +76 -9
- 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
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"display_name": "Knowledge Base",
|
|
3
|
+
"tagline": "Reference a knowledge base",
|
|
4
|
+
"description": "References a workspace knowledge base by id and emits a handle to it. Wire this node's output into a Semantic Search (or Query Knowledge Base) node to search that KB. Attach one Knowledge Base node to several search nodes to reuse the same KB — like importing a subflow, the KB is one thing they all depend on.",
|
|
5
|
+
"icon": "database",
|
|
6
|
+
"category": "knowledge",
|
|
7
|
+
"is_constant": true,
|
|
8
|
+
"is_plugin": false,
|
|
9
|
+
"is_output": false,
|
|
10
|
+
"custom_render_height": 76,
|
|
11
|
+
"inputs": [],
|
|
12
|
+
"outputs": [
|
|
13
|
+
{
|
|
14
|
+
"name": "knowledge_base",
|
|
15
|
+
"primary": true,
|
|
16
|
+
"display_name": "Knowledge Base",
|
|
17
|
+
"type": "knowledge_base",
|
|
18
|
+
"description": "A reference (handle) to the selected knowledge base."
|
|
19
|
+
}
|
|
20
|
+
],
|
|
21
|
+
"settings": [
|
|
22
|
+
{
|
|
23
|
+
"name": "uuid",
|
|
24
|
+
"display_name": "Knowledge Base",
|
|
25
|
+
"type": "knowledge_base_ref",
|
|
26
|
+
"description": "The knowledge base this node references.",
|
|
27
|
+
"default": null
|
|
28
|
+
}
|
|
29
|
+
],
|
|
30
|
+
"timeout": 3000,
|
|
31
|
+
"retry_limit": 1
|
|
32
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export default async ({ inputs, settings, config }) => {
|
|
2
|
+
// A pure reference/source node: it carries a KB id in its settings and emits
|
|
3
|
+
// it as a `knowledge_base` handle. The runtime KB itself (the `.db`) is
|
|
4
|
+
// resolved host-side (ZeroWidth's flow-runner pulls each referenced KB and
|
|
5
|
+
// hands the engine an integration keyed by this uuid) — this node only names
|
|
6
|
+
// which one. Consumers (semantic-search) read the uuid off the handle and
|
|
7
|
+
// look up config.integrations.knowledgeBases[uuid].
|
|
8
|
+
const uuid = settings?.uuid ?? null;
|
|
9
|
+
if (!uuid) {
|
|
10
|
+
return { knowledge_base: null };
|
|
11
|
+
}
|
|
12
|
+
const handle = { uuid };
|
|
13
|
+
if (settings?.name) handle.name = settings.name;
|
|
14
|
+
return { knowledge_base: handle };
|
|
15
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"display_name": "List Documents",
|
|
3
|
+
"tagline": "List everything in a KB",
|
|
4
|
+
"description": "List the documents in a knowledge base (id, name, type, size, chunk count) — for browsing or letting an agent pick a document to read. Wire a Knowledge Base node into knowledge_base, same as the search nodes.",
|
|
5
|
+
"category": "knowledge",
|
|
6
|
+
"is_constant": false,
|
|
7
|
+
"is_plugin": true,
|
|
8
|
+
"is_output": false,
|
|
9
|
+
"inputs": [
|
|
10
|
+
{
|
|
11
|
+
"name": "knowledge_base",
|
|
12
|
+
"display_name": "Knowledge Base",
|
|
13
|
+
"type": "knowledge_base",
|
|
14
|
+
"description": "The knowledge base to list, from a Knowledge Base node. If omitted, falls back to the flow's default knowledge base.",
|
|
15
|
+
"required": false,
|
|
16
|
+
"default": null
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"name": "limit",
|
|
20
|
+
"display_name": "Limit",
|
|
21
|
+
"type": "number",
|
|
22
|
+
"description": "Maximum number of documents to return",
|
|
23
|
+
"default": 100
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"name": "offset",
|
|
27
|
+
"display_name": "Offset",
|
|
28
|
+
"type": "number",
|
|
29
|
+
"description": "Number of documents to skip (pagination)",
|
|
30
|
+
"default": 0
|
|
31
|
+
}
|
|
32
|
+
],
|
|
33
|
+
"outputs": [
|
|
34
|
+
{
|
|
35
|
+
"name": "documents",
|
|
36
|
+
"display_name": "Documents",
|
|
37
|
+
"type": "array",
|
|
38
|
+
"description": "Document rows (id, display_name, file_type, file_size, chunk_count, …)"
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"name": "count",
|
|
42
|
+
"display_name": "Count",
|
|
43
|
+
"type": "number",
|
|
44
|
+
"description": "Number of documents returned"
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"name": "success",
|
|
48
|
+
"display_name": "Success",
|
|
49
|
+
"type": "boolean",
|
|
50
|
+
"description": "Whether the listing completed successfully"
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"name": "error",
|
|
54
|
+
"display_name": "Error",
|
|
55
|
+
"type": "string",
|
|
56
|
+
"description": "Error message if it failed"
|
|
57
|
+
}
|
|
58
|
+
],
|
|
59
|
+
"settings": []
|
|
60
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export default async ({ inputs, config }) => {
|
|
2
|
+
try {
|
|
3
|
+
// Shared KB wiring (see semantic-search / keyword-search): a
|
|
4
|
+
// knowledge_base handle names a KB by uuid, falling back to the
|
|
5
|
+
// flow-global KB. See ADR 0023.
|
|
6
|
+
const kbRef = inputs.knowledge_base;
|
|
7
|
+
const knowledgeBase =
|
|
8
|
+
(kbRef && kbRef.uuid
|
|
9
|
+
? config.integrations?.[`knowledgeBase:${kbRef.uuid}`]
|
|
10
|
+
: null) ||
|
|
11
|
+
config.integrations?.knowledgeBase ||
|
|
12
|
+
config.integrations?.sqlite;
|
|
13
|
+
|
|
14
|
+
if (!knowledgeBase) {
|
|
15
|
+
throw new Error("Knowledge base integration not found. Make sure a knowledge database is available.");
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const { limit = 100, offset = 0 } = inputs;
|
|
19
|
+
const documents = await knowledgeBase.listDocuments({ limit, offset });
|
|
20
|
+
|
|
21
|
+
return { documents, count: documents.length, success: true, error: null };
|
|
22
|
+
} catch (error) {
|
|
23
|
+
return { documents: [], count: 0, success: false, error: error.message };
|
|
24
|
+
}
|
|
25
|
+
};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
{
|
|
2
|
+
"display_name": "List Entities",
|
|
3
|
+
"tagline": "Browse a knowledge graph",
|
|
4
|
+
"description": "List the entities in a Graph knowledge base, highest-degree (most-connected) first — for browsing the graph or letting an agent discover what it can traverse. Optionally filter by entity type or a name substring. Wire a Knowledge Base node into knowledge_base, same as the search nodes.",
|
|
5
|
+
"category": "knowledge",
|
|
6
|
+
"is_constant": false,
|
|
7
|
+
"is_plugin": true,
|
|
8
|
+
"is_output": false,
|
|
9
|
+
"inputs": [
|
|
10
|
+
{
|
|
11
|
+
"name": "knowledge_base",
|
|
12
|
+
"display_name": "Knowledge Base",
|
|
13
|
+
"type": "knowledge_base",
|
|
14
|
+
"description": "The knowledge base to list, from a Knowledge Base node. If omitted, falls back to the flow's default knowledge base.",
|
|
15
|
+
"required": false,
|
|
16
|
+
"default": null
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"name": "type",
|
|
20
|
+
"display_name": "Entity Type",
|
|
21
|
+
"type": "string",
|
|
22
|
+
"description": "Optional: only entities of this type (e.g. person, organization). Case-insensitive.",
|
|
23
|
+
"default": null
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"name": "search",
|
|
27
|
+
"display_name": "Name Search",
|
|
28
|
+
"type": "string",
|
|
29
|
+
"description": "Optional: only entities whose name contains this text (case-insensitive).",
|
|
30
|
+
"default": null
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"name": "limit",
|
|
34
|
+
"display_name": "Limit",
|
|
35
|
+
"type": "number",
|
|
36
|
+
"description": "Maximum number of entities to return",
|
|
37
|
+
"default": 100
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"name": "offset",
|
|
41
|
+
"display_name": "Offset",
|
|
42
|
+
"type": "number",
|
|
43
|
+
"description": "Number of entities to skip (pagination)",
|
|
44
|
+
"default": 0
|
|
45
|
+
}
|
|
46
|
+
],
|
|
47
|
+
"outputs": [
|
|
48
|
+
{
|
|
49
|
+
"name": "entities",
|
|
50
|
+
"display_name": "Entities",
|
|
51
|
+
"type": "array",
|
|
52
|
+
"description": "Entity rows (id, name, type, description, degree, …), most-connected first"
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"name": "count",
|
|
56
|
+
"display_name": "Count",
|
|
57
|
+
"type": "number",
|
|
58
|
+
"description": "Number of entities returned"
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"name": "success",
|
|
62
|
+
"display_name": "Success",
|
|
63
|
+
"type": "boolean",
|
|
64
|
+
"description": "Whether the listing completed successfully"
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"name": "error",
|
|
68
|
+
"display_name": "Error",
|
|
69
|
+
"type": "string",
|
|
70
|
+
"description": "Error message if it failed"
|
|
71
|
+
}
|
|
72
|
+
],
|
|
73
|
+
"settings": []
|
|
74
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export default async ({ inputs, config }) => {
|
|
2
|
+
try {
|
|
3
|
+
// Shared KB wiring (see semantic-search / keyword-search): a
|
|
4
|
+
// knowledge_base handle names a KB by uuid, falling back to the
|
|
5
|
+
// flow-global KB. See ADR 0023.
|
|
6
|
+
const kbRef = inputs.knowledge_base;
|
|
7
|
+
const knowledgeBase =
|
|
8
|
+
(kbRef && kbRef.uuid
|
|
9
|
+
? config.integrations?.[`knowledgeBase:${kbRef.uuid}`]
|
|
10
|
+
: null) ||
|
|
11
|
+
config.integrations?.knowledgeBase ||
|
|
12
|
+
config.integrations?.sqlite;
|
|
13
|
+
|
|
14
|
+
if (!knowledgeBase) {
|
|
15
|
+
throw new Error("Knowledge base integration not found. Make sure a knowledge database is available.");
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const { limit = 100, offset = 0, type = null, search = null } = inputs;
|
|
19
|
+
const entities = await knowledgeBase.listEntities({ limit, offset, type, search });
|
|
20
|
+
|
|
21
|
+
return { entities, count: entities.length, success: true, error: null };
|
|
22
|
+
} catch (error) {
|
|
23
|
+
return { entities: [], count: 0, success: false, error: error.message };
|
|
24
|
+
}
|
|
25
|
+
};
|
|
@@ -14,6 +14,14 @@
|
|
|
14
14
|
"description": "SQL query to execute against the knowledge database",
|
|
15
15
|
"required": true
|
|
16
16
|
},
|
|
17
|
+
{
|
|
18
|
+
"name": "knowledge_base",
|
|
19
|
+
"display_name": "Knowledge Base",
|
|
20
|
+
"type": "knowledge_base",
|
|
21
|
+
"description": "The knowledge base to query, from a Knowledge Base node. If omitted, falls back to the flow's default knowledge base.",
|
|
22
|
+
"required": false,
|
|
23
|
+
"default": null
|
|
24
|
+
},
|
|
17
25
|
{
|
|
18
26
|
"name": "params",
|
|
19
27
|
"display_name": "Parameters",
|
|
@@ -49,6 +57,12 @@
|
|
|
49
57
|
"type": "number",
|
|
50
58
|
"description": "Number of rows affected or returned"
|
|
51
59
|
},
|
|
60
|
+
{
|
|
61
|
+
"name": "truncated",
|
|
62
|
+
"display_name": "Truncated",
|
|
63
|
+
"type": "boolean",
|
|
64
|
+
"description": "True when the returned data was capped (>1000 rows) — add a LIMIT to the query for the full set"
|
|
65
|
+
},
|
|
52
66
|
{
|
|
53
67
|
"name": "operation",
|
|
54
68
|
"display_name": "Operation",
|
|
@@ -63,13 +77,6 @@
|
|
|
63
77
|
}
|
|
64
78
|
],
|
|
65
79
|
"settings": [
|
|
66
|
-
{
|
|
67
|
-
"name": "database_path",
|
|
68
|
-
"display_name": "Database Path",
|
|
69
|
-
"type": "string",
|
|
70
|
-
"description": "Path to the knowledge database file (relative to orchestration)",
|
|
71
|
-
"default": "knowledge.db"
|
|
72
|
-
},
|
|
73
80
|
{
|
|
74
81
|
"name": "timeout",
|
|
75
82
|
"display_name": "Query Timeout",
|
|
@@ -1,12 +1,20 @@
|
|
|
1
|
-
export default async ({inputs,
|
|
2
|
-
//
|
|
3
|
-
|
|
1
|
+
export default async ({inputs, config}) => {
|
|
2
|
+
// Resolve which knowledge base to query — SAME wiring as semantic-search
|
|
3
|
+
// and keyword-search: a `knowledge_base` handle wired in from a Knowledge
|
|
4
|
+
// Base node names a specific KB by uuid (resolved to a per-KB integration
|
|
5
|
+
// keyed knowledgeBase:<uuid>), falling back to the flow-global KB. See ADR 0023.
|
|
6
|
+
const kbRef = inputs.knowledge_base;
|
|
7
|
+
const knowledgeBase =
|
|
8
|
+
(kbRef && kbRef.uuid
|
|
9
|
+
? config.integrations?.[`knowledgeBase:${kbRef.uuid}`]
|
|
10
|
+
: null) ||
|
|
11
|
+
config.integrations?.knowledgeBase ||
|
|
12
|
+
config.integrations?.sqlite;
|
|
4
13
|
if (!knowledgeBase) {
|
|
5
14
|
throw new Error("Knowledge base integration not found. Make sure a knowledge database is available.");
|
|
6
15
|
}
|
|
7
16
|
|
|
8
17
|
const { query, params = [], operation = 'SELECT' } = inputs;
|
|
9
|
-
const { database_path = 'knowledge.db', timeout = 5000 } = settings;
|
|
10
18
|
|
|
11
19
|
if (!query || typeof query !== 'string') {
|
|
12
20
|
throw new Error("Query is required and must be a string");
|
|
@@ -25,11 +33,23 @@ export default async ({inputs, settings, config, nodeConfig}) => {
|
|
|
25
33
|
// Execute the query
|
|
26
34
|
const result = await knowledgeBase.query(query, params, operation);
|
|
27
35
|
|
|
36
|
+
// Cap the rows returned into the flow so a broad SELECT can't bloat the run
|
|
37
|
+
// payload. rowCount stays the true count; `truncated` flags the cut (never
|
|
38
|
+
// silently). Bump the SQL's own LIMIT for fewer/more rows.
|
|
39
|
+
const MAX_ROWS = 1000;
|
|
40
|
+
let data = result.data;
|
|
41
|
+
let truncated = false;
|
|
42
|
+
if (Array.isArray(data) && data.length > MAX_ROWS) {
|
|
43
|
+
data = data.slice(0, MAX_ROWS);
|
|
44
|
+
truncated = true;
|
|
45
|
+
}
|
|
46
|
+
|
|
28
47
|
return {
|
|
29
|
-
data
|
|
48
|
+
data,
|
|
30
49
|
success: result.success,
|
|
31
50
|
rowCount: result.rowCount,
|
|
32
51
|
operation: result.operation,
|
|
52
|
+
truncated,
|
|
33
53
|
error: null
|
|
34
54
|
};
|
|
35
55
|
};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
{
|
|
2
|
+
"display_name": "Read Chunks",
|
|
3
|
+
"tagline": "Paginate through a document",
|
|
4
|
+
"description": "Read a document's chunks in order starting at a chunk index — paginate through a document, or read what came AFTER a chunk you found via search (set start_index to that chunk's index + 1). Feed next_index back in to page forward.",
|
|
5
|
+
"category": "knowledge",
|
|
6
|
+
"is_constant": false,
|
|
7
|
+
"is_plugin": true,
|
|
8
|
+
"is_output": false,
|
|
9
|
+
"inputs": [
|
|
10
|
+
{
|
|
11
|
+
"name": "document_id",
|
|
12
|
+
"display_name": "Document ID",
|
|
13
|
+
"type": "string",
|
|
14
|
+
"description": "The document to read chunks from",
|
|
15
|
+
"required": true
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"name": "knowledge_base",
|
|
19
|
+
"display_name": "Knowledge Base",
|
|
20
|
+
"type": "knowledge_base",
|
|
21
|
+
"description": "The knowledge base, from a Knowledge Base node. If omitted, falls back to the flow's default knowledge base.",
|
|
22
|
+
"required": false,
|
|
23
|
+
"default": null
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"name": "start_index",
|
|
27
|
+
"display_name": "Start Index",
|
|
28
|
+
"type": "number",
|
|
29
|
+
"description": "First chunk index to read (0-based). To continue after a found chunk, use its index + 1.",
|
|
30
|
+
"default": 0
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"name": "limit",
|
|
34
|
+
"display_name": "Limit",
|
|
35
|
+
"type": "number",
|
|
36
|
+
"description": "Maximum number of chunks to return",
|
|
37
|
+
"default": 10
|
|
38
|
+
}
|
|
39
|
+
],
|
|
40
|
+
"outputs": [
|
|
41
|
+
{
|
|
42
|
+
"name": "chunks",
|
|
43
|
+
"display_name": "Chunks",
|
|
44
|
+
"type": "array",
|
|
45
|
+
"description": "Chunks with index >= start_index, ascending"
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"name": "count",
|
|
49
|
+
"display_name": "Count",
|
|
50
|
+
"type": "number",
|
|
51
|
+
"description": "Number of chunks returned"
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"name": "next_index",
|
|
55
|
+
"display_name": "Next Index",
|
|
56
|
+
"type": "number",
|
|
57
|
+
"description": "start_index for the next page, or null if there are no more chunks"
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"name": "has_more",
|
|
61
|
+
"display_name": "Has More",
|
|
62
|
+
"type": "boolean",
|
|
63
|
+
"description": "Whether more chunks remain after this page"
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"name": "success",
|
|
67
|
+
"display_name": "Success",
|
|
68
|
+
"type": "boolean",
|
|
69
|
+
"description": "Whether the read completed successfully"
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
"name": "error",
|
|
73
|
+
"display_name": "Error",
|
|
74
|
+
"type": "string",
|
|
75
|
+
"description": "Error message if it failed"
|
|
76
|
+
}
|
|
77
|
+
],
|
|
78
|
+
"settings": []
|
|
79
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
export default async ({ inputs, config }) => {
|
|
2
|
+
try {
|
|
3
|
+
// Shared KB wiring (see semantic-search / keyword-search). ADR 0023.
|
|
4
|
+
const kbRef = inputs.knowledge_base;
|
|
5
|
+
const knowledgeBase =
|
|
6
|
+
(kbRef && kbRef.uuid
|
|
7
|
+
? config.integrations?.[`knowledgeBase:${kbRef.uuid}`]
|
|
8
|
+
: null) ||
|
|
9
|
+
config.integrations?.knowledgeBase ||
|
|
10
|
+
config.integrations?.sqlite;
|
|
11
|
+
|
|
12
|
+
if (!knowledgeBase) {
|
|
13
|
+
throw new Error("Knowledge base integration not found. Make sure a knowledge database is available.");
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const { document_id, start_index = 0, limit = 10 } = inputs;
|
|
17
|
+
if (!document_id || typeof document_id !== 'string') {
|
|
18
|
+
throw new Error("document_id is required and must be a string");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Fetch one extra to detect whether more chunks remain after this page.
|
|
22
|
+
const fetched = await knowledgeBase.getChunks(document_id, {
|
|
23
|
+
startIndex: start_index,
|
|
24
|
+
limit: limit + 1,
|
|
25
|
+
});
|
|
26
|
+
const hasMore = fetched.length > limit;
|
|
27
|
+
const chunks = hasMore ? fetched.slice(0, limit) : fetched;
|
|
28
|
+
const last = chunks[chunks.length - 1];
|
|
29
|
+
const nextIndex = hasMore && last ? last.chunk_index + 1 : null;
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
chunks,
|
|
33
|
+
count: chunks.length,
|
|
34
|
+
next_index: nextIndex,
|
|
35
|
+
has_more: hasMore,
|
|
36
|
+
success: true,
|
|
37
|
+
error: null,
|
|
38
|
+
};
|
|
39
|
+
} catch (error) {
|
|
40
|
+
return {
|
|
41
|
+
chunks: [],
|
|
42
|
+
count: 0,
|
|
43
|
+
next_index: null,
|
|
44
|
+
has_more: false,
|
|
45
|
+
success: false,
|
|
46
|
+
error: error.message,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
};
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
"is_constant": false,
|
|
7
7
|
"is_plugin": true,
|
|
8
8
|
"is_output": false,
|
|
9
|
-
"needs_key_from": ["
|
|
9
|
+
"needs_key_from": ["openrouter"],
|
|
10
10
|
"inputs": [
|
|
11
11
|
{
|
|
12
12
|
"name": "query",
|
|
@@ -15,6 +15,14 @@
|
|
|
15
15
|
"description": "Text query to search for semantically similar content",
|
|
16
16
|
"required": true
|
|
17
17
|
},
|
|
18
|
+
{
|
|
19
|
+
"name": "knowledge_base",
|
|
20
|
+
"display_name": "Knowledge Base",
|
|
21
|
+
"type": "knowledge_base",
|
|
22
|
+
"description": "The knowledge base to search, from a Knowledge Base node. If omitted, falls back to the flow's default knowledge base.",
|
|
23
|
+
"required": false,
|
|
24
|
+
"default": null
|
|
25
|
+
},
|
|
18
26
|
{
|
|
19
27
|
"name": "limit",
|
|
20
28
|
"display_name": "Result Limit",
|
|
@@ -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.0.0
|
|
3
|
+
"version": "2.0.0",
|
|
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",
|