@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.
@@ -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
- const { batchSize, delayBetweenBatches } = this.getNodeParameter("options.batching", 0, {
176
- batchSize: 100,
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
- itemIndex,
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
- return {
191
- result: null,
192
- itemIndex,
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", itemIndex, {});
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
- {format_instructions}`
178
+ {format_instructions}`
212
179
  );
213
- const input = this.getNodeParameter("inputText", itemIndex);
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[itemIndex] };
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
- return {
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
- return {
256
- result: null,
257
- itemIndex,
258
- error: new import_n8n_workflow.NodeOperationError(
259
- this.getNode(),
260
- "Error during parsing of LLM output, please check your LLM model and configuration",
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
- const batchResults = await Promise.all(batchPromises);
269
- batchResults.forEach(({ result, itemIndex, error }) => {
270
- if (error) {
271
- if (this.continueOnFail()) {
272
- const executionErrorData = this.helpers.constructExecutionMetaData(
273
- this.helpers.returnJsonArray({ error: error.message }),
274
- { itemData: { item: itemIndex } }
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 i = 0; i < items.length; i += batchSize) {
239
- const batch = items.slice(i, i + batchSize);
240
- const batchPromises = batch.map(async (_item, batchItemIndex) => {
241
- const itemIdx = i + batchItemIndex;
242
- const item = items[itemIdx];
243
- item.pairedItem = { item: itemIdx };
244
- const input = this.getNodeParameter("inputText", itemIdx);
245
- if (input === void 0 || input === null) {
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
- const inputPrompt = new import_messages.HumanMessage(input);
252
- const systemPromptTemplateOpt = this.getNodeParameter(
253
- "options.systemPromptTemplate",
254
- itemIdx,
255
- SYSTEM_PROMPT_TEMPLATE
256
- );
257
- const systemPromptTemplate = import_prompts.SystemMessagePromptTemplate.fromTemplate(
258
- `${systemPromptTemplateOpt ?? SYSTEM_PROMPT_TEMPLATE}
259
- {format_instructions}
260
- ${multiClassPrompt}
261
- ${fallbackPrompt}`
262
- );
263
- const messages = [
264
- await systemPromptTemplate.format({
265
- categories: categories.map((cat) => cat.category).join(", "),
266
- format_instructions: parser.getFormatInstructions()
267
- }),
268
- inputPrompt
269
- ];
270
- const prompt = import_prompts.ChatPromptTemplate.fromMessages(messages);
271
- const chain = prompt.pipe(llm).pipe(parser).withConfig((0, import_tracing.getTracingConfig)(this));
272
- return await chain.invoke(messages);
273
- });
274
- const batchResults = await Promise.allSettled(batchPromises);
275
- batchResults.forEach((response, batchItemIndex) => {
276
- const index = i + batchItemIndex;
277
- if (response.status === "rejected") {
278
- const error = response.reason;
279
- if (this.continueOnFail()) {
280
- returnData[0].push({
281
- json: { error: error.message },
282
- pairedItem: { item: index }
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
- if (fallback === "other" && output.fallback) returnData[returnData.length - 1].push(item);
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":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AACA,sBAA6B;AAC7B,qBAAgE;AAChE,4BAA2D;AAC3D,0BAA+D;AAS/D,iBAAkB;AAElB,qBAAiC;AAEjC,MAAM,yBACL;AAED,MAAM,oBAAoB,CAAC,eAAgC;AAC1D,QAAM,aAAe,WAAW,YAA4B,cAAgC,CAAC;AAC7F,QAAM,WAAY,WAAW,SAAyB;AACtD,QAAM,MAAM,WAAW,IAAI,CAAC,QAAQ;AACnC,WAAO,EAAE,MAAM,QAAQ,aAAa,IAAI,SAAS;AAAA,EAClD,CAAC;AACD,MAAI,aAAa,QAAS,KAAI,KAAK,EAAE,MAAM,QAAQ,aAAa,QAAQ,CAAC;AACzE,SAAO;AACR;AAEO,MAAM,eAAoC;AAAA,EAA1C;AACN,uBAAoC;AAAA,MACnC,aAAa;AAAA,MACb,MAAM;AAAA,MACN,MAAM;AAAA,MACN,WAAW;AAAA,MACX,OAAO,CAAC,WAAW;AAAA,MACnB,SAAS;AAAA,MACT,aAAa;AAAA,MACb,OAAO;AAAA,QACN,YAAY,CAAC,IAAI;AAAA,QACjB,eAAe;AAAA,UACd,IAAI,CAAC,UAAU,YAAY;AAAA,QAC5B;AAAA,QACA,WAAW;AAAA,UACV,sBAAsB;AAAA,YACrB;AAAA,cACC,KAAK;AAAA,YACN;AAAA,UACD;AAAA,QACD;AAAA,MACD;AAAA,MACA,UAAU;AAAA,QACT,MAAM;AAAA,MACP;AAAA,MACA,QAAQ;AAAA,QACP,EAAE,aAAa,IAAI,MAAM,wCAAoB,KAAK;AAAA,QAClD;AAAA,UACC,aAAa;AAAA,UACb,gBAAgB;AAAA,UAChB,MAAM,wCAAoB;AAAA,UAC1B,UAAU;AAAA,QACX;AAAA,MACD;AAAA,MACA,SAAS,OAAO,iBAAiB;AAAA,MACjC,YAAY;AAAA,QACX;AAAA,UACC,aAAa;AAAA,UACb,MAAM;AAAA,UACN,MAAM;AAAA,UACN,UAAU;AAAA,UACV,SAAS;AAAA,UACT,aAAa;AAAA,UACb,aAAa;AAAA,YACZ,MAAM;AAAA,UACP;AAAA,QACD;AAAA,QACA;AAAA,UACC,aAAa;AAAA,UACb,MAAM;AAAA,UACN,aAAa;AAAA,UACb,MAAM;AAAA,UACN,SAAS,CAAC;AAAA,UACV,aAAa;AAAA,YACZ,gBAAgB;AAAA,UACjB;AAAA,UACA,SAAS;AAAA,YACR;AAAA,cACC,MAAM;AAAA,cACN,aAAa;AAAA,cACb,QAAQ;AAAA,gBACP;AAAA,kBACC,aAAa;AAAA,kBACb,MAAM;AAAA,kBACN,MAAM;AAAA,kBACN,SAAS;AAAA,kBACT,aAAa;AAAA,kBACb,UAAU;AAAA,gBACX;AAAA,gBACA;AAAA,kBACC,aAAa;AAAA,kBACb,MAAM;AAAA,kBACN,MAAM;AAAA,kBACN,SAAS;AAAA,kBACT,aAAa;AAAA,gBACd;AAAA,cACD;AAAA,YACD;AAAA,UACD;AAAA,QACD;AAAA,QACA;AAAA,UACC,aAAa;AAAA,UACb,MAAM;AAAA,UACN,MAAM;AAAA,UACN,SAAS,CAAC;AAAA,UACV,aAAa;AAAA,UACb,SAAS;AAAA,YACR;AAAA,cACC,aAAa;AAAA,cACb,MAAM;AAAA,cACN,MAAM;AAAA,cACN,SAAS;AAAA,YACV;AAAA,YACA;AAAA,cACC,aAAa;AAAA,cACb,MAAM;AAAA,cACN,MAAM;AAAA,cACN,SAAS;AAAA,cACT,aAAa;AAAA,cACb,SAAS;AAAA,gBACR;AAAA,kBACC,MAAM;AAAA,kBACN,OAAO;AAAA,kBACP,aAAa;AAAA,gBACd;AAAA,gBACA;AAAA,kBACC,MAAM;AAAA,kBACN,OAAO;AAAA,kBACP,aAAa;AAAA,gBACd;AAAA,cACD;AAAA,YACD;AAAA,YACA;AAAA,cACC,aAAa;AAAA,cACb,MAAM;AAAA,cACN,MAAM;AAAA,cACN,SAAS;AAAA,cACT,aAAa;AAAA,cACb,aAAa;AAAA,gBACZ,MAAM;AAAA,cACP;AAAA,YACD;AAAA,YACA;AAAA,cACC,aAAa;AAAA,cACb,MAAM;AAAA,cACN,MAAM;AAAA,cACN,SAAS;AAAA,cACT,aACC;AAAA,YACF;AAAA,YACA;AAAA,cACC,aAAa;AAAA,cACb,MAAM;AAAA,cACN,MAAM;AAAA,cACN,aAAa;AAAA,cACb,SAAS,CAAC;AAAA,cACV,SAAS;AAAA,gBACR;AAAA,kBACC,aAAa;AAAA,kBACb,MAAM;AAAA,kBACN,SAAS;AAAA,kBACT,MAAM;AAAA,kBACN,aACC;AAAA,gBACF;AAAA,gBACA;AAAA,kBACC,aAAa;AAAA,kBACb,MAAM;AAAA,kBACN,SAAS;AAAA,kBACT,MAAM;AAAA,kBACN,aACC;AAAA,gBACF;AAAA,cACD;AAAA,YACD;AAAA,UACD;AAAA,QACD;AAAA,MACD;AAAA,IACD;AAAA;AAAA,EAEA,MAAM,UAAkE;AACvE,UAAM,QAAQ,KAAK,aAAa;AAChC,UAAM,EAAE,WAAW,oBAAoB,IAAI,KAAK,iBAAiB,oBAAoB,GAAG;AAAA,MACvF,WAAW;AAAA,MACX,qBAAqB;AAAA,IACtB,CAAC;AAKD,UAAM,MAAO,MAAM,KAAK;AAAA,MACvB,wCAAoB;AAAA,MACpB;AAAA,IACD;AAEA,UAAM,aAAa,KAAK,iBAAiB,yBAAyB,GAAG,CAAC,CAAC;AAKvE,QAAI,WAAW,WAAW,GAAG;AAC5B,YAAM,IAAI,uCAAmB,KAAK,QAAQ,GAAG,uCAAuC;AAAA,IACrF;AAEA,UAAM,UAAU,KAAK,iBAAiB,WAAW,GAAG,CAAC,CAAC;AAMtD,UAAM,aAAa,SAAS,cAAc;AAC1C,UAAM,WAAW,SAAS,YAAY;AAEtC,UAAM,gBAAgB,WAAW,IAAI,CAAC,QAAQ;AAAA,MAC7C,IAAI;AAAA,MACJ,aACE,QAAQ,EACR;AAAA,QACA,6CAA6C,IAAI,QAAQ,mBAAmB,IAAI,WAAW;AAAA,MAC5F;AAAA,IACF,CAAC;AACD,QAAI,aAAa;AAChB,oBAAc,KAAK;AAAA,QAClB;AAAA,QACA,aAAE,QAAQ,EAAE,SAAS,sDAAsD;AAAA,MAC5E,CAAC;AACF,UAAM,SAAS,aAAE,OAAO,OAAO,YAAY,aAAa,CAAC;AAEzD,UAAM,mBAAmB,6CAAuB,cAAc,MAAM;AAEpE,UAAM,SAAS,QAAQ,mBACpB,yCAAmB,QAAQ,KAAK,gBAAgB,IAChD;AAEH,UAAM,mBAAmB,aACtB,oEACA;AAEH,UAAM,iBAAiB;AAAA,MACtB,OAAO;AAAA,MACP,SAAS;AAAA,IACV,EAAE,QAAQ;AAEV,UAAM,aAAqC,MAAM;AAAA,MAChD,EAAE,QAAQ,WAAW,UAAU,aAAa,UAAU,IAAI,GAAG;AAAA,MAC7D,CAAC,MAAM,CAAC;AAAA,IACT;AAEA,aAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK,WAAW;AACjD,YAAM,QAAQ,MAAM,MAAM,GAAG,IAAI,SAAS;AAC1C,YAAM,gBAAgB,MAAM,IAAI,OAAO,OAAO,mBAAmB;AAChE,cAAM,UAAU,IAAI;AACpB,cAAM,OAAO,MAAM,OAAO;AAC1B,aAAK,aAAa,EAAE,MAAM,QAAQ;AAClC,cAAM,QAAQ,KAAK,iBAAiB,aAAa,OAAO;AAExD,YAAI,UAAU,UAAa,UAAU,MAAM;AAC1C,gBAAM,IAAI;AAAA,YACT,KAAK,QAAQ;AAAA,YACb,6BAA6B,OAAO;AAAA,UACrC;AAAA,QACD;AAEA,cAAM,cAAc,IAAI,6BAAa,KAAK;AAE1C,cAAM,0BAA0B,KAAK;AAAA,UACpC;AAAA,UACA;AAAA,UACA;AAAA,QACD;AACA,cAAM,uBAAuB,2CAA4B;AAAA,UACxD,GAAG,2BAA2B,sBAAsB;AAAA;AAAA,GAEtD,gBAAgB;AAAA,GAChB,cAAc;AAAA,QACb;AAEA,cAAM,WAAW;AAAA,UAChB,MAAM,qBAAqB,OAAO;AAAA,YACjC,YAAY,WAAW,IAAI,CAAC,QAAQ,IAAI,QAAQ,EAAE,KAAK,IAAI;AAAA,YAC3D,qBAAqB,OAAO,sBAAsB;AAAA,UACnD,CAAC;AAAA,UACD;AAAA,QACD;AACA,cAAM,SAAS,kCAAmB,aAAa,QAAQ;AACvD,cAAM,QAAQ,OAAO,KAAK,GAAG,EAAE,KAAK,MAAM,EAAE,eAAW,iCAAiB,IAAI,CAAC;AAE7E,eAAO,MAAM,MAAM,OAAO,QAAQ;AAAA,MACnC,CAAC;AAED,YAAM,eAAe,MAAM,QAAQ,WAAW,aAAa;AAE3D,mBAAa,QAAQ,CAAC,UAAU,mBAAmB;AAClD,cAAM,QAAQ,IAAI;AAClB,YAAI,SAAS,WAAW,YAAY;AACnC,gBAAM,QAAQ,SAAS;AACvB,cAAI,KAAK,eAAe,GAAG;AAC1B,uBAAW,CAAC,EAAE,KAAK;AAAA,cAClB,MAAM,EAAE,OAAO,MAAM,QAAQ;AAAA,cAC7B,YAAY,EAAE,MAAM,MAAM;AAAA,YAC3B,CAAC;AACD;AAAA,UACD,OAAO;AACN,kBAAM,IAAI,uCAAmB,KAAK,QAAQ,GAAG,MAAM,OAAO;AAAA,UAC3D;AAAA,QACD,OAAO;AACN,gBAAM,SAAS,SAAS;AACxB,gBAAM,OAAO,MAAM,KAAK;AAExB,qBAAW,QAAQ,CAAC,KAAK,QAAQ;AAChC,gBAAI,OAAO,IAAI,QAAQ,EAAG,YAAW,GAAG,EAAE,KAAK,IAAI;AAAA,UACpD,CAAC;AAED,cAAI,aAAa,WAAW,OAAO,SAAU,YAAW,WAAW,SAAS,CAAC,EAAE,KAAK,IAAI;AAAA,QACzF;AAAA,MACD,CAAC;AAGD,UAAI,IAAI,YAAY,MAAM,UAAU,sBAAsB,GAAG;AAC5D,kBAAM,2BAAM,mBAAmB;AAAA,MAChC;AAAA,IACD;AAEA,WAAO;AAAA,EACR;AACD;","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":[]}