Skip to content

Commit 2c8a7bd

Browse files
committed
refactor(api, web): register RagSearchNode and update workflow components to support new dataset handling for RAGSearch
1 parent d511b8d commit 2c8a7bd

4 files changed

Lines changed: 167 additions & 2 deletions

File tree

apps/api/src/nodes/node-registry.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { Node, WorkflowType } from "@dafthunk/types";
22
import { NodeType } from "@dafthunk/types";
33

4-
import { RagAiSearchNode } from "./rag/rag-ai-search-node";
54
import { AudioRecorderNode } from "./audio/audio-recorder-node";
65
import { MelottsNode } from "./audio/melotts-node";
76
import { WhisperLargeV3TurboNode } from "./audio/whisper-large-v3-turbo-node";
@@ -87,6 +86,8 @@ import { FormDataBooleanNode } from "./parameter/form-data-boolean-node";
8786
import { FormDataNumberNode } from "./parameter/form-data-number-node";
8887
import { FormDataStringNode } from "./parameter/form-data-string-node";
8988
import { JsonBodyNode } from "./parameter/json-body-node";
89+
import { RagAiSearchNode } from "./rag/rag-ai-search-node";
90+
import { RagSearchNode } from "./rag/rag-search-node";
9091
import { BartLargeCnnNode } from "./text/bart-large-cnn-node";
9192
import { BgeRerankerBaseNode } from "./text/bge-reranker-base-node";
9293
import { DeepseekR1DistillQwen32BNode } from "./text/deepseek-r1-distill-qwen-32b-node";
@@ -150,6 +151,7 @@ export class NodeRegistry {
150151
);
151152

