dqbd / tiktoken

JS port and JS/WASM bindings for openai/tiktoken
MIT License
649 stars 49 forks source link

Unhandled Runtime Error. Element type is invalid. Received a promise that resolves to: undefined. Lazy element type must resolve to a class or function. #29

Closed Andision closed 11 months ago

Andision commented 1 year ago

I an using "@dqbd/tiktoken": "^1.0.6", with Next.js 13.2.4 like this.

import { encoding_for_model, type TiktokenModel } from "@dqbd/tiktoken";

export function getChatGPTEncoding(messages: Message[], model: string) {
  const isGpt3 = model === 'gpt-3.5-turbo' || model === 'gpt-3.5-turbo-0301';

  const encoder = encoding_for_model(model as TiktokenModel, {
    "<|im_start|>": 100264,
    "<|im_end|>": 100265,
    "<|im_sep|>": 100266,
  });

  const msgSep = isGpt3 ? '\n' : '';
  const roleSep = isGpt3 ? '\n' : '<|im_sep|>';

  const serialized = [
    messages
      .map(({ role, content }) => {
        return `<|im_start|>${role}${roleSep}${content}<|im_end|>`;
      })
      .join(msgSep),
    `<|im_start|>assistant${roleSep}`,
  ].join(msgSep);

  return encoder.encode(serialized, "all");
}

export function countTokens(messages: Message[], model: string) {
  if (messages.length === 0) return 0;
  return getChatGPTEncoding(messages, model).length;
}

I got an error like this

Unhandled Runtime Error
Element type is invalid. Received a promise that resolves to: undefined. Lazy element type must resolve to a class or function.

Call Stack

mountLazyComponent
node_modules/next/dist/compiled/react-dom/cjs/react-dom.development.js(22492:0)
beginWork$1
node_modules/next/dist/compiled/react-dom/cjs/react-dom.development.js(24134:0)
beginWork
node_modules/next/dist/compiled/react-dom/cjs/react-dom.development.js (32040:0)
performUnitofWork
node_modules/next/dist/compiled/react-dom/cjs/react-dom.development.js (30938:0)
workLoopSync
node_modules/next/dist/compiled/react-dom/cjs/react-dom.development.js (30724:0)
renderRootSync
node_modules/next/dist/compiled/react-dom/cjs/react-dom.development.js (30689:0)
recoverFromConcurrentError
node_modules/next/dist/compiled/react-dom/cjs/react-dom.development.js(29950:0)
performConcurrentWorkOnRoot
node_modules/next/dist/compiled/react-dom/cjs/react-dom.development.js (29849:0)
workLoop
node_modules/next/dist/compiled/scheduler/index.js(10:3921)
flushWork
node_modules/next/dist/compiled/scheduler/index.js (10:3629)
performWorkUntilDeadline
node_modules/next/dist/compiled/scheduler/index.js (10:1811)

I found when I comment these lines, it will be fine. I searched on Internet and I think this issue may be related to this question but I have no idea how to fix it.

dqbd commented 1 year ago

Hi @Andision, could you please share a reproducible repository? Thanks 😄

Andision commented 1 year ago

Hi @Andision, could you please share a reproducible repository? Thanks 😄

Thank you for your reply! Here is the repo and branch I am working on. You can open it in Gitpod and use yarn install && yarn run dev to dev it.

dqbd commented 1 year ago

I see now, the issue is caused by importing @dqbd/tiktoken via "use client" directive. Will need to investigate further, but it does seem like a bug related to RSC webpack plugin.

There are various possible workarounds which you may consider when using experimental appDir:

  1. Use @dqbd/tiktoken only in API routes and create a fetch request. That should be well supported.
  2. Copy tiktoken_bg.wasm from node_modules/@dqbd/tiktoken to a public folder and use @dqbd/tiktoken/init to load via fetch
"use client";

import { type Tiktoken, encoding_for_model, init } from "@dqbd/tiktoken/init";
import { useEffect, useState } from "react";

export default function ClientComponent() {
  const [encoder, setEncoder] = useState<Tiktoken>();

  useEffect(() => {
    fetch("/tiktoken_bg.wasm")
      .then(async (wasm) => {
        const buffer = await wasm.arrayBuffer();
        await init((imports) => WebAssembly.instantiate(buffer, imports));

        setEncoder(encoding_for_model("gpt-4"));
      })
      .catch((e) => {
        console.error(e);
      });
  });

  const [input, setInput] = useState("hello world");
  const tokens = encoder?.encode(input);
}

Please note that it might be a better idea to feth tiktoken_bg.wasm only once.

mahyarstudocu commented 1 year ago

I see now, the issue is caused by importing @dqbd/tiktoken via "use client" directive. Will need to investigate further, but it does seem like a bug related to RSC webpack plugin.

There are various possible workarounds which you may consider when using experimental appDir:

  1. Use @dqbd/tiktoken only in API routes and create a fetch request. That should be well supported.
  2. Copy tiktoken_bg.wasm from node_modules/@dqbd/tiktoken to a public folder and use @dqbd/tiktoken/init to load via fetch
"use client";

import { type Tiktoken, encoding_for_model, init } from "@dqbd/tiktoken/init";
import { useEffect, useState } from "react";

export default function ClientComponent() {
  const [encoder, setEncoder] = useState<Tiktoken>();

  useEffect(() => {
    fetch("/tiktoken_bg.wasm")
      .then(async (wasm) => {
        const buffer = await wasm.arrayBuffer();
        await init((imports) => WebAssembly.instantiate(buffer, imports));

        setEncoder(encoding_for_model("gpt-4"));
      })
      .catch((e) => {
        console.error(e);
      });
  });

  const [input, setInput] = useState("hello world");
  const tokens = encoder?.encode(input);
}

Please note that it might be a better idea to feth tiktoken_bg.wasm only once.

but with move this file to a static folder manually , everytime that the package is updated we need to copy it again, is there a better way to fetch it?

dqbd commented 11 months ago

Hello! Haven't gotten around to research deeper into RSC, but I think js-tiktoken should suffice for now! Closing, let me know if you have any more issues.