mirror of
https://github.com/farcasclaudiu/Flowise.git
synced 2026-06-29 07:01:04 +03:00
[Feature] improve CsvLoader & clean code (#3830)
* Improve CSV Loader * Improve S3 Loaders --------- Co-authored-by: Henry <hzj94@hotmail.com>
This commit is contained in:
@@ -1,7 +1,6 @@
|
|||||||
import { omit } from 'lodash'
|
|
||||||
import { TextSplitter } from 'langchain/text_splitter'
|
import { TextSplitter } from 'langchain/text_splitter'
|
||||||
import { CSVLoader } from '@langchain/community/document_loaders/fs/csv'
|
import { CSVLoader } from './CsvLoader'
|
||||||
import { getFileFromStorage, handleEscapeCharacters } from '../../../src'
|
import { getFileFromStorage, handleDocumentLoaderDocuments, handleDocumentLoaderMetadata, handleDocumentLoaderOutput } from '../../../src'
|
||||||
import { ICommonObject, IDocument, INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface'
|
import { ICommonObject, IDocument, INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface'
|
||||||
|
|
||||||
class Csv_DocumentLoaders implements INode {
|
class Csv_DocumentLoaders implements INode {
|
||||||
@@ -19,7 +18,7 @@ class Csv_DocumentLoaders implements INode {
|
|||||||
constructor() {
|
constructor() {
|
||||||
this.label = 'Csv File'
|
this.label = 'Csv File'
|
||||||
this.name = 'csvFile'
|
this.name = 'csvFile'
|
||||||
this.version = 2.0
|
this.version = 3.0
|
||||||
this.type = 'Document'
|
this.type = 'Document'
|
||||||
this.icon = 'csv.svg'
|
this.icon = 'csv.svg'
|
||||||
this.category = 'Document Loaders'
|
this.category = 'Document Loaders'
|
||||||
@@ -82,21 +81,11 @@ class Csv_DocumentLoaders implements INode {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
async init(nodeData: INodeData, _: string, options: ICommonObject): Promise<any> {
|
getFiles(nodeData: INodeData) {
|
||||||
const textSplitter = nodeData.inputs?.textSplitter as TextSplitter
|
|
||||||
const csvFileBase64 = nodeData.inputs?.csvFile as string
|
const csvFileBase64 = nodeData.inputs?.csvFile as string
|
||||||
const columnName = nodeData.inputs?.columnName as string
|
|
||||||
const metadata = nodeData.inputs?.metadata
|
|
||||||
const output = nodeData.outputs?.output as string
|
|
||||||
const _omitMetadataKeys = nodeData.inputs?.omitMetadataKeys as string
|
|
||||||
|
|
||||||
let omitMetadataKeys: string[] = []
|
|
||||||
if (_omitMetadataKeys) {
|
|
||||||
omitMetadataKeys = _omitMetadataKeys.split(',').map((key) => key.trim())
|
|
||||||
}
|
|
||||||
|
|
||||||
let docs: IDocument[] = []
|
|
||||||
let files: string[] = []
|
let files: string[] = []
|
||||||
|
let fromStorage: boolean = true
|
||||||
|
|
||||||
if (csvFileBase64.startsWith('FILE-STORAGE::')) {
|
if (csvFileBase64.startsWith('FILE-STORAGE::')) {
|
||||||
const fileName = csvFileBase64.replace('FILE-STORAGE::', '')
|
const fileName = csvFileBase64.replace('FILE-STORAGE::', '')
|
||||||
@@ -105,21 +94,6 @@ class Csv_DocumentLoaders implements INode {
|
|||||||
} else {
|
} else {
|
||||||
files = [fileName]
|
files = [fileName]
|
||||||
}
|
}
|
||||||
const chatflowid = options.chatflowid
|
|
||||||
|
|
||||||
for (const file of files) {
|
|
||||||
if (!file) continue
|
|
||||||
const fileData = await getFileFromStorage(file, chatflowid)
|
|
||||||
const blob = new Blob([fileData])
|
|
||||||
const loader = new CSVLoader(blob, columnName.trim().length === 0 ? undefined : columnName.trim())
|
|
||||||
|
|
||||||
if (textSplitter) {
|
|
||||||
docs = await loader.load()
|
|
||||||
docs = await textSplitter.splitDocuments(docs)
|
|
||||||
} else {
|
|
||||||
docs.push(...(await loader.load()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if (csvFileBase64.startsWith('[') && csvFileBase64.endsWith(']')) {
|
if (csvFileBase64.startsWith('[') && csvFileBase64.endsWith(']')) {
|
||||||
files = JSON.parse(csvFileBase64)
|
files = JSON.parse(csvFileBase64)
|
||||||
@@ -127,64 +101,49 @@ class Csv_DocumentLoaders implements INode {
|
|||||||
files = [csvFileBase64]
|
files = [csvFileBase64]
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const file of files) {
|
fromStorage = false
|
||||||
if (!file) continue
|
}
|
||||||
|
|
||||||
|
return { files, fromStorage }
|
||||||
|
}
|
||||||
|
|
||||||
|
async getFileData(file: string, { chatflowid }: { chatflowid: string }, fromStorage?: boolean) {
|
||||||
|
if (fromStorage) {
|
||||||
|
return getFileFromStorage(file, chatflowid)
|
||||||
|
} else {
|
||||||
const splitDataURI = file.split(',')
|
const splitDataURI = file.split(',')
|
||||||
splitDataURI.pop()
|
splitDataURI.pop()
|
||||||
const bf = Buffer.from(splitDataURI.pop() || '', 'base64')
|
return Buffer.from(splitDataURI.pop() || '', 'base64')
|
||||||
const blob = new Blob([bf])
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async init(nodeData: INodeData, _: string, options: ICommonObject): Promise<any> {
|
||||||
|
const textSplitter = nodeData.inputs?.textSplitter as TextSplitter
|
||||||
|
const columnName = nodeData.inputs?.columnName as string
|
||||||
|
const metadata = nodeData.inputs?.metadata
|
||||||
|
const output = nodeData.outputs?.output as string
|
||||||
|
const _omitMetadataKeys = nodeData.inputs?.omitMetadataKeys as string
|
||||||
|
|
||||||
|
let docs: IDocument[] = []
|
||||||
|
|
||||||
|
const chatflowid = options.chatflowid
|
||||||
|
|
||||||
|
const { files, fromStorage } = this.getFiles(nodeData)
|
||||||
|
|
||||||
|
for (const file of files) {
|
||||||
|
if (!file) continue
|
||||||
|
|
||||||
|
const fileData = await this.getFileData(file, { chatflowid }, fromStorage)
|
||||||
|
const blob = new Blob([fileData])
|
||||||
const loader = new CSVLoader(blob, columnName.trim().length === 0 ? undefined : columnName.trim())
|
const loader = new CSVLoader(blob, columnName.trim().length === 0 ? undefined : columnName.trim())
|
||||||
|
|
||||||
if (textSplitter) {
|
// use spread instead of push, because it raises RangeError: Maximum call stack size exceeded when too many docs
|
||||||
docs = await loader.load()
|
docs = [...docs, ...(await handleDocumentLoaderDocuments(loader, textSplitter))]
|
||||||
docs = await textSplitter.splitDocuments(docs)
|
|
||||||
} else {
|
|
||||||
docs.push(...(await loader.load()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (metadata) {
|
docs = handleDocumentLoaderMetadata(docs, _omitMetadataKeys, metadata)
|
||||||
const parsedMetadata = typeof metadata === 'object' ? metadata : JSON.parse(metadata)
|
|
||||||
docs = docs.map((doc) => ({
|
|
||||||
...doc,
|
|
||||||
metadata:
|
|
||||||
_omitMetadataKeys === '*'
|
|
||||||
? {
|
|
||||||
...parsedMetadata
|
|
||||||
}
|
|
||||||
: omit(
|
|
||||||
{
|
|
||||||
...doc.metadata,
|
|
||||||
...parsedMetadata
|
|
||||||
},
|
|
||||||
omitMetadataKeys
|
|
||||||
)
|
|
||||||
}))
|
|
||||||
} else {
|
|
||||||
docs = docs.map((doc) => ({
|
|
||||||
...doc,
|
|
||||||
metadata:
|
|
||||||
_omitMetadataKeys === '*'
|
|
||||||
? {}
|
|
||||||
: omit(
|
|
||||||
{
|
|
||||||
...doc.metadata
|
|
||||||
},
|
|
||||||
omitMetadataKeys
|
|
||||||
)
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
if (output === 'document') {
|
return handleDocumentLoaderOutput(docs, output)
|
||||||
return docs
|
|
||||||
} else {
|
|
||||||
let finaltext = ''
|
|
||||||
for (const doc of docs) {
|
|
||||||
finaltext += `${doc.pageContent}\n`
|
|
||||||
}
|
|
||||||
return handleEscapeCharacters(finaltext, false)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
import { TextLoader } from 'langchain/document_loaders/fs/text'
|
||||||
|
import Papa from 'papaparse'
|
||||||
|
|
||||||
|
type CSVLoaderOptions = {
|
||||||
|
// Return specifific column from key (string) or index (integer)
|
||||||
|
column?: string | number
|
||||||
|
// Force separator (default: auto detect)
|
||||||
|
separator?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class that extends the TextLoader class. It represents a document
|
||||||
|
* loader that loads documents from a CSV file. It has a constructor that
|
||||||
|
* takes a `filePathOrBlob` parameter representing the path to the CSV
|
||||||
|
* file or a Blob object, and an optional `options` parameter of type
|
||||||
|
* `CSVLoaderOptions` or a string representing the column to use as the
|
||||||
|
* document's pageContent.
|
||||||
|
*/
|
||||||
|
export class CSVLoader extends TextLoader {
|
||||||
|
protected options: CSVLoaderOptions = {}
|
||||||
|
|
||||||
|
constructor(filePathOrBlob: ConstructorParameters<typeof TextLoader>[0], options?: CSVLoaderOptions | string) {
|
||||||
|
super(filePathOrBlob)
|
||||||
|
|
||||||
|
if (typeof options === 'string') {
|
||||||
|
this.options = { column: options }
|
||||||
|
} else {
|
||||||
|
this.options = options ?? this.options
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* A protected method that parses the raw CSV data and returns an array of
|
||||||
|
* strings representing the pageContent of each document. It uses the
|
||||||
|
* `papaparse` to parse the CSV data. If
|
||||||
|
* the `column` option is specified, it checks if the column exists in the
|
||||||
|
* CSV file and returns the values of that column as the pageContent. If
|
||||||
|
* the `column` option is not specified, it converts each row of the CSV
|
||||||
|
* data into key/value pairs and joins them with newline characters.
|
||||||
|
* @param raw The raw CSV data to be parsed.
|
||||||
|
* @returns An array of strings representing the pageContent of each document.
|
||||||
|
*/
|
||||||
|
async parse(raw: string): Promise<string[]> {
|
||||||
|
const { column, separator } = this.options
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: parsed,
|
||||||
|
meta: { fields = [] }
|
||||||
|
} = Papa.parse<{ [K: string]: string }>(raw.trim(), {
|
||||||
|
delimiter: separator,
|
||||||
|
header: true
|
||||||
|
})
|
||||||
|
|
||||||
|
if (column !== undefined) {
|
||||||
|
if (!fields.length) {
|
||||||
|
throw new Error(`Unable to resolve fields from header.`)
|
||||||
|
}
|
||||||
|
|
||||||
|
let searchIdx = column
|
||||||
|
|
||||||
|
if (typeof column == 'number') {
|
||||||
|
searchIdx = fields[column]
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fields.includes(searchIdx as string)) {
|
||||||
|
throw new Error(`Column ${column} not found in CSV file.`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note TextLoader will raise an exception if the value is null.
|
||||||
|
return parsed.map((row) => row[searchIdx])
|
||||||
|
}
|
||||||
|
|
||||||
|
return parsed.map((row) => fields.map((key) => `${key.trim() || '_0'}: ${row[key]?.trim()}`).join('\n'))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,11 @@
|
|||||||
import { omit } from 'lodash'
|
|
||||||
import { ICommonObject, INode, INodeData, INodeOptionsValue, INodeOutputsValue, INodeParams } from '../../../src/Interface'
|
import { ICommonObject, INode, INodeData, INodeOptionsValue, INodeOutputsValue, INodeParams } from '../../../src/Interface'
|
||||||
import { getCredentialData, getCredentialParam, handleEscapeCharacters } from '../../../src/utils'
|
import {
|
||||||
|
getCredentialData,
|
||||||
|
getCredentialParam,
|
||||||
|
handleDocumentLoaderDocuments,
|
||||||
|
handleDocumentLoaderMetadata,
|
||||||
|
handleDocumentLoaderOutput
|
||||||
|
} from '../../../src/utils'
|
||||||
import { S3Client, GetObjectCommand, S3ClientConfig, ListObjectsV2Command, ListObjectsV2Output } from '@aws-sdk/client-s3'
|
import { S3Client, GetObjectCommand, S3ClientConfig, ListObjectsV2Command, ListObjectsV2Output } from '@aws-sdk/client-s3'
|
||||||
import { getRegions, MODEL_TYPE } from '../../../src/modelLoader'
|
import { getRegions, MODEL_TYPE } from '../../../src/modelLoader'
|
||||||
import { Readable } from 'node:stream'
|
import { Readable } from 'node:stream'
|
||||||
@@ -10,12 +15,13 @@ import * as os from 'node:os'
|
|||||||
|
|
||||||
import { DirectoryLoader } from 'langchain/document_loaders/fs/directory'
|
import { DirectoryLoader } from 'langchain/document_loaders/fs/directory'
|
||||||
import { JSONLoader } from 'langchain/document_loaders/fs/json'
|
import { JSONLoader } from 'langchain/document_loaders/fs/json'
|
||||||
import { CSVLoader } from '@langchain/community/document_loaders/fs/csv'
|
|
||||||
import { PDFLoader } from '@langchain/community/document_loaders/fs/pdf'
|
import { PDFLoader } from '@langchain/community/document_loaders/fs/pdf'
|
||||||
import { DocxLoader } from '@langchain/community/document_loaders/fs/docx'
|
import { DocxLoader } from '@langchain/community/document_loaders/fs/docx'
|
||||||
import { TextLoader } from 'langchain/document_loaders/fs/text'
|
import { TextLoader } from 'langchain/document_loaders/fs/text'
|
||||||
import { TextSplitter } from 'langchain/text_splitter'
|
import { TextSplitter } from 'langchain/text_splitter'
|
||||||
|
|
||||||
|
import { CSVLoader } from '../Csv/CsvLoader'
|
||||||
|
|
||||||
class S3_DocumentLoaders implements INode {
|
class S3_DocumentLoaders implements INode {
|
||||||
label: string
|
label: string
|
||||||
name: string
|
name: string
|
||||||
@@ -151,11 +157,6 @@ class S3_DocumentLoaders implements INode {
|
|||||||
const _omitMetadataKeys = nodeData.inputs?.omitMetadataKeys as string
|
const _omitMetadataKeys = nodeData.inputs?.omitMetadataKeys as string
|
||||||
const output = nodeData.outputs?.output as string
|
const output = nodeData.outputs?.output as string
|
||||||
|
|
||||||
let omitMetadataKeys: string[] = []
|
|
||||||
if (_omitMetadataKeys) {
|
|
||||||
omitMetadataKeys = _omitMetadataKeys.split(',').map((key) => key.trim())
|
|
||||||
}
|
|
||||||
|
|
||||||
let credentials: S3ClientConfig['credentials'] | undefined
|
let credentials: S3ClientConfig['credentials'] | undefined
|
||||||
|
|
||||||
if (nodeData.credential) {
|
if (nodeData.credential) {
|
||||||
@@ -241,11 +242,11 @@ class S3_DocumentLoaders implements INode {
|
|||||||
'.csv': (path) => new CSVLoader(path),
|
'.csv': (path) => new CSVLoader(path),
|
||||||
'.docx': (path) => new DocxLoader(path),
|
'.docx': (path) => new DocxLoader(path),
|
||||||
'.pdf': (path) =>
|
'.pdf': (path) =>
|
||||||
pdfUsage === 'perFile'
|
new PDFLoader(path, {
|
||||||
? // @ts-ignore
|
splitPages: pdfUsage !== 'perFile',
|
||||||
new PDFLoader(path, { splitPages: false, pdfjs: () => import('pdf-parse/lib/pdf.js/v1.10.100/build/pdf.js') })
|
// @ts-ignore
|
||||||
: // @ts-ignore
|
pdfjs: () => import('pdf-parse/lib/pdf.js/v1.10.100/build/pdf.js')
|
||||||
new PDFLoader(path, { pdfjs: () => import('pdf-parse/lib/pdf.js/v1.10.100/build/pdf.js') }),
|
}),
|
||||||
'.aspx': (path) => new TextLoader(path),
|
'.aspx': (path) => new TextLoader(path),
|
||||||
'.asp': (path) => new TextLoader(path),
|
'.asp': (path) => new TextLoader(path),
|
||||||
'.cpp': (path) => new TextLoader(path), // C++
|
'.cpp': (path) => new TextLoader(path), // C++
|
||||||
@@ -284,63 +285,16 @@ class S3_DocumentLoaders implements INode {
|
|||||||
true
|
true
|
||||||
)
|
)
|
||||||
|
|
||||||
let docs = []
|
let docs = await handleDocumentLoaderDocuments(loader, textSplitter)
|
||||||
|
|
||||||
if (textSplitter) {
|
docs = handleDocumentLoaderMetadata(docs, _omitMetadataKeys, metadata)
|
||||||
let splittedDocs = await loader.load()
|
|
||||||
splittedDocs = await textSplitter.splitDocuments(splittedDocs)
|
|
||||||
docs.push(...splittedDocs)
|
|
||||||
} else {
|
|
||||||
docs = await loader.load()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (metadata) {
|
|
||||||
const parsedMetadata = typeof metadata === 'object' ? metadata : JSON.parse(metadata)
|
|
||||||
docs = docs.map((doc) => ({
|
|
||||||
...doc,
|
|
||||||
metadata:
|
|
||||||
_omitMetadataKeys === '*'
|
|
||||||
? {
|
|
||||||
...parsedMetadata
|
|
||||||
}
|
|
||||||
: omit(
|
|
||||||
{
|
|
||||||
...doc.metadata,
|
|
||||||
...parsedMetadata
|
|
||||||
},
|
|
||||||
omitMetadataKeys
|
|
||||||
)
|
|
||||||
}))
|
|
||||||
} else {
|
|
||||||
docs = docs.map((doc) => ({
|
|
||||||
...doc,
|
|
||||||
metadata:
|
|
||||||
_omitMetadataKeys === '*'
|
|
||||||
? {}
|
|
||||||
: omit(
|
|
||||||
{
|
|
||||||
...doc.metadata
|
|
||||||
},
|
|
||||||
omitMetadataKeys
|
|
||||||
)
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
|
return handleDocumentLoaderOutput(docs, output)
|
||||||
|
} catch (e: any) {
|
||||||
|
throw new Error(`Failed to load data from bucket ${bucketName}: ${e.message}`)
|
||||||
|
} finally {
|
||||||
// remove the temp directory before returning docs
|
// remove the temp directory before returning docs
|
||||||
fsDefault.rmSync(tempDir, { recursive: true })
|
fsDefault.rmSync(tempDir, { recursive: true })
|
||||||
|
|
||||||
if (output === 'document') {
|
|
||||||
return docs
|
|
||||||
} else {
|
|
||||||
let finaltext = ''
|
|
||||||
for (const doc of docs) {
|
|
||||||
finaltext += `${doc.pageContent}\n`
|
|
||||||
}
|
|
||||||
return handleEscapeCharacters(finaltext, false)
|
|
||||||
}
|
|
||||||
} catch (e: any) {
|
|
||||||
fsDefault.rmSync(tempDir, { recursive: true })
|
|
||||||
throw new Error(`Failed to load data from bucket ${bucketName}: ${e.message}`)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { omit } from 'lodash'
|
|
||||||
import { ICommonObject, INode, INodeData, INodeOptionsValue, INodeOutputsValue, INodeParams } from '../../../src/Interface'
|
import { ICommonObject, INode, INodeData, INodeOptionsValue, INodeOutputsValue, INodeParams } from '../../../src/Interface'
|
||||||
import { S3Loader } from '@langchain/community/document_loaders/web/s3'
|
import { S3Loader } from '@langchain/community/document_loaders/web/s3'
|
||||||
import {
|
import {
|
||||||
@@ -8,7 +7,13 @@ import {
|
|||||||
SkipInferTableTypes,
|
SkipInferTableTypes,
|
||||||
HiResModelName
|
HiResModelName
|
||||||
} from '@langchain/community/document_loaders/fs/unstructured'
|
} from '@langchain/community/document_loaders/fs/unstructured'
|
||||||
import { getCredentialData, getCredentialParam, handleEscapeCharacters } from '../../../src/utils'
|
import {
|
||||||
|
getCredentialData,
|
||||||
|
getCredentialParam,
|
||||||
|
handleDocumentLoaderDocuments,
|
||||||
|
handleDocumentLoaderMetadata,
|
||||||
|
handleDocumentLoaderOutput
|
||||||
|
} from '../../../src/utils'
|
||||||
import { S3Client, GetObjectCommand, S3ClientConfig } from '@aws-sdk/client-s3'
|
import { S3Client, GetObjectCommand, S3ClientConfig } from '@aws-sdk/client-s3'
|
||||||
import { getRegions, MODEL_TYPE } from '../../../src/modelLoader'
|
import { getRegions, MODEL_TYPE } from '../../../src/modelLoader'
|
||||||
import { Readable } from 'node:stream'
|
import { Readable } from 'node:stream'
|
||||||
@@ -483,11 +488,6 @@ class S3_DocumentLoaders implements INode {
|
|||||||
const _omitMetadataKeys = nodeData.inputs?.omitMetadataKeys as string
|
const _omitMetadataKeys = nodeData.inputs?.omitMetadataKeys as string
|
||||||
const output = nodeData.outputs?.output as string
|
const output = nodeData.outputs?.output as string
|
||||||
|
|
||||||
let omitMetadataKeys: string[] = []
|
|
||||||
if (_omitMetadataKeys) {
|
|
||||||
omitMetadataKeys = _omitMetadataKeys.split(',').map((key) => key.trim())
|
|
||||||
}
|
|
||||||
|
|
||||||
let credentials: S3ClientConfig['credentials'] | undefined
|
let credentials: S3ClientConfig['credentials'] | undefined
|
||||||
|
|
||||||
if (nodeData.credential) {
|
if (nodeData.credential) {
|
||||||
@@ -572,56 +572,15 @@ class S3_DocumentLoaders implements INode {
|
|||||||
|
|
||||||
const unstructuredLoader = new UnstructuredLoader(filePath, obj)
|
const unstructuredLoader = new UnstructuredLoader(filePath, obj)
|
||||||
|
|
||||||
let docs = await unstructuredLoader.load()
|
let docs = await handleDocumentLoaderDocuments(unstructuredLoader)
|
||||||
|
|
||||||
if (metadata) {
|
docs = handleDocumentLoaderMetadata(docs, _omitMetadataKeys, metadata, sourceIdKey)
|
||||||
const parsedMetadata = typeof metadata === 'object' ? metadata : JSON.parse(metadata)
|
|
||||||
docs = docs.map((doc) => ({
|
|
||||||
...doc,
|
|
||||||
metadata:
|
|
||||||
_omitMetadataKeys === '*'
|
|
||||||
? {
|
|
||||||
...parsedMetadata
|
|
||||||
}
|
|
||||||
: omit(
|
|
||||||
{
|
|
||||||
...doc.metadata,
|
|
||||||
...parsedMetadata,
|
|
||||||
[sourceIdKey]: doc.metadata[sourceIdKey] || sourceIdKey
|
|
||||||
},
|
|
||||||
omitMetadataKeys
|
|
||||||
)
|
|
||||||
}))
|
|
||||||
} else {
|
|
||||||
docs = docs.map((doc) => ({
|
|
||||||
...doc,
|
|
||||||
metadata:
|
|
||||||
_omitMetadataKeys === '*'
|
|
||||||
? {}
|
|
||||||
: omit(
|
|
||||||
{
|
|
||||||
...doc.metadata,
|
|
||||||
[sourceIdKey]: doc.metadata[sourceIdKey] || sourceIdKey
|
|
||||||
},
|
|
||||||
omitMetadataKeys
|
|
||||||
)
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
fsDefault.rmSync(path.dirname(filePath), { recursive: true })
|
return handleDocumentLoaderOutput(docs, output)
|
||||||
|
|
||||||
if (output === 'document') {
|
|
||||||
return docs
|
|
||||||
} else {
|
|
||||||
let finaltext = ''
|
|
||||||
for (const doc of docs) {
|
|
||||||
finaltext += `${doc.pageContent}\n`
|
|
||||||
}
|
|
||||||
return handleEscapeCharacters(finaltext, false)
|
|
||||||
}
|
|
||||||
} catch {
|
} catch {
|
||||||
fsDefault.rmSync(path.dirname(filePath), { recursive: true })
|
|
||||||
throw new Error(`Failed to load file ${filePath} using unstructured loader.`)
|
throw new Error(`Failed to load file ${filePath} using unstructured loader.`)
|
||||||
|
} finally {
|
||||||
|
fsDefault.rmSync(path.dirname(filePath), { recursive: true })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -115,6 +115,7 @@
|
|||||||
"object-hash": "^3.0.0",
|
"object-hash": "^3.0.0",
|
||||||
"ollama": "^0.5.11",
|
"ollama": "^0.5.11",
|
||||||
"openai": "^4.57.3",
|
"openai": "^4.57.3",
|
||||||
|
"papaparse": "^5.4.1",
|
||||||
"pdf-parse": "^1.1.1",
|
"pdf-parse": "^1.1.1",
|
||||||
"pdfjs-dist": "^3.7.107",
|
"pdfjs-dist": "^3.7.107",
|
||||||
"pg": "^8.11.2",
|
"pg": "^8.11.2",
|
||||||
@@ -140,6 +141,7 @@
|
|||||||
"@types/lodash": "^4.14.202",
|
"@types/lodash": "^4.14.202",
|
||||||
"@types/node-fetch": "2.6.2",
|
"@types/node-fetch": "2.6.2",
|
||||||
"@types/object-hash": "^3.0.2",
|
"@types/object-hash": "^3.0.2",
|
||||||
|
"@types/papaparse": "^5.3.15",
|
||||||
"@types/pg": "^8.10.2",
|
"@types/pg": "^8.10.2",
|
||||||
"@types/ws": "^8.5.3",
|
"@types/ws": "^8.5.3",
|
||||||
"babel-register": "^6.26.0",
|
"babel-register": "^6.26.0",
|
||||||
|
|||||||
@@ -7,10 +7,14 @@ import { z } from 'zod'
|
|||||||
import { DataSource } from 'typeorm'
|
import { DataSource } from 'typeorm'
|
||||||
import { ICommonObject, IDatabaseEntity, IDocument, IMessage, INodeData, IVariable, MessageContentImageUrl } from './Interface'
|
import { ICommonObject, IDatabaseEntity, IDocument, IMessage, INodeData, IVariable, MessageContentImageUrl } from './Interface'
|
||||||
import { AES, enc } from 'crypto-js'
|
import { AES, enc } from 'crypto-js'
|
||||||
|
import { omit } from 'lodash'
|
||||||
import { AIMessage, HumanMessage, BaseMessage } from '@langchain/core/messages'
|
import { AIMessage, HumanMessage, BaseMessage } from '@langchain/core/messages'
|
||||||
|
import { Document } from '@langchain/core/documents'
|
||||||
import { getFileFromStorage } from './storageUtils'
|
import { getFileFromStorage } from './storageUtils'
|
||||||
import { GetSecretValueCommand, SecretsManagerClient, SecretsManagerClientConfig } from '@aws-sdk/client-secrets-manager'
|
import { GetSecretValueCommand, SecretsManagerClient, SecretsManagerClientConfig } from '@aws-sdk/client-secrets-manager'
|
||||||
import { customGet } from '../nodes/sequentialagents/commonUtils'
|
import { customGet } from '../nodes/sequentialagents/commonUtils'
|
||||||
|
import { TextSplitter } from 'langchain/text_splitter'
|
||||||
|
import { DocumentLoader } from 'langchain/document_loaders/base'
|
||||||
|
|
||||||
export const numberOrExpressionRegex = '^(\\d+\\.?\\d*|{{.*}})$' //return true if string consists only numbers OR expression {{}}
|
export const numberOrExpressionRegex = '^(\\d+\\.?\\d*|{{.*}})$' //return true if string consists only numbers OR expression {{}}
|
||||||
export const notEmptyRegex = '(.|\\s)*\\S(.|\\s)*' //return true if string is not empty or blank
|
export const notEmptyRegex = '(.|\\s)*\\S(.|\\s)*' //return true if string is not empty or blank
|
||||||
@@ -1077,3 +1081,68 @@ export const resolveFlowObjValue = (obj: any, sourceObj: any): any => {
|
|||||||
return obj
|
return obj
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const handleDocumentLoaderOutput = (docs: Document[], output: string) => {
|
||||||
|
if (output === 'document') {
|
||||||
|
return docs
|
||||||
|
} else {
|
||||||
|
let finaltext = ''
|
||||||
|
for (const doc of docs) {
|
||||||
|
finaltext += `${doc.pageContent}\n`
|
||||||
|
}
|
||||||
|
return handleEscapeCharacters(finaltext, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const parseDocumentLoaderMetadata = (metadata: object | string): object => {
|
||||||
|
if (!metadata) return {}
|
||||||
|
|
||||||
|
if (typeof metadata !== 'object') {
|
||||||
|
return JSON.parse(metadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
return metadata
|
||||||
|
}
|
||||||
|
|
||||||
|
export const handleDocumentLoaderMetadata = (
|
||||||
|
docs: Document[],
|
||||||
|
_omitMetadataKeys: string,
|
||||||
|
metadata: object | string = {},
|
||||||
|
sourceIdKey?: string
|
||||||
|
) => {
|
||||||
|
let omitMetadataKeys: string[] = []
|
||||||
|
if (_omitMetadataKeys) {
|
||||||
|
omitMetadataKeys = _omitMetadataKeys.split(',').map((key) => key.trim())
|
||||||
|
}
|
||||||
|
|
||||||
|
metadata = parseDocumentLoaderMetadata(metadata)
|
||||||
|
|
||||||
|
return docs.map((doc) => ({
|
||||||
|
...doc,
|
||||||
|
metadata:
|
||||||
|
_omitMetadataKeys === '*'
|
||||||
|
? metadata
|
||||||
|
: omit(
|
||||||
|
{
|
||||||
|
...metadata,
|
||||||
|
...doc.metadata,
|
||||||
|
...(sourceIdKey ? { [sourceIdKey]: doc.metadata[sourceIdKey] || sourceIdKey } : undefined)
|
||||||
|
},
|
||||||
|
omitMetadataKeys
|
||||||
|
)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
export const handleDocumentLoaderDocuments = async (loader: DocumentLoader, textSplitter?: TextSplitter) => {
|
||||||
|
let docs: Document[] = []
|
||||||
|
|
||||||
|
if (textSplitter) {
|
||||||
|
let splittedDocs = await loader.load()
|
||||||
|
splittedDocs = await textSplitter.splitDocuments(splittedDocs)
|
||||||
|
docs = splittedDocs
|
||||||
|
} else {
|
||||||
|
docs = await loader.load()
|
||||||
|
}
|
||||||
|
|
||||||
|
return docs
|
||||||
|
}
|
||||||
|
|||||||
Generated
+10
-4
@@ -388,6 +388,9 @@ importers:
|
|||||||
openai:
|
openai:
|
||||||
specifier: 4.57.3
|
specifier: 4.57.3
|
||||||
version: 4.57.3(encoding@0.1.13)(zod@3.22.4)
|
version: 4.57.3(encoding@0.1.13)(zod@3.22.4)
|
||||||
|
papaparse:
|
||||||
|
specifier: ^5.4.1
|
||||||
|
version: 5.4.1
|
||||||
pdf-parse:
|
pdf-parse:
|
||||||
specifier: ^1.1.1
|
specifier: ^1.1.1
|
||||||
version: 1.1.1
|
version: 1.1.1
|
||||||
@@ -458,6 +461,9 @@ importers:
|
|||||||
'@types/object-hash':
|
'@types/object-hash':
|
||||||
specifier: ^3.0.2
|
specifier: ^3.0.2
|
||||||
version: 3.0.6
|
version: 3.0.6
|
||||||
|
'@types/papaparse':
|
||||||
|
specifier: ^5.3.15
|
||||||
|
version: 5.3.15
|
||||||
'@types/pg':
|
'@types/pg':
|
||||||
specifier: ^8.10.2
|
specifier: ^8.10.2
|
||||||
version: 8.11.2
|
version: 8.11.2
|
||||||
@@ -6365,8 +6371,8 @@ packages:
|
|||||||
'@types/object-hash@3.0.6':
|
'@types/object-hash@3.0.6':
|
||||||
resolution: { integrity: sha512-fOBV8C1FIu2ELinoILQ+ApxcUKz4ngq+IWUYrxSGjXzzjUALijilampwkMgEtJ+h2njAW3pi853QpzNVCHB73w== }
|
resolution: { integrity: sha512-fOBV8C1FIu2ELinoILQ+ApxcUKz4ngq+IWUYrxSGjXzzjUALijilampwkMgEtJ+h2njAW3pi853QpzNVCHB73w== }
|
||||||
|
|
||||||
'@types/papaparse@5.3.14':
|
'@types/papaparse@5.3.15':
|
||||||
resolution: { integrity: sha512-LxJ4iEFcpqc6METwp9f6BV6VVc43m6MfH0VqFosHvrUgfXiFe6ww7R3itkOQ+TCK6Y+Iv/+RnnvtRZnkc5Kc9g== }
|
resolution: { integrity: sha512-JHe6vF6x/8Z85nCX4yFdDslN11d+1pr12E526X8WAfhadOeaOTx5AuIkvDKIBopfvlzpzkdMx4YyvSKCM9oqtw== }
|
||||||
|
|
||||||
'@types/parse-json@4.0.2':
|
'@types/parse-json@4.0.2':
|
||||||
resolution: { integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw== }
|
resolution: { integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw== }
|
||||||
@@ -24808,7 +24814,7 @@ snapshots:
|
|||||||
|
|
||||||
'@types/object-hash@3.0.6': {}
|
'@types/object-hash@3.0.6': {}
|
||||||
|
|
||||||
'@types/papaparse@5.3.14':
|
'@types/papaparse@5.3.15':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 20.12.12
|
'@types/node': 20.12.12
|
||||||
|
|
||||||
@@ -31514,7 +31520,7 @@ snapshots:
|
|||||||
'@pinecone-database/pinecone': 2.2.2
|
'@pinecone-database/pinecone': 2.2.2
|
||||||
'@qdrant/js-client-rest': 1.9.0(typescript@5.5.2)
|
'@qdrant/js-client-rest': 1.9.0(typescript@5.5.2)
|
||||||
'@types/lodash': 4.17.4
|
'@types/lodash': 4.17.4
|
||||||
'@types/papaparse': 5.3.14
|
'@types/papaparse': 5.3.15
|
||||||
'@types/pg': 8.11.6
|
'@types/pg': 8.11.6
|
||||||
'@xenova/transformers': 2.17.1
|
'@xenova/transformers': 2.17.1
|
||||||
'@zilliz/milvus2-sdk-node': 2.4.2
|
'@zilliz/milvus2-sdk-node': 2.4.2
|
||||||
|
|||||||
Reference in New Issue
Block a user