152153
this.registerImplementation(RagAiSearchNode);
154+
this.registerImplementation(RagSearchNode);
153155
this.registerImplementation(FormDataStringNode);
154156
this.registerImplementation(FormDataNumberNode);
155157
this.registerImplementation(FormDataBooleanNode);
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
import { NodeExecution, NodeType } from "@dafthunk/types";
2+
3+
import { ExecutableNode } from "../types";
4+
import { NodeContext } from "../types";
5+
6+
/**
7+
* RAG Search node implementation
8+
* This node provides a dataset selector widget and performs search
9+
* using Cloudflare's AutoRAG search() method with multi-tenant folder filtering.
10+
* Returns search results without AI-generated response.
11+
*/
12+
export class RagSearchNode extends ExecutableNode {
13+
public static readonly nodeType: NodeType = {
14+
id: "rag-search",
15+
name: "RAG Search",
16+
type: "rag-search",
17+
description: "Search through datasets and return relevant results",
18+
category: "AI",
19+
icon: "search",
20+
inputs: [
21+
{
22+
name: "query",
23+
type: "string",
24+
description: "The search query to find relevant content",
25+
required: true,
26+
},
27+
{
28+
name: "datasetId",
29+
type: "string",
30+
description: "Selected dataset ID for the search",
31+
hidden: true,
32+
required: true,
33+
},
34+
{
35+
name: "rewriteQuery",
36+
type: "boolean",
37+
description: "Rewrite query for better search optimization",
38+
hidden: true,
39+
value: true,
40+
},
41+
{
42+
name: "maxResults",
43+
type: "number",
44+
description: "Maximum number of results to return",
45+
hidden: true,
46+
value: 10,
47+
},
48+
{
49+
name: "scoreThreshold",
50+
type: "number",
51+
description: "Minimum match score for results",
52+
hidden: true,
53+
value: 0.3,
54+
},
55+
],
56+
outputs: [
57+
{
58+
name: "searchResults",
59+
type: "json",
60+
description: "Search results from the dataset",
61+
},
62+
{
63+
name: "searchQuery",
64+
type: "string",
65+
description: "The processed search query that was used",
66+
hidden: true,
67+
},
68+
{
69+
name: "hasMore",
70+
type: "boolean",
71+
description: "Whether there are more results available",
72+
hidden: true,
73+
},
74+
],
75+
};
76+
77+
async execute(context: NodeContext): Promise<NodeExecution> {
78+
try {
79+
const { query, datasetId, rewriteQuery, maxResults, scoreThreshold } =
80+
context.inputs;
81+
82+
const { organizationId } = context;
83+
84+
// Validate required inputs
85+
if (!query || typeof query !== "string") {
86+
return this.createErrorResult("Query is required and must be a string");
87+
}
88+
89+
if (!datasetId || typeof datasetId !== "string") {
90+
return this.createErrorResult("Dataset ID is required");
91+
}
92+
93+
if (!organizationId || typeof organizationId !== "string") {
94+
return this.createErrorResult("Organization ID is required");
95+
}
96+
97+
if (!context.env.AI) {
98+
return this.createErrorResult("AI binding is not available");
99+
}
100+
101+
// Create multi-tenant folder filter
102+
const folderPrefix = `${organizationId}/${datasetId}/`;
103+
104+
// Prepare AutoRAG search parameters
105+
const searchParams = {
106+
query: query.trim(),
107+
rewrite_query: Boolean(rewriteQuery),
108+
max_num_results: Math.min(Math.max(Number(maxResults) || 10, 1), 50),
109+
ranking_options: {
110+
score_threshold: Math.min(
111+
Math.max(Number(scoreThreshold) || 0.3, 0),
112+
1
113+
),
114+
},
115+
filters: {
116+
type: "eq" as const,
117+
key: "folder",
118+
value: folderPrefix,
119+
},
120+
};
121+
122+
// Execute AutoRAG search (search only, no generation)
123+
const autoragInstance = context.env.AI.autorag(
124+
context.env.DATASETS_AUTORAG
125+
);
126+
const result = await autoragInstance.search(searchParams);
127+
128+
// Handle the response
129+
let searchResults = null;
130+
let searchQuery = "";
131+
let hasMore = false;
132+
133+
if (result && typeof result === "object") {
134+
searchResults = (result as any).data || [];
135+
searchQuery = (result as any).search_query || query;
136+
hasMore = (result as any).has_more || false;
137+
}
138+
139+
return this.createSuccessResult({
140+
searchResults,
141+
searchQuery,
142+
hasMore,
143+
});
144+
} catch (error) {
145+
return this.createErrorResult(
146+
error instanceof Error
147+
? error.message
148+
: "Unknown error during RAG search"
149+
);
150+
}
151+
}
152+
}

apps/web/src/components/workflow/widgets/widget-factory.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,16 @@ export function createWidgetConfig(
317317
value: value || "",
318318
};
319319
}
320+
case "rag-search": {
321+
const value = inputs.find((i) => i.id === "datasetId")?.value as string;
322+
323+
return {
324+
type: "dataset-selector",
325+
id: nodeId,
326+
name: "Dataset Selector",
327+
value: value || "",
328+
};
329+
}
320330
default:
321331
return null;
322332
}

apps/web/src/components/workflow/workflow-node.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,7 @@ export const WorkflowNode = memo(
189189
"audio-recorder": AudioRecorderWidget,
190190
document: DocumentWidget,
191191
"rag-ai-search": DatasetSelectorWidget,
192+
"rag-search": DatasetSelectorWidget,
192193
};
193194

194195
// Get widget configuration if this is a widget node
@@ -201,7 +202,7 @@ export const WorkflowNode = memo(
201202
if (readonly || !updateNodeData || !widgetConfig) return;
202203

203204
// Handle dataset selector specifically
204-
if (nodeType === "rag-ai-search") {
205+
if (nodeType === "rag-ai-search" || nodeType === "rag-search") {
205206
const datasetIdInput = data.inputs.find((i) => i.id === "datasetId");
206207
if (datasetIdInput) {
207208
updateNodeInput(

0 commit comments

Comments
 (0)