@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
|
@@ -25,6 +25,14 @@
|
|
|
25
25
|
"type": "string",
|
|
26
26
|
"description": "String to use as separator between chunks",
|
|
27
27
|
"default": "\n\n---\n\n"
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"name": "knowledge_base",
|
|
31
|
+
"display_name": "Knowledge Base",
|
|
32
|
+
"type": "knowledge_base",
|
|
33
|
+
"description": "The knowledge base, from a Knowledge Base node. If omitted, falls back to the flow's default knowledge base.",
|
|
34
|
+
"required": false,
|
|
35
|
+
"default": null
|
|
28
36
|
}
|
|
29
37
|
],
|
|
30
38
|
"outputs": [
|
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
export default async ({inputs, settings, config, nodeConfig}) => {
|
|
2
2
|
try {
|
|
3
|
-
//
|
|
4
|
-
|
|
3
|
+
// Resolve the KB from a knowledge_base handle (shared wiring), falling
|
|
4
|
+
// back to the flow-global KB. See ADR 0023.
|
|
5
|
+
const kbRef = inputs.knowledge_base;
|
|
6
|
+
const knowledgeBase =
|
|
7
|
+
(kbRef && kbRef.uuid
|
|
8
|
+
? config.integrations?.[`knowledgeBase:${kbRef.uuid}`]
|
|
9
|
+
: null) ||
|
|
10
|
+
config.integrations?.knowledgeBase ||
|
|
11
|
+
config.integrations?.sqlite;
|
|
5
12
|
|
|
6
13
|
if (!knowledgeBase) {
|
|
7
14
|
throw new Error("Knowledge base integration not found. Make sure a knowledge database is available.");
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"display_name": "Describe Tables",
|
|
3
|
+
"tagline": "See a KB's tables + columns",
|
|
4
|
+
"description": "List the tables in a Tabular knowledge base with their columns, types, and row counts — so a model can discover the schema before writing SQL for the Query Knowledge Base node. Wire a Knowledge Base node into knowledge_base, same as the other KB nodes. Returns [] for a docs KB.",
|
|
5
|
+
"category": "knowledge",
|
|
6
|
+
"is_constant": false,
|
|
7
|
+
"is_plugin": false,
|
|
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 describe, 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": "sample_rows",
|
|
20
|
+
"display_name": "Sample Rows",
|
|
21
|
+
"type": "number",
|
|
22
|
+
"description": "Rows to sample per table (0 = none). Samples help an LLM write correct SQL.",
|
|
23
|
+
"default": 5
|
|
24
|
+
}
|
|
25
|
+
],
|
|
26
|
+
"outputs": [
|
|
27
|
+
{
|
|
28
|
+
"name": "schema",
|
|
29
|
+
"display_name": "Schema (for LLM)",
|
|
30
|
+
"type": "string",
|
|
31
|
+
"description": "Ready-to-paste schema block: CREATE TABLE DDL + a few sample rows (as comments) per table. Feed into an LLM node's context to write SQL."
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"name": "tables",
|
|
35
|
+
"display_name": "Tables",
|
|
36
|
+
"type": "array",
|
|
37
|
+
"description": "[{ table_name, source_name, row_count, columns: [{ name, type }], ddl, sample_rows }]"
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"name": "count",
|
|
41
|
+
"display_name": "Table Count",
|
|
42
|
+
"type": "number",
|
|
43
|
+
"description": "Number of tables"
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"name": "success",
|
|
47
|
+
"display_name": "Success",
|
|
48
|
+
"type": "boolean",
|
|
49
|
+
"description": "Whether it completed successfully"
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"name": "error",
|
|
53
|
+
"display_name": "Error",
|
|
54
|
+
"type": "string",
|
|
55
|
+
"description": "Error message if it failed"
|
|
56
|
+
}
|
|
57
|
+
],
|
|
58
|
+
"settings": []
|
|
59
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
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 sampleLimit =
|
|
17
|
+
typeof inputs.sample_rows === "number" ? inputs.sample_rows : 5;
|
|
18
|
+
const tables = await knowledgeBase.listTables({ sampleLimit });
|
|
19
|
+
|
|
20
|
+
// A ready-to-paste schema block for an LLM: CREATE TABLE DDL + a few
|
|
21
|
+
// sample rows (as comments) per table. Drop `schema` straight into an
|
|
22
|
+
// LLM node's context to write SQL for the Query Knowledge Base node.
|
|
23
|
+
const schema = tables
|
|
24
|
+
.map((t) => {
|
|
25
|
+
const lines = [t.ddl];
|
|
26
|
+
if (Array.isArray(t.sample_rows) && t.sample_rows.length > 0) {
|
|
27
|
+
lines.push("-- sample rows:");
|
|
28
|
+
for (const row of t.sample_rows) {
|
|
29
|
+
lines.push("-- " + JSON.stringify(row));
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return lines.join("\n");
|
|
33
|
+
})
|
|
34
|
+
.join("\n\n");
|
|
35
|
+
|
|
36
|
+
return { tables, schema, count: tables.length, success: true, error: null };
|
|
37
|
+
} catch (error) {
|
|
38
|
+
return { tables: [], schema: "", count: 0, success: false, error: error.message };
|
|
39
|
+
}
|
|
40
|
+
};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
{
|
|
2
|
+
"display_name": "Expand Chunk Context",
|
|
3
|
+
"tagline": "Grab chunks around a hit",
|
|
4
|
+
"description": "Given a chunk (its document_id + chunk_index, e.g. from a search result), return the surrounding chunks — the N before and N after — plus their combined text. The classic RAG move: search finds one chunk, this widens the window so the model has the context around it.",
|
|
5
|
+
"category": "knowledge",
|
|
6
|
+
"is_constant": false,
|
|
7
|
+
"is_plugin": false,
|
|
8
|
+
"is_output": false,
|
|
9
|
+
"inputs": [
|
|
10
|
+
{
|
|
11
|
+
"name": "document_id",
|
|
12
|
+
"display_name": "Document ID",
|
|
13
|
+
"type": "string",
|
|
14
|
+
"description": "The document the anchor chunk belongs to",
|
|
15
|
+
"required": true
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"name": "chunk_index",
|
|
19
|
+
"display_name": "Chunk Index",
|
|
20
|
+
"type": "number",
|
|
21
|
+
"description": "The anchor chunk's index (e.g. from a search result)",
|
|
22
|
+
"required": true
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"name": "knowledge_base",
|
|
26
|
+
"display_name": "Knowledge Base",
|
|
27
|
+
"type": "knowledge_base",
|
|
28
|
+
"description": "The knowledge base, from a Knowledge Base node. If omitted, falls back to the flow's default knowledge base.",
|
|
29
|
+
"required": false,
|
|
30
|
+
"default": null
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"name": "before",
|
|
34
|
+
"display_name": "Chunks Before",
|
|
35
|
+
"type": "number",
|
|
36
|
+
"description": "How many chunks before the anchor to include",
|
|
37
|
+
"default": 1
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"name": "after",
|
|
41
|
+
"display_name": "Chunks After",
|
|
42
|
+
"type": "number",
|
|
43
|
+
"description": "How many chunks after the anchor to include",
|
|
44
|
+
"default": 2
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"name": "separator",
|
|
48
|
+
"display_name": "Separator",
|
|
49
|
+
"type": "string",
|
|
50
|
+
"description": "Joins chunk contents in the combined output",
|
|
51
|
+
"default": "\n\n"
|
|
52
|
+
}
|
|
53
|
+
],
|
|
54
|
+
"outputs": [
|
|
55
|
+
{
|
|
56
|
+
"name": "chunks",
|
|
57
|
+
"display_name": "Chunks",
|
|
58
|
+
"type": "array",
|
|
59
|
+
"description": "The window of chunks, ascending by index"
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
"name": "content",
|
|
63
|
+
"display_name": "Combined Content",
|
|
64
|
+
"type": "string",
|
|
65
|
+
"description": "The window's chunk contents joined by the separator"
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"name": "count",
|
|
69
|
+
"display_name": "Count",
|
|
70
|
+
"type": "number",
|
|
71
|
+
"description": "Number of chunks in the window"
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
"name": "success",
|
|
75
|
+
"display_name": "Success",
|
|
76
|
+
"type": "boolean",
|
|
77
|
+
"description": "Whether it completed successfully"
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"name": "error",
|
|
81
|
+
"display_name": "Error",
|
|
82
|
+
"type": "string",
|
|
83
|
+
"description": "Error message if it failed"
|
|
84
|
+
}
|
|
85
|
+
],
|
|
86
|
+
"settings": []
|
|
87
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
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, chunk_index, before = 1, after = 2, separator = "\n\n" } = inputs;
|
|
17
|
+
if (!document_id || typeof document_id !== 'string') {
|
|
18
|
+
throw new Error("document_id is required and must be a string");
|
|
19
|
+
}
|
|
20
|
+
if (chunk_index === undefined || chunk_index === null || Number.isNaN(Number(chunk_index))) {
|
|
21
|
+
throw new Error("chunk_index is required and must be a number");
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const chunks = await knowledgeBase.getChunkWindow(document_id, Number(chunk_index), {
|
|
25
|
+
before,
|
|
26
|
+
after,
|
|
27
|
+
});
|
|
28
|
+
const content = chunks.map((c) => c.content ?? '').join(separator);
|
|
29
|
+
|
|
30
|
+
return { chunks, content, count: chunks.length, success: true, error: null };
|
|
31
|
+
} catch (error) {
|
|
32
|
+
return { chunks: [], content: '', count: 0, success: false, error: error.message };
|
|
33
|
+
}
|
|
34
|
+
};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
{
|
|
2
|
+
"display_name": "Find Entity Path",
|
|
3
|
+
"tagline": "Connect two graph entities",
|
|
4
|
+
"description": "Find the shortest chain of relations connecting two entities in a Graph knowledge base (breadth-first, direction-agnostic). Answers \"how are X and Y related?\" with the hop-by-hop path. 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": "from",
|
|
12
|
+
"display_name": "From Entity",
|
|
13
|
+
"type": "string",
|
|
14
|
+
"description": "The starting entity — its name (case-insensitive) or id.",
|
|
15
|
+
"required": true
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"name": "to",
|
|
19
|
+
"display_name": "To Entity",
|
|
20
|
+
"type": "string",
|
|
21
|
+
"description": "The target entity — its name (case-insensitive) or id.",
|
|
22
|
+
"required": true
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"name": "knowledge_base",
|
|
26
|
+
"display_name": "Knowledge Base",
|
|
27
|
+
"type": "knowledge_base",
|
|
28
|
+
"description": "The knowledge base to traverse, from a Knowledge Base node. If omitted, falls back to the flow's default knowledge base.",
|
|
29
|
+
"required": false,
|
|
30
|
+
"default": null
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"name": "max_depth",
|
|
34
|
+
"display_name": "Max Depth",
|
|
35
|
+
"type": "number",
|
|
36
|
+
"description": "Maximum number of hops to search before giving up.",
|
|
37
|
+
"default": 4
|
|
38
|
+
}
|
|
39
|
+
],
|
|
40
|
+
"outputs": [
|
|
41
|
+
{
|
|
42
|
+
"name": "found",
|
|
43
|
+
"display_name": "Found",
|
|
44
|
+
"type": "boolean",
|
|
45
|
+
"description": "Whether a path exists within max_depth"
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"name": "hops",
|
|
49
|
+
"display_name": "Hops",
|
|
50
|
+
"type": "number",
|
|
51
|
+
"description": "Number of relations in the path (0 when from = to)"
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"name": "steps",
|
|
55
|
+
"display_name": "Steps",
|
|
56
|
+
"type": "array",
|
|
57
|
+
"description": "The path, node by node: [{ node, via_edge? }] — via_edge names the relation that reached each node"
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"name": "success",
|
|
61
|
+
"display_name": "Success",
|
|
62
|
+
"type": "boolean",
|
|
63
|
+
"description": "Whether the search completed successfully"
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"name": "error",
|
|
67
|
+
"display_name": "Error",
|
|
68
|
+
"type": "string",
|
|
69
|
+
"description": "Error message if it failed (including unknown entities)"
|
|
70
|
+
}
|
|
71
|
+
],
|
|
72
|
+
"settings": []
|
|
73
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
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 { from, to, max_depth = 4 } = inputs;
|
|
19
|
+
if (typeof from !== "string" || from.trim().length === 0) {
|
|
20
|
+
throw new Error("A from entity name or id is required.");
|
|
21
|
+
}
|
|
22
|
+
if (typeof to !== "string" || to.trim().length === 0) {
|
|
23
|
+
throw new Error("A to entity name or id is required.");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const result = await knowledgeBase.findPath(from.trim(), to.trim(), {
|
|
27
|
+
max_depth,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
if (!result.from || !result.to) {
|
|
31
|
+
const missing = !result.from ? from : to;
|
|
32
|
+
return {
|
|
33
|
+
found: false,
|
|
34
|
+
hops: 0,
|
|
35
|
+
steps: [],
|
|
36
|
+
success: false,
|
|
37
|
+
error: `No entity named "${missing}" in this knowledge base. Use List Entities to see what exists.`,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
found: result.found,
|
|
43
|
+
hops: result.hops,
|
|
44
|
+
steps: result.steps,
|
|
45
|
+
success: true,
|
|
46
|
+
error: null,
|
|
47
|
+
};
|
|
48
|
+
} catch (error) {
|
|
49
|
+
return { found: false, hops: 0, steps: [], success: false, error: error.message };
|
|
50
|
+
}
|
|
51
|
+
};
|
|
@@ -18,6 +18,14 @@
|
|
|
18
18
|
"type": "number",
|
|
19
19
|
"description": "The index of the chunk to retrieve (0-based)",
|
|
20
20
|
"required": true
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"name": "knowledge_base",
|
|
24
|
+
"display_name": "Knowledge Base",
|
|
25
|
+
"type": "knowledge_base",
|
|
26
|
+
"description": "The knowledge base, from a Knowledge Base node. If omitted, falls back to the flow's default knowledge base.",
|
|
27
|
+
"required": false,
|
|
28
|
+
"default": null
|
|
21
29
|
}
|
|
22
30
|
],
|
|
23
31
|
"outputs": [
|
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
export default async ({inputs, settings, config, nodeConfig}) => {
|
|
2
2
|
try {
|
|
3
|
-
//
|
|
4
|
-
|
|
3
|
+
// Resolve the KB from a knowledge_base handle (shared wiring), falling
|
|
4
|
+
// back to the flow-global KB. See ADR 0023.
|
|
5
|
+
const kbRef = inputs.knowledge_base;
|
|
6
|
+
const knowledgeBase =
|
|
7
|
+
(kbRef && kbRef.uuid
|
|
8
|
+
? config.integrations?.[`knowledgeBase:${kbRef.uuid}`]
|
|
9
|
+
: null) ||
|
|
10
|
+
config.integrations?.knowledgeBase ||
|
|
11
|
+
config.integrations?.sqlite;
|
|
5
12
|
|
|
6
13
|
if (!knowledgeBase) {
|
|
7
14
|
throw new Error("Knowledge base integration not found. Make sure a knowledge database is available.");
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
{
|
|
2
|
+
"display_name": "Get Entity Neighbors",
|
|
3
|
+
"tagline": "Walk one hop in a graph",
|
|
4
|
+
"description": "Look up an entity in a Graph knowledge base (by name or id) and return every relation touching it, each with the entity on the far side — one hop of graph traversal. Filter by direction or relation type. 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": "entity",
|
|
12
|
+
"display_name": "Entity",
|
|
13
|
+
"type": "string",
|
|
14
|
+
"description": "The entity to look up — its name (case-insensitive) or id.",
|
|
15
|
+
"required": true
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"name": "knowledge_base",
|
|
19
|
+
"display_name": "Knowledge Base",
|
|
20
|
+
"type": "knowledge_base",
|
|
21
|
+
"description": "The knowledge base to traverse, 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": "direction",
|
|
27
|
+
"display_name": "Direction",
|
|
28
|
+
"type": "string",
|
|
29
|
+
"description": "Which relations to include: both, out (entity is the source), or in (entity is the target).",
|
|
30
|
+
"default": "both",
|
|
31
|
+
"options": ["both", "out", "in"]
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"name": "relation_type",
|
|
35
|
+
"display_name": "Relation Type",
|
|
36
|
+
"type": "string",
|
|
37
|
+
"description": "Optional: only relations of this type (e.g. works_at). Case-insensitive.",
|
|
38
|
+
"default": null
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"name": "limit",
|
|
42
|
+
"display_name": "Limit",
|
|
43
|
+
"type": "number",
|
|
44
|
+
"description": "Maximum number of neighbors to return",
|
|
45
|
+
"default": 50
|
|
46
|
+
}
|
|
47
|
+
],
|
|
48
|
+
"outputs": [
|
|
49
|
+
{
|
|
50
|
+
"name": "entity",
|
|
51
|
+
"display_name": "Entity",
|
|
52
|
+
"type": "object",
|
|
53
|
+
"description": "The resolved entity row (null if not found)"
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"name": "neighbors",
|
|
57
|
+
"display_name": "Neighbors",
|
|
58
|
+
"type": "array",
|
|
59
|
+
"description": "Relations touching the entity: { edge_id, relation_type, description, direction, node }"
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
"name": "count",
|
|
63
|
+
"display_name": "Count",
|
|
64
|
+
"type": "number",
|
|
65
|
+
"description": "Number of neighbors returned"
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"name": "success",
|
|
69
|
+
"display_name": "Success",
|
|
70
|
+
"type": "boolean",
|
|
71
|
+
"description": "Whether the lookup completed successfully"
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
"name": "error",
|
|
75
|
+
"display_name": "Error",
|
|
76
|
+
"type": "string",
|
|
77
|
+
"description": "Error message if it failed (including entity not found)"
|
|
78
|
+
}
|
|
79
|
+
],
|
|
80
|
+
"settings": []
|
|
81
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
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 { entity, direction = "both", relation_type = null, limit = 50 } = inputs;
|
|
19
|
+
if (typeof entity !== "string" || entity.trim().length === 0) {
|
|
20
|
+
throw new Error("An entity name or id is required.");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const result = await knowledgeBase.getNeighbors(entity.trim(), {
|
|
24
|
+
direction,
|
|
25
|
+
relation_type,
|
|
26
|
+
limit,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
if (!result.entity) {
|
|
30
|
+
// Soft-fail with a helpful message so an agent can recover by
|
|
31
|
+
// listing entities instead of dead-ending.
|
|
32
|
+
return {
|
|
33
|
+
entity: null,
|
|
34
|
+
neighbors: [],
|
|
35
|
+
count: 0,
|
|
36
|
+
success: false,
|
|
37
|
+
error: `No entity named "${entity}" in this knowledge base. Use List Entities to see what exists.`,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
entity: result.entity,
|
|
43
|
+
neighbors: result.neighbors,
|
|
44
|
+
count: result.neighbors.length,
|
|
45
|
+
success: true,
|
|
46
|
+
error: null,
|
|
47
|
+
};
|
|
48
|
+
} catch (error) {
|
|
49
|
+
return { entity: null, neighbors: [], count: 0, success: false, error: error.message };
|
|
50
|
+
}
|
|
51
|
+
};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
{
|
|
2
|
+
"display_name": "Keyword Search",
|
|
3
|
+
"tagline": "Search by exact words",
|
|
4
|
+
"description": "Search a knowledge base for chunks containing a word or phrase (case-insensitive substring match). Free and instant — no embeddings. The exact-term complement to Semantic Search; wire a Knowledge Base node into its knowledge_base input the same way.",
|
|
5
|
+
"category": "knowledge",
|
|
6
|
+
"is_constant": false,
|
|
7
|
+
"is_plugin": true,
|
|
8
|
+
"is_output": false,
|
|
9
|
+
"inputs": [
|
|
10
|
+
{
|
|
11
|
+
"name": "query",
|
|
12
|
+
"display_name": "Search Text",
|
|
13
|
+
"type": "string",
|
|
14
|
+
"description": "Word or phrase to match within chunk content",
|
|
15
|
+
"required": true
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"name": "knowledge_base",
|
|
19
|
+
"display_name": "Knowledge Base",
|
|
20
|
+
"type": "knowledge_base",
|
|
21
|
+
"description": "The knowledge base to search, 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": "limit",
|
|
27
|
+
"display_name": "Result Limit",
|
|
28
|
+
"type": "number",
|
|
29
|
+
"description": "Maximum number of results to return",
|
|
30
|
+
"default": 10
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"name": "document_id",
|
|
34
|
+
"display_name": "Document ID Filter",
|
|
35
|
+
"type": "string",
|
|
36
|
+
"description": "Optional: restrict results to a specific document uuid.",
|
|
37
|
+
"default": null
|
|
38
|
+
}
|
|
39
|
+
],
|
|
40
|
+
"outputs": [
|
|
41
|
+
{
|
|
42
|
+
"name": "results",
|
|
43
|
+
"display_name": "Search Results",
|
|
44
|
+
"type": "array",
|
|
45
|
+
"description": "Matching chunks, most matches first"
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"name": "count",
|
|
49
|
+
"display_name": "Result Count",
|
|
50
|
+
"type": "number",
|
|
51
|
+
"description": "Number of results returned"
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"name": "success",
|
|
55
|
+
"display_name": "Success",
|
|
56
|
+
"type": "boolean",
|
|
57
|
+
"description": "Whether the search completed successfully"
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"name": "error",
|
|
61
|
+
"display_name": "Error",
|
|
62
|
+
"type": "string",
|
|
63
|
+
"description": "Error message if search failed"
|
|
64
|
+
}
|
|
65
|
+
],
|
|
66
|
+
"settings": []
|
|
67
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export default async ({ inputs, config }) => {
|
|
2
|
+
try {
|
|
3
|
+
// Resolve which knowledge base to search — SAME wiring as semantic-search
|
|
4
|
+
// and query-knowledge-base: a `knowledge_base` handle wired in from a
|
|
5
|
+
// Knowledge Base node names a specific KB by uuid (resolved to a per-KB
|
|
6
|
+
// integration keyed knowledgeBase:<uuid>), falling back to the
|
|
7
|
+
// flow-global KB. See ADR 0023.
|
|
8
|
+
const kbRef = inputs.knowledge_base;
|
|
9
|
+
const knowledgeBase =
|
|
10
|
+
(kbRef && kbRef.uuid
|
|
11
|
+
? config.integrations?.[`knowledgeBase:${kbRef.uuid}`]
|
|
12
|
+
: null) ||
|
|
13
|
+
config.integrations?.knowledgeBase ||
|
|
14
|
+
config.integrations?.sqlite;
|
|
15
|
+
|
|
16
|
+
if (!knowledgeBase) {
|
|
17
|
+
throw new Error("Knowledge base integration not found. Make sure a knowledge database is available.");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const { query, limit = 10, document_id = null } = inputs;
|
|
21
|
+
|
|
22
|
+
if (!query || typeof query !== 'string') {
|
|
23
|
+
throw new Error("Query is required and must be a string");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// No embeddings — a case-insensitive substring match over chunk content.
|
|
27
|
+
const results = await knowledgeBase.keywordSearch(query, { limit, document_id });
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
results: results,
|
|
31
|
+
count: results.length,
|
|
32
|
+
success: true,
|
|
33
|
+
error: null
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
} catch (error) {
|
|
37
|
+
// Return error information instead of throwing to prevent engine crash
|
|
38
|
+
return {
|
|
39
|
+
results: [],
|
|
40
|
+
count: 0,
|
|
41
|
+
success: false,
|
|
42
|
+
error: error.message
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
};
|