diff --git a/examples/qwen3/README.md b/examples/qwen3/README.md
new file mode 100644
index 00000000..91b5d9f3
--- /dev/null
+++ b/examples/qwen3/README.md
@@ -0,0 +1,8 @@
+### OpenAI API Demos w/ Qwen3
+
+Run `npm install` first, followed by `npm start`.
+
+Note if you would like to hack WebLLM core package,
+you can change web-llm dependencies as `"file:../.."`, and follow the build from source
+instruction in the project to build webllm locally. This option is only recommended
+if you would like to hack WebLLM core package.
diff --git a/examples/qwen3/package.json b/examples/qwen3/package.json
new file mode 100644
index 00000000..71316714
--- /dev/null
+++ b/examples/qwen3/package.json
@@ -0,0 +1,20 @@
+{
+ "name": "qwen3_example",
+ "version": "0.1.0",
+ "private": true,
+ "scripts": {
+ "start": "parcel src/qwen3_example.html --port 8883",
+ "build": "parcel build src/qwen3_example.html --dist-dir lib"
+ },
+ "devDependencies": {
+ "buffer": "^5.7.1",
+ "parcel": "^2.8.3",
+ "process": "^0.11.10",
+ "tslib": "^2.3.1",
+ "typescript": "^4.9.5",
+ "url": "^0.11.3"
+ },
+ "dependencies": {
+ "@mlc-ai/web-llm": "^0.2.78"
+ }
+}
diff --git a/examples/qwen3/src/qwen3_example.html b/examples/qwen3/src/qwen3_example.html
new file mode 100644
index 00000000..6e5ef37d
--- /dev/null
+++ b/examples/qwen3/src/qwen3_example.html
@@ -0,0 +1,17 @@
+
+
+
+
+
+ WebLLM Test Page
+ Open console to see output
+
+
+
+ Response
+
+
+
+
diff --git a/examples/qwen3/src/qwen3_example.ts b/examples/qwen3/src/qwen3_example.ts
new file mode 100644
index 00000000..8d647272
--- /dev/null
+++ b/examples/qwen3/src/qwen3_example.ts
@@ -0,0 +1,147 @@
+import * as webllm from "@mlc-ai/web-llm";
+
+function setLabel(id: string, text: string) {
+ const label = document.getElementById(id);
+ if (label == null) {
+ throw Error("Cannot find label " + id);
+ }
+ label.innerText = text;
+}
+
+// Helper method to stream responses from the engine
+async function streamResponse(
+ engine: webllm.MLCEngineInterface,
+ request: webllm.ChatCompletionRequestStreaming,
+): Promise {
+ console.log("Requesting chat completion with request:", request);
+ const asyncChunkGenerator = await engine.chat.completions.create(request);
+ let message = "";
+ for await (const chunk of asyncChunkGenerator) {
+ message += chunk.choices[0]?.delta?.content || "";
+ setLabel("generate-label", message);
+ if (chunk.usage) {
+ console.log(chunk.usage); // only last chunk has usage
+ }
+ // engine.interruptGenerate(); // works with interrupt as well
+ }
+ console.log("Final message:\n", await engine.getMessage()); // the concatenated message
+}
+
+/**
+ * We demonstrate how Qwen3's best practices can be followed in WebLLM. For more, see
+ * https://huggingface.co/Qwen/Qwen3-8B#best-practices.
+ */
+async function main() {
+ const initProgressCallback = (report: webllm.InitProgressReport) => {
+ setLabel("init-label", report.text);
+ };
+ const selectedModel = "Qwen3-4B-q4f16_1-MLC";
+ const engine: webllm.MLCEngineInterface = await webllm.CreateMLCEngine(
+ selectedModel,
+ { initProgressCallback: initProgressCallback },
+ );
+
+ /**
+ * 1. Default behavior: enable thinking
+ */
+ let request: webllm.ChatCompletionRequest = {
+ stream: true,
+ stream_options: { include_usage: true },
+ messages: [
+ {
+ role: "user",
+ content: "How many r's are there in the word strawberry?",
+ },
+ ],
+ // Specifying `enable_thinking` is optional, as it defaults to think.
+ // extra_body: {
+ // enable_thinking: true,
+ // }
+ };
+ await streamResponse(engine, request);
+
+ /**
+ * 2. Disable thinking with `enable_thinking: false`.
+ */
+ request = {
+ stream: true,
+ stream_options: { include_usage: true },
+ messages: [
+ {
+ role: "user",
+ content: "How many r's are there in the word strawberry?",
+ },
+ ],
+ extra_body: {
+ enable_thinking: false,
+ },
+ };
+ await streamResponse(engine, request);
+
+ /**
+ * 3. Disable thinking with soft switch /no_think
+ * or enable thinking with soft switch /think.
+ * Using soft switch: "When enable_thinking=True, regardless of whether the user
+ * uses /think or /no_think, the model will always output a block wrapped in
+ * .... However, the content inside this block may be empty if
+ * thinking is disabled. When enable_thinking=False, the soft switches are not
+ * valid. Regardless of any /think or /no_think tags input by the user, the
+ * model will not generate think content and will not include a ... block.
+ */
+ request = {
+ stream: true,
+ stream_options: { include_usage: true },
+ messages: [
+ {
+ role: "user",
+ content: "How many r's are there in the word strawberry? /no_think",
+ // content: "How many r's are there in the word strawberry? /think",
+ },
+ ],
+ };
+ await streamResponse(engine, request);
+
+ /**
+ * 4. For multi-turn messages, it is recommended to
+ * parse out the thinking content in the history
+ * messages as described in the Best Practices section.
+ */
+ const history: webllm.ChatCompletionMessageParam[] = [
+ {
+ role: "user",
+ content: "How many r's are there in the word strawberry? /think",
+ },
+ {
+ role: "assistant",
+ content:
+ "Dummy thinking content here...\n\nThe answer is 3.",
+ },
+ ];
+ // Preprocess history to remove thinking content
+ const preprocessedHistory = history.map((msg) => {
+ if (msg.role === "assistant") {
+ // Remove ... block from assistant messages that is at the start
+ // and may contain two \n\n line breaks.
+ const thinkRegex = /.*?<\/think>\n?\n?/s; // Match ... with optional \n\n
+ const contentWithoutThink = msg.content!.replace(thinkRegex, "").trim();
+ return { ...msg, content: contentWithoutThink };
+ }
+ return msg; // User messages remain unchanged
+ });
+ console.log("Preprocessed history:", preprocessedHistory);
+
+ // Now use the preprocessed history in the request
+ const newMessage: webllm.ChatCompletionMessageParam = {
+ role: "user",
+ content: "What about blueberries?",
+ };
+
+ request = {
+ stream: true,
+ stream_options: { include_usage: true },
+ messages: [...preprocessedHistory, newMessage],
+ };
+ await streamResponse(engine, request);
+}
+
+main();
diff --git a/examples/simple-chat-ts/src/simple_chat.ts b/examples/simple-chat-ts/src/simple_chat.ts
index 7c882a7a..823f140a 100644
--- a/examples/simple-chat-ts/src/simple_chat.ts
+++ b/examples/simple-chat-ts/src/simple_chat.ts
@@ -303,6 +303,12 @@ class ChatUI {
stream: true,
messages: this.chatHistory,
stream_options: { include_usage: true },
+ // if model starts with "Qwen3", disable thinking.
+ extra_body: this.selectedModel.startsWith("Qwen3")
+ ? {
+ enable_thinking: false,
+ }
+ : undefined,
});
// TODO(Charlie): Processing of � requires changes
for await (const chunk of completion) {
diff --git a/package-lock.json b/package-lock.json
index 12e86a22..90598028 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -13,7 +13,7 @@
},
"devDependencies": {
"@mlc-ai/web-runtime": "0.18.0-dev2",
- "@mlc-ai/web-tokenizers": "^0.1.5",
+ "@mlc-ai/web-tokenizers": "^0.1.6",
"@mlc-ai/web-xgrammar": "0.1.0",
"@next/eslint-plugin-next": "^14.2.3",
"@rollup/plugin-commonjs": "^20.0.0",
@@ -54,44 +54,44 @@
}
},
"node_modules/@babel/code-frame": {
- "version": "7.26.2",
- "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz",
- "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==",
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
+ "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
"dev": true,
"dependencies": {
- "@babel/helper-validator-identifier": "^7.25.9",
+ "@babel/helper-validator-identifier": "^7.27.1",
"js-tokens": "^4.0.0",
- "picocolors": "^1.0.0"
+ "picocolors": "^1.1.1"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/compat-data": {
- "version": "7.26.2",
- "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.2.tgz",
- "integrity": "sha512-Z0WgzSEa+aUcdiJuCIqgujCshpMWgUpgOxXotrYPSA53hA3qopNaqcJpyr0hVb1FeWdnqFA35/fUtXgBK8srQg==",
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.1.tgz",
+ "integrity": "sha512-Q+E+rd/yBzNQhXkG+zQnF58e4zoZfBedaxwzPmicKsiK3nt8iJYrSrDbjwFFDGC4f+rPafqRaPH6TsDoSvMf7A==",
"dev": true,
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/core": {
- "version": "7.26.0",
- "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.0.tgz",
- "integrity": "sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==",
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.1.tgz",
+ "integrity": "sha512-IaaGWsQqfsQWVLqMn9OB92MNN7zukfVA4s7KKAI0KfrrDsZ0yhi5uV4baBuLuN7n3vsZpwP8asPPcVwApxvjBQ==",
"dev": true,
"dependencies": {
"@ampproject/remapping": "^2.2.0",
- "@babel/code-frame": "^7.26.0",
- "@babel/generator": "^7.26.0",
- "@babel/helper-compilation-targets": "^7.25.9",
- "@babel/helper-module-transforms": "^7.26.0",
- "@babel/helpers": "^7.26.0",
- "@babel/parser": "^7.26.0",
- "@babel/template": "^7.25.9",
- "@babel/traverse": "^7.25.9",
- "@babel/types": "^7.26.0",
+ "@babel/code-frame": "^7.27.1",
+ "@babel/generator": "^7.27.1",
+ "@babel/helper-compilation-targets": "^7.27.1",
+ "@babel/helper-module-transforms": "^7.27.1",
+ "@babel/helpers": "^7.27.1",
+ "@babel/parser": "^7.27.1",
+ "@babel/template": "^7.27.1",
+ "@babel/traverse": "^7.27.1",
+ "@babel/types": "^7.27.1",
"convert-source-map": "^2.0.0",
"debug": "^4.1.0",
"gensync": "^1.0.0-beta.2",
@@ -116,13 +116,13 @@
}
},
"node_modules/@babel/generator": {
- "version": "7.26.2",
- "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.2.tgz",
- "integrity": "sha512-zevQbhbau95nkoxSq3f/DC/SC+EEOUZd3DYqfSkMhY2/wfSeaHV1Ew4vk8e+x8lja31IbyuUa2uQ3JONqKbysw==",
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.1.tgz",
+ "integrity": "sha512-UnJfnIpc/+JO0/+KRVQNGU+y5taA5vCbwN8+azkX6beii/ZF+enZJSOKo11ZSzGJjlNfJHfQtmQT8H+9TXPG2w==",
"dev": true,
"dependencies": {
- "@babel/parser": "^7.26.2",
- "@babel/types": "^7.26.0",
+ "@babel/parser": "^7.27.1",
+ "@babel/types": "^7.27.1",
"@jridgewell/gen-mapping": "^0.3.5",
"@jridgewell/trace-mapping": "^0.3.25",
"jsesc": "^3.0.2"
@@ -132,13 +132,13 @@
}
},
"node_modules/@babel/helper-compilation-targets": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.9.tgz",
- "integrity": "sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ==",
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.1.tgz",
+ "integrity": "sha512-2YaDd/Rd9E598B5+WIc8wJPmWETiiJXFYVE60oX8FDohv7rAUU3CQj+A1MgeEmcsk2+dQuEjIe/GDvig0SqL4g==",
"dev": true,
"dependencies": {
- "@babel/compat-data": "^7.25.9",
- "@babel/helper-validator-option": "^7.25.9",
+ "@babel/compat-data": "^7.27.1",
+ "@babel/helper-validator-option": "^7.27.1",
"browserslist": "^4.24.0",
"lru-cache": "^5.1.1",
"semver": "^6.3.1"
@@ -157,27 +157,27 @@
}
},
"node_modules/@babel/helper-module-imports": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz",
- "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==",
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz",
+ "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==",
"dev": true,
"dependencies": {
- "@babel/traverse": "^7.25.9",
- "@babel/types": "^7.25.9"
+ "@babel/traverse": "^7.27.1",
+ "@babel/types": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-module-transforms": {
- "version": "7.26.0",
- "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz",
- "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==",
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.1.tgz",
+ "integrity": "sha512-9yHn519/8KvTU5BjTVEEeIM3w9/2yXNKoD82JifINImhpKkARMJKPP59kLo+BafpdN5zgNeIcS4jsGDmd3l58g==",
"dev": true,
"dependencies": {
- "@babel/helper-module-imports": "^7.25.9",
- "@babel/helper-validator-identifier": "^7.25.9",
- "@babel/traverse": "^7.25.9"
+ "@babel/helper-module-imports": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.27.1",
+ "@babel/traverse": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
@@ -187,61 +187,61 @@
}
},
"node_modules/@babel/helper-plugin-utils": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.9.tgz",
- "integrity": "sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw==",
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz",
+ "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==",
"dev": true,
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-string-parser": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz",
- "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==",
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
+ "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
"dev": true,
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-validator-identifier": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz",
- "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==",
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz",
+ "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==",
"dev": true,
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-validator-option": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz",
- "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==",
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz",
+ "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==",
"dev": true,
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helpers": {
- "version": "7.26.0",
- "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.0.tgz",
- "integrity": "sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==",
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.1.tgz",
+ "integrity": "sha512-FCvFTm0sWV8Fxhpp2McP5/W53GPllQ9QeQ7SiqGWjMf/LVG07lFa5+pgK05IRhVwtvafT22KF+ZSnM9I545CvQ==",
"dev": true,
"dependencies": {
- "@babel/template": "^7.25.9",
- "@babel/types": "^7.26.0"
+ "@babel/template": "^7.27.1",
+ "@babel/types": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/parser": {
- "version": "7.26.2",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.2.tgz",
- "integrity": "sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==",
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.1.tgz",
+ "integrity": "sha512-I0dZ3ZpCrJ1c04OqlNsQcKiZlsrXf/kkE4FXzID9rIOYICsAbA8mMDzhW/luRNAHdCNt7os/u8wenklZDlUVUQ==",
"dev": true,
"dependencies": {
- "@babel/types": "^7.26.0"
+ "@babel/types": "^7.27.1"
},
"bin": {
"parser": "bin/babel-parser.js"
@@ -302,12 +302,12 @@
}
},
"node_modules/@babel/plugin-syntax-import-attributes": {
- "version": "7.26.0",
- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.26.0.tgz",
- "integrity": "sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A==",
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz",
+ "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==",
"dev": true,
"dependencies": {
- "@babel/helper-plugin-utils": "^7.25.9"
+ "@babel/helper-plugin-utils": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
@@ -341,12 +341,12 @@
}
},
"node_modules/@babel/plugin-syntax-jsx": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.25.9.tgz",
- "integrity": "sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==",
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz",
+ "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==",
"dev": true,
"dependencies": {
- "@babel/helper-plugin-utils": "^7.25.9"
+ "@babel/helper-plugin-utils": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
@@ -458,12 +458,12 @@
}
},
"node_modules/@babel/plugin-syntax-typescript": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.9.tgz",
- "integrity": "sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ==",
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz",
+ "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==",
"dev": true,
"dependencies": {
- "@babel/helper-plugin-utils": "^7.25.9"
+ "@babel/helper-plugin-utils": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
@@ -473,30 +473,30 @@
}
},
"node_modules/@babel/template": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz",
- "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==",
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.1.tgz",
+ "integrity": "sha512-Fyo3ghWMqkHHpHQCoBs2VnYjR4iWFFjguTDEqA5WgZDOrFesVjMhMM2FSqTKSoUSDO1VQtavj8NFpdRBEvJTtg==",
"dev": true,
"dependencies": {
- "@babel/code-frame": "^7.25.9",
- "@babel/parser": "^7.25.9",
- "@babel/types": "^7.25.9"
+ "@babel/code-frame": "^7.27.1",
+ "@babel/parser": "^7.27.1",
+ "@babel/types": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/traverse": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.9.tgz",
- "integrity": "sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw==",
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.1.tgz",
+ "integrity": "sha512-ZCYtZciz1IWJB4U61UPu4KEaqyfj+r5T1Q5mqPo+IBpcG9kHv30Z0aD8LXPgC1trYa6rK0orRyAhqUgk4MjmEg==",
"dev": true,
"dependencies": {
- "@babel/code-frame": "^7.25.9",
- "@babel/generator": "^7.25.9",
- "@babel/parser": "^7.25.9",
- "@babel/template": "^7.25.9",
- "@babel/types": "^7.25.9",
+ "@babel/code-frame": "^7.27.1",
+ "@babel/generator": "^7.27.1",
+ "@babel/parser": "^7.27.1",
+ "@babel/template": "^7.27.1",
+ "@babel/types": "^7.27.1",
"debug": "^4.3.1",
"globals": "^11.1.0"
},
@@ -514,13 +514,13 @@
}
},
"node_modules/@babel/types": {
- "version": "7.26.0",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.0.tgz",
- "integrity": "sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==",
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.1.tgz",
+ "integrity": "sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q==",
"dev": true,
"dependencies": {
- "@babel/helper-string-parser": "^7.25.9",
- "@babel/helper-validator-identifier": "^7.25.9"
+ "@babel/helper-string-parser": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
@@ -533,9 +533,9 @@
"dev": true
},
"node_modules/@eslint-community/eslint-utils": {
- "version": "4.4.1",
- "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz",
- "integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==",
+ "version": "4.7.0",
+ "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz",
+ "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==",
"dev": true,
"dependencies": {
"eslint-visitor-keys": "^3.4.3"
@@ -1078,9 +1078,9 @@
}
},
"node_modules/@jridgewell/gen-mapping": {
- "version": "0.3.5",
- "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz",
- "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==",
+ "version": "0.3.8",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz",
+ "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==",
"dev": true,
"dependencies": {
"@jridgewell/set-array": "^1.2.1",
@@ -1132,9 +1132,9 @@
"dev": true
},
"node_modules/@mlc-ai/web-tokenizers": {
- "version": "0.1.5",
- "resolved": "https://registry.npmjs.org/@mlc-ai/web-tokenizers/-/web-tokenizers-0.1.5.tgz",
- "integrity": "sha512-G7vjJzZyOFJvAfx42kPEU7Z2hkAAGWvKJHfMTLdvY8QDLFvvvVOwmEk89Mh+7PBVPpcfh3PW0npTTupFnLMwHw==",
+ "version": "0.1.6",
+ "resolved": "https://registry.npmjs.org/@mlc-ai/web-tokenizers/-/web-tokenizers-0.1.6.tgz",
+ "integrity": "sha512-A5GSqUSnMjDkPoXBFFtbbW3F/qygCixuwbi7/EUMzcpgwFOAhD9vSZZBchK3IpC0c6TKlcDqAYDYp8vpb8/4vA==",
"dev": true
},
"node_modules/@mlc-ai/web-xgrammar": {
@@ -1144,9 +1144,9 @@
"dev": true
},
"node_modules/@next/eslint-plugin-next": {
- "version": "14.2.18",
- "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-14.2.18.tgz",
- "integrity": "sha512-KyYTbZ3GQwWOjX3Vi1YcQbekyGP0gdammb7pbmmi25HBUCINzDReyrzCMOJIeZisK1Q3U6DT5Rlc4nm2/pQeXA==",
+ "version": "14.2.28",
+ "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-14.2.28.tgz",
+ "integrity": "sha512-GQUPA1bTZy5qZdPV5MOHB18465azzhg8xm5o2SqxMF+h1rWNjB43y6xmIPHG5OV2OiU3WxuINpusXom49DdaIQ==",
"dev": true,
"dependencies": {
"glob": "10.3.10"
@@ -1198,15 +1198,15 @@
}
},
"node_modules/@pkgr/core": {
- "version": "0.1.1",
- "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz",
- "integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==",
+ "version": "0.2.4",
+ "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.4.tgz",
+ "integrity": "sha512-ROFF39F6ZrnzSUEmQQZUar0Jt4xVoP9WnDRdWwF4NNcXs3xBTLgBUDoOwW141y1jP+S8nahIbdxbFC7IShw9Iw==",
"dev": true,
"engines": {
"node": "^12.20.0 || ^14.18.0 || >=16.0.0"
},
"funding": {
- "url": "https://opencollective.com/unts"
+ "url": "https://opencollective.com/pkgr"
}
},
"node_modules/@rollup/plugin-commonjs": {
@@ -1332,9 +1332,9 @@
}
},
"node_modules/@types/babel__generator": {
- "version": "7.6.8",
- "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz",
- "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==",
+ "version": "7.27.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
+ "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
"dev": true,
"dependencies": {
"@babel/types": "^7.0.0"
@@ -1351,9 +1351,9 @@
}
},
"node_modules/@types/babel__traverse": {
- "version": "7.20.6",
- "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.6.tgz",
- "integrity": "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==",
+ "version": "7.20.7",
+ "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.7.tgz",
+ "integrity": "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==",
"dev": true,
"dependencies": {
"@babel/types": "^7.20.7"
@@ -1446,12 +1446,12 @@
"dev": true
},
"node_modules/@types/node": {
- "version": "22.9.1",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-22.9.1.tgz",
- "integrity": "sha512-p8Yy/8sw1caA8CdRIQBG5tiLHmxtQKObCijiAa9Ez+d4+PRffM4054xbju0msf+cvhJpnFEeNjxmVT/0ipktrg==",
+ "version": "22.15.3",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.3.tgz",
+ "integrity": "sha512-lX7HFZeHf4QG/J7tBZqrCAXwz9J5RD56Y6MpP0eJkka8p+K0RY/yBTW7CYFJ4VGCclxqOLKmiGP5juQc6MKgcw==",
"dev": true,
"dependencies": {
- "undici-types": "~6.19.8"
+ "undici-types": "~6.21.0"
}
},
"node_modules/@types/resolve": {
@@ -1464,9 +1464,9 @@
}
},
"node_modules/@types/semver": {
- "version": "7.5.8",
- "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz",
- "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==",
+ "version": "7.7.0",
+ "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.7.0.tgz",
+ "integrity": "sha512-k107IF4+Xr7UHjwDc7Cfd6PRQfbdkiRabXGRjo07b4WyPahFBZCZ1sE+BNxYIJPPg73UkfOsVOLwqVc/6ETrIA==",
"dev": true
},
"node_modules/@types/serviceworker": {
@@ -1685,21 +1685,21 @@
}
},
"node_modules/@ungap/structured-clone": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz",
- "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==",
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz",
+ "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==",
"dev": true
},
"node_modules/@webgpu/types": {
- "version": "0.1.51",
- "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.51.tgz",
- "integrity": "sha512-ktR3u64NPjwIViNCck+z9QeyN0iPkQCUOQ07ZCV1RzlkfP+olLTeEZ95O1QHS+v4w9vJeY9xj/uJuSphsHy5rQ==",
+ "version": "0.1.60",
+ "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.60.tgz",
+ "integrity": "sha512-8B/tdfRFKdrnejqmvq95ogp8tf52oZ51p3f4QD5m5Paey/qlX4Rhhy5Y8tgFMi7Ms70HzcMMw3EQjH/jdhTwlA==",
"dev": true
},
"node_modules/acorn": {
- "version": "8.14.0",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
- "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==",
+ "version": "8.14.1",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz",
+ "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==",
"dev": true,
"bin": {
"acorn": "bin/acorn"
@@ -1986,9 +1986,9 @@
}
},
"node_modules/browserslist": {
- "version": "4.24.2",
- "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.2.tgz",
- "integrity": "sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==",
+ "version": "4.24.5",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.5.tgz",
+ "integrity": "sha512-FDToo4Wo82hIdgc1CQ+NQD0hEhmpPjrZ3hiUgwgOG6IuTdlpr8jdjyG24P6cNP1yJpTLzS5OcGgSw0xmDU1/Tw==",
"dev": true,
"funding": [
{
@@ -2005,10 +2005,10 @@
}
],
"dependencies": {
- "caniuse-lite": "^1.0.30001669",
- "electron-to-chromium": "^1.5.41",
- "node-releases": "^2.0.18",
- "update-browserslist-db": "^1.1.1"
+ "caniuse-lite": "^1.0.30001716",
+ "electron-to-chromium": "^1.5.149",
+ "node-releases": "^2.0.19",
+ "update-browserslist-db": "^1.1.3"
},
"bin": {
"browserslist": "cli.js"
@@ -2099,9 +2099,9 @@
}
},
"node_modules/caniuse-lite": {
- "version": "1.0.30001683",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001683.tgz",
- "integrity": "sha512-iqmNnThZ0n70mNwvxpEC2nBJ037ZHZUoBI5Gorh1Mw6IlEAZujEoU1tXA628iZfzm7R9FvFzxbfdgml82a3k8Q==",
+ "version": "1.0.30001716",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001716.tgz",
+ "integrity": "sha512-49/c1+x3Kwz7ZIWt+4DvK3aMJy9oYXXG6/97JKsnjdCk/6n9vVyWL8NAwVt95Lwt9eigI10Hl782kDfZUUlRXw==",
"dev": true,
"funding": [
{
@@ -2159,9 +2159,9 @@
}
},
"node_modules/cjs-module-lexer": {
- "version": "1.4.1",
- "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.1.tgz",
- "integrity": "sha512-cuSVIHi9/9E/+821Qjdvngor+xpnlwnuwIyZOaLmHBVdXL+gP+I6QQB9VkO7RI77YIcTV+S1W9AreJ5eN63JBA==",
+ "version": "1.4.3",
+ "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz",
+ "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==",
"dev": true
},
"node_modules/cliui": {
@@ -2303,9 +2303,9 @@
}
},
"node_modules/debug": {
- "version": "4.3.7",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
- "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
+ "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
"dev": true,
"dependencies": {
"ms": "^2.1.3"
@@ -2320,9 +2320,9 @@
}
},
"node_modules/dedent": {
- "version": "1.5.3",
- "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz",
- "integrity": "sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==",
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.6.0.tgz",
+ "integrity": "sha512-F1Z+5UCFpmQUzJa11agbyPVMbpgT/qA3/SKyJ1jyBgm7dUcUEa8v9JwDkerSQXfakBwFljIxhOJqGkjUwZ9FSA==",
"dev": true,
"peerDependencies": {
"babel-plugin-macros": "^3.1.0"
@@ -2412,9 +2412,9 @@
}
},
"node_modules/electron-to-chromium": {
- "version": "1.5.64",
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.64.tgz",
- "integrity": "sha512-IXEuxU+5ClW2IGEYFC2T7szbyVgehupCWQe5GNh+H065CD6U6IFN0s4KeAMFGNmQolRU4IV7zGBWSYMmZ8uuqQ==",
+ "version": "1.5.149",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.149.tgz",
+ "integrity": "sha512-UyiO82eb9dVOx8YO3ajDf9jz2kKyt98DEITRdeLPstOEuTlLzDA4Gyq5K9he71TQziU5jUVu2OAu5N48HmQiyQ==",
"dev": true
},
"node_modules/emittery": {
@@ -2534,13 +2534,13 @@
}
},
"node_modules/eslint-plugin-prettier": {
- "version": "5.2.1",
- "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.1.tgz",
- "integrity": "sha512-gH3iR3g4JfF+yYPaJYkN7jEl9QbweL/YfkoRlNnuIEHEz1vHVlCmWOS+eGGiRuzHQXdJFCOTxRgvju9b8VUmrw==",
+ "version": "5.3.1",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.3.1.tgz",
+ "integrity": "sha512-vad9VWgEm9xaVXRNmb4aeOt0PWDc61IAdzghkbYQ2wavgax148iKoX1rNJcgkBGCipzLzOnHYVgL7xudM9yccQ==",
"dev": true,
"dependencies": {
"prettier-linter-helpers": "^1.0.0",
- "synckit": "^0.9.1"
+ "synckit": "^0.11.0"
},
"engines": {
"node": "^14.18.0 || >=16.0.0"
@@ -2551,7 +2551,7 @@
"peerDependencies": {
"@types/eslint": ">=8.0.0",
"eslint": ">=8.0.0",
- "eslint-config-prettier": "*",
+ "eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0",
"prettier": ">=3.0.0"
},
"peerDependenciesMeta": {
@@ -2776,16 +2776,16 @@
"dev": true
},
"node_modules/fast-glob": {
- "version": "3.3.2",
- "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz",
- "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==",
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
+ "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
"dev": true,
"dependencies": {
"@nodelib/fs.stat": "^2.0.2",
"@nodelib/fs.walk": "^1.2.3",
"glob-parent": "^5.1.2",
"merge2": "^1.3.0",
- "micromatch": "^4.0.4"
+ "micromatch": "^4.0.8"
},
"engines": {
"node": ">=8.6.0"
@@ -2816,9 +2816,9 @@
"dev": true
},
"node_modules/fastq": {
- "version": "1.17.1",
- "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz",
- "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==",
+ "version": "1.19.1",
+ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz",
+ "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==",
"dev": true,
"dependencies": {
"reusify": "^1.0.4"
@@ -2959,18 +2959,18 @@
}
},
"node_modules/flatted": {
- "version": "3.3.2",
- "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.2.tgz",
- "integrity": "sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==",
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz",
+ "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==",
"dev": true
},
"node_modules/foreground-child": {
- "version": "3.3.0",
- "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz",
- "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==",
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
+ "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==",
"dev": true,
"dependencies": {
- "cross-spawn": "^7.0.0",
+ "cross-spawn": "^7.0.6",
"signal-exit": "^4.0.1"
},
"engines": {
@@ -3248,9 +3248,9 @@
}
},
"node_modules/import-fresh": {
- "version": "3.3.0",
- "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
- "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
+ "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
"dev": true,
"dependencies": {
"parent-module": "^1.0.0",
@@ -3330,9 +3330,9 @@
}
},
"node_modules/is-core-module": {
- "version": "2.15.1",
- "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz",
- "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==",
+ "version": "2.16.1",
+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
+ "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
"dev": true,
"dependencies": {
"hasown": "^2.0.2"
@@ -4152,9 +4152,9 @@
}
},
"node_modules/jsesc": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz",
- "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==",
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
+ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
"dev": true,
"bin": {
"jsesc": "bin/jsesc"
@@ -4427,9 +4427,9 @@
"dev": true
},
"node_modules/node-releases": {
- "version": "2.0.18",
- "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz",
- "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==",
+ "version": "2.0.19",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz",
+ "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==",
"dev": true
},
"node_modules/normalize-path": {
@@ -4646,9 +4646,9 @@
}
},
"node_modules/pirates": {
- "version": "4.0.6",
- "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz",
- "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==",
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz",
+ "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==",
"dev": true,
"engines": {
"node": ">= 6"
@@ -4863,18 +4863,21 @@
}
},
"node_modules/resolve": {
- "version": "1.22.8",
- "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
- "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==",
+ "version": "1.22.10",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
+ "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==",
"dev": true,
"dependencies": {
- "is-core-module": "^2.13.0",
+ "is-core-module": "^2.16.0",
"path-parse": "^1.0.7",
"supports-preserve-symlinks-flag": "^1.0.0"
},
"bin": {
"resolve": "bin/resolve"
},
+ "engines": {
+ "node": ">= 0.4"
+ },
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
@@ -4910,18 +4913,18 @@
}
},
"node_modules/resolve.exports": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz",
- "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==",
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz",
+ "integrity": "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==",
"dev": true,
"engines": {
"node": ">=10"
}
},
"node_modules/reusify": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
- "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
+ "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==",
"dev": true,
"engines": {
"iojs": ">=1.0.0",
@@ -5040,9 +5043,9 @@
}
},
"node_modules/semver": {
- "version": "7.6.3",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
- "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
+ "version": "7.7.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
+ "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==",
"dev": true,
"bin": {
"semver": "bin/semver.js"
@@ -5310,19 +5313,19 @@
}
},
"node_modules/synckit": {
- "version": "0.9.2",
- "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.2.tgz",
- "integrity": "sha512-vrozgXDQwYO72vHjUb/HnFbQx1exDjoKzqx23aXEg2a9VIg2TSFZ8FmeZpTjUCFMYw7mpX4BE2SFu8wI7asYsw==",
+ "version": "0.11.4",
+ "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.4.tgz",
+ "integrity": "sha512-Q/XQKRaJiLiFIBNN+mndW7S/RHxvwzuZS6ZwmRzUBqJBv/5QIKCEwkBC8GBf8EQJKYnaFs0wOZbKTXBPj8L9oQ==",
"dev": true,
"dependencies": {
- "@pkgr/core": "^0.1.0",
- "tslib": "^2.6.2"
+ "@pkgr/core": "^0.2.3",
+ "tslib": "^2.8.1"
},
"engines": {
"node": "^14.18.0 || >=16.0.0"
},
"funding": {
- "url": "https://opencollective.com/unts"
+ "url": "https://opencollective.com/synckit"
}
},
"node_modules/test-exclude": {
@@ -5385,9 +5388,9 @@
}
},
"node_modules/ts-jest": {
- "version": "29.2.5",
- "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.2.5.tgz",
- "integrity": "sha512-KD8zB2aAZrcKIdGk4OwpJggeLcH1FgrICqDSROWqlnJXGCXK4Mn6FcdK2B6670Xr73lHMG1kHw8R87A0ecZ+vA==",
+ "version": "29.3.2",
+ "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.3.2.tgz",
+ "integrity": "sha512-bJJkrWc6PjFVz5g2DGCNUo8z7oFEYaz1xP1NpeDU7KNLMWPpEyV8Chbpkn8xjzgRDpQhnGMyvyldoL7h8JXyug==",
"dev": true,
"dependencies": {
"bs-logger": "^0.2.6",
@@ -5397,7 +5400,8 @@
"json5": "^2.2.3",
"lodash.memoize": "^4.1.2",
"make-error": "^1.3.6",
- "semver": "^7.6.3",
+ "semver": "^7.7.1",
+ "type-fest": "^4.39.1",
"yargs-parser": "^21.1.1"
},
"bin": {
@@ -5432,6 +5436,18 @@
}
}
},
+ "node_modules/ts-jest/node_modules/type-fest": {
+ "version": "4.40.1",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.40.1.tgz",
+ "integrity": "sha512-9YvLNnORDpI+vghLU/Nf+zSv0kL47KbVJ1o3sKgoTefl6i+zebxbiDQWoe/oWWqPhIgQdRZRT1KA9sCPL810SA==",
+ "dev": true,
+ "engines": {
+ "node": ">=16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
@@ -5506,9 +5522,9 @@
}
},
"node_modules/undici-types": {
- "version": "6.19.8",
- "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
- "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
+ "version": "6.21.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
+ "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
"dev": true
},
"node_modules/universalify": {
@@ -5521,9 +5537,9 @@
}
},
"node_modules/update-browserslist-db": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz",
- "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==",
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz",
+ "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==",
"dev": true,
"funding": [
{
@@ -5541,7 +5557,7 @@
],
"dependencies": {
"escalade": "^3.2.0",
- "picocolors": "^1.1.0"
+ "picocolors": "^1.1.1"
},
"bin": {
"update-browserslist-db": "cli.js"
diff --git a/package.json b/package.json
index a1e2909b..0e027076 100644
--- a/package.json
+++ b/package.json
@@ -27,7 +27,7 @@
"license": "Apache-2.0",
"homepage": "https://github.com/mlc-ai/web-llm",
"devDependencies": {
- "@mlc-ai/web-tokenizers": "^0.1.5",
+ "@mlc-ai/web-tokenizers": "^0.1.6",
"@next/eslint-plugin-next": "^14.2.3",
"@rollup/plugin-commonjs": "^20.0.0",
"@rollup/plugin-node-resolve": "^13.0.4",
diff --git a/src/config.ts b/src/config.ts
index 3f6a39a6..dfeb1913 100644
--- a/src/config.ts
+++ b/src/config.ts
@@ -141,6 +141,8 @@ export interface GenerationConfig {
logprobs?: boolean | null;
top_logprobs?: number | null;
response_format?: ResponseFormat | null;
+ // extra_body in ChatCompletionsRequest
+ enable_thinking?: boolean | null;
}
export function postInitAndCheckGenerationConfigValues(
@@ -1050,6 +1052,137 @@ export const prebuiltAppConfig: AppConfig = {
context_window_size: 4096,
},
},
+ // Qwen-3
+ {
+ model: "https://huggingface.co/mlc-ai/Qwen3-0.6B-q4f16_1-MLC",
+ model_id: "Qwen3-0.6B-q4f16_1-MLC",
+ model_lib:
+ modelLibURLPrefix +
+ modelVersion +
+ "/Qwen3-0.6B-q4f16_1-ctx4k_cs1k-webgpu.wasm",
+ vram_required_MB: 1403.34,
+ low_resource_required: true,
+ overrides: {
+ context_window_size: 4096,
+ },
+ },
+ {
+ model: "https://huggingface.co/mlc-ai/Qwen3-0.6B-q4f32_1-MLC",
+ model_id: "Qwen3-0.6B-q4f32_1-MLC",
+ model_lib:
+ modelLibURLPrefix +
+ modelVersion +
+ "/Qwen3-0.6B-q4f32_1-ctx4k_cs1k-webgpu.wasm",
+ vram_required_MB: 1924.98,
+ low_resource_required: true,
+ overrides: {
+ context_window_size: 4096,
+ },
+ },
+ {
+ model: "https://huggingface.co/mlc-ai/Qwen3-0.6B-q0f16-MLC",
+ model_id: "Qwen3-0.6B-q0f16-MLC",
+ model_lib:
+ modelLibURLPrefix +
+ modelVersion +
+ "/Qwen3-0.6B-q0f16-ctx4k_cs1k-webgpu.wasm",
+ vram_required_MB: 2220.38,
+ low_resource_required: true,
+ overrides: {
+ context_window_size: 4096,
+ },
+ },
+ {
+ model: "https://huggingface.co/mlc-ai/Qwen3-0.6B-q0f32-MLC",
+ model_id: "Qwen3-0.6B-q0f32-MLC",
+ model_lib:
+ modelLibURLPrefix +
+ modelVersion +
+ "/Qwen3-0.6B-q0f32-ctx4k_cs1k-webgpu.wasm",
+ vram_required_MB: 3843.25,
+ low_resource_required: true,
+ overrides: {
+ context_window_size: 4096,
+ },
+ },
+ {
+ model: "https://huggingface.co/mlc-ai/Qwen3-1.7B-q4f16_1-MLC",
+ model_id: "Qwen3-1.7B-q4f16_1-MLC",
+ model_lib:
+ modelLibURLPrefix +
+ modelVersion +
+ "/Qwen3-1.7B-q4f16_1-ctx4k_cs1k-webgpu.wasm",
+ vram_required_MB: 2036.66,
+ low_resource_required: true,
+ overrides: {
+ context_window_size: 4096,
+ },
+ },
+ {
+ model: "https://huggingface.co/mlc-ai/Qwen3-1.7B-q4f32_1-MLC",
+ model_id: "Qwen3-1.7B-q4f32_1-MLC",
+ model_lib:
+ modelLibURLPrefix +
+ modelVersion +
+ "/Qwen3-1.7B-q4f32_1-ctx4k_cs1k-webgpu.wasm",
+ vram_required_MB: 2635.44,
+ low_resource_required: true,
+ overrides: {
+ context_window_size: 4096,
+ },
+ },
+ {
+ model: "https://huggingface.co/mlc-ai/Qwen3-4B-q4f16_1-MLC",
+ model_id: "Qwen3-4B-q4f16_1-MLC",
+ model_lib:
+ modelLibURLPrefix +
+ modelVersion +
+ "/Qwen3-4B-q4f16_1-ctx4k_cs1k-webgpu.wasm",
+ vram_required_MB: 3431.59,
+ low_resource_required: true,
+ overrides: {
+ context_window_size: 4096,
+ },
+ },
+ {
+ model: "https://huggingface.co/mlc-ai/Qwen3-4B-q4f32_1-MLC",
+ model_id: "Qwen3-4B-q4f32_1-MLC",
+ model_lib:
+ modelLibURLPrefix +
+ modelVersion +
+ "/Qwen3-4B-q4f32_1-ctx4k_cs1k-webgpu.wasm",
+ vram_required_MB: 4327.71,
+ low_resource_required: true,
+ overrides: {
+ context_window_size: 4096,
+ },
+ },
+ {
+ model: "https://huggingface.co/mlc-ai/Qwen3-8B-q4f16_1-MLC",
+ model_id: "Qwen3-8B-q4f16_1-MLC",
+ model_lib:
+ modelLibURLPrefix +
+ modelVersion +
+ "/Qwen3-8B-q4f16_1-ctx4k_cs1k-webgpu.wasm",
+ vram_required_MB: 5695.78,
+ low_resource_required: false,
+ overrides: {
+ context_window_size: 4096,
+ },
+ },
+ {
+ model: "https://huggingface.co/mlc-ai/Qwen3-8B-q4f32_1-MLC",
+ model_id: "Qwen3-8B-q4f32_1-MLC",
+ model_lib:
+ modelLibURLPrefix +
+ modelVersion +
+ "/Qwen3-8B-q4f32_1-ctx4k_cs1k-webgpu.wasm",
+ vram_required_MB: 6852.55,
+ low_resource_required: false,
+ overrides: {
+ context_window_size: 4096,
+ },
+ },
// Qwen-2
{
model: "https://huggingface.co/mlc-ai/Qwen2.5-0.5B-Instruct-q4f16_1-MLC",
diff --git a/src/conversation.ts b/src/conversation.ts
index a79ec8cc..5a3595aa 100644
--- a/src/conversation.ts
+++ b/src/conversation.ts
@@ -48,6 +48,13 @@ export class Conversation {
public use_function_calling = false;
public override_system_message?: string = undefined;
+ /**
+ * Tracks whether the last message is an empty thinking block. Should only
+ * be true when we are in the middle of a generation. Will be set to
+ * false when the reply is finished with `finishReply()`.
+ */
+ private isLastMessageEmptyThinkingReplyHeader = false;
+
// TODO(tvm-team) confirm and remove
// private contextWindowStart = 0;
@@ -102,7 +109,21 @@ export class Conversation {
continue;
}
- // 2. Each messageContent consists of one textPart, and >= 0 imageParts, regardless whether
+ // 2. Message from `appendEmptyThinkingReplyHeader()`, message is an empty thinking block.
+ if (
+ this.isLastMessageEmptyThinkingReplyHeader &&
+ i === this.messages.length - 1
+ ) {
+ // TODO(Charlie): content_sep or empty_sep? For Qwen3, both are "\n".
+ const content_sep =
+ this.config.role_content_sep || this.config.role_content_sep == ""
+ ? this.config.role_content_sep
+ : ": ";
+ ret.push(role_str + content_sep + messageContent);
+ continue;
+ }
+
+ // 3. Each messageContent consists of one textPart, and >= 0 imageParts, regardless whether
// it is Array or text message. So we extract out each.
let textContentPart = ""; // if no textPart, use an empty string
const imageContentParts: ImageURL[] = [];
@@ -304,6 +325,16 @@ export class Conversation {
this.messages.push([role, this.config.roles[role], undefined]);
}
+ appendEmptyThinkingReplyHeader(role: Role, emptyThinkingBlockStr: string) {
+ if (this.isTextCompletion) {
+ throw new TextCompletionConversationError(
+ "appendEmptyThinkingReplyHeader",
+ );
+ }
+ this.isLastMessageEmptyThinkingReplyHeader = true;
+ this.messages.push([role, this.config.roles[role], emptyThinkingBlockStr]);
+ }
+
finishReply(message: string) {
if (this.isTextCompletion) {
throw new TextCompletionConversationError("finishReply");
@@ -311,10 +342,16 @@ export class Conversation {
if (this.messages.length == 0) {
throw Error("Message error should not be 0");
}
- if (this.messages[this.messages.length - 1][2] !== undefined) {
+ if (
+ this.messages[this.messages.length - 1][2] !== undefined &&
+ // If the last message has an empty thinknig block, last message is expected
+ // to be non-empty.
+ this.isLastMessageEmptyThinkingReplyHeader === false
+ ) {
throw Error("Already assigned");
}
this.messages[this.messages.length - 1][2] = message;
+ this.isLastMessageEmptyThinkingReplyHeader = false;
}
}
diff --git a/src/engine.ts b/src/engine.ts
index 6fe3f577..fe4c427e 100644
--- a/src/engine.ts
+++ b/src/engine.ts
@@ -792,6 +792,7 @@ export class MLCEngine implements MLCEngineInterface {
top_logprobs: request.top_logprobs,
response_format: request.response_format,
ignore_eos: request.ignore_eos,
+ enable_thinking: request.extra_body?.enable_thinking,
};
// 0.5 Block wait until this pipeline finishes all previous requests
diff --git a/src/llm_chat.ts b/src/llm_chat.ts
index 7050e00b..5f8ecf00 100644
--- a/src/llm_chat.ts
+++ b/src/llm_chat.ts
@@ -587,7 +587,18 @@ export class LLMChatPipeline {
conversation.prompt = inp;
} else {
conversation.appendMessage(msgRole, inp, inp_role_str);
- conversation.appendReplyHeader(Role.assistant);
+ if (genConfig?.enable_thinking === false) {
+ // TODO(Charlie): In future we should make emptyThinkingBlockStr configurable.
+ const emptyThinkingBlockStr = "\n\n\n\n";
+ const encoded = this.tokenizer.encode(emptyThinkingBlockStr);
+ this.outputIds.push(...encoded);
+ conversation.appendEmptyThinkingReplyHeader(
+ Role.assistant,
+ emptyThinkingBlockStr,
+ );
+ } else {
+ conversation.appendReplyHeader(Role.assistant);
+ }
}
const retGetInputData = this.getInputData();
const inputData: Array | ImageURL> = retGetInputData[0];
diff --git a/src/openai_api_protocols/chat_completion.ts b/src/openai_api_protocols/chat_completion.ts
index 17f472d3..3934b732 100644
--- a/src/openai_api_protocols/chat_completion.ts
+++ b/src/openai_api_protocols/chat_completion.ts
@@ -256,6 +256,19 @@ export interface ChatCompletionRequestBase {
* are loaded, this is required.
*/
model?: string | null;
+
+ /**
+ * Fields specific to WebLLM, not present in OpenAI.
+ */
+ extra_body?: {
+ /**
+ * If set to false, prepend a "" to the response, preventing the model from
+ * generating thinking tokens. If set to true or undefined, does nothing.
+ *
+ * @note Currently only allowed to be used for Qwen3 models, though not explicitly checked.
+ */
+ enable_thinking?: boolean | null;
+ };
}
export interface ChatCompletionRequestNonStreaming
diff --git a/tests/constants.ts b/tests/constants.ts
index 2591f2b2..5aadc552 100644
--- a/tests/constants.ts
+++ b/tests/constants.ts
@@ -219,3 +219,90 @@ export const phi3_5VisionChatConfigJSONString = String.raw`{
"bos_token_id": 1,
"eos_token_id": 2
}`;
+
+export const qwen3ChatConfigJSONString = String.raw`{
+ "version": "0.1.0",
+ "model_type": "qwen3",
+ "quantization": "q0f32",
+ "model_config": {
+ "hidden_act": "silu",
+ "hidden_size": 1024,
+ "intermediate_size": 3072,
+ "attention_bias": false,
+ "num_attention_heads": 16,
+ "num_hidden_layers": 28,
+ "num_key_value_heads": 8,
+ "rms_norm_eps": 1e-06,
+ "rope_theta": 1000000,
+ "vocab_size": 151936,
+ "tie_word_embeddings": true,
+ "context_window_size": 40960,
+ "prefill_chunk_size": 2048,
+ "tensor_parallel_shards": 1,
+ "head_dim": 128,
+ "dtype": "float32",
+ "max_batch_size": 128,
+ "weight_block_size": null
+ },
+ "vocab_size": 151936,
+ "context_window_size": 40960,
+ "sliding_window_size": -1,
+ "prefill_chunk_size": 2048,
+ "attention_sink_size": -1,
+ "tensor_parallel_shards": 1,
+ "pipeline_parallel_stages": 1,
+ "temperature": 0.6,
+ "presence_penalty": 0.0,
+ "frequency_penalty": 0.0,
+ "repetition_penalty": 1.0,
+ "top_p": 0.95,
+ "tokenizer_files": [
+ "tokenizer.json",
+ "vocab.json",
+ "merges.txt",
+ "tokenizer_config.json"
+ ],
+ "tokenizer_info": {
+ "token_postproc_method": "byte_level",
+ "prepend_space_in_encode": false,
+ "strip_space_in_decode": false
+ },
+ "conv_template": {
+ "name": "qwen2",
+ "system_template": "<|im_start|>system\n{system_message}<|im_end|>\n",
+ "system_message": "You are a helpful assistant.",
+ "system_prefix_token_ids": null,
+ "add_role_after_system_message": true,
+ "roles": {
+ "user": "<|im_start|>user",
+ "assistant": "<|im_start|>assistant"
+ },
+ "role_templates": {
+ "user": "{user_message}",
+ "assistant": "{assistant_message}",
+ "tool": "{tool_message}"
+ },
+ "messages": [],
+ "seps": [
+ "<|im_end|>\n"
+ ],
+ "role_content_sep": "\n",
+ "role_empty_sep": "\n",
+ "stop_str": [
+ "<|endoftext|>",
+ "<|im_end|>"
+ ],
+ "stop_token_ids": [
+ 151643,
+ 151645
+ ],
+ "function_string": "",
+ "use_function_calling": false
+ },
+ "pad_token_id": 151643,
+ "bos_token_id": 151643,
+ "eos_token_id": [
+ 151645,
+ 151643
+ ]
+}`;
diff --git a/tests/conversation.test.ts b/tests/conversation.test.ts
index bdb42bf7..a6d90f36 100644
--- a/tests/conversation.test.ts
+++ b/tests/conversation.test.ts
@@ -8,6 +8,7 @@ import { describe, expect, test } from "@jest/globals";
import {
llama2ChatConfigJSONString,
phi3_5VisionChatConfigJSONString,
+ qwen3ChatConfigJSONString,
} from "./constants";
import {
ChatCompletionContentPartImage,
@@ -51,6 +52,38 @@ describe("Test basic conversation loading and getPromptArray", () => {
});
});
+describe("Test getConversationFromChatCompletionRequest with Qwen3", () => {
+ test("Test Qwen3 appendEmptyThinkingReplyHeader", () => {
+ const config_json = JSON.parse(qwen3ChatConfigJSONString);
+ const config = { ...config_json } as ChatConfig;
+ const conversation = getConversation(config.conv_template);
+
+ conversation.appendMessage(Role.user, "test1");
+ conversation.appendMessage(Role.assistant, "test2");
+ const emptyThinkingBlockStr = "\n\n\n\n";
+ conversation.appendEmptyThinkingReplyHeader(
+ Role.user,
+ emptyThinkingBlockStr,
+ );
+ const prompt = conversation.getPromptArray().join("");
+ expect(prompt).toEqual(
+ "<|im_start|>system\nYou are a helpful assistant.<|im_end|>\n" +
+ "<|im_start|>user\n" +
+ "test1<|im_end|>\n" +
+ "<|im_start|>assistant\n" +
+ "test2<|im_end|>\n" +
+ "<|im_start|>user\n" +
+ emptyThinkingBlockStr,
+ );
+
+ const message = emptyThinkingBlockStr + "test3";
+ conversation.finishReply(message);
+ expect(conversation.messages[conversation.messages.length - 1][2]).toEqual(
+ message,
+ );
+ });
+});
+
describe("Test getConversationFromChatCompletionRequest with image", () => {
// Constants for testing
type ImageURL = ChatCompletionContentPartImage.ImageURL;