Skip to content
Open
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ __pycache__
node_modules
deno.lock
package-lock.json
*.local.*

# Do not track build artifacts/generated files
packages/*/dist
Expand Down
60 changes: 60 additions & 0 deletions packages/transformers-llguidance/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# @huggingface/transformers-llguidance

Experimental constrained-generation helpers for Transformers.js.

This package exports `LlguidanceConstraint`, which turns an llguidance response format into the `logits_processor` and `stopping_criteria` objects accepted by Transformers.js generation.

```js
import { LlguidanceConstraint } from "@huggingface/transformers-llguidance";

const constraint = await LlguidanceConstraint.fromResponseFormat(tokenizer, {
type: "json_schema",
json_schema: {
type: "object",
properties: {
answer: { type: "string" },
},
required: ["answer"],
additionalProperties: false,
},
});

try {
await model.generate({
...inputs,
logits_processor: constraint.logits_processor,
stopping_criteria: constraint.stopping_criteria,
});
} finally {
constraint.dispose();
}
```

Constraints currently support a single generated sequence at a time. Generation throws when the logical batch size is not `1` rather than sharing mutable grammar state across sequences.

The interpreter is automatically disposed when llguidance reaches a terminal state. Call `dispose()` in a `finally` block as shown above to also release resources when generation ends for another reason, such as `max_new_tokens` or cancellation.

If llguidance reports acceptance before sampling, the constraint forces the tokenizer's EOS token so no unconstrained token is appended. Compatible tokenizer objects must expose an EOS ID such as `eos_token_id` or `eosTokenId`; generation fails closed if acceptance occurs without one.

## Regex constraints

Use `type: "regex"` to constrain generation to a regular expression. For example, this only allows ISO-like dates in `YYYY-MM-DD` format:

```js
import { LlguidanceConstraint } from "@huggingface/transformers-llguidance";

const constraint = await LlguidanceConstraint.fromResponseFormat(tokenizer, {
type: "regex",
regex: "\\d{4}-\\d{2}-\\d{2}",
});

try {
const output = await model.generate({
...inputs,
logits_processor: constraint.logits_processor,
stopping_criteria: constraint.stopping_criteria,
});
} finally {
constraint.dispose();
}
```
11 changes: 11 additions & 0 deletions packages/transformers-llguidance/jest.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/** @type {import('jest').Config} */
export default {
clearMocks: true,
collectCoverage: true,
coverageDirectory: "coverage",
coveragePathIgnorePatterns: ["node_modules", "tests"],
coverageProvider: "v8",
roots: ["./tests/"],
testTimeout: 32000,
transform: {},
};
68 changes: 68 additions & 0 deletions packages/transformers-llguidance/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
{
"name": "@huggingface/transformers-llguidance",
"version": "0.0.0",
"description": "llguidance integration helpers for Transformers.js constrained generation",
"main": "./dist/index.cjs",
"types": "./types/index.d.ts",
"type": "module",
"exports": {
"import": {
"types": "./types/index.d.ts",
"default": "./dist/index.js"
},
"require": {
"types": "./types/index.d.ts",
"default": "./dist/index.cjs"
}
},
"scripts": {
"format": "prettier --write . --ignore-path ../../.prettierignore",
"format:check": "prettier --check . --ignore-path ../../.prettierignore",
"typegen": "tsc --build",
"dev": "node scripts/dev.mjs",
"build": "node scripts/build.mjs && pnpm typegen",
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js --verbose"
},
"repository": {
"type": "git",
"url": "git+https://github.com/huggingface/transformers.js.git"
},
"keywords": [
"transformers",
"transformers.js",
"huggingface",
"llguidance",
"constrained-generation"
],
"author": "Hugging Face",
"license": "Apache-2.0",
"bugs": {
"url": "https://github.com/huggingface/transformers.js/issues"
},
"homepage": "https://github.com/huggingface/transformers.js#readme",
"peerDependencies": {
"@huggingface/transformers": "^4.2.0"
},
"devDependencies": {
"@huggingface/transformers": "workspace:*",
"@types/jest": "^30.0.0",
"@types/node": "^24.1.0",
"esbuild": "^0.27.2",
"jest": "^30.2.0",
"typescript": "5.9.3"
},
"files": [
"src",
"dist",
"types",
"README.md",
"LICENSE",
"!**/*.tsbuildinfo"
],
"publishConfig": {
"access": "public"
},
"dependencies": {
"llguidance": "0.2.0"
}
}
26 changes: 26 additions & 0 deletions packages/transformers-llguidance/scripts/build.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { build } from "esbuild";
import { rmSync } from "node:fs";

rmSync("dist", { recursive: true, force: true });

const common = {
entryPoints: ["src/index.ts"],
bundle: true,
platform: "neutral",
target: "es2022",
sourcemap: true,
external: ["@huggingface/transformers", "llguidance"],
};

await Promise.all([
build({
...common,
format: "esm",
outfile: "dist/index.js",
}),
build({
...common,
format: "cjs",
outfile: "dist/index.cjs",
}),
]);
65 changes: 65 additions & 0 deletions packages/transformers-llguidance/scripts/dev.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { context } from "esbuild";
import { spawn } from "node:child_process";
import { rmSync } from "node:fs";

rmSync("dist", { recursive: true, force: true });
rmSync("types", { recursive: true, force: true });

const watchLogger = {
name: "watch-logger",
setup(build) {
let startTime = 0;

build.onStart(() => {
startTime = performance.now();
console.log(`[transformers-llguidance] rebuilding ${build.initialOptions.outfile}...`);
});

build.onEnd((result) => {
const duration = (performance.now() - startTime).toFixed(2);
if (result.errors.length > 0) {
console.log(`[transformers-llguidance] rebuild failed in ${duration}ms`);
} else {
console.log(`[transformers-llguidance] rebuilt ${build.initialOptions.outfile} in ${duration}ms`);
}
});
},
};

const common = {
entryPoints: ["src/index.ts"],
bundle: true,
platform: "neutral",
target: "es2022",
sourcemap: true,
external: ["@huggingface/transformers", "llguidance"],
plugins: [watchLogger],
};

const contexts = await Promise.all([
context({
...common,
format: "esm",
outfile: "dist/index.js",
}),
context({
...common,
format: "cjs",
outfile: "dist/index.cjs",
}),
]);

await Promise.all(contexts.map((ctx) => ctx.watch()));

const tscWatch = spawn("tsc", ["--build", "--watch", "--preserveWatchOutput"], {
stdio: "inherit",
shell: true,
});

console.log("Watching @huggingface/transformers-llguidance...");

process.on("SIGINT", async () => {
tscWatch.kill();
await Promise.all(contexts.map((ctx) => ctx.dispose()));
process.exit(0);
});
Loading
Loading