@n8n/n8n-nodes-langchain 1.92.0 → 1.92.2
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/dist/nodes/agents/Agent/agents/ToolsAgent/description.js +0 -23
- package/dist/nodes/agents/Agent/agents/ToolsAgent/description.js.map +1 -1
- package/dist/nodes/agents/Agent/agents/ToolsAgent/execute.js +13 -28
- package/dist/nodes/agents/Agent/agents/ToolsAgent/execute.js.map +1 -1
- package/dist/nodes/chains/ChainLLM/ChainLlm.node.js +21 -39
- package/dist/nodes/chains/ChainLLM/ChainLlm.node.js.map +1 -1
- package/dist/nodes/chains/ChainLLM/methods/config.js +0 -24
- package/dist/nodes/chains/ChainLLM/methods/config.js.map +1 -1
- package/dist/nodes/chains/ChainRetrievalQA/ChainRetrievalQa.node.js +15 -55
- package/dist/nodes/chains/ChainRetrievalQA/ChainRetrievalQa.node.js.map +1 -1
- package/dist/nodes/chains/ChainSummarization/V2/ChainSummarizationV2.node.js +11 -54
- package/dist/nodes/chains/ChainSummarization/V2/ChainSummarizationV2.node.js.map +1 -1
- package/dist/nodes/chains/InformationExtractor/InformationExtractor.node.js +24 -66
- package/dist/nodes/chains/InformationExtractor/InformationExtractor.node.js.map +1 -1
- package/dist/nodes/chains/SentimentAnalysis/SentimentAnalysis.node.js +27 -86
- package/dist/nodes/chains/SentimentAnalysis/SentimentAnalysis.node.js.map +1 -1
- package/dist/nodes/chains/TextClassifier/TextClassifier.node.js +47 -81
- package/dist/nodes/chains/TextClassifier/TextClassifier.node.js.map +1 -1
- package/dist/types/nodes.json +7 -7
- package/package.json +3 -3
|
@@ -136,29 +136,6 @@ class SentimentAnalysis {
|
|
|
136
136
|
type: "boolean",
|
|
137
137
|
default: true,
|
|
138
138
|
description: "Whether to enable auto-fixing (may trigger an additional LLM call if output is broken)"
|
|
139
|
-
},
|
|
140
|
-
{
|
|
141
|
-
displayName: "Batch Processing",
|
|
142
|
-
name: "batching",
|
|
143
|
-
type: "collection",
|
|
144
|
-
description: "Batch processing options for rate limiting",
|
|
145
|
-
default: {},
|
|
146
|
-
options: [
|
|
147
|
-
{
|
|
148
|
-
displayName: "Batch Size",
|
|
149
|
-
name: "batchSize",
|
|
150
|
-
default: 100,
|
|
151
|
-
type: "number",
|
|
152
|
-
description: "How many items to process in parallel. This is useful for rate limiting."
|
|
153
|
-
},
|
|
154
|
-
{
|
|
155
|
-
displayName: "Delay Between Batches",
|
|
156
|
-
name: "delayBetweenBatches",
|
|
157
|
-
default: 0,
|
|
158
|
-
type: "number",
|
|
159
|
-
description: "Delay in milliseconds between batches. This is useful for rate limiting."
|
|
160
|
-
}
|
|
161
|
-
]
|
|
162
139
|
}
|
|
163
140
|
]
|
|
164
141
|
}
|
|
@@ -172,33 +149,23 @@ class SentimentAnalysis {
|
|
|
172
149
|
0
|
|
173
150
|
);
|
|
174
151
|
const returnData = [];
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
delayBetweenBatches: 0
|
|
178
|
-
});
|
|
179
|
-
for (let i = 0; i < items.length; i += batchSize) {
|
|
180
|
-
const batch = items.slice(i, i + batchSize);
|
|
181
|
-
const batchPromises = batch.map(async (_item, batchItemIndex) => {
|
|
182
|
-
const itemIndex = i + batchItemIndex;
|
|
152
|
+
for (let i = 0; i < items.length; i++) {
|
|
153
|
+
try {
|
|
183
154
|
const sentimentCategories = this.getNodeParameter(
|
|
184
155
|
"options.categories",
|
|
185
|
-
|
|
156
|
+
i,
|
|
186
157
|
DEFAULT_CATEGORIES
|
|
187
158
|
);
|
|
188
159
|
const categories = sentimentCategories.split(",").map((cat) => cat.trim()).filter(Boolean);
|
|
189
160
|
if (categories.length === 0) {
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
error: new import_n8n_workflow.NodeOperationError(this.getNode(), "No sentiment categories provided", {
|
|
194
|
-
itemIndex
|
|
195
|
-
})
|
|
196
|
-
};
|
|
161
|
+
throw new import_n8n_workflow.NodeOperationError(this.getNode(), "No sentiment categories provided", {
|
|
162
|
+
itemIndex: i
|
|
163
|
+
});
|
|
197
164
|
}
|
|
198
165
|
if (returnData.length === 0) {
|
|
199
166
|
returnData.push(...Array.from({ length: categories.length }, () => []));
|
|
200
167
|
}
|
|
201
|
-
const options = this.getNodeParameter("options",
|
|
168
|
+
const options = this.getNodeParameter("options", i, {});
|
|
202
169
|
const schema = import_zod.z.object({
|
|
203
170
|
sentiment: import_zod.z.enum(categories),
|
|
204
171
|
strength: import_zod.z.number().min(0).max(1).describe("Strength score for sentiment in relation to the category"),
|
|
@@ -208,9 +175,9 @@ class SentimentAnalysis {
|
|
|
208
175
|
const parser = options.enableAutoFixing ? import_output_parsers.OutputFixingParser.fromLLM(llm, structuredParser) : structuredParser;
|
|
209
176
|
const systemPromptTemplate = import_prompts.SystemMessagePromptTemplate.fromTemplate(
|
|
210
177
|
`${options.systemPromptTemplate ?? DEFAULT_SYSTEM_PROMPT_TEMPLATE}
|
|
211
|
-
|
|
178
|
+
{format_instructions}`
|
|
212
179
|
);
|
|
213
|
-
const input = this.getNodeParameter("inputText",
|
|
180
|
+
const input = this.getNodeParameter("inputText", i);
|
|
214
181
|
const inputPrompt = new import_messages.HumanMessage(input);
|
|
215
182
|
const messages = [
|
|
216
183
|
await systemPromptTemplate.format({
|
|
@@ -227,7 +194,7 @@ class SentimentAnalysis {
|
|
|
227
194
|
(s) => s.toLowerCase() === output.sentiment.toLowerCase()
|
|
228
195
|
);
|
|
229
196
|
if (sentimentIndex !== -1) {
|
|
230
|
-
const resultItem = { ...items[
|
|
197
|
+
const resultItem = { ...items[i] };
|
|
231
198
|
const sentimentAnalysis = {
|
|
232
199
|
category: output.sentiment
|
|
233
200
|
};
|
|
@@ -239,53 +206,27 @@ class SentimentAnalysis {
|
|
|
239
206
|
...resultItem.json,
|
|
240
207
|
sentimentAnalysis
|
|
241
208
|
};
|
|
242
|
-
|
|
243
|
-
result: {
|
|
244
|
-
resultItem,
|
|
245
|
-
sentimentIndex
|
|
246
|
-
},
|
|
247
|
-
itemIndex
|
|
248
|
-
};
|
|
209
|
+
returnData[sentimentIndex].push(resultItem);
|
|
249
210
|
}
|
|
250
|
-
return {
|
|
251
|
-
result: {},
|
|
252
|
-
itemIndex
|
|
253
|
-
};
|
|
254
211
|
} catch (error) {
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
itemIndex
|
|
263
|
-
}
|
|
264
|
-
)
|
|
265
|
-
};
|
|
212
|
+
throw new import_n8n_workflow.NodeOperationError(
|
|
213
|
+
this.getNode(),
|
|
214
|
+
"Error during parsing of LLM output, please check your LLM model and configuration",
|
|
215
|
+
{
|
|
216
|
+
itemIndex: i
|
|
217
|
+
}
|
|
218
|
+
);
|
|
266
219
|
}
|
|
267
|
-
})
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
);
|
|
276
|
-
returnData[0].push(...executionErrorData);
|
|
277
|
-
return;
|
|
278
|
-
} else {
|
|
279
|
-
throw error;
|
|
280
|
-
}
|
|
281
|
-
} else if (result.resultItem && result.sentimentIndex) {
|
|
282
|
-
const sentimentIndex = result.sentimentIndex;
|
|
283
|
-
const resultItem = result.resultItem;
|
|
284
|
-
returnData[sentimentIndex].push(resultItem);
|
|
220
|
+
} catch (error) {
|
|
221
|
+
if (this.continueOnFail()) {
|
|
222
|
+
const executionErrorData = this.helpers.constructExecutionMetaData(
|
|
223
|
+
this.helpers.returnJsonArray({ error: error.message }),
|
|
224
|
+
{ itemData: { item: i } }
|
|
225
|
+
);
|
|
226
|
+
returnData[0].push(...executionErrorData);
|
|
227
|
+
continue;
|
|
285
228
|
}
|
|
286
|
-
|
|
287
|
-
if (i + batchSize < items.length && delayBetweenBatches > 0) {
|
|
288
|
-
await (0, import_n8n_workflow.sleep)(delayBetweenBatches);
|
|
229
|
+
throw error;
|
|
289
230
|
}
|
|
290
231
|
}
|
|
291
232
|
return returnData;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../nodes/chains/SentimentAnalysis/SentimentAnalysis.node.ts"],"sourcesContent":["import type { BaseLanguageModel } from '@langchain/core/language_models/base';\nimport { HumanMessage } from '@langchain/core/messages';\nimport { SystemMessagePromptTemplate, ChatPromptTemplate } from '@langchain/core/prompts';\nimport { OutputFixingParser, StructuredOutputParser } from 'langchain/output_parsers';\nimport { NodeConnectionTypes, NodeOperationError, sleep } from 'n8n-workflow';\nimport type {\n\tIDataObject,\n\tIExecuteFunctions,\n\tINodeExecutionData,\n\tINodeParameters,\n\tINodeType,\n\tINodeTypeDescription,\n} from 'n8n-workflow';\nimport { z } from 'zod';\n\nimport { getTracingConfig } from '@utils/tracing';\n\nconst DEFAULT_SYSTEM_PROMPT_TEMPLATE =\n\t'You are highly intelligent and accurate sentiment analyzer. Analyze the sentiment of the provided text. Categorize it into one of the following: {categories}. Use the provided formatting instructions. Only output the JSON.';\n\nconst DEFAULT_CATEGORIES = 'Positive, Neutral, Negative';\nconst configuredOutputs = (parameters: INodeParameters, defaultCategories: string) => {\n\tconst options = (parameters?.options ?? {}) as IDataObject;\n\tconst categories = (options?.categories as string) ?? defaultCategories;\n\tconst categoriesArray = categories.split(',').map((cat) => cat.trim());\n\n\tconst ret = categoriesArray.map((cat) => ({ type: 'main', displayName: cat }));\n\treturn ret;\n};\n\nexport class SentimentAnalysis implements INodeType {\n\tdescription: INodeTypeDescription = {\n\t\tdisplayName: 'Sentiment Analysis',\n\t\tname: 'sentimentAnalysis',\n\t\ticon: 'fa:balance-scale-left',\n\t\ticonColor: 'black',\n\t\tgroup: ['transform'],\n\t\tversion: 1,\n\t\tdescription: 'Analyze the sentiment of your text',\n\t\tcodex: {\n\t\t\tcategories: ['AI'],\n\t\t\tsubcategories: {\n\t\t\t\tAI: ['Chains', 'Root Nodes'],\n\t\t\t},\n\t\t\tresources: {\n\t\t\t\tprimaryDocumentation: [\n\t\t\t\t\t{\n\t\t\t\t\t\turl: 'https://docs.n8n.io/integrations/builtin/cluster-nodes/root-nodes/n8n-nodes-langchain.sentimentanalysis/',\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t},\n\t\t},\n\t\tdefaults: {\n\t\t\tname: 'Sentiment Analysis',\n\t\t},\n\t\tinputs: [\n\t\t\t{ displayName: '', type: NodeConnectionTypes.Main },\n\t\t\t{\n\t\t\t\tdisplayName: 'Model',\n\t\t\t\tmaxConnections: 1,\n\t\t\t\ttype: NodeConnectionTypes.AiLanguageModel,\n\t\t\t\trequired: true,\n\t\t\t},\n\t\t],\n\t\toutputs: `={{(${configuredOutputs})($parameter, \"${DEFAULT_CATEGORIES}\")}}`,\n\t\tproperties: [\n\t\t\t{\n\t\t\t\tdisplayName: 'Text to Analyze',\n\t\t\t\tname: 'inputText',\n\t\t\t\ttype: 'string',\n\t\t\t\trequired: true,\n\t\t\t\tdefault: '',\n\t\t\t\tdescription: 'Use an expression to reference data in previous nodes or enter static text',\n\t\t\t\ttypeOptions: {\n\t\t\t\t\trows: 2,\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tdisplayName:\n\t\t\t\t\t'Sentiment scores are LLM-generated estimates, not statistically rigorous measurements. They may be inconsistent across runs and should be used as rough indicators only.',\n\t\t\t\tname: 'detailedResultsNotice',\n\t\t\t\ttype: 'notice',\n\t\t\t\tdefault: '',\n\t\t\t\tdisplayOptions: {\n\t\t\t\t\tshow: {\n\t\t\t\t\t\t'/options.includeDetailedResults': [true],\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tdisplayName: 'Options',\n\t\t\t\tname: 'options',\n\t\t\t\ttype: 'collection',\n\t\t\t\tdefault: {},\n\t\t\t\tplaceholder: 'Add Option',\n\t\t\t\toptions: [\n\t\t\t\t\t{\n\t\t\t\t\t\tdisplayName: 'Sentiment Categories',\n\t\t\t\t\t\tname: 'categories',\n\t\t\t\t\t\ttype: 'string',\n\t\t\t\t\t\tdefault: DEFAULT_CATEGORIES,\n\t\t\t\t\t\tdescription: 'A comma-separated list of categories to analyze',\n\t\t\t\t\t\tnoDataExpression: true,\n\t\t\t\t\t\ttypeOptions: {\n\t\t\t\t\t\t\trows: 2,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tdisplayName: 'System Prompt Template',\n\t\t\t\t\t\tname: 'systemPromptTemplate',\n\t\t\t\t\t\ttype: 'string',\n\t\t\t\t\t\tdefault: DEFAULT_SYSTEM_PROMPT_TEMPLATE,\n\t\t\t\t\t\tdescription: 'String to use directly as the system prompt template',\n\t\t\t\t\t\ttypeOptions: {\n\t\t\t\t\t\t\trows: 6,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tdisplayName: 'Include Detailed Results',\n\t\t\t\t\t\tname: 'includeDetailedResults',\n\t\t\t\t\t\ttype: 'boolean',\n\t\t\t\t\t\tdefault: false,\n\t\t\t\t\t\tdescription:\n\t\t\t\t\t\t\t'Whether to include sentiment strength and confidence scores in the output',\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tdisplayName: 'Enable Auto-Fixing',\n\t\t\t\t\t\tname: 'enableAutoFixing',\n\t\t\t\t\t\ttype: 'boolean',\n\t\t\t\t\t\tdefault: true,\n\t\t\t\t\t\tdescription:\n\t\t\t\t\t\t\t'Whether to enable auto-fixing (may trigger an additional LLM call if output is broken)',\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tdisplayName: 'Batch Processing',\n\t\t\t\t\t\tname: 'batching',\n\t\t\t\t\t\ttype: 'collection',\n\t\t\t\t\t\tdescription: 'Batch processing options for rate limiting',\n\t\t\t\t\t\tdefault: {},\n\t\t\t\t\t\toptions: [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tdisplayName: 'Batch Size',\n\t\t\t\t\t\t\t\tname: 'batchSize',\n\t\t\t\t\t\t\t\tdefault: 100,\n\t\t\t\t\t\t\t\ttype: 'number',\n\t\t\t\t\t\t\t\tdescription:\n\t\t\t\t\t\t\t\t\t'How many items to process in parallel. This is useful for rate limiting.',\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tdisplayName: 'Delay Between Batches',\n\t\t\t\t\t\t\t\tname: 'delayBetweenBatches',\n\t\t\t\t\t\t\t\tdefault: 0,\n\t\t\t\t\t\t\t\ttype: 'number',\n\t\t\t\t\t\t\t\tdescription:\n\t\t\t\t\t\t\t\t\t'Delay in milliseconds between batches. This is useful for rate limiting.',\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t],\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t},\n\t\t],\n\t};\n\n\tasync execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {\n\t\tconst items = this.getInputData();\n\n\t\tconst llm = (await this.getInputConnectionData(\n\t\t\tNodeConnectionTypes.AiLanguageModel,\n\t\t\t0,\n\t\t)) as BaseLanguageModel;\n\n\t\tconst returnData: INodeExecutionData[][] = [];\n\t\tconst { batchSize, delayBetweenBatches } = this.getNodeParameter('options.batching', 0, {\n\t\t\tbatchSize: 100,\n\t\t\tdelayBetweenBatches: 0,\n\t\t}) as {\n\t\t\tbatchSize: number;\n\t\t\tdelayBetweenBatches: number;\n\t\t};\n\n\t\tfor (let i = 0; i < items.length; i += batchSize) {\n\t\t\tconst batch = items.slice(i, i + batchSize);\n\t\t\tconst batchPromises = batch.map(async (_item, batchItemIndex) => {\n\t\t\t\tconst itemIndex = i + batchItemIndex;\n\t\t\t\tconst sentimentCategories = this.getNodeParameter(\n\t\t\t\t\t'options.categories',\n\t\t\t\t\titemIndex,\n\t\t\t\t\tDEFAULT_CATEGORIES,\n\t\t\t\t) as string;\n\n\t\t\t\tconst categories = sentimentCategories\n\t\t\t\t\t.split(',')\n\t\t\t\t\t.map((cat) => cat.trim())\n\t\t\t\t\t.filter(Boolean);\n\n\t\t\t\tif (categories.length === 0) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tresult: null,\n\t\t\t\t\t\titemIndex,\n\t\t\t\t\t\terror: new NodeOperationError(this.getNode(), 'No sentiment categories provided', {\n\t\t\t\t\t\t\titemIndex,\n\t\t\t\t\t\t}),\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\t// Initialize returnData with empty arrays for each category\n\t\t\t\tif (returnData.length === 0) {\n\t\t\t\t\treturnData.push(...Array.from({ length: categories.length }, () => []));\n\t\t\t\t}\n\n\t\t\t\tconst options = this.getNodeParameter('options', itemIndex, {}) as {\n\t\t\t\t\tsystemPromptTemplate?: string;\n\t\t\t\t\tincludeDetailedResults?: boolean;\n\t\t\t\t\tenableAutoFixing?: boolean;\n\t\t\t\t};\n\n\t\t\t\tconst schema = z.object({\n\t\t\t\t\tsentiment: z.enum(categories as [string, ...string[]]),\n\t\t\t\t\tstrength: z\n\t\t\t\t\t\t.number()\n\t\t\t\t\t\t.min(0)\n\t\t\t\t\t\t.max(1)\n\t\t\t\t\t\t.describe('Strength score for sentiment in relation to the category'),\n\t\t\t\t\tconfidence: z.number().min(0).max(1),\n\t\t\t\t});\n\n\t\t\t\tconst structuredParser = StructuredOutputParser.fromZodSchema(schema);\n\n\t\t\t\tconst parser = options.enableAutoFixing\n\t\t\t\t\t? OutputFixingParser.fromLLM(llm, structuredParser)\n\t\t\t\t\t: structuredParser;\n\n\t\t\t\tconst systemPromptTemplate = SystemMessagePromptTemplate.fromTemplate(\n\t\t\t\t\t`${options.systemPromptTemplate ?? DEFAULT_SYSTEM_PROMPT_TEMPLATE}\n\t\t\t{format_instructions}`,\n\t\t\t\t);\n\n\t\t\t\tconst input = this.getNodeParameter('inputText', itemIndex) as string;\n\t\t\t\tconst inputPrompt = new HumanMessage(input);\n\t\t\t\tconst messages = [\n\t\t\t\t\tawait systemPromptTemplate.format({\n\t\t\t\t\t\tcategories: sentimentCategories,\n\t\t\t\t\t\tformat_instructions: parser.getFormatInstructions(),\n\t\t\t\t\t}),\n\t\t\t\t\tinputPrompt,\n\t\t\t\t];\n\n\t\t\t\tconst prompt = ChatPromptTemplate.fromMessages(messages);\n\t\t\t\tconst chain = prompt.pipe(llm).pipe(parser).withConfig(getTracingConfig(this));\n\n\t\t\t\ttry {\n\t\t\t\t\tconst output = await chain.invoke(messages);\n\t\t\t\t\tconst sentimentIndex = categories.findIndex(\n\t\t\t\t\t\t(s) => s.toLowerCase() === output.sentiment.toLowerCase(),\n\t\t\t\t\t);\n\n\t\t\t\t\tif (sentimentIndex !== -1) {\n\t\t\t\t\t\tconst resultItem = { ...items[itemIndex] };\n\t\t\t\t\t\tconst sentimentAnalysis: IDataObject = {\n\t\t\t\t\t\t\tcategory: output.sentiment,\n\t\t\t\t\t\t};\n\t\t\t\t\t\tif (options.includeDetailedResults) {\n\t\t\t\t\t\t\tsentimentAnalysis.strength = output.strength;\n\t\t\t\t\t\t\tsentimentAnalysis.confidence = output.confidence;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tresultItem.json = {\n\t\t\t\t\t\t\t...resultItem.json,\n\t\t\t\t\t\t\tsentimentAnalysis,\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tresult: {\n\t\t\t\t\t\t\t\tresultItem,\n\t\t\t\t\t\t\t\tsentimentIndex,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\titemIndex,\n\t\t\t\t\t\t};\n\t\t\t\t\t}\n\n\t\t\t\t\treturn {\n\t\t\t\t\t\tresult: {},\n\t\t\t\t\t\titemIndex,\n\t\t\t\t\t};\n\t\t\t\t} catch (error) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tresult: null,\n\t\t\t\t\t\titemIndex,\n\t\t\t\t\t\terror: new NodeOperationError(\n\t\t\t\t\t\t\tthis.getNode(),\n\t\t\t\t\t\t\t'Error during parsing of LLM output, please check your LLM model and configuration',\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\titemIndex,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t),\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t});\n\t\t\tconst batchResults = await Promise.all(batchPromises);\n\n\t\t\tbatchResults.forEach(({ result, itemIndex, error }) => {\n\t\t\t\tif (error) {\n\t\t\t\t\tif (this.continueOnFail()) {\n\t\t\t\t\t\tconst executionErrorData = this.helpers.constructExecutionMetaData(\n\t\t\t\t\t\t\tthis.helpers.returnJsonArray({ error: error.message }),\n\t\t\t\t\t\t\t{ itemData: { item: itemIndex } },\n\t\t\t\t\t\t);\n\n\t\t\t\t\t\treturnData[0].push(...executionErrorData);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthrow error;\n\t\t\t\t\t}\n\t\t\t\t} else if (result.resultItem && result.sentimentIndex) {\n\t\t\t\t\tconst sentimentIndex = result.sentimentIndex;\n\t\t\t\t\tconst resultItem = result.resultItem;\n\t\t\t\t\treturnData[sentimentIndex].push(resultItem);\n\t\t\t\t}\n\t\t\t});\n\n\t\t\t// Add delay between batches if not the last batch\n\t\t\tif (i + batchSize < items.length && delayBetweenBatches > 0) {\n\t\t\t\tawait sleep(delayBetweenBatches);\n\t\t\t}\n\t\t}\n\t\treturn returnData;\n\t}\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../../nodes/chains/SentimentAnalysis/SentimentAnalysis.node.ts"],"sourcesContent":["import type { BaseLanguageModel } from '@langchain/core/language_models/base';\nimport { HumanMessage } from '@langchain/core/messages';\nimport { SystemMessagePromptTemplate, ChatPromptTemplate } from '@langchain/core/prompts';\nimport { OutputFixingParser, StructuredOutputParser } from 'langchain/output_parsers';\nimport { NodeConnectionTypes, NodeOperationError } from 'n8n-workflow';\nimport type {\n\tIDataObject,\n\tIExecuteFunctions,\n\tINodeExecutionData,\n\tINodeParameters,\n\tINodeType,\n\tINodeTypeDescription,\n} from 'n8n-workflow';\nimport { z } from 'zod';\n\nimport { getTracingConfig } from '@utils/tracing';\n\nconst DEFAULT_SYSTEM_PROMPT_TEMPLATE =\n\t'You are highly intelligent and accurate sentiment analyzer. Analyze the sentiment of the provided text. Categorize it into one of the following: {categories}. Use the provided formatting instructions. Only output the JSON.';\n\nconst DEFAULT_CATEGORIES = 'Positive, Neutral, Negative';\nconst configuredOutputs = (parameters: INodeParameters, defaultCategories: string) => {\n\tconst options = (parameters?.options ?? {}) as IDataObject;\n\tconst categories = (options?.categories as string) ?? defaultCategories;\n\tconst categoriesArray = categories.split(',').map((cat) => cat.trim());\n\n\tconst ret = categoriesArray.map((cat) => ({ type: 'main', displayName: cat }));\n\treturn ret;\n};\n\nexport class SentimentAnalysis implements INodeType {\n\tdescription: INodeTypeDescription = {\n\t\tdisplayName: 'Sentiment Analysis',\n\t\tname: 'sentimentAnalysis',\n\t\ticon: 'fa:balance-scale-left',\n\t\ticonColor: 'black',\n\t\tgroup: ['transform'],\n\t\tversion: 1,\n\t\tdescription: 'Analyze the sentiment of your text',\n\t\tcodex: {\n\t\t\tcategories: ['AI'],\n\t\t\tsubcategories: {\n\t\t\t\tAI: ['Chains', 'Root Nodes'],\n\t\t\t},\n\t\t\tresources: {\n\t\t\t\tprimaryDocumentation: [\n\t\t\t\t\t{\n\t\t\t\t\t\turl: 'https://docs.n8n.io/integrations/builtin/cluster-nodes/root-nodes/n8n-nodes-langchain.sentimentanalysis/',\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t},\n\t\t},\n\t\tdefaults: {\n\t\t\tname: 'Sentiment Analysis',\n\t\t},\n\t\tinputs: [\n\t\t\t{ displayName: '', type: NodeConnectionTypes.Main },\n\t\t\t{\n\t\t\t\tdisplayName: 'Model',\n\t\t\t\tmaxConnections: 1,\n\t\t\t\ttype: NodeConnectionTypes.AiLanguageModel,\n\t\t\t\trequired: true,\n\t\t\t},\n\t\t],\n\t\toutputs: `={{(${configuredOutputs})($parameter, \"${DEFAULT_CATEGORIES}\")}}`,\n\t\tproperties: [\n\t\t\t{\n\t\t\t\tdisplayName: 'Text to Analyze',\n\t\t\t\tname: 'inputText',\n\t\t\t\ttype: 'string',\n\t\t\t\trequired: true,\n\t\t\t\tdefault: '',\n\t\t\t\tdescription: 'Use an expression to reference data in previous nodes or enter static text',\n\t\t\t\ttypeOptions: {\n\t\t\t\t\trows: 2,\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tdisplayName:\n\t\t\t\t\t'Sentiment scores are LLM-generated estimates, not statistically rigorous measurements. They may be inconsistent across runs and should be used as rough indicators only.',\n\t\t\t\tname: 'detailedResultsNotice',\n\t\t\t\ttype: 'notice',\n\t\t\t\tdefault: '',\n\t\t\t\tdisplayOptions: {\n\t\t\t\t\tshow: {\n\t\t\t\t\t\t'/options.includeDetailedResults': [true],\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tdisplayName: 'Options',\n\t\t\t\tname: 'options',\n\t\t\t\ttype: 'collection',\n\t\t\t\tdefault: {},\n\t\t\t\tplaceholder: 'Add Option',\n\t\t\t\toptions: [\n\t\t\t\t\t{\n\t\t\t\t\t\tdisplayName: 'Sentiment Categories',\n\t\t\t\t\t\tname: 'categories',\n\t\t\t\t\t\ttype: 'string',\n\t\t\t\t\t\tdefault: DEFAULT_CATEGORIES,\n\t\t\t\t\t\tdescription: 'A comma-separated list of categories to analyze',\n\t\t\t\t\t\tnoDataExpression: true,\n\t\t\t\t\t\ttypeOptions: {\n\t\t\t\t\t\t\trows: 2,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tdisplayName: 'System Prompt Template',\n\t\t\t\t\t\tname: 'systemPromptTemplate',\n\t\t\t\t\t\ttype: 'string',\n\t\t\t\t\t\tdefault: DEFAULT_SYSTEM_PROMPT_TEMPLATE,\n\t\t\t\t\t\tdescription: 'String to use directly as the system prompt template',\n\t\t\t\t\t\ttypeOptions: {\n\t\t\t\t\t\t\trows: 6,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tdisplayName: 'Include Detailed Results',\n\t\t\t\t\t\tname: 'includeDetailedResults',\n\t\t\t\t\t\ttype: 'boolean',\n\t\t\t\t\t\tdefault: false,\n\t\t\t\t\t\tdescription:\n\t\t\t\t\t\t\t'Whether to include sentiment strength and confidence scores in the output',\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tdisplayName: 'Enable Auto-Fixing',\n\t\t\t\t\t\tname: 'enableAutoFixing',\n\t\t\t\t\t\ttype: 'boolean',\n\t\t\t\t\t\tdefault: true,\n\t\t\t\t\t\tdescription:\n\t\t\t\t\t\t\t'Whether to enable auto-fixing (may trigger an additional LLM call if output is broken)',\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t},\n\t\t],\n\t};\n\n\tasync execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {\n\t\tconst items = this.getInputData();\n\n\t\tconst llm = (await this.getInputConnectionData(\n\t\t\tNodeConnectionTypes.AiLanguageModel,\n\t\t\t0,\n\t\t)) as BaseLanguageModel;\n\n\t\tconst returnData: INodeExecutionData[][] = [];\n\n\t\tfor (let i = 0; i < items.length; i++) {\n\t\t\ttry {\n\t\t\t\tconst sentimentCategories = this.getNodeParameter(\n\t\t\t\t\t'options.categories',\n\t\t\t\t\ti,\n\t\t\t\t\tDEFAULT_CATEGORIES,\n\t\t\t\t) as string;\n\n\t\t\t\tconst categories = sentimentCategories\n\t\t\t\t\t.split(',')\n\t\t\t\t\t.map((cat) => cat.trim())\n\t\t\t\t\t.filter(Boolean);\n\n\t\t\t\tif (categories.length === 0) {\n\t\t\t\t\tthrow new NodeOperationError(this.getNode(), 'No sentiment categories provided', {\n\t\t\t\t\t\titemIndex: i,\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\t// Initialize returnData with empty arrays for each category\n\t\t\t\tif (returnData.length === 0) {\n\t\t\t\t\treturnData.push(...Array.from({ length: categories.length }, () => []));\n\t\t\t\t}\n\n\t\t\t\tconst options = this.getNodeParameter('options', i, {}) as {\n\t\t\t\t\tsystemPromptTemplate?: string;\n\t\t\t\t\tincludeDetailedResults?: boolean;\n\t\t\t\t\tenableAutoFixing?: boolean;\n\t\t\t\t};\n\n\t\t\t\tconst schema = z.object({\n\t\t\t\t\tsentiment: z.enum(categories as [string, ...string[]]),\n\t\t\t\t\tstrength: z\n\t\t\t\t\t\t.number()\n\t\t\t\t\t\t.min(0)\n\t\t\t\t\t\t.max(1)\n\t\t\t\t\t\t.describe('Strength score for sentiment in relation to the category'),\n\t\t\t\t\tconfidence: z.number().min(0).max(1),\n\t\t\t\t});\n\n\t\t\t\tconst structuredParser = StructuredOutputParser.fromZodSchema(schema);\n\n\t\t\t\tconst parser = options.enableAutoFixing\n\t\t\t\t\t? OutputFixingParser.fromLLM(llm, structuredParser)\n\t\t\t\t\t: structuredParser;\n\n\t\t\t\tconst systemPromptTemplate = SystemMessagePromptTemplate.fromTemplate(\n\t\t\t\t\t`${options.systemPromptTemplate ?? DEFAULT_SYSTEM_PROMPT_TEMPLATE}\n\t\t{format_instructions}`,\n\t\t\t\t);\n\n\t\t\t\tconst input = this.getNodeParameter('inputText', i) as string;\n\t\t\t\tconst inputPrompt = new HumanMessage(input);\n\t\t\t\tconst messages = [\n\t\t\t\t\tawait systemPromptTemplate.format({\n\t\t\t\t\t\tcategories: sentimentCategories,\n\t\t\t\t\t\tformat_instructions: parser.getFormatInstructions(),\n\t\t\t\t\t}),\n\t\t\t\t\tinputPrompt,\n\t\t\t\t];\n\n\t\t\t\tconst prompt = ChatPromptTemplate.fromMessages(messages);\n\t\t\t\tconst chain = prompt.pipe(llm).pipe(parser).withConfig(getTracingConfig(this));\n\n\t\t\t\ttry {\n\t\t\t\t\tconst output = await chain.invoke(messages);\n\t\t\t\t\tconst sentimentIndex = categories.findIndex(\n\t\t\t\t\t\t(s) => s.toLowerCase() === output.sentiment.toLowerCase(),\n\t\t\t\t\t);\n\n\t\t\t\t\tif (sentimentIndex !== -1) {\n\t\t\t\t\t\tconst resultItem = { ...items[i] };\n\t\t\t\t\t\tconst sentimentAnalysis: IDataObject = {\n\t\t\t\t\t\t\tcategory: output.sentiment,\n\t\t\t\t\t\t};\n\t\t\t\t\t\tif (options.includeDetailedResults) {\n\t\t\t\t\t\t\tsentimentAnalysis.strength = output.strength;\n\t\t\t\t\t\t\tsentimentAnalysis.confidence = output.confidence;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tresultItem.json = {\n\t\t\t\t\t\t\t...resultItem.json,\n\t\t\t\t\t\t\tsentimentAnalysis,\n\t\t\t\t\t\t};\n\t\t\t\t\t\treturnData[sentimentIndex].push(resultItem);\n\t\t\t\t\t}\n\t\t\t\t} catch (error) {\n\t\t\t\t\tthrow new NodeOperationError(\n\t\t\t\t\t\tthis.getNode(),\n\t\t\t\t\t\t'Error during parsing of LLM output, please check your LLM model and configuration',\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\titemIndex: i,\n\t\t\t\t\t\t},\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tif (this.continueOnFail()) {\n\t\t\t\t\tconst executionErrorData = this.helpers.constructExecutionMetaData(\n\t\t\t\t\t\tthis.helpers.returnJsonArray({ error: error.message }),\n\t\t\t\t\t\t{ itemData: { item: i } },\n\t\t\t\t\t);\n\t\t\t\t\treturnData[0].push(...executionErrorData);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t}\n\t\treturn returnData;\n\t}\n}\n"],"mappings":"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","names":[]}
|
|
@@ -166,29 +166,6 @@ class TextClassifier {
|
|
|
166
166
|
type: "boolean",
|
|
167
167
|
default: true,
|
|
168
168
|
description: "Whether to enable auto-fixing (may trigger an additional LLM call if output is broken)"
|
|
169
|
-
},
|
|
170
|
-
{
|
|
171
|
-
displayName: "Batch Processing",
|
|
172
|
-
name: "batching",
|
|
173
|
-
type: "collection",
|
|
174
|
-
description: "Batch processing options for rate limiting",
|
|
175
|
-
default: {},
|
|
176
|
-
options: [
|
|
177
|
-
{
|
|
178
|
-
displayName: "Batch Size",
|
|
179
|
-
name: "batchSize",
|
|
180
|
-
default: 100,
|
|
181
|
-
type: "number",
|
|
182
|
-
description: "How many items to process in parallel. This is useful for rate limiting."
|
|
183
|
-
},
|
|
184
|
-
{
|
|
185
|
-
displayName: "Delay Between Batches",
|
|
186
|
-
name: "delayBetweenBatches",
|
|
187
|
-
default: 0,
|
|
188
|
-
type: "number",
|
|
189
|
-
description: "Delay in milliseconds between batches. This is useful for rate limiting."
|
|
190
|
-
}
|
|
191
|
-
]
|
|
192
169
|
}
|
|
193
170
|
]
|
|
194
171
|
}
|
|
@@ -197,10 +174,6 @@ class TextClassifier {
|
|
|
197
174
|
}
|
|
198
175
|
async execute() {
|
|
199
176
|
const items = this.getInputData();
|
|
200
|
-
const { batchSize, delayBetweenBatches } = this.getNodeParameter("options.batching", 0, {
|
|
201
|
-
batchSize: 100,
|
|
202
|
-
delayBetweenBatches: 0
|
|
203
|
-
});
|
|
204
177
|
const llm = await this.getInputConnectionData(
|
|
205
178
|
import_n8n_workflow.NodeConnectionTypes.AiLanguageModel,
|
|
206
179
|
0
|
|
@@ -235,67 +208,60 @@ class TextClassifier {
|
|
|
235
208
|
{ length: categories.length + (fallback === "other" ? 1 : 0) },
|
|
236
209
|
(_) => []
|
|
237
210
|
);
|
|
238
|
-
for (let
|
|
239
|
-
const
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
211
|
+
for (let itemIdx = 0; itemIdx < items.length; itemIdx++) {
|
|
212
|
+
const item = items[itemIdx];
|
|
213
|
+
item.pairedItem = { item: itemIdx };
|
|
214
|
+
const input = this.getNodeParameter("inputText", itemIdx);
|
|
215
|
+
if (input === void 0 || input === null) {
|
|
216
|
+
if (this.continueOnFail()) {
|
|
217
|
+
returnData[0].push({
|
|
218
|
+
json: { error: "Text to classify is not defined" },
|
|
219
|
+
pairedItem: { item: itemIdx }
|
|
220
|
+
});
|
|
221
|
+
continue;
|
|
222
|
+
} else {
|
|
246
223
|
throw new import_n8n_workflow.NodeOperationError(
|
|
247
224
|
this.getNode(),
|
|
248
225
|
`Text to classify for item ${itemIdx} is not defined`
|
|
249
226
|
);
|
|
250
227
|
}
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
}
|
|
284
|
-
return;
|
|
285
|
-
} else {
|
|
286
|
-
throw new import_n8n_workflow.NodeOperationError(this.getNode(), error.message);
|
|
287
|
-
}
|
|
288
|
-
} else {
|
|
289
|
-
const output = response.value;
|
|
290
|
-
const item = items[index];
|
|
291
|
-
categories.forEach((cat, idx) => {
|
|
292
|
-
if (output[cat.category]) returnData[idx].push(item);
|
|
228
|
+
}
|
|
229
|
+
const inputPrompt = new import_messages.HumanMessage(input);
|
|
230
|
+
const systemPromptTemplateOpt = this.getNodeParameter(
|
|
231
|
+
"options.systemPromptTemplate",
|
|
232
|
+
itemIdx,
|
|
233
|
+
SYSTEM_PROMPT_TEMPLATE
|
|
234
|
+
);
|
|
235
|
+
const systemPromptTemplate = import_prompts.SystemMessagePromptTemplate.fromTemplate(
|
|
236
|
+
`${systemPromptTemplateOpt ?? SYSTEM_PROMPT_TEMPLATE}
|
|
237
|
+
{format_instructions}
|
|
238
|
+
${multiClassPrompt}
|
|
239
|
+
${fallbackPrompt}`
|
|
240
|
+
);
|
|
241
|
+
const messages = [
|
|
242
|
+
await systemPromptTemplate.format({
|
|
243
|
+
categories: categories.map((cat) => cat.category).join(", "),
|
|
244
|
+
format_instructions: parser.getFormatInstructions()
|
|
245
|
+
}),
|
|
246
|
+
inputPrompt
|
|
247
|
+
];
|
|
248
|
+
const prompt = import_prompts.ChatPromptTemplate.fromMessages(messages);
|
|
249
|
+
const chain = prompt.pipe(llm).pipe(parser).withConfig((0, import_tracing.getTracingConfig)(this));
|
|
250
|
+
try {
|
|
251
|
+
const output = await chain.invoke(messages);
|
|
252
|
+
categories.forEach((cat, idx) => {
|
|
253
|
+
if (output[cat.category]) returnData[idx].push(item);
|
|
254
|
+
});
|
|
255
|
+
if (fallback === "other" && output.fallback) returnData[returnData.length - 1].push(item);
|
|
256
|
+
} catch (error) {
|
|
257
|
+
if (this.continueOnFail()) {
|
|
258
|
+
returnData[0].push({
|
|
259
|
+
json: { error: error.message },
|
|
260
|
+
pairedItem: { item: itemIdx }
|
|
293
261
|
});
|
|
294
|
-
|
|
262
|
+
continue;
|
|
295
263
|
}
|
|
296
|
-
|
|
297
|
-
if (i + batchSize < items.length && delayBetweenBatches > 0) {
|
|
298
|
-
await (0, import_n8n_workflow.sleep)(delayBetweenBatches);
|
|
264
|
+
throw error;
|
|
299
265
|
}
|
|
300
266
|
}
|
|
301
267
|
return returnData;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../nodes/chains/TextClassifier/TextClassifier.node.ts"],"sourcesContent":["import type { BaseLanguageModel } from '@langchain/core/language_models/base';\nimport { HumanMessage } from '@langchain/core/messages';\nimport { SystemMessagePromptTemplate, ChatPromptTemplate } from '@langchain/core/prompts';\nimport { OutputFixingParser, StructuredOutputParser } from 'langchain/output_parsers';\nimport { NodeOperationError, NodeConnectionTypes, sleep } from 'n8n-workflow';\nimport type {\n\tIDataObject,\n\tIExecuteFunctions,\n\tINodeExecutionData,\n\tINodeParameters,\n\tINodeType,\n\tINodeTypeDescription,\n} from 'n8n-workflow';\nimport { z } from 'zod';\n\nimport { getTracingConfig } from '@utils/tracing';\n\nconst SYSTEM_PROMPT_TEMPLATE =\n\t\"Please classify the text provided by the user into one of the following categories: {categories}, and use the provided formatting instructions below. Don't explain, and only output the json.\";\n\nconst configuredOutputs = (parameters: INodeParameters) => {\n\tconst categories = ((parameters.categories as IDataObject)?.categories as IDataObject[]) ?? [];\n\tconst fallback = (parameters.options as IDataObject)?.fallback as string;\n\tconst ret = categories.map((cat) => {\n\t\treturn { type: 'main', displayName: cat.category };\n\t});\n\tif (fallback === 'other') ret.push({ type: 'main', displayName: 'Other' });\n\treturn ret;\n};\n\nexport class TextClassifier implements INodeType {\n\tdescription: INodeTypeDescription = {\n\t\tdisplayName: 'Text Classifier',\n\t\tname: 'textClassifier',\n\t\ticon: 'fa:tags',\n\t\ticonColor: 'black',\n\t\tgroup: ['transform'],\n\t\tversion: 1,\n\t\tdescription: 'Classify your text into distinct categories',\n\t\tcodex: {\n\t\t\tcategories: ['AI'],\n\t\t\tsubcategories: {\n\t\t\t\tAI: ['Chains', 'Root Nodes'],\n\t\t\t},\n\t\t\tresources: {\n\t\t\t\tprimaryDocumentation: [\n\t\t\t\t\t{\n\t\t\t\t\t\turl: 'https://docs.n8n.io/integrations/builtin/cluster-nodes/root-nodes/n8n-nodes-langchain.text-classifier/',\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t},\n\t\t},\n\t\tdefaults: {\n\t\t\tname: 'Text Classifier',\n\t\t},\n\t\tinputs: [\n\t\t\t{ displayName: '', type: NodeConnectionTypes.Main },\n\t\t\t{\n\t\t\t\tdisplayName: 'Model',\n\t\t\t\tmaxConnections: 1,\n\t\t\t\ttype: NodeConnectionTypes.AiLanguageModel,\n\t\t\t\trequired: true,\n\t\t\t},\n\t\t],\n\t\toutputs: `={{(${configuredOutputs})($parameter)}}`,\n\t\tproperties: [\n\t\t\t{\n\t\t\t\tdisplayName: 'Text to Classify',\n\t\t\t\tname: 'inputText',\n\t\t\t\ttype: 'string',\n\t\t\t\trequired: true,\n\t\t\t\tdefault: '',\n\t\t\t\tdescription: 'Use an expression to reference data in previous nodes or enter static text',\n\t\t\t\ttypeOptions: {\n\t\t\t\t\trows: 2,\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tdisplayName: 'Categories',\n\t\t\t\tname: 'categories',\n\t\t\t\tplaceholder: 'Add Category',\n\t\t\t\ttype: 'fixedCollection',\n\t\t\t\tdefault: {},\n\t\t\t\ttypeOptions: {\n\t\t\t\t\tmultipleValues: true,\n\t\t\t\t},\n\t\t\t\toptions: [\n\t\t\t\t\t{\n\t\t\t\t\t\tname: 'categories',\n\t\t\t\t\t\tdisplayName: 'Categories',\n\t\t\t\t\t\tvalues: [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tdisplayName: 'Category',\n\t\t\t\t\t\t\t\tname: 'category',\n\t\t\t\t\t\t\t\ttype: 'string',\n\t\t\t\t\t\t\t\tdefault: '',\n\t\t\t\t\t\t\t\tdescription: 'Category to add',\n\t\t\t\t\t\t\t\trequired: true,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tdisplayName: 'Description',\n\t\t\t\t\t\t\t\tname: 'description',\n\t\t\t\t\t\t\t\ttype: 'string',\n\t\t\t\t\t\t\t\tdefault: '',\n\t\t\t\t\t\t\t\tdescription: \"Describe your category if it's not obvious\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t],\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t},\n\t\t\t{\n\t\t\t\tdisplayName: 'Options',\n\t\t\t\tname: 'options',\n\t\t\t\ttype: 'collection',\n\t\t\t\tdefault: {},\n\t\t\t\tplaceholder: 'Add Option',\n\t\t\t\toptions: [\n\t\t\t\t\t{\n\t\t\t\t\t\tdisplayName: 'Allow Multiple Classes To Be True',\n\t\t\t\t\t\tname: 'multiClass',\n\t\t\t\t\t\ttype: 'boolean',\n\t\t\t\t\t\tdefault: false,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tdisplayName: 'When No Clear Match',\n\t\t\t\t\t\tname: 'fallback',\n\t\t\t\t\t\ttype: 'options',\n\t\t\t\t\t\tdefault: 'discard',\n\t\t\t\t\t\tdescription: 'What to do with items that don’t match the categories exactly',\n\t\t\t\t\t\toptions: [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tname: 'Discard Item',\n\t\t\t\t\t\t\t\tvalue: 'discard',\n\t\t\t\t\t\t\t\tdescription: 'Ignore the item and drop it from the output',\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tname: \"Output on Extra, 'Other' Branch\",\n\t\t\t\t\t\t\t\tvalue: 'other',\n\t\t\t\t\t\t\t\tdescription: \"Create a separate output branch called 'Other'\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t],\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tdisplayName: 'System Prompt Template',\n\t\t\t\t\t\tname: 'systemPromptTemplate',\n\t\t\t\t\t\ttype: 'string',\n\t\t\t\t\t\tdefault: SYSTEM_PROMPT_TEMPLATE,\n\t\t\t\t\t\tdescription: 'String to use directly as the system prompt template',\n\t\t\t\t\t\ttypeOptions: {\n\t\t\t\t\t\t\trows: 6,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tdisplayName: 'Enable Auto-Fixing',\n\t\t\t\t\t\tname: 'enableAutoFixing',\n\t\t\t\t\t\ttype: 'boolean',\n\t\t\t\t\t\tdefault: true,\n\t\t\t\t\t\tdescription:\n\t\t\t\t\t\t\t'Whether to enable auto-fixing (may trigger an additional LLM call if output is broken)',\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tdisplayName: 'Batch Processing',\n\t\t\t\t\t\tname: 'batching',\n\t\t\t\t\t\ttype: 'collection',\n\t\t\t\t\t\tdescription: 'Batch processing options for rate limiting',\n\t\t\t\t\t\tdefault: {},\n\t\t\t\t\t\toptions: [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tdisplayName: 'Batch Size',\n\t\t\t\t\t\t\t\tname: 'batchSize',\n\t\t\t\t\t\t\t\tdefault: 100,\n\t\t\t\t\t\t\t\ttype: 'number',\n\t\t\t\t\t\t\t\tdescription:\n\t\t\t\t\t\t\t\t\t'How many items to process in parallel. This is useful for rate limiting.',\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tdisplayName: 'Delay Between Batches',\n\t\t\t\t\t\t\t\tname: 'delayBetweenBatches',\n\t\t\t\t\t\t\t\tdefault: 0,\n\t\t\t\t\t\t\t\ttype: 'number',\n\t\t\t\t\t\t\t\tdescription:\n\t\t\t\t\t\t\t\t\t'Delay in milliseconds between batches. This is useful for rate limiting.',\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t],\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t},\n\t\t],\n\t};\n\n\tasync execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {\n\t\tconst items = this.getInputData();\n\t\tconst { batchSize, delayBetweenBatches } = this.getNodeParameter('options.batching', 0, {\n\t\t\tbatchSize: 100,\n\t\t\tdelayBetweenBatches: 0,\n\t\t}) as {\n\t\t\tbatchSize: number;\n\t\t\tdelayBetweenBatches: number;\n\t\t};\n\n\t\tconst llm = (await this.getInputConnectionData(\n\t\t\tNodeConnectionTypes.AiLanguageModel,\n\t\t\t0,\n\t\t)) as BaseLanguageModel;\n\n\t\tconst categories = this.getNodeParameter('categories.categories', 0, []) as Array<{\n\t\t\tcategory: string;\n\t\t\tdescription: string;\n\t\t}>;\n\n\t\tif (categories.length === 0) {\n\t\t\tthrow new NodeOperationError(this.getNode(), 'At least one category must be defined');\n\t\t}\n\n\t\tconst options = this.getNodeParameter('options', 0, {}) as {\n\t\t\tmultiClass: boolean;\n\t\t\tfallback?: string;\n\t\t\tsystemPromptTemplate?: string;\n\t\t\tenableAutoFixing: boolean;\n\t\t};\n\t\tconst multiClass = options?.multiClass ?? false;\n\t\tconst fallback = options?.fallback ?? 'discard';\n\n\t\tconst schemaEntries = categories.map((cat) => [\n\t\t\tcat.category,\n\t\t\tz\n\t\t\t\t.boolean()\n\t\t\t\t.describe(\n\t\t\t\t\t`Should be true if the input has category \"${cat.category}\" (description: ${cat.description})`,\n\t\t\t\t),\n\t\t]);\n\t\tif (fallback === 'other')\n\t\t\tschemaEntries.push([\n\t\t\t\t'fallback',\n\t\t\t\tz.boolean().describe('Should be true if none of the other categories apply'),\n\t\t\t]);\n\t\tconst schema = z.object(Object.fromEntries(schemaEntries));\n\n\t\tconst structuredParser = StructuredOutputParser.fromZodSchema(schema);\n\n\t\tconst parser = options.enableAutoFixing\n\t\t\t? OutputFixingParser.fromLLM(llm, structuredParser)\n\t\t\t: structuredParser;\n\n\t\tconst multiClassPrompt = multiClass\n\t\t\t? 'Categories are not mutually exclusive, and multiple can be true'\n\t\t\t: 'Categories are mutually exclusive, and only one can be true';\n\n\t\tconst fallbackPrompt = {\n\t\t\tother: 'If no categories apply, select the \"fallback\" option.',\n\t\t\tdiscard: 'If there is not a very fitting category, select none of the categories.',\n\t\t}[fallback];\n\n\t\tconst returnData: INodeExecutionData[][] = Array.from(\n\t\t\t{ length: categories.length + (fallback === 'other' ? 1 : 0) },\n\t\t\t(_) => [],\n\t\t);\n\n\t\tfor (let i = 0; i < items.length; i += batchSize) {\n\t\t\tconst batch = items.slice(i, i + batchSize);\n\t\t\tconst batchPromises = batch.map(async (_item, batchItemIndex) => {\n\t\t\t\tconst itemIdx = i + batchItemIndex;\n\t\t\t\tconst item = items[itemIdx];\n\t\t\t\titem.pairedItem = { item: itemIdx };\n\t\t\t\tconst input = this.getNodeParameter('inputText', itemIdx) as string;\n\n\t\t\t\tif (input === undefined || input === null) {\n\t\t\t\t\tthrow new NodeOperationError(\n\t\t\t\t\t\tthis.getNode(),\n\t\t\t\t\t\t`Text to classify for item ${itemIdx} is not defined`,\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tconst inputPrompt = new HumanMessage(input);\n\n\t\t\t\tconst systemPromptTemplateOpt = this.getNodeParameter(\n\t\t\t\t\t'options.systemPromptTemplate',\n\t\t\t\t\titemIdx,\n\t\t\t\t\tSYSTEM_PROMPT_TEMPLATE,\n\t\t\t\t) as string;\n\t\t\t\tconst systemPromptTemplate = SystemMessagePromptTemplate.fromTemplate(\n\t\t\t\t\t`${systemPromptTemplateOpt ?? SYSTEM_PROMPT_TEMPLATE}\n\t{format_instructions}\n\t${multiClassPrompt}\n\t${fallbackPrompt}`,\n\t\t\t\t);\n\n\t\t\t\tconst messages = [\n\t\t\t\t\tawait systemPromptTemplate.format({\n\t\t\t\t\t\tcategories: categories.map((cat) => cat.category).join(', '),\n\t\t\t\t\t\tformat_instructions: parser.getFormatInstructions(),\n\t\t\t\t\t}),\n\t\t\t\t\tinputPrompt,\n\t\t\t\t];\n\t\t\t\tconst prompt = ChatPromptTemplate.fromMessages(messages);\n\t\t\t\tconst chain = prompt.pipe(llm).pipe(parser).withConfig(getTracingConfig(this));\n\n\t\t\t\treturn await chain.invoke(messages);\n\t\t\t});\n\n\t\t\tconst batchResults = await Promise.allSettled(batchPromises);\n\n\t\t\tbatchResults.forEach((response, batchItemIndex) => {\n\t\t\t\tconst index = i + batchItemIndex;\n\t\t\t\tif (response.status === 'rejected') {\n\t\t\t\t\tconst error = response.reason as Error;\n\t\t\t\t\tif (this.continueOnFail()) {\n\t\t\t\t\t\treturnData[0].push({\n\t\t\t\t\t\t\tjson: { error: error.message },\n\t\t\t\t\t\t\tpairedItem: { item: index },\n\t\t\t\t\t\t});\n\t\t\t\t\t\treturn;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthrow new NodeOperationError(this.getNode(), error.message);\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tconst output = response.value;\n\t\t\t\t\tconst item = items[index];\n\n\t\t\t\t\tcategories.forEach((cat, idx) => {\n\t\t\t\t\t\tif (output[cat.category]) returnData[idx].push(item);\n\t\t\t\t\t});\n\n\t\t\t\t\tif (fallback === 'other' && output.fallback) returnData[returnData.length - 1].push(item);\n\t\t\t\t}\n\t\t\t});\n\n\t\t\t// Add delay between batches if not the last batch\n\t\t\tif (i + batchSize < items.length && delayBetweenBatches > 0) {\n\t\t\t\tawait sleep(delayBetweenBatches);\n\t\t\t}\n\t\t}\n\n\t\treturn returnData;\n\t}\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../../nodes/chains/TextClassifier/TextClassifier.node.ts"],"sourcesContent":["import type { BaseLanguageModel } from '@langchain/core/language_models/base';\nimport { HumanMessage } from '@langchain/core/messages';\nimport { SystemMessagePromptTemplate, ChatPromptTemplate } from '@langchain/core/prompts';\nimport { OutputFixingParser, StructuredOutputParser } from 'langchain/output_parsers';\nimport { NodeOperationError, NodeConnectionTypes } from 'n8n-workflow';\nimport type {\n\tIDataObject,\n\tIExecuteFunctions,\n\tINodeExecutionData,\n\tINodeParameters,\n\tINodeType,\n\tINodeTypeDescription,\n} from 'n8n-workflow';\nimport { z } from 'zod';\n\nimport { getTracingConfig } from '@utils/tracing';\n\nconst SYSTEM_PROMPT_TEMPLATE =\n\t\"Please classify the text provided by the user into one of the following categories: {categories}, and use the provided formatting instructions below. Don't explain, and only output the json.\";\n\nconst configuredOutputs = (parameters: INodeParameters) => {\n\tconst categories = ((parameters.categories as IDataObject)?.categories as IDataObject[]) ?? [];\n\tconst fallback = (parameters.options as IDataObject)?.fallback as string;\n\tconst ret = categories.map((cat) => {\n\t\treturn { type: 'main', displayName: cat.category };\n\t});\n\tif (fallback === 'other') ret.push({ type: 'main', displayName: 'Other' });\n\treturn ret;\n};\n\nexport class TextClassifier implements INodeType {\n\tdescription: INodeTypeDescription = {\n\t\tdisplayName: 'Text Classifier',\n\t\tname: 'textClassifier',\n\t\ticon: 'fa:tags',\n\t\ticonColor: 'black',\n\t\tgroup: ['transform'],\n\t\tversion: 1,\n\t\tdescription: 'Classify your text into distinct categories',\n\t\tcodex: {\n\t\t\tcategories: ['AI'],\n\t\t\tsubcategories: {\n\t\t\t\tAI: ['Chains', 'Root Nodes'],\n\t\t\t},\n\t\t\tresources: {\n\t\t\t\tprimaryDocumentation: [\n\t\t\t\t\t{\n\t\t\t\t\t\turl: 'https://docs.n8n.io/integrations/builtin/cluster-nodes/root-nodes/n8n-nodes-langchain.text-classifier/',\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t},\n\t\t},\n\t\tdefaults: {\n\t\t\tname: 'Text Classifier',\n\t\t},\n\t\tinputs: [\n\t\t\t{ displayName: '', type: NodeConnectionTypes.Main },\n\t\t\t{\n\t\t\t\tdisplayName: 'Model',\n\t\t\t\tmaxConnections: 1,\n\t\t\t\ttype: NodeConnectionTypes.AiLanguageModel,\n\t\t\t\trequired: true,\n\t\t\t},\n\t\t],\n\t\toutputs: `={{(${configuredOutputs})($parameter)}}`,\n\t\tproperties: [\n\t\t\t{\n\t\t\t\tdisplayName: 'Text to Classify',\n\t\t\t\tname: 'inputText',\n\t\t\t\ttype: 'string',\n\t\t\t\trequired: true,\n\t\t\t\tdefault: '',\n\t\t\t\tdescription: 'Use an expression to reference data in previous nodes or enter static text',\n\t\t\t\ttypeOptions: {\n\t\t\t\t\trows: 2,\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tdisplayName: 'Categories',\n\t\t\t\tname: 'categories',\n\t\t\t\tplaceholder: 'Add Category',\n\t\t\t\ttype: 'fixedCollection',\n\t\t\t\tdefault: {},\n\t\t\t\ttypeOptions: {\n\t\t\t\t\tmultipleValues: true,\n\t\t\t\t},\n\t\t\t\toptions: [\n\t\t\t\t\t{\n\t\t\t\t\t\tname: 'categories',\n\t\t\t\t\t\tdisplayName: 'Categories',\n\t\t\t\t\t\tvalues: [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tdisplayName: 'Category',\n\t\t\t\t\t\t\t\tname: 'category',\n\t\t\t\t\t\t\t\ttype: 'string',\n\t\t\t\t\t\t\t\tdefault: '',\n\t\t\t\t\t\t\t\tdescription: 'Category to add',\n\t\t\t\t\t\t\t\trequired: true,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tdisplayName: 'Description',\n\t\t\t\t\t\t\t\tname: 'description',\n\t\t\t\t\t\t\t\ttype: 'string',\n\t\t\t\t\t\t\t\tdefault: '',\n\t\t\t\t\t\t\t\tdescription: \"Describe your category if it's not obvious\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t],\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t},\n\t\t\t{\n\t\t\t\tdisplayName: 'Options',\n\t\t\t\tname: 'options',\n\t\t\t\ttype: 'collection',\n\t\t\t\tdefault: {},\n\t\t\t\tplaceholder: 'Add Option',\n\t\t\t\toptions: [\n\t\t\t\t\t{\n\t\t\t\t\t\tdisplayName: 'Allow Multiple Classes To Be True',\n\t\t\t\t\t\tname: 'multiClass',\n\t\t\t\t\t\ttype: 'boolean',\n\t\t\t\t\t\tdefault: false,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tdisplayName: 'When No Clear Match',\n\t\t\t\t\t\tname: 'fallback',\n\t\t\t\t\t\ttype: 'options',\n\t\t\t\t\t\tdefault: 'discard',\n\t\t\t\t\t\tdescription: 'What to do with items that don’t match the categories exactly',\n\t\t\t\t\t\toptions: [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tname: 'Discard Item',\n\t\t\t\t\t\t\t\tvalue: 'discard',\n\t\t\t\t\t\t\t\tdescription: 'Ignore the item and drop it from the output',\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tname: \"Output on Extra, 'Other' Branch\",\n\t\t\t\t\t\t\t\tvalue: 'other',\n\t\t\t\t\t\t\t\tdescription: \"Create a separate output branch called 'Other'\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t],\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tdisplayName: 'System Prompt Template',\n\t\t\t\t\t\tname: 'systemPromptTemplate',\n\t\t\t\t\t\ttype: 'string',\n\t\t\t\t\t\tdefault: SYSTEM_PROMPT_TEMPLATE,\n\t\t\t\t\t\tdescription: 'String to use directly as the system prompt template',\n\t\t\t\t\t\ttypeOptions: {\n\t\t\t\t\t\t\trows: 6,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tdisplayName: 'Enable Auto-Fixing',\n\t\t\t\t\t\tname: 'enableAutoFixing',\n\t\t\t\t\t\ttype: 'boolean',\n\t\t\t\t\t\tdefault: true,\n\t\t\t\t\t\tdescription:\n\t\t\t\t\t\t\t'Whether to enable auto-fixing (may trigger an additional LLM call if output is broken)',\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t},\n\t\t],\n\t};\n\n\tasync execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {\n\t\tconst items = this.getInputData();\n\n\t\tconst llm = (await this.getInputConnectionData(\n\t\t\tNodeConnectionTypes.AiLanguageModel,\n\t\t\t0,\n\t\t)) as BaseLanguageModel;\n\n\t\tconst categories = this.getNodeParameter('categories.categories', 0, []) as Array<{\n\t\t\tcategory: string;\n\t\t\tdescription: string;\n\t\t}>;\n\n\t\tif (categories.length === 0) {\n\t\t\tthrow new NodeOperationError(this.getNode(), 'At least one category must be defined');\n\t\t}\n\n\t\tconst options = this.getNodeParameter('options', 0, {}) as {\n\t\t\tmultiClass: boolean;\n\t\t\tfallback?: string;\n\t\t\tsystemPromptTemplate?: string;\n\t\t\tenableAutoFixing: boolean;\n\t\t};\n\t\tconst multiClass = options?.multiClass ?? false;\n\t\tconst fallback = options?.fallback ?? 'discard';\n\n\t\tconst schemaEntries = categories.map((cat) => [\n\t\t\tcat.category,\n\t\t\tz\n\t\t\t\t.boolean()\n\t\t\t\t.describe(\n\t\t\t\t\t`Should be true if the input has category \"${cat.category}\" (description: ${cat.description})`,\n\t\t\t\t),\n\t\t]);\n\t\tif (fallback === 'other')\n\t\t\tschemaEntries.push([\n\t\t\t\t'fallback',\n\t\t\t\tz.boolean().describe('Should be true if none of the other categories apply'),\n\t\t\t]);\n\t\tconst schema = z.object(Object.fromEntries(schemaEntries));\n\n\t\tconst structuredParser = StructuredOutputParser.fromZodSchema(schema);\n\n\t\tconst parser = options.enableAutoFixing\n\t\t\t? OutputFixingParser.fromLLM(llm, structuredParser)\n\t\t\t: structuredParser;\n\n\t\tconst multiClassPrompt = multiClass\n\t\t\t? 'Categories are not mutually exclusive, and multiple can be true'\n\t\t\t: 'Categories are mutually exclusive, and only one can be true';\n\n\t\tconst fallbackPrompt = {\n\t\t\tother: 'If no categories apply, select the \"fallback\" option.',\n\t\t\tdiscard: 'If there is not a very fitting category, select none of the categories.',\n\t\t}[fallback];\n\n\t\tconst returnData: INodeExecutionData[][] = Array.from(\n\t\t\t{ length: categories.length + (fallback === 'other' ? 1 : 0) },\n\t\t\t(_) => [],\n\t\t);\n\t\tfor (let itemIdx = 0; itemIdx < items.length; itemIdx++) {\n\t\t\tconst item = items[itemIdx];\n\t\t\titem.pairedItem = { item: itemIdx };\n\t\t\tconst input = this.getNodeParameter('inputText', itemIdx) as string;\n\n\t\t\tif (input === undefined || input === null) {\n\t\t\t\tif (this.continueOnFail()) {\n\t\t\t\t\treturnData[0].push({\n\t\t\t\t\t\tjson: { error: 'Text to classify is not defined' },\n\t\t\t\t\t\tpairedItem: { item: itemIdx },\n\t\t\t\t\t});\n\t\t\t\t\tcontinue;\n\t\t\t\t} else {\n\t\t\t\t\tthrow new NodeOperationError(\n\t\t\t\t\t\tthis.getNode(),\n\t\t\t\t\t\t`Text to classify for item ${itemIdx} is not defined`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst inputPrompt = new HumanMessage(input);\n\n\t\t\tconst systemPromptTemplateOpt = this.getNodeParameter(\n\t\t\t\t'options.systemPromptTemplate',\n\t\t\t\titemIdx,\n\t\t\t\tSYSTEM_PROMPT_TEMPLATE,\n\t\t\t) as string;\n\t\t\tconst systemPromptTemplate = SystemMessagePromptTemplate.fromTemplate(\n\t\t\t\t`${systemPromptTemplateOpt ?? SYSTEM_PROMPT_TEMPLATE}\n{format_instructions}\n${multiClassPrompt}\n${fallbackPrompt}`,\n\t\t\t);\n\n\t\t\tconst messages = [\n\t\t\t\tawait systemPromptTemplate.format({\n\t\t\t\t\tcategories: categories.map((cat) => cat.category).join(', '),\n\t\t\t\t\tformat_instructions: parser.getFormatInstructions(),\n\t\t\t\t}),\n\t\t\t\tinputPrompt,\n\t\t\t];\n\t\t\tconst prompt = ChatPromptTemplate.fromMessages(messages);\n\t\t\tconst chain = prompt.pipe(llm).pipe(parser).withConfig(getTracingConfig(this));\n\n\t\t\ttry {\n\t\t\t\tconst output = await chain.invoke(messages);\n\n\t\t\t\tcategories.forEach((cat, idx) => {\n\t\t\t\t\tif (output[cat.category]) returnData[idx].push(item);\n\t\t\t\t});\n\t\t\t\tif (fallback === 'other' && output.fallback) returnData[returnData.length - 1].push(item);\n\t\t\t} catch (error) {\n\t\t\t\tif (this.continueOnFail()) {\n\t\t\t\t\treturnData[0].push({\n\t\t\t\t\t\tjson: { error: error.message },\n\t\t\t\t\t\tpairedItem: { item: itemIdx },\n\t\t\t\t\t});\n\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t}\n\n\t\treturn returnData;\n\t}\n}\n"],"mappings":"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","names":[]}
|