Skip to content

Commit 8f45111

Browse files
committed
[refactor] Refactor AI provider registration and introduce HuggingFace and TensorFlow MediaPipe providers
- Replaced `register_HFT_InlineJobFns` and `register_HFT_ClientJobFns` with `HuggingFaceTransformersProvider` and `TensorFlowMediaPipeProvider` for improved modularity and clarity. - Updated documentation and examples to reflect the new provider registration methods. - Enhanced worker registration for HuggingFace and TensorFlow MediaPipe tasks, streamlining the integration of AI models. - Removed deprecated registration functions to clean up the codebase.
1 parent 6b057b4 commit 8f45111

36 files changed

Lines changed: 955 additions & 651 deletions

docs/developers/01_getting_started.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -138,10 +138,10 @@ import {
138138
import { DebugLogTask } from "@workglow/tasks";
139139
import { ConcurrencyLimiter, JobQueueClient, JobQueueServer } from "@workglow/job-queue";
140140
import { InMemoryQueueStorage } from "@workglow/storage";
141-
import { HF_TRANSFORMERS_ONNX, register_HFT_InlineJobFns } from "@workglow/ai-provider";
141+
import { HF_TRANSFORMERS_ONNX, HuggingFaceTransformersProvider } from "@workglow/ai-provider";
142142

143143
// Provider run functions on this thread
144-
await register_HFT_InlineJobFns();
144+
await new HuggingFaceTransformersProvider().register({ mode: "inline" });
145145

146146
// Set up a model repo and models
147147
const modelRepo = new InMemoryModelRepository();
@@ -218,17 +218,17 @@ You can use as much or as little "magic" as you want. The config helpers are the
218218
219219
Tasks are agnostic to the provider. Text embedding can be done with several providers, such as Hugging Face Transformers (ONNX) or MediaPipe locally, or OpenAI etc via API calls.
220220
221-
- **`register_HFT_InlineJobFns()`** - Registers the Hugging Face Transformers local provider. Now you can use an ONNX model name for `TextEmbedding`, etc.
222-
- **`register_TFMP_InlineJobFns()`** - Registers the MediaPipe TF.js local provider. Now you can use one of the MediaPipe models.
221+
- **`new HuggingFaceTransformersProvider().register({ mode: "inline" })`** - Registers the Hugging Face Transformers local provider. Now you can use an ONNX model name for `TextEmbedding`, etc.
222+
- **`new TensorFlowMediaPipeProvider().register({ mode: "inline" })`** - Registers the MediaPipe TF.js local provider. Now you can use one of the MediaPipe models.
223223
224224
### Registering Provider plus related Job Queue
225225
226-
LLM providers have long running functions. These are handled by a Job Queue. There are some pre-built ones:
226+
LLM providers have long running functions. These are handled by a Job Queue. The `provider.register()` call creates the queue automatically. For convenience:
227227
228228
#### In memory:
229229
230-
- **`register_HFT_InMemoryQueue`** sets up the Hugging Face Transformers provider (above), and a job queue with `JobQueueServer` and `JobQueueClient` with a `ConcurrencyLimiter` so the ONNX queue only runs one task/job at a time.
231-
- **`register_TFMP_InMemoryQueue`** does the same for MediaPipe.
230+
- **`register_HFT_InMemoryQueue`** (from `@workglow/test`) - Equivalent to `new HuggingFaceTransformersProvider().register({ mode: "inline" })`.
231+
- **`register_TFMP_InMemoryQueue`** (from `@workglow/test`) - Equivalent to `new TensorFlowMediaPipeProvider().register({ mode: "inline" })`.
232232
233233
#### Using SQLite:
234234

examples/cli/src/worker_hft.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
* SPDX-License-Identifier: Apache-2.0
55
*/
66

7-
import { HFT_WORKER_JOBRUN, HFT_WORKER_JOBRUN_REGISTER } from "@workglow/ai-provider";
8-
import { globalServiceRegistry } from "@workglow/util";
7+
import { env } from "@sroussey/transformers";
8+
import { HFT_WORKER_JOBRUN_REGISTER } from "@workglow/ai-provider";
99

10-
globalServiceRegistry.get(HFT_WORKER_JOBRUN);
11-
console.log("worker_htf loaded", HFT_WORKER_JOBRUN_REGISTER);
10+
env.backends!.onnx!.wasm!.proxy = true;
11+
HFT_WORKER_JOBRUN_REGISTER();

examples/cli/src/workglow.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#!/usr/bin/env bun
22

3-
import { register_HFT_InlineJobFns } from "@workglow/ai-provider";
3+
import { HFT_TASKS, HuggingFaceTransformersProvider } from "@workglow/ai-provider";
44
import { getTaskQueueRegistry } from "@workglow/task-graph";
55
import { registerHuggingfaceLocalModels } from "@workglow/test";
66
import { program } from "commander";
@@ -11,10 +11,7 @@ program.version("1.0.0").description("A CLI to run tasks.");
1111
AddBaseCommands(program);
1212

1313
await registerHuggingfaceLocalModels();
14-
await register_HFT_InlineJobFns();
15-
16-
// await registerMediaPipeTfJsLocalModels();
17-
// await register_TFMP_InlineJobFns();
14+
await new HuggingFaceTransformersProvider(HFT_TASKS).register({ mode: "inline" });
1815

1916
await program.parseAsync(process.argv);
2017

examples/cli/src/workglow_worker.ts

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#!/usr/bin/env bun
22

3-
import { register_HFT_ClientJobFns } from "@workglow/ai-provider";
3+
import { HuggingFaceTransformersProvider } from "@workglow/ai-provider";
44
import { getTaskQueueRegistry } from "@workglow/task-graph";
55
import { registerHuggingfaceLocalModels } from "@workglow/test";
66
import { program } from "commander";
@@ -11,14 +11,10 @@ program.version("1.0.0").description("A CLI to run tasks.");
1111
AddBaseCommands(program);
1212

1313
await registerHuggingfaceLocalModels();
14-
await register_HFT_ClientJobFns(
15-
new Worker(new URL("./worker_hft.ts", import.meta.url), { type: "module" })
16-
);
17-
18-
// await registerMediaPipeTfJsLocalModels();
19-
// await register_TFMP_ClientJobFns(
20-
// new Worker(new URL("./worker_tfmp.ts", import.meta.url), { type: "module" })
21-
// );
14+
await new HuggingFaceTransformersProvider().register({
15+
mode: "worker",
16+
worker: new Worker(new URL("./worker_hft.ts", import.meta.url), { type: "module" }),
17+
});
2218

2319
await program.parseAsync(process.argv);
2420

examples/web/src/App.tsx

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@
44
* SPDX-License-Identifier: Apache-2.0
55
*/
66

7-
import { register_HFT_ClientJobFns, register_TFMP_ClientJobFns } from "@workglow/ai-provider";
7+
import {
8+
HuggingFaceTransformersProvider,
9+
TensorFlowMediaPipeProvider,
10+
} from "@workglow/ai-provider";
811
import { getTaskQueueRegistry, JsonTaskItem, TaskGraph, Workflow } from "@workglow/task-graph";
912
import { JsonTask } from "@workglow/tasks";
1013
import {
@@ -22,12 +25,14 @@ import { GraphStoreStatus } from "./status/GraphStoreStatus";
2225
import { OutputRepositoryStatus } from "./status/OutputRepositoryStatus";
2326
import { QueuesStatus } from "./status/QueueStatus";
2427

25-
await register_TFMP_ClientJobFns(
26-
new Worker(new URL("./worker_tfmp.ts", import.meta.url), { type: "module" })
27-
);
28-
await register_HFT_ClientJobFns(
29-
new Worker(new URL("./worker_hft.ts", import.meta.url), { type: "module" })
30-
);
28+
await new TensorFlowMediaPipeProvider().register({
29+
mode: "worker",
30+
worker: new Worker(new URL("./worker_tfmp.ts", import.meta.url), { type: "module" }),
31+
});
32+
await new HuggingFaceTransformersProvider().register({
33+
mode: "worker",
34+
worker: new Worker(new URL("./worker_hft.ts", import.meta.url), { type: "module" }),
35+
});
3136

3237
const queueRegistry = getTaskQueueRegistry();
3338
queueRegistry.clearQueues();

examples/web/src/worker_hft.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@
55
*/
66

77
import { env } from "@sroussey/transformers";
8-
import { HFT_WORKER_JOBRUN, HFT_WORKER_JOBRUN_REGISTER } from "@workglow/ai-provider";
9-
import { globalServiceRegistry } from "@workglow/util";
8+
import { HFT_WORKER_JOBRUN_REGISTER } from "@workglow/ai-provider";
109

11-
env.backends.onnx.wasm.proxy = true;
12-
globalServiceRegistry.get(HFT_WORKER_JOBRUN);
13-
console.log("worker_htf loaded", HFT_WORKER_JOBRUN_REGISTER);
10+
const onnx = env?.backends?.onnx;
11+
if (onnx) {
12+
onnx.wasm!.proxy = true;
13+
}
14+
15+
HFT_WORKER_JOBRUN_REGISTER();

examples/web/src/worker_tfmp.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@
44
* SPDX-License-Identifier: Apache-2.0
55
*/
66

7-
import { TFMP_WORKER_JOBRUN, TFMP_WORKER_JOBRUN_REGISTER } from "@workglow/ai-provider";
8-
import { globalServiceRegistry } from "@workglow/util";
7+
import { TFMP_WORKER_JOBRUN_REGISTER } from "@workglow/ai-provider";
98

10-
globalServiceRegistry.get(TFMP_WORKER_JOBRUN);
11-
console.log("worker_tfmp loaded", TFMP_WORKER_JOBRUN_REGISTER);
9+
TFMP_WORKER_JOBRUN_REGISTER();

packages/ai-provider/README.md

Lines changed: 37 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,14 @@ npm install @mediapipe/tasks-text @mediapipe/tasks-vision @mediapipe/tasks-audio
4141
### 1. Basic Setup
4242

4343
```typescript
44-
import { register_HFT_InlineJobFns, register_TFMP_InlineJobFns } from "@workglow/ai-provider";
44+
import {
45+
HuggingFaceTransformersProvider,
46+
TensorFlowMediaPipeProvider,
47+
} from "@workglow/ai-provider";
4548

4649
// Register AI providers
47-
await register_HFT_InlineJobFns();
48-
await register_TFMP_InlineJobFns();
50+
await new HuggingFaceTransformersProvider().register({ mode: "inline" });
51+
await new TensorFlowMediaPipeProvider().register({ mode: "inline" });
4952
```
5053

5154
### 2. Using AI Tasks in Workflows
@@ -234,37 +237,42 @@ For better performance, especially in browser environments, run AI inference in
234237
#### Main Thread Setup
235238

236239
```typescript
237-
import { register_HFT_ClientJobFns, register_TFMP_ClientJobFns } from "@workglow/ai-provider";
240+
import {
241+
HuggingFaceTransformersProvider,
242+
TensorFlowMediaPipeProvider,
243+
} from "@workglow/ai-provider";
238244

239245
// Register HuggingFace Transformers with worker
240-
register_HFT_ClientJobFns(
241-
new Worker(new URL("./hft-worker.ts", import.meta.url), { type: "module" })
242-
);
246+
await new HuggingFaceTransformersProvider().register({
247+
mode: "worker",
248+
worker: new Worker(new URL("./hft-worker.ts", import.meta.url), { type: "module" }),
249+
});
243250

244251
// Register MediaPipe with worker
245-
register_TFMP_ClientJobFns(
246-
new Worker(new URL("./tfmp-worker.ts", import.meta.url), { type: "module" })
247-
);
252+
await new TensorFlowMediaPipeProvider().register({
253+
mode: "worker",
254+
worker: new Worker(new URL("./tfmp-worker.ts", import.meta.url), { type: "module" }),
255+
});
248256
```
249257

250258
#### Worker Setup Files
251259

252260
**hft-worker.ts:**
253261

254262
```typescript
255-
import { register_HFT_WorkerJobFns } from "@workglow/ai-provider";
263+
import { HFT_WORKER_JOBRUN_REGISTER } from "@workglow/ai-provider";
256264

257265
// Register HuggingFace Transformers worker functions
258-
register_HFT_WorkerJobFns();
266+
HFT_WORKER_JOBRUN_REGISTER();
259267
```
260268

261269
**tfmp-worker.ts:**
262270

263271
```typescript
264-
import { register_TFMP_WorkerJobFns } from "@workglow/ai-provider";
272+
import { TFMP_WORKER_JOBRUN_REGISTER } from "@workglow/ai-provider";
265273

266274
// Register MediaPipe worker functions
267-
register_TFMP_WorkerJobFns();
275+
TFMP_WORKER_JOBRUN_REGISTER();
268276
```
269277

270278
### Model Management
@@ -286,30 +294,20 @@ await downloadTask.execute();
286294
### Custom Job Queue Configuration
287295

288296
```typescript
289-
import {
290-
JobQueueClient,
291-
JobQueueServer,
292-
ConcurrencyLimiter,
293-
DelayLimiter,
294-
} from "@workglow/job-queue";
295-
import { InMemoryQueueStorage } from "@workglow/storage";
296-
import { register_HFT_InlineJobFns, HF_TRANSFORMERS_ONNX } from "@workglow/ai-provider";
297-
298-
// Configure queue with custom limits
299-
const customQueue = new JobQueueServer(HF_TRANSFORMERS_ONNX, AiJob, {
300-
storage: new InMemoryQueueStorage(HF_TRANSFORMERS_ONNX),
301-
queueName: HF_TRANSFORMERS_ONNX,
302-
limiter: new ConcurrencyLimiter(2, 1000), // 2 concurrent jobs, 1000ms timeout
303-
});
297+
import { HuggingFaceTransformersProvider } from "@workglow/ai-provider";
304298

305-
const client = new JobQueueClient({
306-
storage: new InMemoryQueueStorage(HF_TRANSFORMERS_ONNX),
307-
queueName: HF_TRANSFORMERS_ONNX,
299+
// Register with custom queue concurrency (provider creates queue with concurrency: 2)
300+
await new HuggingFaceTransformersProvider().register({
301+
mode: "inline",
302+
queue: { concurrency: 2 },
308303
});
309304

310-
client.attach(customQueue);
311-
// Register AI providers
312-
await register_HFT_InlineJobFns(client);
305+
// Or skip auto-creation and use your own queue:
306+
await new HuggingFaceTransformersProvider().register({
307+
mode: "inline",
308+
queue: { autoCreate: false },
309+
});
310+
// Then register your custom queue with getTaskQueueRegistry().registerQueue(...)
313311
```
314312

315313
### Error Handling
@@ -352,13 +350,13 @@ await task.execute();
352350
## Complete Working Example
353351

354352
```typescript
355-
import { HF_TRANSFORMERS_ONNX, register_HFT_InlineJobFns } from "@workglow/ai-provider";
356-
import { TextGenerationTask, TextEmbeddingTask, AiJob } from "@workglow/ai";
357-
import { Workflow, getTaskQueueRegistry } from "@workglow/task-graph";
353+
import { HuggingFaceTransformersProvider } from "@workglow/ai-provider";
354+
import { TextGenerationTask, TextEmbeddingTask } from "@workglow/ai";
355+
import { Workflow } from "@workglow/task-graph";
358356

359357
async function main() {
360358
// 1. Register the AI provider
361-
await register_HFT_InlineJobFns();
359+
await new HuggingFaceTransformersProvider().register({ mode: "inline" });
362360

363361
// 2. Create and run workflow
364362
const workflow = new Workflow();
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/**
2+
* @license
3+
* Copyright 2025 Steven Roussey <sroussey@gmail.com>
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
import { globalServiceRegistry, parentPort, WORKER_SERVER } from "@workglow/util";
8+
import { HFT_TASKS } from "./common/HFT_JobRunFns";
9+
import { HuggingFaceTransformersProvider } from "./HuggingFaceTransformersProvider";
10+
11+
export function HFT_WORKER_JOBRUN_REGISTER() {
12+
const workerServer = globalServiceRegistry.get(WORKER_SERVER);
13+
new HuggingFaceTransformersProvider(HFT_TASKS).registerOnWorkerServer(workerServer);
14+
parentPort.postMessage({ type: "ready" });
15+
console.log("HFT_WORKER_JOBRUN registered");
16+
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/**
2+
* @license
3+
* Copyright 2025 Steven Roussey <sroussey@gmail.com>
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
import { AiProvider, type AiProviderRegisterOptions, type AiProviderRunFn } from "@workglow/ai";
8+
import { HF_TRANSFORMERS_ONNX } from "./common/HFT_Constants";
9+
import type { HfTransformersOnnxModelConfig } from "./common/HFT_ModelSchema";
10+
11+
/**
12+
* AI provider for HuggingFace Transformers ONNX models.
13+
*
14+
* Supports text, vision, and multimodal tasks via the @sroussey/transformers library.
15+
*
16+
* Task run functions are injected via the constructor so that the heavy
17+
* `@sroussey/transformers` library is only imported where actually needed
18+
* (inline mode, worker server), not on the main thread in worker mode.
19+
*
20+
* @example
21+
* ```typescript
22+
* // Worker mode (main thread) -- lightweight, no heavy imports:
23+
* await new HuggingFaceTransformersProvider().register({
24+
* mode: "worker",
25+
* worker: new Worker(new URL("./worker_hft.ts", import.meta.url), { type: "module" }),
26+
* });
27+
*
28+
* // Inline mode -- caller provides the tasks:
29+
* import { HFT_TASKS } from "@workglow/ai-provider";
30+
* await new HuggingFaceTransformersProvider(HFT_TASKS).register({ mode: "inline" });
31+
*
32+
* // Worker side -- caller provides the tasks:
33+
* import { HFT_TASKS } from "@workglow/ai-provider";
34+
* new HuggingFaceTransformersProvider(HFT_TASKS).registerOnWorkerServer(workerServer);
35+
* ```
36+
*/
37+
export class HuggingFaceTransformersProvider extends AiProvider<HfTransformersOnnxModelConfig> {
38+
readonly name = HF_TRANSFORMERS_ONNX;
39+
40+
readonly taskTypes = [
41+
"DownloadModelTask",
42+
"UnloadModelTask",
43+
"TextEmbeddingTask",
44+
"TextGenerationTask",
45+
"TextQuestionAnswerTask",
46+
"TextLanguageDetectionTask",
47+
"TextClassificationTask",
48+
"TextFillMaskTask",
49+
"TextNamedEntityRecognitionTask",
50+
"TextRewriterTask",
51+
"TextSummaryTask",
52+
"TextTranslationTask",
53+
"ImageSegmentationTask",
54+
"ImageToTextTask",
55+
"BackgroundRemovalTask",
56+
"ImageEmbeddingTask",
57+
"ImageClassificationTask",
58+
"ObjectDetectionTask",
59+
] as const;
60+
61+
constructor(tasks?: Record<string, AiProviderRunFn<any, any, HfTransformersOnnxModelConfig>>) {
62+
super(tasks);
63+
}
64+
65+
protected override async onInitialize(options: AiProviderRegisterOptions): Promise<void> {
66+
if (options.mode === "inline") {
67+
const { env } = await import("@sroussey/transformers");
68+
// @ts-ignore -- backends.onnx.wasm.proxy is not fully typed
69+
env.backends.onnx.wasm.proxy = true;
70+
}
71+
}
72+
73+
override async dispose(): Promise<void> {
74+
if (this.tasks) {
75+
const { clearPipelineCache } = await import("./common/HFT_JobRunFns");
76+
clearPipelineCache();
77+
}
78+
}
79+
}

0 commit comments

Comments
 (0)