UNPKG

n8n-nodes-rckflr-ner

Version:

N8N node for Named Entity Recognition (NER) using Transformer.js

130 lines (116 loc) 4.28 kB
import type { IExecuteFunctions, INodeExecutionData, INodeType, INodeTypeDescription, Logger, } from 'n8n-workflow'; import { NodeConnectionType, NodeOperationError } from 'n8n-workflow'; import { pipeline } from '@huggingface/transformers'; export class NamedEntityRecognition implements INodeType { description: INodeTypeDescription = { displayName: 'Named Entity Recognition', name: 'namedEntityRecognition', icon: 'fa:tags', group: ['ai'], version: 1, description: 'Extracts named entities (like persons, locations, organizations) from text using Transformers.js.', defaults: { name: 'Named Entity Recognition', }, inputs: [NodeConnectionType.Main], outputs: [NodeConnectionType.Main], usableAsTool: true, properties: [ { displayName: 'Input Text', name: 'inputText', type: 'string', typeOptions: { rows: 5, }, default: '', required: true, description: 'The text from which to extract named entities.', placeholder: 'e.g., N8N is a workflow automation tool based in Berlin, developed by Johannes and a great team.', }, { displayName: 'Aggregation Strategy', name: 'aggregationStrategy', type: 'options', options: [ { name: 'None', value: 'none' }, { name: 'Simple (Recommended)', value: 'simple' }, { name: 'First', value: 'first' }, { name: 'Average', value: 'average' }, { name: 'Max', value: 'max' }, ], default: 'simple', description: "Strategy to group token parts (e.g. B-PER, I-PER) into single entities. 'Simple' is often a good default.", }, { displayName: 'Output Field Name', name: 'outputFieldName', type: 'string', default: 'entities', required: true, description: 'The field name where the array of extracted entities will be stored.', }, ], }; private static pipelineCache: Map<string, any> = new Map(); async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> { const itemIndex = 0; // We will only ever process one item. // Define the model to use const model = 'Xenova/bert-base-NER'; // Get node parameters const outputFieldName = this.getNodeParameter('outputFieldName', itemIndex, 'entities') as string; const aggregationStrategy = this.getNodeParameter('aggregationStrategy', itemIndex, 'simple') as string; const inputText = this.getNodeParameter('inputText', itemIndex, '') as string; if (!inputText || !inputText.trim()) { throw new NodeOperationError(this.getNode(), 'Input Text parameter is required. Please provide a static value or an expression.'); } // Initialize the pipeline (cache it to avoid reloading) let pipe = NamedEntityRecognition.pipelineCache.get(model); if (!pipe) { try { this.logger.info(`Loading NER model: ${model}`); pipe = await pipeline('token-classification', model); NamedEntityRecognition.pipelineCache.set(model, pipe); this.logger.info(`NER Model ${model} loaded successfully`); } catch (error) { throw new NodeOperationError( this.getNode(), `Failed to load NER model: ${(error as Error).message}`, ); } } // Process the text try { this.logger.info(`Extracting entities from text...`); const pipelineOptions: any = {}; if (aggregationStrategy && aggregationStrategy !== 'none') { pipelineOptions.aggregation_strategy = aggregationStrategy; } const processedOutput = await pipe(inputText, pipelineOptions); this.logger.info(`Raw NER output: ${JSON.stringify(processedOutput)}`); if (!Array.isArray(processedOutput)) { throw new NodeOperationError(this.getNode(), 'NER output is not in the expected array format.'); } const resultJson: any = {}; resultJson[outputFieldName] = processedOutput; return [this.helpers.returnJsonArray([{ json: resultJson }])]; } catch (error) { if (this.continueOnFail()) { const errorItem = { json: { error: (error as Error).message, }, }; return [this.helpers.returnJsonArray([errorItem])]; } else { throw new NodeOperationError(this.getNode(), (error as Error).message || String(error)); } } } }