@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
|
@@ -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
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"display_name": "Firecrawl Search",
|
|
3
|
+
"tagline": "Search the web with Firecrawl",
|
|
4
|
+
"description": "Search the web using the Firecrawl Search API - returns clean search results with titles, links, and snippets",
|
|
5
|
+
"category": "third-party",
|
|
6
|
+
"provider": "firecrawl",
|
|
7
|
+
"needs_key_from": ["firecrawl"],
|
|
8
|
+
"is_plugin": true,
|
|
9
|
+
"inputs": [
|
|
10
|
+
{
|
|
11
|
+
"name": "query",
|
|
12
|
+
"display_name": "Search Query",
|
|
13
|
+
"type": "string",
|
|
14
|
+
"description": "The search query string",
|
|
15
|
+
"required": true
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"name": "limit",
|
|
19
|
+
"display_name": "Number of Results",
|
|
20
|
+
"type": "number",
|
|
21
|
+
"description": "Maximum number of search results to return (1-100)",
|
|
22
|
+
"default": 5
|
|
23
|
+
}
|
|
24
|
+
],
|
|
25
|
+
"outputs": [
|
|
26
|
+
{
|
|
27
|
+
"name": "items",
|
|
28
|
+
"display_name": "Search Results",
|
|
29
|
+
"type": "array of objects",
|
|
30
|
+
"description": "Array of cleaned search result items with title, link, displayLink, snippet, and position"
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"name": "total_results",
|
|
34
|
+
"display_name": "Total Results",
|
|
35
|
+
"type": "number",
|
|
36
|
+
"description": "Number of search results returned"
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"name": "warning",
|
|
40
|
+
"display_name": "Warning",
|
|
41
|
+
"type": "string",
|
|
42
|
+
"description": "Any warning message returned by the Firecrawl API"
|
|
43
|
+
}
|
|
44
|
+
],
|
|
45
|
+
"settings": []
|
|
46
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
export default async ({inputs, settings, config, nodeConfig}) => {
|
|
2
|
+
// Get Firecrawl integration from engine
|
|
3
|
+
const firecrawl = config.integrations?.firecrawl;
|
|
4
|
+
if (!firecrawl) {
|
|
5
|
+
throw new Error("Firecrawl integration not found");
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
// Build parameters
|
|
9
|
+
const params = {
|
|
10
|
+
query: inputs.query
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
// Clamp limit into Firecrawl's accepted 1-100 range
|
|
14
|
+
if (inputs.limit !== null && inputs.limit !== undefined && inputs.limit !== '') {
|
|
15
|
+
const limit = Number(inputs.limit);
|
|
16
|
+
if (!Number.isNaN(limit)) {
|
|
17
|
+
params.limit = Math.max(1, Math.min(100, Math.floor(limit)));
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Make API request - let errors bubble up to be caught by error manager
|
|
22
|
+
const response = await firecrawl.search(params);
|
|
23
|
+
|
|
24
|
+
// Firecrawl v2 keys results by source ({ web: [...], news: [...], images: [...] });
|
|
25
|
+
// tolerate a flat array (v1-style envelope) as well.
|
|
26
|
+
const data = response.data || {};
|
|
27
|
+
const rawResults = Array.isArray(data) ? data : (data.web || []);
|
|
28
|
+
|
|
29
|
+
// Clean up the items to match the shared search-result shape
|
|
30
|
+
// (title / link / displayLink / snippet) used by search nodes.
|
|
31
|
+
const items = rawResults.map((result, index) => {
|
|
32
|
+
let displayLink = null;
|
|
33
|
+
if (result.url) {
|
|
34
|
+
try {
|
|
35
|
+
displayLink = new URL(result.url).hostname;
|
|
36
|
+
} catch {
|
|
37
|
+
displayLink = null;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return {
|
|
41
|
+
title: result.title || null,
|
|
42
|
+
link: result.url || null,
|
|
43
|
+
displayLink: displayLink,
|
|
44
|
+
snippet: result.description || null,
|
|
45
|
+
position: result.position !== undefined && result.position !== null ? result.position : index + 1
|
|
46
|
+
};
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
items: items,
|
|
51
|
+
total_results: items.length,
|
|
52
|
+
warning: response.warning || null
|
|
53
|
+
};
|
|
54
|
+
};
|
|
@@ -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
|
+
};
|