From e0dd3984b987fb8cf1bd3cb89b99bd345b18b0b2 Mon Sep 17 00:00:00 2001 From: Ser0n-ath Date: Sat, 30 Nov 2024 14:52:46 -0500 Subject: [PATCH 1/5] Add document loader skeleton --- .../document_loaders/web_loaders/dropbox.mdx | 1 + langchain/.env.example | 1 + libs/langchain-community/.gitignore | 4 ++++ libs/langchain-community/langchain.config.js | 2 ++ libs/langchain-community/package.json | 14 ++++++++++++++ .../document_loaders/tests/dropbox.int.test.ts | 1 + .../example_data/dropbox/folder_1/example.txt | 4 ++++ .../example_data/dropbox/folder_1/example2.txt | 4 ++++ .../dropbox/folder_2/Jacob_Lee_Resume_2023.pdf | Bin 0 -> 73667 bytes .../tests/example_data/dropbox/hello.txt | 2 ++ .../src/document_loaders/web/dropbox.ts | 1 + .../src/load/import_constants.ts | 1 + yarn.lock | 12 ++++++++++++ 13 files changed, 47 insertions(+) create mode 100644 docs/core_docs/docs/integrations/document_loaders/web_loaders/dropbox.mdx create mode 100644 libs/langchain-community/src/document_loaders/tests/dropbox.int.test.ts create mode 100644 libs/langchain-community/src/document_loaders/tests/example_data/dropbox/folder_1/example.txt create mode 100644 libs/langchain-community/src/document_loaders/tests/example_data/dropbox/folder_1/example2.txt create mode 100644 libs/langchain-community/src/document_loaders/tests/example_data/dropbox/folder_2/Jacob_Lee_Resume_2023.pdf create mode 100644 libs/langchain-community/src/document_loaders/tests/example_data/dropbox/hello.txt create mode 100644 libs/langchain-community/src/document_loaders/web/dropbox.ts diff --git a/docs/core_docs/docs/integrations/document_loaders/web_loaders/dropbox.mdx b/docs/core_docs/docs/integrations/document_loaders/web_loaders/dropbox.mdx new file mode 100644 index 000000000000..6b9129c1687f --- /dev/null +++ b/docs/core_docs/docs/integrations/document_loaders/web_loaders/dropbox.mdx @@ -0,0 +1 @@ +// documentation goes here \ No newline at end of file diff --git a/langchain/.env.example b/langchain/.env.example index 2eda74311a41..d91802e79f0f 100644 --- a/langchain/.env.example +++ b/langchain/.env.example @@ -102,3 +102,4 @@ GOOGLE_ROUTES_API_KEY=ADD_YOURS_HERE CONFLUENCE_USERNAME=ADD_YOURS_HERE CONFLUENCE_PASSWORD=ADD_YOURS_HERE CONFLUENCE_PATH=ADD_YOURS_HERE +DROPBOX_ACCESS_TOKEN=ADD_YOURS_HERE \ No newline at end of file diff --git a/libs/langchain-community/.gitignore b/libs/langchain-community/.gitignore index e6ae5fa54a4f..b0ccac535902 100644 --- a/libs/langchain-community/.gitignore +++ b/libs/langchain-community/.gitignore @@ -874,6 +874,10 @@ document_loaders/web/cheerio.cjs document_loaders/web/cheerio.js document_loaders/web/cheerio.d.ts document_loaders/web/cheerio.d.cts +document_loaders/web/dropbox.cjs +document_loaders/web/dropbox.js +document_loaders/web/dropbox.d.ts +document_loaders/web/dropbox.d.cts document_loaders/web/html.cjs document_loaders/web/html.js document_loaders/web/html.d.ts diff --git a/libs/langchain-community/langchain.config.js b/libs/langchain-community/langchain.config.js index 4a402c6941e8..75fc7ff4de73 100644 --- a/libs/langchain-community/langchain.config.js +++ b/libs/langchain-community/langchain.config.js @@ -273,6 +273,7 @@ export const config = { "document_loaders/web/azure_blob_storage_file", "document_loaders/web/browserbase": "document_loaders/web/browserbase", "document_loaders/web/cheerio": "document_loaders/web/cheerio", + "document_loaders/web/dropbox": "document_loaders/web/dropbox", "document_loaders/web/html": "document_loaders/web/html", "document_loaders/web/puppeteer": "document_loaders/web/puppeteer", "document_loaders/web/playwright": "document_loaders/web/playwright", @@ -493,6 +494,7 @@ export const config = { "document_loaders/web/azure_blob_storage_file", "document_loaders/web/browserbase", "document_loaders/web/cheerio", + "document_loaders/web/dropbox", "document_loaders/web/puppeteer", "document_loaders/web/playwright", "document_loaders/web/college_confidential", diff --git a/libs/langchain-community/package.json b/libs/langchain-community/package.json index 7b826ad1e106..a84f9d8c8ba6 100644 --- a/libs/langchain-community/package.json +++ b/libs/langchain-community/package.json @@ -37,6 +37,7 @@ "dependencies": { "@langchain/openai": ">=0.2.0 <0.4.0", "binary-extensions": "^2.2.0", + "dropbox": "^10.34.0", "expr-eval": "^2.0.2", "flat": "^5.0.2", "js-yaml": "^4.1.0", @@ -2683,6 +2684,15 @@ "import": "./document_loaders/web/cheerio.js", "require": "./document_loaders/web/cheerio.cjs" }, + "./document_loaders/web/dropbox": { + "types": { + "import": "./document_loaders/web/dropbox.d.ts", + "require": "./document_loaders/web/dropbox.d.cts", + "default": "./document_loaders/web/dropbox.d.ts" + }, + "import": "./document_loaders/web/dropbox.js", + "require": "./document_loaders/web/dropbox.cjs" + }, "./document_loaders/web/html": { "types": { "import": "./document_loaders/web/html.d.ts", @@ -3977,6 +3987,10 @@ "document_loaders/web/cheerio.js", "document_loaders/web/cheerio.d.ts", "document_loaders/web/cheerio.d.cts", + "document_loaders/web/dropbox.cjs", + "document_loaders/web/dropbox.js", + "document_loaders/web/dropbox.d.ts", + "document_loaders/web/dropbox.d.cts", "document_loaders/web/html.cjs", "document_loaders/web/html.js", "document_loaders/web/html.d.ts", diff --git a/libs/langchain-community/src/document_loaders/tests/dropbox.int.test.ts b/libs/langchain-community/src/document_loaders/tests/dropbox.int.test.ts new file mode 100644 index 000000000000..1f67d97c1b70 --- /dev/null +++ b/libs/langchain-community/src/document_loaders/tests/dropbox.int.test.ts @@ -0,0 +1 @@ +// Int tests go here. \ No newline at end of file diff --git a/libs/langchain-community/src/document_loaders/tests/example_data/dropbox/folder_1/example.txt b/libs/langchain-community/src/document_loaders/tests/example_data/dropbox/folder_1/example.txt new file mode 100644 index 000000000000..04861c126cfe --- /dev/null +++ b/libs/langchain-community/src/document_loaders/tests/example_data/dropbox/folder_1/example.txt @@ -0,0 +1,4 @@ +Foo +Bar +Baz + diff --git a/libs/langchain-community/src/document_loaders/tests/example_data/dropbox/folder_1/example2.txt b/libs/langchain-community/src/document_loaders/tests/example_data/dropbox/folder_1/example2.txt new file mode 100644 index 000000000000..e6087a3b49b7 --- /dev/null +++ b/libs/langchain-community/src/document_loaders/tests/example_data/dropbox/folder_1/example2.txt @@ -0,0 +1,4 @@ +Alice +Bob +Mallory + diff --git a/libs/langchain-community/src/document_loaders/tests/example_data/dropbox/folder_2/Jacob_Lee_Resume_2023.pdf b/libs/langchain-community/src/document_loaders/tests/example_data/dropbox/folder_2/Jacob_Lee_Resume_2023.pdf new file mode 100644 index 0000000000000000000000000000000000000000..de0724b537719d6ecf4f33a541d425423f820b20 GIT binary patch literal 73667 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

