huggingface / transformers.js

State-of-the-art Machine Learning for the web. Run 🤗 Transformers directly in your browser, with no need for a server!
https://huggingface.co/docs/transformers.js
Apache License 2.0
11.37k stars 708 forks source link

The "path" argument must be of type string or an instance of URL. Received undefined #916

Open firdausai opened 1 month ago

firdausai commented 1 month ago

System Info

"@xenova/transformers": "^2.17.2" Electron (Obsidian.md)

Environment/Platform

Description

I am trying to build an obsidian.md plugin, where I want to load a local model (or hosted on huggingface is fine too) using transformer.js. I went through several issues.

  1. Current obsidian.md plugin template uses ES2018, but transformer.js need at least ES2020 due to BigInt. I believe I solved that by changing the target build.
  2. After changing the target, the error changed to The "path" argument must be of type string or an instance of URL. Received undefined.

Not sure if its a pure transformer.js or it is caused something by changing the target build.

Code in question:

[main.ts]

import { pipeline } from '@xenova/transformers';

export default class MyPlugin extends Plugin {
   ...

   const classifier = await pipeline('sentiment-analysis');       // Error starts here
   const output = await classifier('I love transformers!');

   ...
}

I also tried other pipeline value like below, but the error is still the same:

const extractor = await pipeline('feature-extraction', 'Xenova/bert-base-uncased', { revision: 'default' });
const output = await extractor('This is a simple test.');

Reproduction

  1. git clone https://github.com/obsidianmd/obsidian-sample-plugin.git
  2. add const classifier = await pipeline('sentiment-analysis'); const output = await classifier('I love transformers!'); in MyPlugin class.
import { App, Editor, MarkdownView, Modal, Notice, Plugin, PluginSettingTab, Setting, WorkspaceLeaf } from 'obsidian';
import { ExampleView, VIEW_TYPE_EXAMPLE } from "./view";
import { pipeline } from '@xenova/transformers';

// Remember to rename these classes and interfaces!

interface MyPluginSettings {
    mySetting: string;
}

const DEFAULT_SETTINGS: MyPluginSettings = {
    mySetting: 'default'
}

export default class MyPlugin extends Plugin {
    settings: MyPluginSettings;

    async onload() {

        console.log("foo asd")

        const classifier = await pipeline('sentiment-analysis');
        const output = await classifier('I love transformers!');

        console.log("output")
        console.log(output)

        this.registerView(
      VIEW_TYPE_EXAMPLE,
      (leaf) => new ExampleView(leaf)
    );

        await this.loadSettings();

        // This creates an icon in the left ribbon.
        const ribbonIconEl = this.addRibbonIcon('dice', 'Sample Plugin', (evt: MouseEvent) => {
            // Called when the user clicks the icon.
            new Notice('This is a notice!');
        });

        this.addRibbonIcon("brain", "Open Plugin", () => {
      this.activateView();
    });

        // Perform additional things with the ribbon
        ribbonIconEl.addClass('my-plugin-ribbon-class');

        // This adds a status bar item to the bottom of the app. Does not work on mobile apps.
        const statusBarItemEl = this.addStatusBarItem();
        statusBarItemEl.setText('Status Bar Text');

        // This adds a simple command that can be triggered anywhere
        this.addCommand({
            id: 'open-sample-modal-simple',
            name: 'Open sample modal (simple)',
            callback: () => {
                new SampleModal(this.app).open();
            }
        });
        // This adds an editor command that can perform some operation on the current editor instance
        this.addCommand({
            id: 'sample-editor-command',
            name: 'Sample editor command',
            editorCallback: (editor: Editor, view: MarkdownView) => {
                console.log(editor.getSelection());
                editor.replaceSelection('Sample Editor Command');
            }
        });
        // This adds a complex command that can check whether the current state of the app allows execution of the command
        this.addCommand({
            id: 'open-sample-modal-complex',
            name: 'Open sample modal (complex)',
            checkCallback: (checking: boolean) => {
                // Conditions to check
                const markdownView = this.app.workspace.getActiveViewOfType(MarkdownView);
                if (markdownView) {
                    // If checking is true, we're simply "checking" if the command can be run.
                    // If checking is false, then we want to actually perform the operation.
                    if (!checking) {
                        new SampleModal(this.app).open();
                    }

                    // This command will only show up in Command Palette when the check function returns true
                    return true;
                }
            }
        });

        // This adds a settings tab so the user can configure various aspects of the plugin
        this.addSettingTab(new SampleSettingTab(this.app, this));

        // If the plugin hooks up any global DOM events (on parts of the app that doesn't belong to this plugin)
        // Using this function will automatically remove the event listener when this plugin is disabled.
        this.registerDomEvent(document, 'click', (evt: MouseEvent) => {
            console.log('click', evt);
        });

        // When registering intervals, this function will automatically clear the interval when the plugin is disabled.
        this.registerInterval(window.setInterval(() => console.log('setInterval'), 5 * 60 * 1000));
    }

    async activateView() {
    const { workspace } = this.app;

    let leaf: WorkspaceLeaf | null = null;
    const leaves = workspace.getLeavesOfType(VIEW_TYPE_EXAMPLE);

    if (leaves.length > 0) {
      // A leaf with our view already exists, use that
      leaf = leaves[0];
    } else {
      // Our view could not be found in the workspace, create a new leaf
      // in the right sidebar for it
      leaf = workspace.getRightLeaf(false);
      await leaf!.setViewState({ type: VIEW_TYPE_EXAMPLE, active: true });
    }

    // "Reveal" the leaf in case it is in a collapsed sidebar
    workspace.revealLeaf(leaf!);
  }

    onunload() {
        console.log("onunload")

    }

    async loadSettings() {
        this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
    }

    async saveSettings() {
        await this.saveData(this.settings);
    }
}

class SampleModal extends Modal {
    constructor(app: App) {
        super(app);
    }

    onOpen() {
        const {contentEl} = this;
        contentEl.setText('Woah!');
    }

    onClose() {
        const {contentEl} = this;
        contentEl.empty();
    }
}

class SampleSettingTab extends PluginSettingTab {
    plugin: MyPlugin;

    constructor(app: App, plugin: MyPlugin) {
        super(app, plugin);
        this.plugin = plugin;
    }

    display(): void {
        const {containerEl} = this;

        containerEl.empty();

        new Setting(containerEl)
            .setName('Setting #1')
            .setDesc('It\'s a secret')
            .addText(text => text
                .setPlaceholder('Enter your secret')
                .setValue(this.plugin.settings.mySetting)
                .onChange(async (value) => {
                    this.plugin.settings.mySetting = value;
                    await this.plugin.saveSettings();
                }));
    }
}
firdausai commented 1 month ago

I have pinpoint the problem to esbuild. This line of code is the issue

@xenova/transformers/src/env.js

const __dirname = RUNNING_LOCALLY
    ? path.dirname(path.dirname(url.fileURLToPath(import.meta.url)))
    : './';

the current esbuild config change import.meta.url into import_meta.url. However, this was fixed by changing some setting as per this github discussion.

https://github.com/evanw/esbuild/commit/ac97be74c3b5453740f84fc40098ccae95cde19f

...

    The `import.meta` syntax is now passed through unmodified when the target is `es2020` or newer and the output format is `esm`. This lets you use features such as `import.meta.url` in those situations.

This worked and it doesn't change the variable anymore. However, since I changed the output format, I come across a new error, which prevents me from using import and require me to use required() when importing a module. So far, only transformers/src/env.js amd transformers/src/utils/hub.js used import, so I change them manually in the output dir (not ideal, but I just want to see if it would work). It did work, but now I ran into another issue and have this error Cannot use 'import.meta' outside a module. This is where I am still stuck.

gyagp commented 1 month ago

Please refer https://github.com/microsoft/onnxruntime/pull/20165#issuecomment-2311564377. More discussion there is welcome. Thanks!