V_p7IEHRjb_Fu`b|bR5BO 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

j8N!NaODxg` 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 literal 0 HcmV?d00001 diff --git a/libs/langchain-community/src/document_loaders/tests/example_data/dropbox/hello.txt b/libs/langchain-community/src/document_loaders/tests/example_data/dropbox/hello.txt new file mode 100644 index 000000000000..e8cfc9fb65e4 --- /dev/null +++ b/libs/langchain-community/src/document_loaders/tests/example_data/dropbox/hello.txt @@ -0,0 +1,2 @@ +Hello world! +The quick brown fox has jumped over the lazy dog. \ No newline at end of file diff --git a/libs/langchain-community/src/document_loaders/web/dropbox.ts b/libs/langchain-community/src/document_loaders/web/dropbox.ts new file mode 100644 index 000000000000..9cb73e7034c6 --- /dev/null +++ b/libs/langchain-community/src/document_loaders/web/dropbox.ts @@ -0,0 +1 @@ +// dropbox code goes here. \ No newline at end of file diff --git a/libs/langchain-community/src/load/import_constants.ts b/libs/langchain-community/src/load/import_constants.ts index 722dd82e678b..a2188769baa5 100644 --- a/libs/langchain-community/src/load/import_constants.ts +++ b/libs/langchain-community/src/load/import_constants.ts @@ -147,6 +147,7 @@ export const optionalImportEntrypoints: string[] = [ "langchain_community/document_loaders/web/azure_blob_storage_file", "langchain_community/document_loaders/web/browserbase", "langchain_community/document_loaders/web/cheerio", + "langchain_community/document_loaders/web/dropbox", "langchain_community/document_loaders/web/puppeteer", "langchain_community/document_loaders/web/playwright", "langchain_community/document_loaders/web/college_confidential", diff --git a/yarn.lock b/yarn.lock index 5e6b6ed60a57..9b4d549ced33 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11799,6 +11799,7 @@ __metadata: dotenv: ^16.0.3 dpdm: ^3.12.0 dria: ^0.0.3 + dropbox: ^10.34.0 duck-duck-scrape: ^2.2.5 epub2: ^3.0.1 eslint: ^8.33.0 @@ -25893,6 +25894,17 @@ __metadata: languageName: node linkType: hard +"dropbox@npm:^10.34.0": + version: 10.34.0 + resolution: "dropbox@npm:10.34.0" + dependencies: + node-fetch: ^2.6.1 + peerDependencies: + "@types/node-fetch": ^2.5.7 + checksum: 5474e31b0e9c8ff68d140a12b8ef5a06e17125f254ad47702e8ab6932fb0405b467c58098e4e6546e29df57f6fa7581f749b6eb0c2a6daec29644a5a8f5cba75 + languageName: node + linkType: hard + "duck-duck-scrape@npm:^2.2.5": version: 2.2.5 resolution: "duck-duck-scrape@npm:2.2.5" From 53253fede38b8fd17670ac350852add018b47f70 Mon Sep 17 00:00:00 2001 From: Ser0n-ath Date: Sat, 30 Nov 2024 14:58:34 -0500 Subject: [PATCH 2/5] Add dropbox document loader --- .../src/document_loaders/web/dropbox.ts | 337 +++++++++++++++++- 1 file changed, 336 insertions(+), 1 deletion(-) diff --git a/libs/langchain-community/src/document_loaders/web/dropbox.ts b/libs/langchain-community/src/document_loaders/web/dropbox.ts index 9cb73e7034c6..907f6494e7a8 100644 --- a/libs/langchain-community/src/document_loaders/web/dropbox.ts +++ b/libs/langchain-community/src/document_loaders/web/dropbox.ts @@ -1 +1,336 @@ -// dropbox code goes here. \ No newline at end of file +import * as fsDefault from "node:fs"; +import { promises as fsPromises } from "node:fs"; +import * as path from "node:path"; +import * as os from "node:os"; +import { files as DropboxFiles } from "dropbox/types/dropbox_types.js"; +import { Dropbox, DropboxOptions, DropboxAuth } from "dropbox"; + +import { BaseDocumentLoader } from "langchain/document_loaders/base"; +import { Document } from "langchain/document"; +import { getEnvironmentVariable } from "@langchain/core/utils/env"; +import { + UnstructuredLoader as UnstructuredLoaderDefault, + UnstructuredLoaderOptions, +} from "../fs/unstructured.js"; + +/** + * Interface representing the configuration options for the {@link DropboxLoader}. + */ +export interface DropboxLoaderConfig { + /** + * Options for initializing the Dropbox client. + * See [Dropbox SDK Documentation](https://dropbox.github.io/dropbox-sdk-js/Dropbox.html#Dropbox__anchor) for details. + */ + clientOptions: DropboxOptions; + /** + * Options for the UnstructuredLoader used to process downloaded files. + */ + unstructuredOptions: UnstructuredLoaderOptions; + /** + * The path to the folder in Dropbox to load files from. + * Defaults to the root folder if not specified. + */ + folderPath?: string; + /** + * Specific file paths in Dropbox to load. + * Required if `mode` is set to `"file"`. + */ + filePaths?: string[]; + /** + * Whether to recursively traverse folders when `mode` is `"directory"`. + * Defaults to `false`. + */ + recursive?: boolean; + /** + * Mode of operation: `"file"` to load specific files, `"directory"` to load all files in a directory. + * Defaults to `"file"`. + */ + mode?: "file" | "directory"; + /** + * The file system module to use. Defaults to Node's `fs` module. + */ + fs?: typeof fsDefault; + /** + * The UnstructuredLoader class to use for processing files. + * Defaults to the UnstructuredLoader provided by `langchain`. + */ + UnstructuredLoader?: typeof UnstructuredLoaderDefault; +} + +/** + * A document loader that retrieves files from Dropbox and processes them into `Document` instances. + * This loader uses the Dropbox API to download files and the `UnstructuredLoader` to process them. + * + * @example + * ```typescript + * import { DropboxLoader } from "langchain/document_loaders/web/dropbox"; + * + * const loader = new DropboxLoader({ + * clientOptions: { + * accessToken: "your-dropbox-access-token", + * }, + * unstructuredOptions: { + * apiUrl: "http://localhost:8000/general/v0/general", + * }, + * folderPath: "/path/to/folder", + * recursive: true, + * mode: "directory", + * }); + * + * const docs = await loader.load(); + * ``` + */ +export class DropboxLoader extends BaseDocumentLoader { + /** + * The Dropbox client instance used to interact with the Dropbox API. + */ + protected dropboxClient: Dropbox; + + /** + * Options for the UnstructuredLoader used to process downloaded files. + */ + protected unstructuredOptions: UnstructuredLoaderOptions; + + /** + * The path to the folder in Dropbox to load files from. + */ + protected folderPath: string; + + /** + * Specific file paths in Dropbox to load. + */ + protected filePaths: string[]; + + /** + * Whether to recursively traverse folders when `mode` is `"directory"`. + */ + protected recursive: boolean; + + /** + * Mode of operation: `"file"` to load specific files, `"directory"` to load all files in a directory. + */ + protected mode: "file" | "directory"; + + /** + * The file system module to use. + */ + protected fs: typeof fsDefault; + + /** + * The UnstructuredLoader class to use for processing files. + */ + protected _UnstructuredLoader: typeof UnstructuredLoaderDefault; + + /** + * Creates an instance of `DropboxLoader`. + * @param config - Configuration options for the loader. + * @throws Will throw an error if `mode` is `"file"` and `filePaths` is not provided or empty. + */ + constructor({ + clientOptions, + unstructuredOptions, + folderPath = "", + filePaths, + recursive = false, + mode = "file", + fs = fsDefault, + UnstructuredLoader = UnstructuredLoaderDefault, + }: DropboxLoaderConfig) { + super(); + + if (mode === "file" && (!filePaths || filePaths.length === 0)) { + throw new Error(`"filePaths" must be set if "mode" is "file".`); + } + + this.unstructuredOptions = unstructuredOptions; + this.folderPath = folderPath; + this.filePaths = filePaths || []; + this.recursive = recursive; + this.mode = mode; + this.fs = fs; + this._UnstructuredLoader = UnstructuredLoader; + + this.dropboxClient = DropboxLoader._getDropboxClient(clientOptions); + } + + /** + * Asynchronously loads documents from Dropbox, yielding each `Document` as it is loaded. + * Useful for handling large numbers of documents without loading them all into memory at once. + * + * @returns An async generator yielding `Document` instances. + */ + public async *loadLazy(): AsyncGenerator { + let paths: string[] = []; + + if (this.mode === "file") { + paths = this.filePaths; + } else if (this.mode === "directory") { + paths = await this._fetchFilePathList(); + } + + for (const filePath of paths) { + const docs = await this._loadFile(filePath); + for (const doc of docs) { + yield doc; + } + } + } + + /** + * Loads all documents from Dropbox based on the specified configuration. + * + * @returns A promise that resolves to an array of `Document` instances. + */ + async load(): Promise { + const documents: Document[] = []; + for await (const doc of this.loadLazy()) { + documents.push(doc); + } + return documents; + } + + /** + * Generates a list of file paths from the specified Dropbox folder that need to be downloaded + * and processed into documents. This method is called only when the loader is operating in + * `"directory"` mode to determine which files should be downloaded and processed. + * + * @returns A promise that resolves to an array of Dropbox file paths to be downloaded and processed. + */ + private async _fetchFilePathList(): Promise { + const client: Dropbox = this.dropboxClient; + const filePaths: string[] = []; + + /** + * Processes entries returned from Dropbox and adds file paths to the list. + * @param entries - Array of Dropbox metadata entries. + */ + const processEntries = (entries: DropboxFiles.MetadataReference[]) => { + entries + .filter((entry) => entry[".tag"] === "file") + .forEach((fileEntry) => { + if (fileEntry.path_lower) filePaths.push(fileEntry.path_lower); + }); + }; + + try { + let listFolderResponse = await client.filesListFolder({ + path: this.folderPath, + recursive: this.recursive, + }); + + processEntries(listFolderResponse.result.entries); + while (listFolderResponse.result.has_more) { + listFolderResponse = await client.filesListFolderContinue({ + cursor: listFolderResponse.result.cursor, + }); + processEntries(listFolderResponse.result.entries); + } + + return filePaths; + } catch (error) { + console.error(`Error listing files in folder ${this.folderPath}:`, error); + return []; + } + } + + /** + * Downloads a file from Dropbox, processes it into `Document` instances using the `UnstructuredLoader`, + * and returns the resulting documents. This method handles the entire lifecycle of the file processing, + * including downloading, temporary storage, processing, metadata augmentation, and cleanup. + * + * @param filePath - The path to the file in Dropbox. + * @returns A promise that resolves to an array of `Document` instances generated from a dropbox file. + */ + private async _loadFile(filePath: string): Promise { + const client: Dropbox = this.dropboxClient; + let tempDir: string | undefined; + let localFilePath: string | undefined; + try { + const fetchRes = await client.filesDownload({ path: filePath }); + const fileMetadata = + fetchRes.result as DropboxFiles.FileMetadataReference & { + fileBinary: Buffer; + }; + + if (!fileMetadata.fileBinary) { + throw new Error(`Failed to download file: ${filePath}`); + } + + const fileBinary = fileMetadata.fileBinary; + + // Create temporary directory + tempDir = await fsPromises.mkdtemp( + path.join(os.tmpdir(), "dropboxfileloader-") + ); + + // Normalize the file path + const normalizedFilePath = filePath.startsWith("/") + ? filePath.slice(1) + : filePath; + localFilePath = path.join(tempDir, normalizedFilePath); + + await fsPromises.mkdir(path.dirname(localFilePath), { recursive: true }); + + await fsPromises.writeFile(localFilePath, fileBinary, { + encoding: "binary", + }); + + // Create an unstructured loader and load the file. + const unstructuredLoader = new this._UnstructuredLoader( + localFilePath, + this.unstructuredOptions + ); + const docs = await unstructuredLoader.load(); + + // Set the source metadata for each document. + const sourceMetadata = { source: `dropbox://${filePath}` }; + for (const doc of docs) { + doc.metadata = { ...doc.metadata, ...sourceMetadata }; + } + + return docs; + } catch (error) { + console.error(`Error processing file ${filePath}:`, error); + console.error(`File ${filePath} was skipped.`); + return []; // Proceed to the next file + } finally { + // Cleanup temporary files + if (localFilePath) { + try { + await fsPromises.unlink(localFilePath); + } catch (err) { + console.warn(`Failed to delete file ${localFilePath}:`, err); + } + } + if (tempDir) { + try { + await fsPromises.rm(tempDir, { recursive: true, force: true }); + } catch (err) { + console.warn(`Failed to delete temp directory ${tempDir}:`, err); + } + } + } + } + + /** + * Creates and returns a Dropbox client instance configured with the provided options. + * If authentication details are not specified in `clientOptions`, it attempts to use + * the `DROPBOX_ACCESS_TOKEN` environment variable for authentication. + * + * @param clientOptions - Configuration options for initializing the Dropbox client, + * including authentication details. + * @returns An instance of the Dropbox client. + */ + private static _getDropboxClient(clientOptions: DropboxOptions): Dropbox { + const options = clientOptions || {}; + if (options.auth || options.accessToken) { + return new Dropbox(clientOptions); + } + const accessToken = getEnvironmentVariable("DROPBOX_ACCESS_TOKEN"); + const auth = new DropboxAuth({ + ...clientOptions, + accessToken, + }); + return new Dropbox({ ...clientOptions, auth }); + } +} From 81a7db0c2a0bcf44d4f200db21cc38eb73bd4aff Mon Sep 17 00:00:00 2001 From: Yashank Bhola Date: Sat, 30 Nov 2024 15:03:01 -0500 Subject: [PATCH 3/5] add integration tests base --- .../tests/dropbox.int.test.ts | 143 +++++++++++++++++- 1 file changed, 142 insertions(+), 1 deletion(-) diff --git a/libs/langchain-community/src/document_loaders/tests/dropbox.int.test.ts b/libs/langchain-community/src/document_loaders/tests/dropbox.int.test.ts index 1f67d97c1b70..c2af08a2e8a9 100644 --- a/libs/langchain-community/src/document_loaders/tests/dropbox.int.test.ts +++ b/libs/langchain-community/src/document_loaders/tests/dropbox.int.test.ts @@ -1 +1,142 @@ -// Int tests go here. \ No newline at end of file +/** + * NOTE: DROPBOX_ACCESS_TOKEN should be set in environment variables + * NOTE: files.content.write permission is required for testing. + */ +import { expect } from "@jest/globals"; +import * as url from "node:url"; +import * as path from "node:path"; +import * as fs from "node:fs"; +import { v4 as uuid } from "uuid"; +import { getEnvironmentVariable } from "@langchain/core/utils/env"; +import { Document } from "@langchain/core/documents"; +import { Dropbox, DropboxOptions } from "dropbox"; + +import { + UnstructuredDirectoryLoader, + UnstructuredLoader, + UnstructuredLoaderOptions, +} from "../fs/unstructured.js"; +import { DropboxLoader } from "../web/dropbox.js"; + +// Copies over the dropbox example_data to the remote dropbox drive. +const setupDropboxStorageEnvironment = async ( + localPath: string, + dropboxPath: string, + dbx: Dropbox +) => { + for (const item of fs.readdirSync(localPath)) { + const fullPath = path.join(localPath, item); + const dbxPath = `${dropboxPath}/${item}`; + const stats = fs.statSync(fullPath); + if (stats.isDirectory()) { + try { + await dbx.filesCreateFolderV2({ path: dbxPath }); + } catch { + // Ignore folder already exists or auth error + } + await setupDropboxStorageEnvironment(fullPath, dbxPath, dbx); + } else { + await dbx.filesUpload({ + path: dbxPath, + contents: fs.readFileSync(fullPath), + mode: { ".tag": "overwrite" }, + }); + } + } +}; + +// Removes the dropbox example_data from the remote dropbox drive. +const teardownDropboxStorageEnvironment = async ( + dropboxPath: string, + dbx: Dropbox +) => { + try { + await dbx.filesDeleteV2({ path: dropboxPath }); + } catch { + // Folder might not exist or auth error. + } +}; + +describe("DropboxLoader Integration Tests", () => { + // Ensure the enviroment variables are set + + const localTestDataFolder = path.resolve( + path.dirname(url.fileURLToPath(import.meta.url)), + "./example_data/dropbox" + ); + const dropboxTestDataFolder = `/LangchainDropboxLoaderTest_${uuid()}`; + const folder1Files = ["example.txt", "example2.txt"]; + const folder2Files = ["Jacob_Lee_Resume_2023.pdf"]; + const rootFiles = ["hello.txt"]; + const allTestFiles = [...rootFiles, ...folder1Files, ...folder2Files]; + + // Copies over the dropbox example_data over to dropbox drive + beforeAll(() => { + const accessToken = getEnvironmentVariable("DROPBOX_ACCESS_TOKEN"); + const dbx = new Dropbox({ accessToken }); + return setupDropboxStorageEnvironment( + localTestDataFolder, + dropboxTestDataFolder, + dbx + ); + }); + + // Cleanup and removes the added dropbox example_date during setup. + afterAll(() => { + const accessToken = getEnvironmentVariable("DROPBOX_ACCESS_TOKEN"); + const dbx = new Dropbox({ accessToken }); + return teardownDropboxStorageEnvironment(dropboxTestDataFolder, dbx); + }); + + // Integration tests for the load method + describe("load", () => { + it("should load documents from a Dropbox file", async () => { + const localFilename = folder2Files[0]; + const dropboxFilePath = path.join( + dropboxTestDataFolder, + "folder_2", + localFilename + ); + const localFilePath = path.join( + localTestDataFolder, + "folder_2", + localFilename + ); + + const unstructuredOptions: UnstructuredLoaderOptions = { + apiKey: undefined, + apiUrl: "http://localhost:8000/general/v0/general", + }; + + const clientOptions: DropboxOptions = {}; + const dropboxLoader = new DropboxLoader({ + clientOptions, + unstructuredOptions, + filePaths: [dropboxFilePath], + }); + + const directoryLoader = new UnstructuredLoader( + localFilePath, + unstructuredOptions + ); + + const [dropboxDocuments, directoryDocuments] = await Promise.all([ + dropboxLoader.load(), + directoryLoader.load(), + ]); + + expect(dropboxDocuments).toBeDefined(); + expect(dropboxDocuments.length).toBe(directoryDocuments.length); + + const dropboxSourcePath = "dropbox://" + dropboxFilePath; + + dropboxDocuments.forEach((doc) => { + expect(doc).toBeInstanceOf(Document); + expect(doc.pageContent).toBeDefined(); + expect(folder2Files).toContain(doc.metadata.filename); + expect(dropboxSourcePath).toEqual(doc.metadata.source); + }); + }); + + }); +}); From 9efc17e57c6476d37a40081560ba7bc86d108c25 Mon Sep 17 00:00:00 2001 From: ji24077 <80298064+Hanji11@users.noreply.github.com> Date: Sat, 30 Nov 2024 15:05:42 -0500 Subject: [PATCH 4/5] added test cases --- .../tests/dropbox.int.test.ts | 83 ++++++++++++++++++- 1 file changed, 82 insertions(+), 1 deletion(-) diff --git a/libs/langchain-community/src/document_loaders/tests/dropbox.int.test.ts b/libs/langchain-community/src/document_loaders/tests/dropbox.int.test.ts index c2af08a2e8a9..f87f0d4f4a0b 100644 --- a/libs/langchain-community/src/document_loaders/tests/dropbox.int.test.ts +++ b/libs/langchain-community/src/document_loaders/tests/dropbox.int.test.ts @@ -137,6 +137,87 @@ describe("DropboxLoader Integration Tests", () => { expect(dropboxSourcePath).toEqual(doc.metadata.source); }); }); - + + it("should load all documents from a Dropbox folder", async () => { + const dropboxFilenames = folder1Files.map((path) => path.toLowerCase()); + + const dropboxFolderPath = path.join(dropboxTestDataFolder, "folder_1"); + const localFolderPath = path.join(localTestDataFolder, "folder_1"); + + const clientOptions: DropboxOptions = {}; + const unstructuredOptions: UnstructuredLoaderOptions = { + apiKey: "", + apiUrl: "http://localhost:8000/general/v0/general", + }; + + const dropboxLoader = new DropboxLoader({ + clientOptions, + unstructuredOptions, + mode: "directory", + folderPath: dropboxFolderPath, + }); + + const directoryLoader = new UnstructuredDirectoryLoader( + localFolderPath, + unstructuredOptions + ); + + const [dropboxDocuments, directoryDocuments] = await Promise.all([ + dropboxLoader.load(), + directoryLoader.load(), + ]); + + expect(dropboxDocuments).toBeDefined(); + expect(dropboxDocuments.length).toBe(directoryDocuments.length); + + const dropboxSourcePath = folder1Files.map( + (filename) => + "dropbox://" + path.join(dropboxFolderPath, filename).toLowerCase() + ); + + dropboxDocuments.forEach((doc) => { + expect(doc).toBeInstanceOf(Document); + expect(doc.pageContent).toBeDefined(); + expect(dropboxFilenames).toContain(doc.metadata.filename); + expect(dropboxSourcePath).toContain(doc.metadata.source); + }); + }); + + it("should recursively load all documents from a Dropbox folder", async () => { + const dropboxFilenames = allTestFiles.map((path) => path.toLowerCase()); + + const clientOptions: DropboxOptions = {}; + const unstructuredOptions: UnstructuredLoaderOptions = { + apiKey: "", + apiUrl: "http://localhost:8000/general/v0/general", + }; + + const dropboxLoader = new DropboxLoader({ + clientOptions, + unstructuredOptions, + mode: "directory", + folderPath: dropboxTestDataFolder, + recursive: true, + }); + + const directoryLoader = new UnstructuredDirectoryLoader( + localTestDataFolder, + unstructuredOptions + ); + + const [dropboxDocuments, directoryDocuments] = await Promise.all([ + dropboxLoader.load(), + directoryLoader.load(), + ]); + + expect(dropboxDocuments).toBeDefined(); + expect(dropboxDocuments.length).toBe(directoryDocuments.length); + + dropboxDocuments.forEach((doc) => { + expect(doc).toBeInstanceOf(Document); + expect(doc.pageContent).toBeDefined(); + expect(dropboxFilenames).toContain(doc.metadata.filename); + }); + }); }); }); From b2a1dda31d500135486c4f54faffebf33afd5f29 Mon Sep 17 00:00:00 2001 From: Ismail-Bashir Date: Sat, 30 Nov 2024 15:10:05 -0500 Subject: [PATCH 5/5] Add dropbox document loader docs --- .../document_loaders/web_loaders/dropbox.mdx | 139 +++++++++++++++++- 1 file changed, 138 insertions(+), 1 deletion(-) diff --git a/docs/core_docs/docs/integrations/document_loaders/web_loaders/dropbox.mdx b/docs/core_docs/docs/integrations/document_loaders/web_loaders/dropbox.mdx index 6b9129c1687f..1f9eb5857c61 100644 --- a/docs/core_docs/docs/integrations/document_loaders/web_loaders/dropbox.mdx +++ b/docs/core_docs/docs/integrations/document_loaders/web_loaders/dropbox.mdx @@ -1 +1,138 @@ -// documentation goes here \ No newline at end of file +--- +hide_table_of_contents: true +sidebar_class_name: node-only +--- + +# Dropbox Loader + +The `DropboxLoader` allows you to load documents from Dropbox into your LangChain applications. It retrieves files or directories from your Dropbox account and converts them into documents ready for processing. + +## Overview + +Dropbox is a file hosting service that brings all your files—traditional documents, cloud content, and web shortcuts—together in one place. With the `DropboxLoader`, you can seamlessly integrate Dropbox file retrieval into your projects. + +## Setup + +1. Create a dropbox app, using the [Dropbox App Console](https://www.dropbox.com/developers/apps/create). +2. Ensure the app has the `files.metadata.read`, `files.content.read` scope permissions: +3. Generate the access token from the Dropbox App Console. +4. To use this loader, you'll need to have Unstructured already set up and ready to use at an available URL endpoint. It can also be configured to run locally. + See the docs [here](https://www.dropbox.com/developers/apps/create) for information on how to do that. +5. Install the necessary packages: + + ```bash npm2yarn + npm install @langchain/community @langchain/core dropbox + ``` + +## Usage + +### Loading Specific Files + +To load specific files from Dropbox, specify the file paths: + +```typescript +import { DropboxLoader } from "@langchain/community/document_loaders/web/dropbox"; + +const loader = new DropboxLoader({ + clientOptions: { + accessToken: "your-dropbox-access-token", + }, + unstructuredOptions: { + apiUrl: "http://localhost:8000/general/v0/general", // Replace with your Unstructured API URL + }, + filePaths: ["/path/to/file1.txt", "/path/to/file2.pdf"], // Replace with file paths on Dropbox. +}); + +const docs = await loader.load(); +console.log(docs); +``` + +### Loading Files from a Directory + +To load all files from a specific directory, provide the `folderPath` and set the `mode` to `"directory"`. Set `recursive` to `true` to traverse subdirectories: + +```typescript +import { DropboxLoader } from "@langchain/community/document_loaders/web/dropbox"; + +const loader = new DropboxLoader({ + clientOptions: { + accessToken: "your-dropbox-access-token", + }, + unstructuredOptions: { + apiUrl: "http://localhost:8000/general/v0/general", + }, + folderPath: "/path/to/folder", + recursive: true, // Load documents found in subdirectories + mode: "directory", +}); + +const docs = await loader.load(); +console.log(docs); +``` + +### Streaming Documents + +To process large datasets efficiently, use the `loadLazy` method to stream documents asynchronously: + +```typescript +import { DropboxLoader } from "@langchain/community/document_loaders/web/dropbox"; + +const loader = new DropboxLoader({ + clientOptions: { + accessToken: "your-dropbox-access-token", + }, + unstructuredOptions: { + apiUrl: "http://localhost:8000/general/v0/general", + }, + folderPath: "/large/dataset", + recursive: true, + mode: "directory", +}); + +for await (const doc of loader.loadLazy()) { + // Process each document as it's loaded + console.log(doc); +} +``` + +### Authentication with Environment Variables + +You can set the `DROPBOX_ACCESS_TOKEN` environment variable instead of passing the access token in `clientOptions`: + +```bash +export DROPBOX_ACCESS_TOKEN=your-dropbox-access-token +``` + +Then initialize the loader without specifying `accessToken`: + +```typescript +import { DropboxLoader } from "@langchain/community/document_loaders/web/dropbox"; + +const loader = new DropboxLoader({ + clientOptions: {}, + unstructuredOptions: { + apiUrl: "http://localhost:8000/general/v0/general", + }, + filePaths: ["/important/notes.txt"], +}); + +const docs = await loader.load(); +console.log(docs[0].pageContent); +``` + +## Configuration Options + +Here are the configuration options for the `DropboxLoader`: + +| Option | Type | Description | +| --------------------- | ------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `clientOptions` | `DropboxOptions` | Configuration options for initializing the Dropbox client, including authentication details. Refer to the [Dropbox SDK Documentation](https://dropbox.github.io/dropbox-sdk-js/Dropbox.html#Dropbox__anchor) for more information. | +| `unstructuredOptions` | `UnstructuredLoaderOptions` | Options for the `UnstructuredLoader` used to process downloaded files. Includes the `apiUrl` for your Unstructured server. | +| `folderPath` | `string` (optional) | The path to the folder in Dropbox from which to load files. Defaults to the root folder (`""`) if not specified. | +| `filePaths` | `string[]` (optional) | An array of specific file paths in Dropbox to load. Required if `mode` is set to `"file"`. | +| `recursive` | `boolean` (optional) | Indicates whether to recursively traverse folders when `mode` is `"directory"`. Defaults to `false`. | +| `mode` | `"file"` or `"directory"` (optional) | The mode of operation. Set to `"file"` to load specific files or `"directory"` to load all files in a directory. Defaults to `"file"`. | + +## API References + +- [Dropbox SDK for JavaScript](https://github.com/dropbox/dropbox-sdk-js)