From 13df580bd3836d2e4e24eeaa9bd919d5bc723d7b Mon Sep 17 00:00:00 2001 From: Steven Roussey Date: Wed, 21 Feb 2024 16:48:07 -0800 Subject: [PATCH] Move to graph instead of tree Also use packages/workspace --- .gitignore | 8 + .vscode/settings.json | 4 +- bun.lockb | Bin 34877 -> 557256 bytes docs/01_motivations.md | 2 +- docs/03_matrix_operations.md | 4 + docs/05_task_chains.md | 27 +- docs/06_run_graph_orchestration.md | 59 +++- lerna.json | 5 + nx.json | 13 + package.json | 49 ++- packages/cli/package.json | 18 + {src-examples => packages/cli/src}/TaskCLI.ts | 195 +++++------ .../cli/src}/TaskHelper.ts | 0 packages/cli/src/TaskStreamToListr2.ts | 181 ++++++++++ elmers.ts => packages/cli/src/ellmers.ts | 6 +- packages/cli/src/lib.ts | 9 + packages/cli/tsconfig.json | 14 + packages/core/package.json | 26 ++ packages/core/src/lib.ts | 24 ++ .../core/src/model/HuggingFaceModel.ts | 26 +- packages/core/src/model/MediaPipeModel.ts | 15 + {src => packages/core/src/model}/Model.ts | 0 .../core/src}/query/InMemoryQuery.ts | 2 +- {src => packages/core/src/source}/Document.ts | 0 .../core/src}/storage/InMemoryStorage.ts | 85 +---- packages/core/src/task/ArrayTask.ts | 76 ++++ packages/core/src/task/BasicTasks.ts | 98 ++++++ packages/core/src/task/JsonTask.ts | 95 +++++ packages/core/src/task/ModelFactory.ts | 78 +++++ packages/core/src/task/ModelFactoryTasks.ts | 240 +++++++++++++ packages/core/src/task/Task.ts | 196 +++++++++++ packages/core/src/task/TaskGraph.ts | 117 +++++++ packages/core/src/task/TaskGraphRunner.ts | 97 +++++ packages/core/src/task/TaskIOTypes.ts | 101 ++++++ packages/core/src/task/TaskRegistry.ts | 19 + .../task/exec/ml/HuggingFaceLocalTaskRun.ts | 256 ++++++++++++++ .../src/task/exec/ml/MediaPipeLocalTaskRun.ts | 81 +++++ {src => packages/core/src}/util/Misc.ts | 0 packages/core/tests/Task.test.ts | 69 ++++ packages/core/tests/TaskGraph.test.ts | 70 ++++ packages/core/tests/TaskGraphRunner.test.ts | 100 ++++++ packages/core/tsconfig.json | 14 + src-examples/ExampleSEC.ts | 309 ---------------- src-examples/TaskStreamToListr2.ts | 93 ----- src/Flow.ts | 39 --- src/JobQueue.ts | 204 ----------- src/Strategy.ts | 27 -- src/Task.ts | 330 ----------------- src/embeddings/GenerateEmbeddings.ts | 61 ---- src/embeddings/TransformerJsService.ts | 99 ------ src/stuff.ts | 55 --- src/tasks/BasicTasks.ts | 70 ---- src/tasks/FactoryTasks.ts | 156 --------- src/tasks/HuggingFaceLocalTasks.ts | 331 ------------------ src/tasks/JsonTask.ts | 184 ---------- src/tasks/MediaPipeLocalTasks.ts | 111 ------ src/tasks/Strategies.ts | 166 --------- tsconfig.json | 10 +- 58 files changed, 2233 insertions(+), 2491 deletions(-) create mode 100644 lerna.json create mode 100644 nx.json create mode 100644 packages/cli/package.json rename {src-examples => packages/cli/src}/TaskCLI.ts (50%) rename {src-examples => packages/cli/src}/TaskHelper.ts (100%) create mode 100644 packages/cli/src/TaskStreamToListr2.ts rename elmers.ts => packages/cli/src/ellmers.ts (52%) create mode 100644 packages/cli/src/lib.ts create mode 100644 packages/cli/tsconfig.json create mode 100644 packages/core/package.json create mode 100644 packages/core/src/lib.ts rename src/Instruct.ts => packages/core/src/model/HuggingFaceModel.ts (51%) create mode 100644 packages/core/src/model/MediaPipeModel.ts rename {src => packages/core/src/model}/Model.ts (100%) rename {src => packages/core/src}/query/InMemoryQuery.ts (97%) rename {src => packages/core/src/source}/Document.ts (100%) rename {src => packages/core/src}/storage/InMemoryStorage.ts (58%) create mode 100644 packages/core/src/task/ArrayTask.ts create mode 100644 packages/core/src/task/BasicTasks.ts create mode 100644 packages/core/src/task/JsonTask.ts create mode 100644 packages/core/src/task/ModelFactory.ts create mode 100644 packages/core/src/task/ModelFactoryTasks.ts create mode 100644 packages/core/src/task/Task.ts create mode 100644 packages/core/src/task/TaskGraph.ts create mode 100644 packages/core/src/task/TaskGraphRunner.ts create mode 100644 packages/core/src/task/TaskIOTypes.ts create mode 100644 packages/core/src/task/TaskRegistry.ts create mode 100644 packages/core/src/task/exec/ml/HuggingFaceLocalTaskRun.ts create mode 100644 packages/core/src/task/exec/ml/MediaPipeLocalTaskRun.ts rename {src => packages/core/src}/util/Misc.ts (100%) create mode 100644 packages/core/tests/Task.test.ts create mode 100644 packages/core/tests/TaskGraph.test.ts create mode 100644 packages/core/tests/TaskGraphRunner.test.ts create mode 100644 packages/core/tsconfig.json delete mode 100644 src-examples/ExampleSEC.ts delete mode 100644 src-examples/TaskStreamToListr2.ts delete mode 100644 src/Flow.ts delete mode 100644 src/JobQueue.ts delete mode 100644 src/Strategy.ts delete mode 100644 src/Task.ts delete mode 100644 src/embeddings/GenerateEmbeddings.ts delete mode 100644 src/embeddings/TransformerJsService.ts delete mode 100644 src/stuff.ts delete mode 100644 src/tasks/BasicTasks.ts delete mode 100644 src/tasks/FactoryTasks.ts delete mode 100644 src/tasks/HuggingFaceLocalTasks.ts delete mode 100644 src/tasks/JsonTask.ts delete mode 100644 src/tasks/MediaPipeLocalTasks.ts delete mode 100644 src/tasks/Strategies.ts diff --git a/.gitignore b/.gitignore index 20cf4dd4e..7f4152128 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,11 @@ data-in/ data-out/ node_modules/ .DS_Store/ +tsconfig.tsbuildinfo +dist +.nx/cache +*.d.ts +storybook-static/ +packages/cli-web/ +packages/cli-sec/ +packages/ngraph/ \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 79d355444..b37cb81ac 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -10,6 +10,7 @@ "search.exclude": { "data-files/**": true, "data-out/**": true, + "dist/**": true, "**/package-lock.json": true }, "editor.codeActionsOnSave": { @@ -27,5 +28,6 @@ "html.format.enable": true, "json.format.enable": true, "javascript.format.enable": true, - "editor.wordWrapColumn": 100 + "editor.wordWrapColumn": 100, + "prettier.printWidth": 100 } diff --git a/bun.lockb b/bun.lockb index bf24743cc461049aaf84d62fe63c110b758d1b68..2ade2c80cf772969444ae375238e9193ec136a9a 100755 GIT binary patch literal 557256 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
e^fkpOLf2QSQTh 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Ð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 W|5VwDUHU>PXHECeI(135;y(fC", "model to download") .action(async (options) => { - let models = findAllModels(); + const models = findAllModels(); + const graph = new TaskGraph(); if (options.model) { const model = findModelByName(options.model); if (model) { - models = [model]; + graph.addTask(new DownloadTask({ input: { model: model.name } })); } else { program.error(`Unknown model ${options.model}`); } + } else { + graph.addTask( + new DownloadMultiModelTask({ + input: { model: models.map((m) => m.name) }, + }) + ); } - - const task = new ParallelTaskList( - { name: "Download Models" }, - models.map((model) => new DownloadTask({}, { model })) - ); - await runTaskToListr(task); - - await sleep(100); + await runTask(graph); }); program @@ -70,24 +61,25 @@ export function AddSampleCommand(program: Command) { .description("get a embedding vector for a piece of text") .argument("", "text to embed") .option("--model ", "model to use") - .action(async (text, options) => { - let task; + .action(async (text: string, options) => { + const graph = new TaskGraph(); if (options.model) { const model = findModelByName(options.model); if (model) { - task = new EmbeddingTask({}, { model, text }); + graph.addTask(new EmbeddingTask({ input: { model: model.name, text } })); } else { program.error(`Unknown model ${options.model}`); } } else { let models = findModelByUseCase(ModelUseCaseEnum.TEXT_EMBEDDING); - task = new EmbeddingStrategy( - { name: "Embed several" }, - { text, models } + graph.addTask( + new EmbeddingMultiModelTask({ + name: "Embed several", + input: { text, model: models.map((m) => m.name) }, + }) ); } - - await runTask(task); + await runTask(graph); }); program @@ -96,20 +88,23 @@ export function AddSampleCommand(program: Command) { .argument("", "text to embed") .option("--model ", "model to use") .action(async (text, options) => { - let task; + const graph = new TaskGraph(); if (options.model) { const model = findModelByName(options.model); if (model) { - task = new SummarizeTask({}, { model, text }); + graph.addTask(new SummarizeTask({ input: { model: model.name, text } })); } else { program.error(`Unknown model ${options.model}`); } } else { let models = findModelByUseCase(ModelUseCaseEnum.TEXT_SUMMARIZATION); - task = new SummarizeStrategy({}, { text, models }); + graph.addTask( + new SummarizeMultiModelTask({ + input: { text, model: models.map((m) => m.name) }, + }) + ); } - - await runTask(task); + await runTask(graph); }); program @@ -119,92 +114,68 @@ export function AddSampleCommand(program: Command) { .option("--instruction ", "instruction for how to rewrite", "") .option("--model ", "model to use") .action(async (text, options) => { - let task; + const graph = new TaskGraph(); if (options.model) { const model = findModelByName(options.model); if (model) { - task = new RewriterTask( - { name: "Rewrite" }, - { model, text, prompt: options.instruction } + graph.addTask( + new TextRewriterTask({ + input: { model: model.name, text, prompt: options.instruction }, + }) ); } else { program.error(`Unknown model ${options.model}`); } } else { let models = findModelByUseCase(ModelUseCaseEnum.TEXT_GENERATION); - task = new RewriterStrategy( - { name: "Rewrite" }, - { - text, - prompt: options.instruction, - model: models, - } + graph.addTask( + new TextRewriterMultiModelTask({ + input: { + text, + prompt: options.instruction, + model: models.map((m) => m.name), + }, + }) ); } - await runTask(task); - }); - - program - .command("rewrite-embedding") - .description("rewrite based on internal prompt list, then embed") - .argument("", "text to rewrite and vectorize") - .action(async (text) => { - const prompt = [ - "Rewrite the following text:", - "Rewrite the following and make it more descriptive:", - ]; - const prompt_model = findModelByUseCase(ModelUseCaseEnum.TEXT_GENERATION); - - const embed_model = findModelByUseCase(ModelUseCaseEnum.TEXT_EMBEDDING); - - const task = new RewriterEmbeddingStrategy( - {}, - { - text, - prompt, - prompt_model, - embed_model, - } - ); - - await runTask(task); + await runTask(graph); }); program .command("json") .description("run based on json input") .argument("[json]", "json text to rewrite and vectorize") - .action(async (jsonText) => { - if (!jsonText) { - const exampleJson: TaskJsonInput[] = [ + .action(async (json) => { + if (!json) { + const exampleJson: JsonTaskArray = [ { - run: "RewriterTask", - config: { - output_name: "results", - }, + id: "1", + type: "DownloadTask", input: { - text: "The quick brown fox jumps over the lazy dog.", - prompt: "Rewrite the following text in reverse:", model: "Xenova/LaMini-Flan-T5-783M", }, }, { - run: "RenameTask", + id: "2", + type: "TextRewriterTask", input: { - output_remap_array: [{ from: "results", to: "reverse" }], + text: "The quick brown fox jumps over the lazy dog.", + prompt: "Rewrite the following text in reverse:", + }, + dependencies: { + model: { + id: "1", + output: "model", + }, }, }, ]; - jsonText = JSON.stringify(exampleJson); + json = JSON.stringify(exampleJson); } - const json = JSON.parse(jsonText); - const task = new JsonStrategy({ name: "Test JSON" }, json); - - await runTask(task); + const task = new JsonTask({ name: "Test JSON", input: { json } }); + const graph = new TaskGraph(); + graph.addTask(task); + await runTask(graph); }); - - program.command("test").action(async () => { - // - }); } diff --git a/src-examples/TaskHelper.ts b/packages/cli/src/TaskHelper.ts similarity index 100% rename from src-examples/TaskHelper.ts rename to packages/cli/src/TaskHelper.ts diff --git a/packages/cli/src/TaskStreamToListr2.ts b/packages/cli/src/TaskStreamToListr2.ts new file mode 100644 index 000000000..84dcc6c77 --- /dev/null +++ b/packages/cli/src/TaskStreamToListr2.ts @@ -0,0 +1,181 @@ +// ******************************************************************************* +// * ELLMERS: Embedding Large Language Model Experiential Retrieval Service * +// * * +// * Copyright Steven Roussey * +// * Licensed under the Apache License, Version 2.0 (the "License"); * +// ******************************************************************************* + +import { Listr, ListrTask, PRESET_TIMER } from "listr2"; +import { Observable } from "rxjs"; +import { + type TaskStream, + TaskStatus, + sleep, + TaskGraph, + TaskGraphRunner, + type Task, + DataFlow, + TaskInputDefinition, +} from "ellmers-core"; +import { createBar } from "./TaskHelper"; + +type TaskTree = { + task: Task; + children?: TaskTree; +}[]; + +/** + * Convert the DAG to a tree for use in a UI like listr2 + * Obviously a DAG can't be turned into a tree, but we can + * skip some edges and make it look like a tree if we want + */ +function convertToTree(runner: TaskGraphRunner) { + const sortedNodes = runner.dag.topologicallySortedNodes(); + // const allEdges = this.dag.getEdges().map(([s, t, e]) => e); + runner.assignLayers(sortedNodes); + const taskToDependency = new Map(); + runner.layers.forEach((nodes, layerNumber) => { + // console.log(`Layer ${layerNumber}`); + nodes.forEach((node) => { + if (layerNumber >= 0) { + const incomingEdges = runner.dag + .inEdges(node.config.id) + .map(([sourceNodeId]: [sourceNodeId: string]) => sourceNodeId); + // console.log(` ${node.config.name} <- ${incomingEdges.join(", ")}`); + for (const sourceNodeId of incomingEdges) { + if (!taskToDependency.has(node)) { + const sourceNode = runner.dag.getNode(sourceNodeId); + if (runner.layers.get(layerNumber - 1)?.find((n) => n == sourceNode)) { + taskToDependency.set(node, sourceNode!); + } + } + } + } + }); + }); + // reverse the map + const dependencyToTask = new Map(); + taskToDependency.forEach((dependency, task) => { + if (!dependencyToTask.has(dependency)) { + dependencyToTask.set(dependency, []); + } + dependencyToTask.get(dependency)?.push(task); + }); + const startNodes = runner.layers.get(0); + + // convert to tree + const convertToTree = (nodes: TaskStream): TaskTree => { + const tree: TaskTree = []; + nodes.forEach((node) => { + const children = dependencyToTask.get(node); + if (children) { + tree.push({ task: node, children: convertToTree(children) }); + } else { + tree.push({ task: node }); + } + }); + return tree; + }; + return convertToTree(startNodes!); +} + +const taskTreeToListr = ( + tree: TaskTree = [], + options: Record = { concurrent: false, exitOnError: true } +) => { + const list: ListrTask[] = []; + + for (const { task, children } of tree) { + list.push({ + title: task.config.name, + task: async (_, t) => { + if (children) { + return t.newListr(taskTreeToListr(children, options), options); + } else if (task.status == TaskStatus.COMPLETED || task.status == TaskStatus.FAILED) { + return; + } + return new Observable((observer) => { + const start = Date.now(); + let lastUpdate = start; + task.on("progress", (progress: any, file: string) => { + const timeSinceLast = Date.now() - lastUpdate; + const timeSinceStart = Date.now() - start; + if (timeSinceLast > 250 || timeSinceStart > 100) { + observer.next(createBar(progress / 100 || 0, 30) + " " + (file || "")); + } + }); + task.on("complete", () => { + observer.complete(); + }); + task.on("error", (error) => { + observer.complete(); + }); + }); + }, + }); + } + return list; +}; + +const flattenCompoundGraph = (dag: TaskGraph) => { + const nodes: Task[] = []; + const edges: DataFlow[] = []; + edges.push(...dag.getDataFlows()); + dag.getNodes().forEach((node) => { + if (node.isCompound) { + const { nodes: subNodes, edges: subEdges } = flattenCompoundGraph(node.subGraph); + // const inputNode = new SingleTask({ name: node.config.name, id: node.config.id }); + nodes.push(node); + nodes.push(...subNodes); + edges.push(...subEdges); + const inputs = (node.constructor as any).inputs as TaskInputDefinition[]; + subNodes.forEach((subNode) => { + inputs.forEach((input) => { + edges.push(new DataFlow(node.config.id, input.id, subNode.config.id, input.id)); + }); + }); + // const outputs = (node.constructor as any).outputs as TaskOutputDefinition[]; + // const outputNode = new SingleTask(); + // subNodes.forEach((subNode) => { + // outputs.forEach((output) => { + // edges.push(new DataFlow(subNode.config.id, output.id, node.config.id, output.id)); + // }); + // }); + } else { + nodes.push(node); + } + }); + return { nodes, edges }; +}; + +const runTaskToListr = async (runner: TaskGraphRunner) => { + const { nodes, edges } = flattenCompoundGraph(runner.dag); + const displayGraph = new TaskGraph(); + displayGraph.addTasks(nodes); + displayGraph.addDataFlows(edges); + const flatRunner = new TaskGraphRunner(displayGraph); + const tree = convertToTree(flatRunner); + const options = { + exitOnError: true, + concurrent: true, + rendererOptions: { timer: PRESET_TIMER }, + }; + const listrTasks = taskTreeToListr(tree, options); + const listr = new Listr(listrTasks, options); + + listr.run({}); + await sleep(250); + const result = await runner.runGraph(); + await sleep(250); + console.log("Result", result); +}; + +export async function runTask(dag: TaskGraph) { + const runner = new TaskGraphRunner(dag); + if (process.stdout.isTTY) { + await runTaskToListr(runner); + } else { + const result = await runner.runGraph(); + console.log(JSON.stringify(result, null, 2)); + } +} diff --git a/elmers.ts b/packages/cli/src/ellmers.ts similarity index 52% rename from elmers.ts rename to packages/cli/src/ellmers.ts index 87b9802a7..97140ae77 100755 --- a/elmers.ts +++ b/packages/cli/src/ellmers.ts @@ -2,12 +2,10 @@ import { program } from "commander"; import { argv } from "process"; -import { AddSecCommands } from "./src-examples/ExampleSEC"; -import { AddSampleCommand } from "./src-examples/TaskCLI"; +import { AddBaseCommands } from "./TaskCLI"; program.version("1.0.0").description("A CLI to run Ellmers."); -AddSecCommands(program); -AddSampleCommand(program); +AddBaseCommands(program); await program.parseAsync(argv); diff --git a/packages/cli/src/lib.ts b/packages/cli/src/lib.ts new file mode 100644 index 000000000..5461620c6 --- /dev/null +++ b/packages/cli/src/lib.ts @@ -0,0 +1,9 @@ +// ******************************************************************************* +// * ELLMERS: Embedding Large Language Model Experiential Retrieval Service * +// * * +// * Copyright Steven Roussey * +// * Licensed under the Apache License, Version 2.0 (the "License"); * +// ******************************************************************************* + +export * from "./TaskHelper"; +export * from "./TaskStreamToListr2"; diff --git a/packages/cli/tsconfig.json b/packages/cli/tsconfig.json new file mode 100644 index 000000000..21c004a3f --- /dev/null +++ b/packages/cli/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "../../tsconfig.json", + "include": ["src/**/*"], + "files": ["src/lib.ts", "src/ellmers.ts"], + "exclude": ["**/*.test.ts"], + "compilerOptions": { + "outDir": "dist", + "baseUrl": "./src", + "rootDir": "./src", + "paths": { + "#/*": ["./src/*"] + } + } +} diff --git a/packages/core/package.json b/packages/core/package.json new file mode 100644 index 000000000..0212665fa --- /dev/null +++ b/packages/core/package.json @@ -0,0 +1,26 @@ +{ + "name": "ellmers-core", + "type": "module", + "version": "0.0.1", + "description": "Ellmers is a tool for building and running DAG pipelines of AI tasks.", + "scripts": { + "watch": "bunx concurrently 'bun run watch-types' 'bun run watch-js'", + "watch-js": "bun build --watch --target=browser --sourcemap=external --external @sroussey/transformers --outdir ./dist ./src/lib.ts", + "watch-types": "tsc --watch", + "build": "bun run build-clean && bun run build-types && bun run build-js && bun run build-types-map", + "build-clean": "rm -fr dist/* tsconfig.tsbuildinfo", + "build-js": "bun build --target=browser --minify-whitespace --minify-syntax --sourcemap=external --external @sroussey/transformers --outdir ./dist ./src/lib.ts", + "build-types": "tsc", + "build-types-map": "tsc --declarationMap", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "module": "dist/lib.js", + "exports": { + ".": { + "import": "./dist/lib.js" + } + }, + "files": [ + "dist" + ] +} diff --git a/packages/core/src/lib.ts b/packages/core/src/lib.ts new file mode 100644 index 000000000..beac8f2e6 --- /dev/null +++ b/packages/core/src/lib.ts @@ -0,0 +1,24 @@ +// ******************************************************************************* +// * ELLMERS: Embedding Large Language Model Experiential Retrieval Service * +// * * +// * Copyright Steven Roussey * +// * Licensed under the Apache License, Version 2.0 (the "License"); * +// ******************************************************************************* + +export * from "./source/Document"; +export * from "./task/Task"; +export * from "./task/TaskRegistry"; +export * from "./task/BasicTasks"; +export * from "./task/ArrayTask"; +export * from "./task/TaskIOTypes"; +export * from "./task/ModelFactory"; +export * from "./task/ModelFactoryTasks"; +export * from "./task/TaskGraph"; +export * from "./task/TaskGraphRunner"; +export * from "./task/JsonTask"; +export * from "./task/exec/ml/HuggingFaceLocalTaskRun"; +export * from "./task/exec/ml/MediaPipeLocalTaskRun"; +export * from "./model/Model"; +export * from "./model/HuggingFaceModel"; +export * from "./storage/InMemoryStorage"; +export * from "./util/Misc"; diff --git a/src/Instruct.ts b/packages/core/src/model/HuggingFaceModel.ts similarity index 51% rename from src/Instruct.ts rename to packages/core/src/model/HuggingFaceModel.ts index c035c0681..fd1c325c7 100644 --- a/src/Instruct.ts +++ b/packages/core/src/model/HuggingFaceModel.ts @@ -5,26 +5,16 @@ // * Licensed under the Apache License, Version 2.0 (the "License"); * // ******************************************************************************* -import type { Model } from "./Model"; - -export class Instruct { - public queryInstruction: string = ""; - public storageInstruction: string = ""; - public model: Model | null = null; - public parameters: Record = {}; +import { Model, ModelProcessorEnum, ModelUseCaseEnum } from "./Model"; +export class ONNXTransformerJsModel extends Model { constructor( - public name: string, - public description: string, - options?: Partial< - Pick< - Instruct, - "queryInstruction" | "storageInstruction" | "model" | "parameters" - > - > + name: string, + useCase: ModelUseCaseEnum[], + public pipeline: string, + options?: Partial> ) { - Object.assign(this, options); + super(name, useCase, options); } + readonly type = ModelProcessorEnum.LOCAL_ONNX_TRANSFORMERJS; } - -export type InstructList = Instruct[]; diff --git a/packages/core/src/model/MediaPipeModel.ts b/packages/core/src/model/MediaPipeModel.ts new file mode 100644 index 000000000..b8ca7b368 --- /dev/null +++ b/packages/core/src/model/MediaPipeModel.ts @@ -0,0 +1,15 @@ +import { Model, ModelProcessorEnum, ModelUseCaseEnum } from "./Model"; + +export class MediaPipeTfJsModel extends Model { + constructor( + name: string, + useCase: ModelUseCaseEnum[], + public url: string, + options?: Partial< + Pick + > + ) { + super(name, useCase, options); + } + readonly type = ModelProcessorEnum.MEDIA_PIPE_TFJS_MODEL; +} diff --git a/src/Model.ts b/packages/core/src/model/Model.ts similarity index 100% rename from src/Model.ts rename to packages/core/src/model/Model.ts diff --git a/src/query/InMemoryQuery.ts b/packages/core/src/query/InMemoryQuery.ts similarity index 97% rename from src/query/InMemoryQuery.ts rename to packages/core/src/query/InMemoryQuery.ts index a13b15cbd..07983d984 100644 --- a/src/query/InMemoryQuery.ts +++ b/packages/core/src/query/InMemoryQuery.ts @@ -5,7 +5,7 @@ // * Licensed under the Apache License, Version 2.0 (the "License"); * // ******************************************************************************* -import { NodeEmbedding, QueryText, TextNode } from "#/Document"; +import { NodeEmbedding, QueryText, TextNode } from "../source/Document"; // import { inner, cosine } from "simsimd"; export function inner(arr1: number[], arr2: number[]) { diff --git a/src/Document.ts b/packages/core/src/source/Document.ts similarity index 100% rename from src/Document.ts rename to packages/core/src/source/Document.ts diff --git a/src/storage/InMemoryStorage.ts b/packages/core/src/storage/InMemoryStorage.ts similarity index 58% rename from src/storage/InMemoryStorage.ts rename to packages/core/src/storage/InMemoryStorage.ts index 148e64b95..884028af3 100644 --- a/src/storage/InMemoryStorage.ts +++ b/packages/core/src/storage/InMemoryStorage.ts @@ -5,11 +5,9 @@ // * Licensed under the Apache License, Version 2.0 (the "License"); * // ******************************************************************************* -import { Instruct, InstructList } from "#/Instruct"; -import { Model, ModelUseCaseEnum } from "#/Model"; -import { StrategyList } from "#/Strategy"; -import { ONNXTransformerJsModel } from "#/tasks/HuggingFaceLocalTasks"; -import { MediaPipeTfJsModel } from "#/tasks/MediaPipeLocalTasks"; +import { ONNXTransformerJsModel } from "../model/HuggingFaceModel"; +import { MediaPipeTfJsModel } from "../model/MediaPipeModel"; +import { Model, ModelUseCaseEnum } from "../model/Model"; export const universal_sentence_encoder = new MediaPipeTfJsModel( "Universal Sentence Encoder", @@ -72,13 +70,12 @@ export const xenovaDistilbertMnli = new ONNXTransformerJsModel( "zero-shot-classification" ); -export const stentancetransformerMultiQaMpnetBaseDotV1 = - new ONNXTransformerJsModel( - "Xenova/multi-qa-mpnet-base-dot-v1", - [ModelUseCaseEnum.TEXT_EMBEDDING], - "feature-extraction", - { dimensions: 768 } - ); +export const stentancetransformerMultiQaMpnetBaseDotV1 = new ONNXTransformerJsModel( + "Xenova/multi-qa-mpnet-base-dot-v1", + [ModelUseCaseEnum.TEXT_EMBEDDING], + "feature-extraction", + { dimensions: 768 } +); export const gpt2 = new ONNXTransformerJsModel( "Xenova/gpt2", @@ -110,70 +107,8 @@ export const distilbartCnn = new ONNXTransformerJsModel( "summarization" ); -export const instructPlain = new Instruct( - "Plain", - "The plain version does nothing extra to queries or storage" -); - -export const instructHighTemp = new Instruct( - "HighTemp", - "This is similar to plain but with a higher temperature and four versions averaged together", - { parameters: { temperature: 2.5, versions: 4 } } // no model, so inert for now -); - -export const instructQuestion = new Instruct( - "EverythingIsAQuestion", - "This converts storage into questions", - { storageInstruction: "Rephrase the following as a question: ", model: gpt2 } -); - -export const instructSummarize = new Instruct( - "Summarize", - "This converts storage into summaries", - { model: distilbartCnn } -); - -export const instructRepresent = new Instruct( - "Represent", - "This tries to coax the model into representing the query or passage", - { - queryInstruction: "Represent this query for searching relevant passages: ", - storageInstruction: "Represent this passage for later retrieval: ", - } // no model, so inert -); - -export const instructKeywords = new Instruct( - "Keywords", - "Try and pull keywords and concepts from both query and storage", - { - queryInstruction: - "What are the most important keywords and concepts that represent the following: ", - storageInstruction: - "What are the most important keywords and concepts that represent the following: ", - model: xenovaDistilbertMnli, // doesn't work - } -); - -export const instructList: InstructList = [ - instructPlain, - // instructHighTemp, - // instructQuestion, - // instructRepresent, - instructSummarize, -]; - -export const strategyAllPairs: StrategyList = []; - -for (const feModel of findModelByUseCase(ModelUseCaseEnum.TEXT_EMBEDDING)) { - for (const instruct of instructList) { - strategyAllPairs.push({ - embeddingModel: feModel, - instruct, - }); - } -} - export function findModelByName(name: string) { + if (typeof name != "string") return undefined; return Model.all.find((m) => m.name.toLowerCase() == name.toLowerCase()); } diff --git a/packages/core/src/task/ArrayTask.ts b/packages/core/src/task/ArrayTask.ts new file mode 100644 index 000000000..fb542dbcd --- /dev/null +++ b/packages/core/src/task/ArrayTask.ts @@ -0,0 +1,76 @@ +// ******************************************************************************* +// * ELLMERS: Embedding Large Language Model Experiential Retrieval Service * +// * * +// * Copyright Steven Roussey * +// * Licensed under the Apache License, Version 2.0 (the "License"); * +// ******************************************************************************* + +import { ModelFactory } from "./ModelFactory"; +import { CompoundTask, TaskInput, TaskConfig, TaskOutput, TaskTypeName } from "./Task"; +import { TaskInputDefinition, TaskOutputDefinition } from "./TaskIOTypes"; +import { TaskRegistry } from "./TaskRegistry"; + +export type ConvertToArrays = { + [P in keyof T]: P extends K ? Array : T[P]; +}; + +type Writeable = { -readonly [P in keyof T]: T[P] }; + +function convertToArray(io: D[], id: string) { + const results: D[] = []; + for (const item of io) { + const newItem: Writeable = { ...item }; + if (newItem.id === id) { + newItem.isArray = true; + } + results.push(newItem); + } + return results as D[]; +} + +export function arrayTaskFactory< + PluralInputType extends TaskInput, + PluralOutputType extends TaskOutput, +>(taskClass: typeof ModelFactory, inputMakeArray: string, outputMakeArray: string, name?: string) { + const inputs = convertToArray(Array.from(taskClass.inputs), inputMakeArray); + const outputs = convertToArray( + Array.from(taskClass.outputs), + outputMakeArray + ); + + const nameWithoutTask = taskClass.type.slice(0, -4); + const capitalized = inputMakeArray.charAt(0).toUpperCase() + inputMakeArray.slice(1); + name ??= nameWithoutTask + "Multi" + capitalized + "Task"; + + class ArrayTask extends CompoundTask { + static readonly displayName = name!; // this is for debuggers as they can't infer the name from code + static readonly type: TaskTypeName = name!; + static readonly category = (taskClass.constructor as any).category; + declare runInputData: PluralInputType; + declare runOutputData: PluralOutputType; + declare defaults: Partial; + + itemClass = taskClass; + + static inputs = inputs; + static outputs = outputs; + constructor(config: TaskConfig & { input?: PluralInputType } = {}) { + super(config); + this.generateGraph(); + } + generateGraph() { + if (Array.isArray(this.runInputData[inputMakeArray])) { + this.runInputData[inputMakeArray].forEach((prop: any) => { + const input = { ...this.runInputData, [inputMakeArray]: prop }; + const current = new taskClass({ input }); + this.subGraph.addTask(current); + }); + } + } + } + TaskRegistry.registerTask(ArrayTask); + + return ArrayTask; +} + +export type ArrayTaskType = ReturnType; diff --git a/packages/core/src/task/BasicTasks.ts b/packages/core/src/task/BasicTasks.ts new file mode 100644 index 000000000..ffc5fa276 --- /dev/null +++ b/packages/core/src/task/BasicTasks.ts @@ -0,0 +1,98 @@ +// ******************************************************************************* +// * ELLMERS: Embedding Large Language Model Experiential Retrieval Service * +// * * +// * Copyright Steven Roussey * +// * Licensed under the Apache License, Version 2.0 (the "License"); * +// ******************************************************************************* + +import { TaskConfig } from "./Task"; +import { SingleTask, TaskInput, TaskOutput } from "./Task"; +import { CreateMappedType } from "./TaskIOTypes"; +import { TaskRegistry } from "./TaskRegistry"; + +export interface RenameTaskInput { + output_remap_array: { + from: string; + to: string; + }[]; +} + +// =============================================================================== + +export type LambdaTaskInput = + | CreateMappedType + | { run: (input: TaskInput) => Promise }; +export type LambdaTaskOutput = CreateMappedType; + +export class LambdaTask extends SingleTask { + static readonly type = "LambdaTask"; + static readonly category = "Utility"; + declare runOutputData: TaskOutput; + public static inputs = [ + { + id: "run", + name: "Run Function", + valueType: "text", + }, + ] as const; + public static outputs = [ + { + id: "output", + name: "Output", + valueType: "any", + }, + ] as const; + constructor(config: TaskConfig & LambdaTaskInput) { + super(config); + } + runSyncOnly() { + if (!this.runInputData.run) { + throw new Error("No runner provided"); + } + if (typeof this.runInputData.run === "string") { + const fnText = this.runInputData.run; + const fn = new Function(fnText); + try { + fn(); + this.runInputData.run = fn; + } catch (e) {} + } + if (typeof this.runInputData.run === "function") { + this.runOutputData.output = this.runInputData.run(this.runInputData); + console.log("lambda output", this.runOutputData); + } else { + console.error("error", "Runner is not a function"); + } + return this.runOutputData; + } +} +TaskRegistry.registerTask(LambdaTask); + +export type DebugLogTaskInput = CreateMappedType; +export type DebugLogTaskOutput = CreateMappedType; + +export class DebugLogTask extends SingleTask { + static readonly type: string = "DebugLogTask"; + static readonly category = "Utility"; + declare runInputData: DebugLogTaskInput; + declare runOutputData: DebugLogTaskOutput; + public static inputs = [ + { + id: "message", + name: "Input", + valueType: "any", + }, + { + id: "level", + name: "Level", + valueType: "log_level", + defaultValue: "info", + }, + ] as const; + public static outputs = [] as const; + runSyncOnly() { + console[this.runInputData.level || "log"](this.runInputData.message); + return this.runOutputData; + } +} +TaskRegistry.registerTask(DebugLogTask); diff --git a/packages/core/src/task/JsonTask.ts b/packages/core/src/task/JsonTask.ts new file mode 100644 index 000000000..f3a9f873e --- /dev/null +++ b/packages/core/src/task/JsonTask.ts @@ -0,0 +1,95 @@ +// // ******************************************************************************* +// // * ELLMERS: Embedding Large Language Model Experiential Retrieval Service * +// // * * +// // * Copyright Steven Roussey * +// // * Licensed under the Apache License, Version 2.0 (the "License"); * +// // ******************************************************************************* + +import { CompoundTask, Task, TaskConfig, TaskInput } from "./Task"; +import { DataFlow, TaskGraph } from "./TaskGraph"; +import { CreateMappedType } from "./TaskIOTypes"; +import { TaskRegistry } from "./TaskRegistry"; + +export type JsonTaskArray = Array; +export type JsonTaskItem = { + id: string; + type: string; + name?: string; + input?: TaskInput; + dependencies?: { + [x: string]: JsonTaskDependecy; + }; +}; +type JsonTaskDependecy = { + id: string; + output: string; +}; + +type JsonTaskInput = CreateMappedType; +type JsonTaskOutput = CreateMappedType; + +export class JsonTask extends CompoundTask { + public static inputs = [ + { + id: "json", + name: "JSON", + valueType: "text", + }, + ] as const; + public static outputs = [] as const; + + declare runInputData: JsonTaskInput; + declare runOutputData: JsonTaskOutput; + declare defaults: Partial; + constructor(config: TaskConfig & { input?: JsonTaskInput }) { + super(config); + if (config?.input?.json) { + this.generateGraph(); + } + } + public setInputData(...overrides: (Partial | undefined)[]): void { + let changed = false; + for (const override of overrides) { + if (override) { + if (override.json !== undefined && override.json != this.runInputData.json) changed = true; + } + } + super.setInputData(...overrides); + if (changed) this.generateGraph(); + } + public generateGraph() { + if (!this.runInputData.json) return; + let data = JSON.parse(this.runInputData.json); + if (!Array.isArray(data)) data = [data]; + const jsonItems: JsonTaskArray = data as JsonTaskArray; + // create the task nodes + this.subGraph = new TaskGraph(); + for (const item of jsonItems) { + if (!item.id) throw new Error("Task id required"); + if (!item.type) throw new Error("Task type required"); + if (item.input && Array.isArray(item.input)) throw new Error("Task input must be an object"); + + const taskClass = TaskRegistry.all.get(item.type); + if (!taskClass) throw new Error(`Task type ${item.type} not found`); + + const taskConfig = { id: item.id, name: item.name, input: item.input ?? {} }; + const task = new taskClass(taskConfig); + this.subGraph.addTask(task); + } + // create the data flow edges + for (const item of jsonItems) { + if (!item.dependencies) continue; + for (const [input, dependency] of Object.entries(item.dependencies)) { + const sourceTask = this.subGraph.getTask(dependency.id); + if (!sourceTask) { + throw new Error(`Dependency id ${dependency.id} not found`); + } + const df = new DataFlow(sourceTask.config.id, dependency.output, item.id, input); + this.subGraph.addDataFlow(df); + } + } + } + + static readonly type = "JsonTask"; + static readonly category = "Utility"; +} diff --git a/packages/core/src/task/ModelFactory.ts b/packages/core/src/task/ModelFactory.ts new file mode 100644 index 000000000..345a04354 --- /dev/null +++ b/packages/core/src/task/ModelFactory.ts @@ -0,0 +1,78 @@ +// ******************************************************************************* +// * ELLMERS: Embedding Large Language Model Experiential Retrieval Service * +// * * +// * Copyright Steven Roussey * +// * Licensed under the Apache License, Version 2.0 (the "License"); * +// ******************************************************************************* + +/** + * @file ModelFactory.ts + * @description This file contains the implementation of the ModelFactory class and its derived classes. + * The ModelFactory class is responsible for creating and running tasks based on different models. + * It provides a common interface for running tasks and handles the execution logic. + * Each derived class defines its own input and output types and implements the run() method to perform the task-specific logic. + */ + +import { findModelByName } from "../storage/InMemoryStorage"; +import { ModelProcessorEnum } from "../model/Model"; +import { SingleTask, TaskInput, TaskConfig, TaskOutput } from "./Task"; +import { TaskInputDefinition, TaskOutputDefinition } from "./TaskIOTypes"; +import { TaskRegistry } from "./TaskRegistry"; + +export class ModelFactory extends SingleTask { + public static inputs: readonly TaskInputDefinition[]; + public static outputs: readonly TaskOutputDefinition[]; + static readonly type: string = "ModelFactory"; + declare runOutputData: TaskOutput; + + static runFnRegistry: Record< + string, + Record Promise> + > = {}; + static registerRunFn( + baseClass: typeof ModelFactory, + modelType: ModelProcessorEnum, + runFn: (task: any, runInputData: any) => Promise + ) { + if (!ModelFactory.runFnRegistry[baseClass.type]) + ModelFactory.runFnRegistry[baseClass.type] = {}; + ModelFactory.runFnRegistry[baseClass.type][modelType] = runFn; + } + static getRunFn(taskClassName: string, modelType: ModelProcessorEnum) { + return ModelFactory.runFnRegistry[taskClassName]?.[modelType]; + } + + constructor(config: TaskConfig = {}) { + config.name ||= `${new.target.name}${config.input?.model ? " with model " + config.input?.model : ""}`; + super(config); + } + + async run(): Promise { + this.emit("start"); + this.runOutputData = {}; + let results; + debugger; + try { + const taskClass = TaskRegistry.all.get(this.constructor.name); + if (!taskClass) throw new Error("ModelFactoryTask: No task class found"); + const modelname = this.runInputData["model"]; + if (!modelname) throw new Error("ModelFactoryTask: No model name found"); + const model = findModelByName(modelname); + if (!model) throw new Error("ModelFactoryTask: No model found"); + const runFn = ModelFactory.getRunFn(this.constructor.name, model.type); + if (!runFn) throw new Error("ModelFactoryTask: No run function found"); + results = await runFn(this, this.runInputData); + } catch (err) { + this.emit("error", err); + console.error(err); + return {}; + } + this.emit("complete"); + this.runOutputData = results ?? {}; + this.runOutputData = this.runSyncOnly(); + return this.runOutputData; + } + runSyncOnly(): TaskOutput { + return this.runOutputData; + } +} diff --git a/packages/core/src/task/ModelFactoryTasks.ts b/packages/core/src/task/ModelFactoryTasks.ts new file mode 100644 index 000000000..095cb8d20 --- /dev/null +++ b/packages/core/src/task/ModelFactoryTasks.ts @@ -0,0 +1,240 @@ +// ******************************************************************************* +// * ELLMERS: Embedding Large Language Model Experiential Retrieval Service * +// * * +// * Copyright Steven Roussey * +// * Licensed under the Apache License, Version 2.0 (the "License"); * +// ******************************************************************************* + +import { ConvertToArrays, arrayTaskFactory } from "./ArrayTask"; +import { CreateMappedType } from "./TaskIOTypes"; +import { TaskRegistry } from "./TaskRegistry"; +import { ModelFactory } from "./ModelFactory"; +import { TaskConfig } from "./Task"; + +// =============================================================================== + +export type DownloadTaskInput = CreateMappedType; +export type DownloadTaskOutput = CreateMappedType; + +export class DownloadTask extends ModelFactory { + public static inputs = [ + { + id: "model", + name: "Model", + valueType: "model", + }, + ] as const; + public static outputs = [ + { + id: "model", + name: "Model", + valueType: "model", + }, + ] as const; + + declare runInputData: DownloadTaskInput; + declare runOutputData: DownloadTaskOutput; + declare defaults: Partial; + constructor(config: TaskConfig & { input?: DownloadTaskInput }) { + super(config); + } + static readonly type = "DownloadTask"; + static readonly category = "Text Model"; +} +TaskRegistry.registerTask(DownloadTask); + +export const DownloadMultiModelTask = arrayTaskFactory< + ConvertToArrays, + ConvertToArrays +>(DownloadTask, "model", "text"); + +// =============================================================================== + +export type EmbeddingTaskInput = CreateMappedType; +export type EmbeddingTaskOutput = CreateMappedType; + +/** + * This is a task that generates an embedding for a single piece of text + */ +export class EmbeddingTask extends ModelFactory { + public static inputs = [ + { + id: "text", + name: "Text", + valueType: "text", + }, + { + id: "model", + name: "Model", + valueType: "text_embedding_model", + }, + ] as const; + public static outputs = [{ id: "vector", name: "Embedding", valueType: "vector" }] as const; + + declare runInputData: EmbeddingTaskInput; + declare runOutputData: EmbeddingTaskOutput; + declare defaults: Partial; + static readonly type = "EmbeddingTask"; + static readonly category = "Text Model"; +} +TaskRegistry.registerTask(EmbeddingTask); + +export const EmbeddingMultiModelTask = arrayTaskFactory< + ConvertToArrays, + ConvertToArrays +>(EmbeddingTask, "model", "text"); + +// =============================================================================== + +export type TextGenerationTaskInput = CreateMappedType; +export type TextGenerationTaskOutput = CreateMappedType; + +/** + * This generates text from a prompt + */ +export class TextGenerationTask extends ModelFactory { + public static inputs = [ + { + id: "prompt", + name: "Prompt", + valueType: "text", + }, + { + id: "model", + name: "Model", + valueType: "text_generation_model", + }, + ] as const; + public static outputs = [{ id: "text", name: "Text", valueType: "text" }] as const; + + declare runInputData: TextGenerationTaskInput; + declare runOutputData: TextGenerationTaskOutput; + declare defaults: Partial; + static readonly type = "TextGenerationTask"; + static readonly category = "Text Model"; +} +TaskRegistry.registerTask(TextGenerationTask); + +export const TextGenerationMultiModelTask = arrayTaskFactory< + ConvertToArrays, + ConvertToArrays +>(TextGenerationTask, "model", "text"); + +// =============================================================================== + +export type SummarizeTaskInput = CreateMappedType; +export type SummarizeTaskOutput = CreateMappedType; + +/** + * This summarizes a piece of text + */ + +export class SummarizeTask extends ModelFactory { + public static inputs = [ + { + id: "text", + name: "Text", + valueType: "text", + }, + { + id: "model", + name: "Model", + valueType: "text_summarization_model", + }, + ] as const; + public static outputs = [{ id: "text", name: "Text", valueType: "text" }] as const; + + declare runInputData: SummarizeTaskInput; + declare runOutputData: SummarizeTaskOutput; + declare defaults: Partial; + static readonly type = "SummarizeTask"; + static readonly category = "Text Model"; +} +TaskRegistry.registerTask(SummarizeTask); + +export const SummarizeMultiModelTask = arrayTaskFactory< + ConvertToArrays, + ConvertToArrays +>(SummarizeTask, "model", "text"); + +// =============================================================================== + +export type TextRewriterTaskInput = CreateMappedType; +export type TextRewriterTaskOutput = CreateMappedType; + +/** + * This is a special case of text generation that takes a prompt and text to rewrite + */ + +export class TextRewriterTask extends ModelFactory { + public static inputs = [ + { + id: "text", + name: "Text", + valueType: "text", + }, + { + id: "prompt", + name: "Prompt", + valueType: "text", + }, + { + id: "model", + name: "Model", + valueType: "text_generation_model", + }, + ] as const; + public static outputs = [{ id: "text", name: "Text", valueType: "text" }] as const; + + declare runInputData: TextRewriterTaskInput; + declare runOutputData: TextRewriterTaskOutput; + declare defaults: Partial; + static readonly type = "TextRewriterTask"; + static readonly category = "Text Model"; +} +TaskRegistry.registerTask(TextRewriterTask); + +export const TextRewriterMultiModelTask = arrayTaskFactory< + ConvertToArrays, + ConvertToArrays +>(TextRewriterTask, "model", "text"); + +// =============================================================================== +export type QuestionAnswerTaskInput = CreateMappedType; +export type QuestionAnswerTaskOutput = CreateMappedType; + +/** + * This is a special case of text generation that takes a context and a question + */ +export class QuestionAnswerTask extends ModelFactory { + public static inputs = [ + { + id: "context", + name: "Context", + valueType: "text", + }, + { + id: "question", + name: "Question", + valueType: "text", + }, + { + id: "model", + name: "Model", + valueType: "text_question_answering_model", + }, + ] as const; + public static outputs = [{ id: "answer", name: "Answer", valueType: "text" }] as const; + + declare runInputData: QuestionAnswerTaskInput; + declare runOutputData: QuestionAnswerTaskOutput; + declare defaults: Partial; + static readonly type = "QuestionAnswerTask"; + static readonly category = "Text Model"; +} +TaskRegistry.registerTask(QuestionAnswerTask); + +export const QuestionAnswerMultiModelTask = arrayTaskFactory< + ConvertToArrays, + ConvertToArrays +>(TextRewriterTask, "model", "answer"); diff --git a/packages/core/src/task/Task.ts b/packages/core/src/task/Task.ts new file mode 100644 index 000000000..6b2ec4cbc --- /dev/null +++ b/packages/core/src/task/Task.ts @@ -0,0 +1,196 @@ +// ******************************************************************************* +// * ELLMERS: Embedding Large Language Model Experiential Retrieval Service * +// * * +// * Copyright Steven Roussey * +// * Licensed under the Apache License, Version 2.0 (the "License"); * +// ******************************************************************************* + +import { EventEmitter } from "eventemitter3"; +import { TaskGraph } from "./TaskGraph"; +import { TaskGraphRunner } from "./TaskGraphRunner"; +import type { TaskInputDefinition, TaskOutputDefinition } from "./TaskIOTypes"; + +export enum TaskStatus { + PENDING = "NEW", + PROCESSING = "PROCESSING", + COMPLETED = "COMPLETED", + FAILED = "FAILED", +} + +/** + * TaskEvents + * + * There is no job queue at the moement. + */ +export type TaskEvents = "start" | "complete" | "error" | "progress"; + +export interface TaskInput { + [key: string]: any; +} +export interface TaskOutput { + [key: string]: any; +} + +export interface ITaskSimple { + readonly isCompound: false; +} +export interface ITaskCompound { + readonly isCompound: true; + subGraph: TaskGraph; +} + +export type ITask = ITaskSimple | ITaskCompound; + +export type TaskTypeName = string; + +export type TaskConfig = Partial & { input?: TaskInput }; + +// =============================================================================== + +export interface IConfig { + id: string; + name?: string; +} + +abstract class TaskBase { + // information about the task that should be overriden by the subclasses + static readonly type: TaskTypeName = "TaskBase"; + static readonly category: string = "Hidden"; + + events = new EventEmitter(); + on(name: TaskEvents, fn: (...args: any[]) => void) { + this.events.on.call(this.events, name, fn); + } + off(name: TaskEvents, fn: (...args: any[]) => void) { + this.events.off.call(this.events, name, fn); + } + emit(name: TaskEvents, ...args: any[]) { + this.events.emit.call(this.events, name, ...args); + } + /** + * Does this task have subtasks? + */ + abstract readonly isCompound: boolean; + /** + * Configuration for the task, might include things like name and id for the database + */ + config: IConfig; + status: TaskStatus = TaskStatus.PENDING; + progress: number = 0; + createdAt: Date = new Date(); + startedAt?: Date; + completedAt?: Date; + error: string | undefined = undefined; + + constructor(config: TaskConfig = {}) { + // pull out input data from the config + const { input = {}, ...rest } = config; + this.defaults = input; + this.setInputData(); + + // setup the configuration + const name = (this.constructor as any).type ?? this.constructor.name; + this.config = Object.assign( + { + id: name + ":" + Math.random().toString(36).substring(2, 9), + name: name, + }, + rest + ); + // setup the events + this.setupEvents(); + } + + public setupEvents() { + Object.defineProperty(this, "events", { enumerable: false }); // in case it is serialized + this.on("start", () => { + this.startedAt = new Date(); + this.progress = 0; + this.status = TaskStatus.PROCESSING; + }); + this.on("complete", () => { + this.completedAt = new Date(); + this.progress = 100; + this.status = TaskStatus.COMPLETED; + }); + this.on("error", (error) => { + this.completedAt = new Date(); + this.progress = 100; + this.status = TaskStatus.FAILED; + this.error = error; + }); + } + /** + * The defaults for the task. If no overrides at run time, then this would be equal to the + * input + */ + defaults: TaskInput; + /** + * The input to the task at the time of the task run. This takes defaults from construction + * time and overrides from run time. It is the input that created the output. + */ + runInputData: TaskInput = {}; + /** + * The output of the task at the time of the task run. This is the result of the task. + * The the defaults and overrides are combined to match the required input of the task. + */ + runOutputData: TaskOutput = {}; + public static inputs: readonly TaskInputDefinition[]; + public static outputs: readonly TaskOutputDefinition[]; + + /** + * + * This calculates the input to the task at the time of the task run. This takes defaults from + * construction and applies run time overrides (which may be output from a previous run if this + * is a serial task or strategy). Caller needs to decide if should set to this classes input + * or not. + */ + setInputData(...overrides: (Partial | undefined)[]) { + this.runInputData = Object.assign({}, this.defaults, ...overrides) as T; + } + runWithInput(input: T) { + this.setInputData(input); + return this.run(); + } + async run(): Promise { + return this.runSyncOnly(); + } + runSyncOnly(): TaskOutput { + return this.runOutputData; + } +} + +export type TaskIdType = TaskBase["config"]["id"]; + +export class SingleTask extends TaskBase implements ITaskSimple { + static readonly type: TaskTypeName = "SingleTask"; + readonly isCompound = false; +} + +export class CompoundTask extends TaskBase implements ITaskCompound { + static readonly type: TaskTypeName = "CompoundTask"; + readonly isCompound = true; + _subGraph: TaskGraph | null = null; + set subGraph(subGraph: TaskGraph) { + this._subGraph = subGraph; + } + get subGraph() { + if (!this._subGraph) { + this._subGraph = new TaskGraph(); + } + return this._subGraph; + } + async run(): Promise { + this.emit("start"); + const runner = new TaskGraphRunner(this.subGraph); + this.runOutputData = await runner.runGraph(); + this.runOutputData = this.runSyncOnly(); + this.emit("complete"); + return this.runOutputData; + } +} + +// =============================================================================== + +export type Task = SingleTask | CompoundTask; +export type TaskStream = Task[]; diff --git a/packages/core/src/task/TaskGraph.ts b/packages/core/src/task/TaskGraph.ts new file mode 100644 index 000000000..e0eb72700 --- /dev/null +++ b/packages/core/src/task/TaskGraph.ts @@ -0,0 +1,117 @@ +// ******************************************************************************* +// * ELLMERS: Embedding Large Language Model Experiential Retrieval Service * +// * * +// * Copyright Steven Roussey * +// * Licensed under the Apache License, Version 2.0 (the "License"); * +// ******************************************************************************* + +import { DirectedAcyclicGraph } from "@sroussey/typescript-graph"; +import { TaskIdType } from "./Task"; +import { Task, TaskStream } from "./Task"; + +export type IDataFlow = { + sourceTaskId: TaskIdType; + sourceTaskOutputId: string; + targetTaskId: TaskIdType; + targetTaskInputId: string; + id: string; +}; + +export type DataFlowIdType = IDataFlow["id"]; + +export class DataFlow implements IDataFlow { + constructor( + public sourceTaskId: TaskIdType, + public sourceTaskOutputId: string, + public targetTaskId: TaskIdType, + public targetTaskInputId: string, + public id: string = `${sourceTaskId}.${sourceTaskOutputId} -> ${targetTaskId}.${targetTaskInputId}` + ) {} +} + +type NodeNodeEdgeTuples = Array< + [sourceTask: TaskIdType, targetTask: TaskIdType, edge?: IDataFlow | undefined] +>; + +export class TaskGraph extends DirectedAcyclicGraph { + constructor() { + super( + (task: Task) => task.config.id, + (dataFlow: IDataFlow) => dataFlow.id + ); + } + public getTask(id: TaskIdType): Task | undefined { + return super.getNode(id); + } + public addTask(task: Task) { + return super.addNode(task); + } + public addTasks(tasks: Task[]) { + return super.addNodes(tasks); + } + public addDataFlow(dataflow: DataFlow) { + return super.addEdge(dataflow.sourceTaskId, dataflow.targetTaskId, dataflow); + } + public addDataFlows(dataflows: DataFlow[]) { + const addedEdges = dataflows.map<[s: string, t: string, e: IDataFlow]>((edge) => { + return [edge.sourceTaskId, edge.targetTaskId, edge]; + }); + return super.addEdges(addedEdges); + } + public getDataFlow(id: DataFlowIdType): IDataFlow | undefined { + for (const i in this.adjacency) { + for (const j in this.adjacency[i]) { + const maybeEdges = this.adjacency[i][j]; + if (maybeEdges !== null) { + for (const edge of maybeEdges) { + if (this.edgeIdentity(edge, "", "") == id) { + return edge; + } + } + } + } + } + } + public getDataFlows(): IDataFlow[] { + return this.getEdges().map((edge) => edge[2]); + } +} + +/** + * Super simple helper if you know the input and output handles, and there is only one each + * + * @param tasks TaskStream + * @param inputHandle TaskIdType + * @param outputHandle TaskIdType + * @returns + */ +function serialGraphEdges( + tasks: TaskStream, + inputHandle: string, + outputHandle: string +): IDataFlow[] { + const edges: IDataFlow[] = []; + for (let i = 0; i < tasks.length - 1; i++) { + edges.push(new DataFlow(tasks[i].config.id, inputHandle, tasks[i + 1].config.id, outputHandle)); + } + return edges; +} + +/** + * Super simple helper if you know the input and output handles, and there is only one each + * + * @param tasks TaskStream + * @param inputHandle TaskIdType + * @param outputHandle TaskIdType + * @returns + */ +export function serialGraph( + tasks: TaskStream, + inputHandle: string, + outputHandle: string +): TaskGraph { + const graph = new TaskGraph(); + graph.addTasks(tasks); + graph.addDataFlows(serialGraphEdges(tasks, inputHandle, outputHandle)); + return graph; +} diff --git a/packages/core/src/task/TaskGraphRunner.ts b/packages/core/src/task/TaskGraphRunner.ts new file mode 100644 index 000000000..4457d0e63 --- /dev/null +++ b/packages/core/src/task/TaskGraphRunner.ts @@ -0,0 +1,97 @@ +// ******************************************************************************* +// * ELLMERS: Embedding Large Language Model Experiential Retrieval Service * +// * * +// * Copyright Steven Roussey * +// * Licensed under the Apache License, Version 2.0 (the "License"); * +// ******************************************************************************* + +import { TaskInput, Task, TaskOutput } from "task/Task"; +import { TaskGraph } from "task/TaskGraph"; + +export class TaskGraphRunner { + public layers: Map; + + constructor(public dag: TaskGraph) { + this.dag = dag; + this.layers = new Map(); + } + + public assignLayers(sortedNodes: Task[]) { + this.layers = new Map(); + const nodeToLayer = new Map(); + + sortedNodes.forEach((node, _index) => { + let maxLayer = -1; + + // Get all incoming edges (dependencies) of the node + const incomingEdges = this.dag.inEdges(node.config.id).map(([from]) => from); + + incomingEdges.forEach((from) => { + // Find the layer of the dependency + const layer = nodeToLayer.get(from); + if (layer !== undefined) { + maxLayer = Math.max(maxLayer, layer); + } + }); + + // Assign the node to the next layer after the maximum layer of its dependencies + const assignedLayer = maxLayer + 1; + nodeToLayer.set(node.config.id, assignedLayer); + + if (!this.layers.has(assignedLayer)) { + this.layers.set(assignedLayer, []); + } + + this.layers.get(assignedLayer)?.push(node); + }); + } + + public async runTasksAsync() { + let results: TaskOutput[] = []; + for (const [_layerNumber, nodes] of this.layers.entries()) { + const layerPromises = nodes.map(async (node) => { + const results = await node.run(); + this.dag.outEdges(node.config.id).forEach(([, , dataFlow]) => { + const toInput: TaskInput = {}; + const targetNode = this.dag.getNode(dataFlow.targetTaskId); + if (results[dataFlow.sourceTaskOutputId] !== undefined) + toInput[dataFlow.targetTaskInputId] = results[dataFlow.sourceTaskOutputId]; + targetNode!.setInputData(targetNode!.runInputData, toInput); + }); + return results; + }); + results = await Promise.all(layerPromises); + } + return results; + } + + public runTasksSync() { + let results: TaskOutput[] = []; + for (const [_layerNumber, nodes] of this.layers.entries()) { + results = nodes.map((node) => { + const results = node.runSyncOnly(); + this.dag.outEdges(node.config.id).forEach(([, , dataFlow]) => { + const toInput: TaskInput = {}; + const targetNode = this.dag.getNode(dataFlow.targetTaskId); + if (results[dataFlow.sourceTaskOutputId] !== undefined) + toInput[dataFlow.targetTaskInputId] = results[dataFlow.sourceTaskOutputId]; + targetNode!.setInputData(targetNode!.runInputData, toInput); + }); + return results; + }); + } + return results; + } + + public async runGraph() { + const sortedNodes = this.dag.topologicallySortedNodes(); + this.assignLayers(sortedNodes); + return await this.runTasksAsync(); + } + + public runGraphSyncOnly() { + const sortedNodes = this.dag.topologicallySortedNodes(); + this.assignLayers(sortedNodes); + return this.runTasksSync(); + } +} diff --git a/packages/core/src/task/TaskIOTypes.ts b/packages/core/src/task/TaskIOTypes.ts new file mode 100644 index 000000000..e04654a4a --- /dev/null +++ b/packages/core/src/task/TaskIOTypes.ts @@ -0,0 +1,101 @@ +// ******************************************************************************* +// * ELLMERS: Embedding Large Language Model Experiential Retrieval Service * +// * * +// * Copyright Steven Roussey * +// * Licensed under the Apache License, Version 2.0 (the "License"); * +// ******************************************************************************* + +export type Vector = number[] | Float32Array; + +export const valueTypes = { + any: { + name: "Any", + tsType: "any", + }, + text: { + name: "Text", + tsType: "string", + defaultValue: "", + }, + number: { + name: "Number", + tsType: "number", + defaultValue: 0, + }, + vector: { + name: "Vector", + tsType: "Vector", + defaultValue: [], + }, + model: { + name: "Model", + tsType: "string", + }, + text_embedding_model: { + name: "Embedding Model", + tsType: "string", + }, + text_generation_model: { + name: "Generation Model", + tsType: "string", + }, + text_summarization_model: { + name: "Summarization Model", + tsType: "string", + }, + text_question_answering_model: { + name: "Q&A Model", + tsType: "string", + }, + log_level: { + name: "Log Level", + tsType: "log_level", + }, +} as const; + +// Provided lookup type +type TsTypes = { + any: any; + string: string; + number: number; + Vector: Vector; + log_level: "debug" | "info" | "warn" | "error"; +}; + +// Extract TypeScript type for a given value type +export type ExtractTsType = + TsTypes[(typeof valueTypes)[VT]["tsType"]]; + +type InputType = { + id: string | number; + valueType: keyof typeof valueTypes; + isArray?: boolean; +}; + +type MappedType = T["isArray"] extends true + ? { [K in T["id"]]: Array> } + : { [K in T["id"]]: ExtractTsType }; + +export type CreateMappedType> = { + [P in T[number] as P["id"]]: MappedType

[P["id"]]; +}; + +export type TaskInputDefinition = { + readonly id: string; + readonly name: string; + readonly valueType: keyof typeof valueTypes; + readonly isArray?: boolean; + readonly defaultValue?: ExtractTsType; +}; + +export type TaskOutputDefinition = { + readonly id: string; + readonly name: string; + readonly valueType: keyof typeof valueTypes; + readonly isArray?: boolean; +}; + +export interface TaskNodeIO { + readonly inputs: TaskInputDefinition[]; + readonly outputs: TaskOutputDefinition[]; +} diff --git a/packages/core/src/task/TaskRegistry.ts b/packages/core/src/task/TaskRegistry.ts new file mode 100644 index 000000000..4c8a5a334 --- /dev/null +++ b/packages/core/src/task/TaskRegistry.ts @@ -0,0 +1,19 @@ +// ******************************************************************************* +// * ELLMERS: Embedding Large Language Model Experiential Retrieval Service * +// * * +// * Copyright Steven Roussey * +// * Licensed under the Apache License, Version 2.0 (the "License"); * +// ******************************************************************************* + +import { CompoundTask, SingleTask } from "./Task"; + +const all = new Map(); + +const registerTask = (baseClass: typeof SingleTask | typeof CompoundTask) => { + all.set(baseClass.type, baseClass); +}; + +export const TaskRegistry = { + registerTask, + all, +}; diff --git a/packages/core/src/task/exec/ml/HuggingFaceLocalTaskRun.ts b/packages/core/src/task/exec/ml/HuggingFaceLocalTaskRun.ts new file mode 100644 index 000000000..5a4b9467d --- /dev/null +++ b/packages/core/src/task/exec/ml/HuggingFaceLocalTaskRun.ts @@ -0,0 +1,256 @@ +// ******************************************************************************* +// * ELLMERS: Embedding Large Language Model Experiential Retrieval Service * +// * * +// * Copyright Steven Roussey * +// * Licensed under the Apache License, Version 2.0 (the "License"); * +// ******************************************************************************* + +import { + pipeline, + type PipelineType, + type FeatureExtractionPipeline, + type TextGenerationPipeline, + type TextGenerationSingle, + type SummarizationPipeline, + type SummarizationSingle, + type QuestionAnsweringPipeline, + type DocumentQuestionAnsweringSingle, + env, +} from "@sroussey/transformers"; +import { ModelFactory } from "../../ModelFactory"; +import { + DownloadTask, + DownloadTaskInput, + EmbeddingTask, + EmbeddingTaskInput, + QuestionAnswerTask, + QuestionAnswerTaskInput, + TextRewriterTaskInput, + SummarizeTask, + SummarizeTaskInput, + TextGenerationTask, + TextGenerationTaskInput, + TextRewriterTask, + DownloadTaskOutput, + EmbeddingTaskOutput, + TextGenerationTaskOutput, + TextRewriterTaskOutput, + SummarizeTaskOutput, + QuestionAnswerTaskOutput, +} from "../../ModelFactoryTasks"; +import { findModelByName } from "../../../storage/InMemoryStorage"; +import { ONNXTransformerJsModel } from "../../../model/HuggingFaceModel"; +import { ModelProcessorEnum } from "../../../model/Model"; +import { Vector } from "../../TaskIOTypes"; +import { SingleTask } from "../../Task"; + +env.backends.onnx.logLevel = "error"; +env.backends.onnx.debug = false; + +/** + * + * This is a helper function to get a pipeline for a model and assign a + * progress callback to the task. + * + * @param task + * @param model + * @param options + */ +const getPipeline = async ( + task: SingleTask, + model: ONNXTransformerJsModel, + { quantized, config }: { quantized: boolean; config: any } = { + quantized: true, + config: null, + } +) => { + return await pipeline(model.pipeline as PipelineType, model.name, { + quantized, + config, + progress_callback: (details: { + file: string; + status: string; + name: string; + progress: number; + loaded: number; + total: number; + }) => { + const { progress, file } = details; + task.progress = progress; + task.emit("progress", progress, file); + }, + }); +}; + +// =============================================================================== + +/** + * This is a task that downloads and caches an onnx model. + */ + +export async function HuggingFaceLocal_DownloadTask( + task: DownloadTask, + runInputData: DownloadTaskInput +): Promise { + const model = findModelByName(runInputData.model) as ONNXTransformerJsModel; + await getPipeline(task, model!); + return { model: model.name }; +} + +/** + * This is a task that generates an embedding for a single piece of text + * + * Model pipeline must be "feature-extraction" + */ +export async function HuggingFaceLocal_EmbeddingTask( + task: EmbeddingTask, + runInputData: EmbeddingTaskInput +): Promise { + const model = findModelByName(runInputData.model) as ONNXTransformerJsModel; + const generateEmbedding = (await getPipeline(task, model)) as FeatureExtractionPipeline; + + var vector = await generateEmbedding(runInputData.text, { + pooling: "mean", + normalize: model.normalize, + }); + + if (vector.size !== model.dimensions) { + throw `Embedding vector length does not match model dimensions v${vector.size} != m${model.dimensions}`; + } + return { vector: vector.data as Vector }; +} + +/** + * This generates text from a prompt + * + * Model pipeline must be "text-generation" or "text2text-generation" + */ +export async function HuggingFaceLocal_TextGenerationTask( + task: TextGenerationTask, + runInputData: TextGenerationTaskInput +): Promise { + const model = findModelByName(runInputData.model) as ONNXTransformerJsModel; + + const generateText = (await getPipeline(task, model)) as TextGenerationPipeline; + + let results = await generateText(runInputData.prompt); + if (!Array.isArray(results)) { + results = [results]; + } + return { + text: (results[0] as TextGenerationSingle)?.generated_text, + }; +} + +/** + * This is a special case of text generation that takes a prompt and text to rewrite + * + * Model pipeline must be "text-generation" or "text2text-generation" + */ +export async function HuggingFaceLocal_TextRewriterTask( + task: TextRewriterTask, + runInputData: TextRewriterTaskInput +): Promise { + const model = findModelByName(runInputData.model) as ONNXTransformerJsModel; + + const generateText = (await getPipeline(task, model)) as TextGenerationPipeline; + + // This lib doesn't support this kind of rewriting with a separate prompt vs text + const promptedtext = (runInputData.prompt ? runInputData.prompt + "\n" : "") + runInputData.text; + let results = await generateText(promptedtext); + if (!Array.isArray(results)) { + results = [results]; + } + + const text = (results[0] as TextGenerationSingle)?.generated_text; + if (text == promptedtext) { + throw "Rewriter failed to generate new text"; + } + + return { text }; +} + +/** + * This summarizes a piece of text + * + * Model pipeline must be "summarization" + */ + +export async function HuggingFaceLocal_SummarizeTask( + task: SummarizeTask, + runInputData: SummarizeTaskInput +): Promise { + const model = findModelByName(runInputData.model) as ONNXTransformerJsModel; + + const generateSummary = (await getPipeline(task, model)) as SummarizationPipeline; + + let results = await generateSummary(runInputData.text); + if (!Array.isArray(results)) { + results = [results]; + } + + return { + text: (results[0] as SummarizationSingle)?.summary_text, + }; +} + +/** + * This is a special case of text generation that takes a context and a question + * + * Model pipeline must be "question-answering" + */ +export async function HuggingFaceLocal_QuestionAnswerTask( + task: QuestionAnswerTask, + runInputData: QuestionAnswerTaskInput +): Promise { + const model = findModelByName(runInputData.model) as ONNXTransformerJsModel; + + const generateAnswer = (await getPipeline(task, model)) as QuestionAnsweringPipeline; + + let results = await generateAnswer(runInputData.question, runInputData.context); + if (!Array.isArray(results)) { + results = [results]; + } + + return { + answer: (results[0] as DocumentQuestionAnsweringSingle)?.answer, + }; +} + +export async function registerHuggingfaceLocalTasks() { + ModelFactory.registerRunFn( + DownloadTask, + ModelProcessorEnum.LOCAL_ONNX_TRANSFORMERJS, + HuggingFaceLocal_DownloadTask + ); + + ModelFactory.registerRunFn( + EmbeddingTask, + ModelProcessorEnum.LOCAL_ONNX_TRANSFORMERJS, + HuggingFaceLocal_EmbeddingTask + ); + + ModelFactory.registerRunFn( + TextGenerationTask, + ModelProcessorEnum.LOCAL_ONNX_TRANSFORMERJS, + HuggingFaceLocal_TextGenerationTask + ); + + ModelFactory.registerRunFn( + TextRewriterTask, + ModelProcessorEnum.LOCAL_ONNX_TRANSFORMERJS, + HuggingFaceLocal_TextRewriterTask + ); + + ModelFactory.registerRunFn( + SummarizeTask, + ModelProcessorEnum.LOCAL_ONNX_TRANSFORMERJS, + HuggingFaceLocal_SummarizeTask + ); + + ModelFactory.registerRunFn( + QuestionAnswerTask, + ModelProcessorEnum.LOCAL_ONNX_TRANSFORMERJS, + HuggingFaceLocal_QuestionAnswerTask + ); +} diff --git a/packages/core/src/task/exec/ml/MediaPipeLocalTaskRun.ts b/packages/core/src/task/exec/ml/MediaPipeLocalTaskRun.ts new file mode 100644 index 000000000..bdf42a4c1 --- /dev/null +++ b/packages/core/src/task/exec/ml/MediaPipeLocalTaskRun.ts @@ -0,0 +1,81 @@ +// ******************************************************************************* +// * ELLMERS: Embedding Large Language Model Experiential Retrieval Service * +// * * +// * Copyright Steven Roussey * +// * Licensed under the Apache License, Version 2.0 (the "License"); * +// ******************************************************************************* + +import { FilesetResolver, TextEmbedder } from "@mediapipe/tasks-text"; +import { ModelFactory } from "../../ModelFactory"; +import { + DownloadTask, + DownloadTaskInput, + EmbeddingTask, + EmbeddingTaskInput, +} from "../../ModelFactoryTasks"; +import { findModelByName } from "../../../storage/InMemoryStorage"; +import { MediaPipeTfJsModel } from "../../../model/MediaPipeModel"; +import { ModelProcessorEnum } from "../../../model/Model"; + +/** + * This is a task that downloads and caches a MediaPipe TFJS model. + */ +export async function MediaPipeTfJsLocal_DownloadTask( + task: DownloadTask, + runInputData: DownloadTaskInput +) { + const textFiles = await FilesetResolver.forTextTasks( + "https://cdn.jsdelivr.net/npm/@mediapipe/tasks-text@latest/wasm" + ); + const model = findModelByName(runInputData.model) as MediaPipeTfJsModel; + const results = await TextEmbedder.createFromOptions(textFiles, { + baseOptions: { + modelAssetPath: model.url, + }, + quantize: true, + }); + + return results; +} + +/** + * This is a task that generates an embedding for a single piece of text + * using a MediaPipe TFJS model. + */ +export async function MediaPipeTfJsLocal_EmbeddingTask( + task: EmbeddingTask, + runInputData: EmbeddingTaskInput +) { + const textFiles = await FilesetResolver.forTextTasks( + "https://cdn.jsdelivr.net/npm/@mediapipe/tasks-text@latest/wasm" + ); + const model = findModelByName(runInputData.model) as MediaPipeTfJsModel; + const textEmbedder = await TextEmbedder.createFromOptions(textFiles, { + baseOptions: { + modelAssetPath: model.url, + }, + quantize: true, + }); + + const output = textEmbedder.embed(runInputData.text); + const vector = output.embeddings[0].floatEmbedding; + + if (vector?.length !== model.dimensions) { + throw `Embedding vector length does not match model dimensions v${vector?.length} != m${model.dimensions}`; + } + return { vector }; +} + +export const registerMediaPipeTfJsLocalTasks = () => { + ModelFactory.registerRunFn( + DownloadTask, + ModelProcessorEnum.MEDIA_PIPE_TFJS_MODEL, + MediaPipeTfJsLocal_DownloadTask + ); + + ModelFactory.registerRunFn( + DownloadTask, + ModelProcessorEnum.MEDIA_PIPE_TFJS_MODEL, + MediaPipeTfJsLocal_EmbeddingTask + ); +}; diff --git a/src/util/Misc.ts b/packages/core/src/util/Misc.ts similarity index 100% rename from src/util/Misc.ts rename to packages/core/src/util/Misc.ts diff --git a/packages/core/tests/Task.test.ts b/packages/core/tests/Task.test.ts new file mode 100644 index 000000000..b0475c0ef --- /dev/null +++ b/packages/core/tests/Task.test.ts @@ -0,0 +1,69 @@ +import { describe, expect, it } from "bun:test"; +import { SingleTask, CompoundTask } from "../src/task/Task"; +import { TaskGraph } from "../src/task/TaskGraph"; +import { TaskOutput } from "../dist/lib"; + +class TestTask extends SingleTask { + static readonly type = "TestTask"; + runSyncOnly(): TaskOutput { + return { syncOnly: true }; + } + async run(): Promise { + return { all: true }; + } +} + +class TestCompoundTask extends CompoundTask { + static readonly type = "TestTask"; + runSyncOnly(): TaskOutput { + return Object.assign(this.runOutputData, this.runInputData, { syncOnly: true }); + } + async run(): Promise { + return Object.assign(this.runOutputData, this.runInputData, { all: true }); + } +} + +describe("Task", () => { + describe("SingleTask", () => { + it("should set input data and run the task", async () => { + const node = new TestTask(); + const input = { key: "value" }; + const output = await node.runWithInput(input); + expect(output).toEqual({ all: true }); + expect(node.runInputData).toEqual(input); + }); + + it("should run the task synchronously", () => { + const node = new TestTask(); + const output = node.runSyncOnly(); + expect(output).toEqual({ syncOnly: true }); + }); + }); + + describe("CompoundTask", () => { + it("should create a CompoundTask", () => { + const node = new TestCompoundTask(); + expect(node).toBeInstanceOf(CompoundTask); + }); + + it("should create a subgraph for the CompoundTask", () => { + const node = new TestCompoundTask(); + const subGraph = node.subGraph; + expect(subGraph).toBeInstanceOf(TaskGraph); + }); + + it("should set input data and run the task", async () => { + const node = new TestCompoundTask(); + const input = { key: "value" }; + const output = await node.runWithInput(input); + expect(output).toEqual({ key: "value", all: true }); + expect(node.runInputData).toEqual(input); + }); + + it("should run the task synchronously", () => { + const node = new TestCompoundTask({ input: { key: "value2" } }); + const output = node.runSyncOnly(); + expect(output).toEqual({ key: "value2", syncOnly: true }); + }); + }); +}); diff --git a/packages/core/tests/TaskGraph.test.ts b/packages/core/tests/TaskGraph.test.ts new file mode 100644 index 000000000..8c2a73f33 --- /dev/null +++ b/packages/core/tests/TaskGraph.test.ts @@ -0,0 +1,70 @@ +// ******************************************************************************* +// * ELLMERS: Embedding Large Language Model Experiential Retrieval Service * +// * * +// * Copyright Steven Roussey * +// * Licensed under the Apache License, Version 2.0 (the "License"); * +// ******************************************************************************* + +import { describe, expect, it, beforeEach } from "bun:test"; +import { SingleTask, Task, TaskOutput } from "../src/task/Task"; +import { TaskGraph, DataFlow, serialGraph } from "../src/task/TaskGraph"; + +class TestTask extends SingleTask { + static readonly type = "TestTask"; + runSyncOnly(): TaskOutput { + return {}; + } + async run(): Promise { + return {}; + } +} + +describe("TaskGraph", () => { + let graph = new TaskGraph(); + let tasks: Task[]; + + beforeEach(() => { + graph = new TaskGraph(); + tasks = [ + new TestTask({ id: "task1" }), + new TestTask({ id: "task2" }), + new TestTask({ id: "task3" }), + ]; + }); + + it("should add nodes to the graph", () => { + graph.addTasks(tasks); + + expect(graph.getTask("task1")).toBeDefined(); + expect(graph.getTask("task2")).toBeDefined(); + expect(graph.getTask("task3")).toBeDefined(); + }); + + it("should add edges to the graph", () => { + const edges: DataFlow[] = [ + new DataFlow("task1", "output1", "task2", "input1"), + new DataFlow("task2", "output2", "task3", "input2"), + ]; + + graph.addTasks(tasks); + graph.addDataFlows(edges); + + expect(graph.getDataFlow("task1.output1 -> task2.input1")).toBeDefined(); + expect(graph.getDataFlow("task2.output2 -> task3.input2")).toBeDefined(); + }); + + it("should create a serial graph", () => { + const inputHandle = "input"; + const outputHandle = "output"; + + const expectedDataFlows: DataFlow[] = [ + new DataFlow("task1", inputHandle, "task2", outputHandle), + new DataFlow("task2", inputHandle, "task3", outputHandle), + ]; + + const result = serialGraph(tasks, inputHandle, outputHandle); + + expect(result).toBeInstanceOf(TaskGraph); + expect(result.getDataFlows()).toEqual(expectedDataFlows); + }); +}); diff --git a/packages/core/tests/TaskGraphRunner.test.ts b/packages/core/tests/TaskGraphRunner.test.ts new file mode 100644 index 000000000..8bc10f8a8 --- /dev/null +++ b/packages/core/tests/TaskGraphRunner.test.ts @@ -0,0 +1,100 @@ +// ******************************************************************************* +// * ELLMERS: Embedding Large Language Model Experiential Retrieval Service * +// * * +// * Copyright Steven Roussey * +// * Licensed under the Apache License, Version 2.0 (the "License"); * +// ******************************************************************************* + +import { describe, expect, it, beforeEach, spyOn } from "bun:test"; +import { TaskGraphRunner } from "../src/task/TaskGraphRunner"; +import { Task, SingleTask, TaskOutput } from "../src/task/Task"; +import { DataFlow, TaskGraph } from "../src/task/TaskGraph"; + +class TestTask extends SingleTask { + static readonly type = "TestTask"; + runSyncOnly(): TaskOutput { + return {}; + } + async run(): Promise { + return {}; + } +} + +describe("TaskGraphRunner", () => { + let runner: TaskGraphRunner; + let graph: TaskGraph; + let nodes: Task[]; + + beforeEach(() => { + graph = new TaskGraph(); + nodes = [ + new TestTask({ id: "task1" }), + new TestTask({ id: "task2" }), + new TestTask({ id: "task3" }), + ]; + graph.addTasks(nodes); + runner = new TaskGraphRunner(graph); + }); + + describe("assignLayers same layer", () => { + it("should assign layers to nodes based on dependencies", () => { + runner.assignLayers(nodes); + + expect(runner.layers.size).toBe(1); + expect(runner.layers.get(0)?.[0]).toEqual(nodes[0]); + expect(runner.layers.get(0)?.[1]).toEqual(nodes[1]); + expect(runner.layers.get(0)?.[2]).toEqual(nodes[2]); + }); + }); + + describe("assignLayers different layers", () => { + it("should assign layers to nodes based on dependencies", () => { + graph.addDataFlows([ + new DataFlow("task1", "output", "task2", "input"), + new DataFlow("task2", "output", "task3", "input"), + ]); + runner.assignLayers(nodes); + + expect(runner.layers.size).toBe(3); + expect(runner.layers.get(0)).toEqual([nodes[0]]); + expect(runner.layers.get(1)).toEqual([nodes[1]]); + expect(runner.layers.get(2)).toEqual([nodes[2]]); + }); + }); + + describe("runNodesAsync", () => { + it("should run nodes in each layer asynchronously", async () => { + const runSpy = spyOn(nodes[0], "run"); + + runner.assignLayers(nodes); + await runner.runTasksAsync(); + + expect(runSpy).toHaveBeenCalledTimes(1); + }); + }); + + describe("runNodesSync", () => { + it("should run nodes in each layer synchronously", () => { + const runSyncOnlySpy = spyOn(nodes[0], "runSyncOnly"); + + runner.assignLayers(nodes); + runner.runTasksSync(); + + expect(runSyncOnlySpy).toHaveBeenCalledTimes(1); + }); + }); + + describe("runGraph", () => { + it("should run the graph in the correct order", async () => { + const assignLayersSpy = spyOn(runner, "assignLayers"); + const runNodesSyncSpy = spyOn(runner, "runTasksSync"); + const runNodesAsyncSpy = spyOn(runner, "runTasksAsync"); + + await runner.runGraph(); + + expect(assignLayersSpy).toHaveBeenCalled(); + expect(runNodesSyncSpy).toHaveBeenCalled(); + expect(runNodesAsyncSpy).toHaveBeenCalled(); + }); + }); +}); diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json new file mode 100644 index 000000000..ef0e5f056 --- /dev/null +++ b/packages/core/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "../../tsconfig.json", + "include": ["src/**/*", "tests/*/*.ts"], + "files": ["src/lib.ts"], + "exclude": ["**/*.test.ts", "dist"], + "compilerOptions": { + "outDir": "dist", + "baseUrl": "./src", + "rootDir": "./src", + "paths": { + "#/*": ["./src/*"] + } + } +} diff --git a/src-examples/ExampleSEC.ts b/src-examples/ExampleSEC.ts deleted file mode 100644 index 0146ddf23..000000000 --- a/src-examples/ExampleSEC.ts +++ /dev/null @@ -1,309 +0,0 @@ -// ******************************************************************************* -// * ELLMERS: Embedding Large Language Model Experiential Retrieval Service * -// * * -// * Copyright Steven Roussey * -// * Licensed under the Apache License, Version 2.0 (the "License"); * -// ******************************************************************************* - -import { Command, InvalidArgumentError } from "commander"; -import { readFile } from "fs/promises"; -import { Listr, PRESET_TIMER } from "listr2"; -import { TaskHelper } from "./TaskHelper"; -import { Document, TextDocument, TextNode } from "#/Document"; -import { getPipeline } from "#/embeddings/TransformerJsService"; -import { - strategyAllPairs, - baaiBgeSmallEnV15, - instructPlain, - supabaseGteSmall, - // instructRepresent, - instructQuestion, - xenovaDistilbert, - whereIsAIUAELargeV1, - gpt2, - xenovaDistilbertMnli, - distilbartCnn, -} from "#/storage/InMemoryStorage"; -import { readFileSync, writeFileSync, mkdirSync } from "fs"; -import { getTopKEmbeddings } from "#/query/InMemoryQuery"; -import { Observable } from "rxjs"; -import { generateDocumentEmbeddings } from "#/embeddings/GenerateEmbeddings"; - -interface Filing { - cik: number; - accession_number: string; - primary_doc: string; - report_date: string; - filing_date: string; - acceptance_date: string; - form: string; - file_number: string; - film_number: string; - documents?: TextDocument[]; -} - -const loadSecAccessionDocument = async ( - cik: number, - accession_number: string, - primary_doc: string -) => { - const filepath = `./data-in/sec/cik-${cik}/files/${accession_number}:${primary_doc}`; - const docraw = await readFile(filepath, "utf8"); - return docraw; -}; - -const processSingleFiling = async (cik: number, accession_number: string) => { - const filing = await getFiling(cik, accession_number); - - const doc = await loadSecAccessionDocument( - cik, - accession_number, - filing.primary_doc - ); - - switch (filing.form) { - case "10-K": - // await process10K(doc); - break; - case "8-K": - return JSON.parse(doc); - case "10-Q": - // await process10Q(doc); - break; - default: - throw new Error(`Form ${filing.form} not supported`); - } - return doc; -}; - -async function getFiling(cik: number, accession_number: string) { - const filings = await getFilingsForCik(cik); - const filing = filings.find( - (filing) => - filing.cik === cik && filing.accession_number === accession_number - ); - if (!filing) throw new Error("Filing not found"); - return filing; -} - -let filingsCache: Filing[]; -async function getFilingsForCik(this: any, cik: number): Promise { - if (filingsCache) return filingsCache; - const filingspath = `./data-in/sec/cik-${cik}/filings.json`; - const filingsraw = await readFile(filingspath, "utf8"); - filingsCache = JSON.parse(filingsraw) as Filing[]; - return filingsCache; -} - -function myParseInt(value: string, dummyPrevious: number) { - // parseInt takes a string and a radix - const parsedValue = parseInt(value, 10); - if (isNaN(parsedValue)) { - throw new InvalidArgumentError("Not a number."); - } - return parsedValue; -} - -export function AddSecCommands(program: Command) { - program - .command("sec-index") - .description("process sec filings") - .argument("", "Run for only one cik", myParseInt) - .argument("[accession]", "Run for only one accession document") - .option("--debug", "Show debug messages") - .option("--form [name]", "Only certain forms") - .action(async (cik, accession, options) => { - const listrTasks = new Listr( - [ - { - title: "Prepare pipelines", - task: () => { - return new Observable((observer) => { - function updateProgress(stat: any) { - const { status, name, file, progress } = stat; - observer.next(`${name} ${file} ${status} ${progress}`); - } - async function run() { - await getPipeline(whereIsAIUAELargeV1, updateProgress); - await getPipeline(baaiBgeSmallEnV15, updateProgress); - await getPipeline(supabaseGteSmall, updateProgress); - await getPipeline(gpt2, updateProgress); - await getPipeline(xenovaDistilbertMnli, updateProgress); - await getPipeline(distilbartCnn, updateProgress); - observer.complete(); - } - run(); - }); - }, - }, - { - title: "Process SEC filings", - task: async (ctx, task) => { - let filings = await getFilingsForCik(cik); - - if (options.form) { - filings = filings.filter( - (f) => f.form === options.form.toUpperCase() - ); - } - - if (accession) { - filings = filings.filter( - (f) => f.accession_number === accession - ); - } - - const helper = new TaskHelper(task, filings.length); - for (const filing of filings) { - const cikStr = cik.toString().padStart(10, "0"); - await helper.onIteration(async () => { - const sections = await processSingleFiling( - filing.cik, - filing.accession_number - ); - filing.documents = [ - new TextDocument( - `${cikStr}:${filing.accession_number}:${filing.primary_doc}`, - Object.values(sections as object) - ), - ]; - await filing.documents.reduce(async (acc, document) => { - await acc; - await generateDocumentEmbeddings( - strategyAllPairs, - document - ); - return acc; - }, Promise.resolve()); - }, `Processing ${cikStr} ${filing.accession_number}`); - } - - mkdirSync(`./data-out/sec/cik-${cik}`, { recursive: true }); - writeFileSync( - `./data-out/sec/cik-${cik}/embeddings.json`, - JSON.stringify(filings, null, 2) - ); - }, - }, - ], - { - exitOnError: true, - concurrent: false, - rendererOptions: { timer: PRESET_TIMER }, - } - ); - await listrTasks.run({ cik, accession, debug: options.debug }); - }); - - program - .command("sec-search") - .description("search sec filings") - .argument("", "Run for only one cik", myParseInt) - .argument("", "Question to ask") - .option("--debug", "Show debug messages") - .option("--form [name]", "Only certain forms") - .action(async (cik, query, options) => { - const listrTasks = new Listr( - [ - { - title: "Prepare pipelines", - task: () => { - return new Observable((observer) => { - function updateProgress(stat: any) { - const { status, name, file, progress } = stat; - observer.next(`${name} ${file} ${status} ${progress}`); - } - async function run() { - await getPipeline(xenovaDistilbert, updateProgress); - await getPipeline(baaiBgeSmallEnV15, updateProgress); - await getPipeline(supabaseGteSmall, updateProgress); - observer.complete(); - } - run(); - }); - }, - }, - { - title: "Search SEC filings", - task: async (ctx, task) => { - let filings = JSON.parse( - readFileSync( - `./data-out/sec/cik-${cik}/embeddings.json`, - "utf8" - ) - ) as Filing[]; - - // filings.forEach((f) => { - // f.documents?.forEach((d) => { - // d.nodes?.forEach((n) => { - // n.embeddings.forEach( - // (e) => (e.vector = new Float32Array(e.vector)) - // ); - // }); - // }); - // }); - - if (options.form) { - filings = filings.filter( - (f) => f.form === options.form.toUpperCase() - ); - } - - const docs = filings.reduce((acc, f) => { - if (!f.documents) return acc; - return acc.concat(f.documents); - }, []); - - const nodes = docs.reduce((acc, d) => { - if (!d.nodes) return acc; - return acc.concat(d.nodes as TextNode[]); - }, []); - - const queryDocument = new TextDocument("query", query); - await generateDocumentEmbeddings( - // strategyAllPairs, - [ - { - embeddingModel: baaiBgeSmallEnV15, - instruct: instructPlain, - }, - ], - queryDocument - ); - - const similarities = getTopKEmbeddings( - queryDocument.nodes[0], - nodes, - 3 - ); - - const answerer = await getPipeline(xenovaDistilbert); - - const context = similarities - .map((s) => s.node.content) - .join("\n\n"); - const output = await answerer(query, context); - - console.log( - output, - // similarities.map((s) => { - // return { - // model: s.embedding.modelName, - // instruct: s.embedding.instructName, - // similarity: s.similarity, - // }; - // }), - "\n\n\n\n\n" - ); - }, - }, - ], - { - exitOnError: true, - concurrent: false, - rendererOptions: { timer: PRESET_TIMER }, - } - ); - await listrTasks.run({ cik, query, debug: options.debug }); - }); -} diff --git a/src-examples/TaskStreamToListr2.ts b/src-examples/TaskStreamToListr2.ts deleted file mode 100644 index 97ec459df..000000000 --- a/src-examples/TaskStreamToListr2.ts +++ /dev/null @@ -1,93 +0,0 @@ -// ******************************************************************************* -// * ELLMERS: Embedding Large Language Model Experiential Retrieval Service * -// * * -// * Copyright Steven Roussey * -// * Licensed under the Apache License, Version 2.0 (the "License"); * -// ******************************************************************************* - -import { - TaskStreamable, - type TaskStream, - TaskStatus, - TaskListOrdering, -} from "#/Task"; -import { Listr, ListrTask } from "listr2"; -import { createBar } from "./TaskHelper"; -import { PRESET_TIMER } from "listr2"; -import { Observable } from "rxjs"; - -const taskArrayToListr = ( - tasks: TaskStream, - options: Record = { concurrent: false, exitOnError: true } -): Listr => { - const list: ListrTask[] = []; - - for (const task of tasks) { - switch (task.kind) { - case "TASK": - list.push({ - title: task.config.name, - task: async (_, t) => { - if ( - task.status == TaskStatus.COMPLETED || - task.status == TaskStatus.FAILED - ) { - return; - } - return new Observable((observer) => { - const start = Date.now(); - let lastUpdate = start; - task.on("progress", (progress, file) => { - const timeSinceLast = Date.now() - lastUpdate; - const timeSinceStart = Date.now() - start; - if (timeSinceLast > 250 || timeSinceStart > 100) { - observer.next( - createBar(progress / 100 || 0, 30) + " " + (file || "") - ); - } - }); - task.on("complete", () => { - observer.complete(); - }); - task.on("error", () => { - observer.complete(); - }); - }); - }, - }); - break; - case "TASK_LIST": - list.push({ - title: task.config.name, - task: async (_, t) => { - return taskArrayToListr(task.tasks, { - concurrent: task.ordering == TaskListOrdering.PARALLEL, - exitOnError: task.ordering == TaskListOrdering.SERIAL, - }); - }, - }); - break; - case "STRATEGY": - list.push({ - title: task.config.name, - task: async (_, t) => { - return taskArrayToListr(task.tasks); - }, - }); - break; - } - } - const listr = new Listr(list, options); - return listr; -}; - -export const runTaskToListr = async (task: TaskStreamable) => { - const listrTasks = taskArrayToListr([task], { - exitOnError: true, - concurrent: false, - rendererOptions: { timer: PRESET_TIMER }, - }); - listrTasks.run({}); - await new Promise((resolve) => setTimeout(resolve, 100)); - await task.run({}); -}; diff --git a/src/Flow.ts b/src/Flow.ts deleted file mode 100644 index a897ea107..000000000 --- a/src/Flow.ts +++ /dev/null @@ -1,39 +0,0 @@ -// ******************************************************************************* -// * ELLMERS: Embedding Large Language Model Experiential Retrieval Service * -// * * -// * Copyright Steven Roussey * -// * Licensed under the Apache License, Version 2.0 (the "License"); * -// ******************************************************************************* - -import { Task, TaskInput, TaskOutput } from "./Task"; -import { DirectedAcyclicGraph } from "@sroussey/typescript-graph"; - -class Transfer { - input?: TaskInput; - output?: TaskOutput; -} -type FlowGraph = DirectedAcyclicGraph; - -export class Flow { - #graph: FlowGraph; - constructor() { - this.#graph = new DirectedAcyclicGraph( - (task) => task.config.id - ); - } - addTask(task: Task) { - this.#graph.insert(task); - } - getTasks(): Task[] { - return this.#graph.topologicallySortedNodes(); - } - removeTask(task: Task) { - this.#graph.remove(task.config.id); - } - addTransfer(from: Task, to: Task, edge: Transfer) { - this.#graph.addEdge(from.config.id, to.config.id, edge); - } - removeTransfer(from: Task, to: Task) { - this.#graph.removeEdge(from.config.id, to.config.id); - } -} diff --git a/src/JobQueue.ts b/src/JobQueue.ts deleted file mode 100644 index 2425cf13f..000000000 --- a/src/JobQueue.ts +++ /dev/null @@ -1,204 +0,0 @@ -// ******************************************************************************* -// * ELLMERS: Embedding Large Language Model Experiential Retrieval Service * -// * * -// * Copyright Steven Roussey * -// * Licensed under the Apache License, Version 2.0 (the "License"); * -// ******************************************************************************* - -import uuid from "uuid"; - -export enum JobStatus { - PENDING = "NEW", - PROCESSING = "PROCESSING", - COMPLETED = "COMPLETED", - FAILED = "FAILED", -} - -// =============================================================================== - -export abstract class Job { - constructor( - public readonly id: unknown, - public readonly queue: string, - public readonly taskName: string, - public readonly input: any, - public readonly maxRetries: number, - public readonly createdAt: Date - ) { - this.runAfter = createdAt; - } - public status: JobStatus = JobStatus.PENDING; - public runAfter: Date; - public output: any = null; - public retries: number = 0; - public ranAt: Date | null = null; - public completedAt: Date | null = null; - public error: string | undefined = undefined; -} - -export abstract class JobQueue { - public abstract add(job: Job): void; - public abstract next(): Job | undefined; - public abstract size(): number; - public abstract complete(id: unknown, output: any, error?: string): void; -} - -// =============================================================================== -// Local Version -// =============================================================================== - -export class LocalJob extends Job { - constructor(queue: string, taskName: string, input: any) { - const id = uuid.v4(); - const createdAt = new Date(); - const maxRetries = 10; - super(id, queue, taskName, input, maxRetries, createdAt); - } -} - -export class LocalJobQueue extends JobQueue { - private readonly queue: LocalJob[] = []; - - #reorderQueue(): void { - this.queue - .filter((job) => job.status === JobStatus.PENDING) - .filter((job) => job.runAfter.getTime() <= Date.now()) - .sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime()); - } - - public add(job: Job): void { - this.queue.push(job); - } - - public next(): Job | undefined { - this.#reorderQueue(); - - const job = this.queue[0]; - job.status = JobStatus.PROCESSING; - return job; - } - - public size(): number { - return this.queue.length; - } - - public complete(id: unknown, output: any, error?: string): void { - const job = this.queue.find((j) => j.id === id); - if (!job) { - throw new Error(`Job ${id} not found`); - } - job.completedAt = new Date(); - if (error) { - job.status = JobStatus.FAILED; - job.error = error; - } else { - job.status = JobStatus.COMPLETED; - job.output = output; - } - } -} - -// =============================================================================== -// PostgreSQL Version (idea for, never executed, todo) -// =============================================================================== - -/* - -CREATE TABLE IF NOT EXISTS job_queue ( - id bigint SERIAL NOT NULL, - fingerprint text NOT NULL, - queue text NOT NULL, - status job_status NOT NULL default 'new', - payload jsonb, - output jsonb, - retries integer default 0, - max_retries integer default 23, - run_after timestamp with time zone DEFAULT now(), - ran_at timestamp with time zone, - created_at timestamp with time zone DEFAULT now(), - error text -); - -CREATE INDEX IF NOT EXISTS job_fetcher_idx ON job_queue (id, status, run_after); -CREATE INDEX IF NOT EXISTS job_queue_fetcher_idx ON job_queue (queue, status, run_after); -CREATE INDEX IF NOT EXISTS job_queue_fingerprint_idx ON job_queue (fingerprint, status); - ---- we could return results from the existing job instead of queuing a new one if we wanted to, not sure, weird way to cache, todo -CREATE UNIQUE INDEX IF NOT EXISTS jobs_fingerprint_unique_idx ON job_queue (fingerprint, status) WHERE NOT (status = 'processed'); - -*/ - -export class PostgresqlJob extends Job { - constructor(queue: string, taskName: string, input: any) { - const id = uuid.v4(); - const createdAt = new Date(); - const maxRetries = 10; - super(id, queue, taskName, input, maxRetries, createdAt); - } -} - -// Do not use "AUTOCOMMIT" mode for any of the below, put inside a transaction - -export class PostgresqlJobQueue extends JobQueue { - public add(job: Job): void { - const AddQuery = ` - INSERT INTO job_queue(queue, fingerprint, payload, run_after, deadline, max_retries) - VALUES ($1, $2, $3, $4, $5, $6) - RETURNING id`; - } - - public get(): Job | undefined { - const JobQuery = ` - SELECT id, fingerprint, queue, status, deadline, payload, retries, max_retries, run_after, ran_at, created_at, error - FROM job_queue - WHERE id = $1 - FOR UPDATE SKIP LOCKED - LIMIT 1`; - - return; - } - - public peek(num: number = 100): Job | undefined { - num = Number(num) || 100; - const FutureJobQuery = ` - SELECT id, fingerprint, queue, status, deadline, payload, retries, max_retries, run_after, ran_at, created_at, error - FROM job_queue - WHERE queue = $1 - AND status = 'new' - AND run_after > NOW() - ORDER BY run_after ASC - LIMIT ${num} - FOR UPDATE SKIP LOCKED`; - - return; - } - - public next(): Job | undefined { - const PendingJobIDQuery = ` - SELECT id - FROM job_queue - WHERE queue = $1 - AND status = 'new' - AND run_after <= NOW() - FOR UPDATE SKIP LOCKED - LIMIT 1`; - - return; - } - - public size(): number { - // why do we even care about size? - return 0; - } - - #update(id: unknown, output: any, error?: string): void { - const UpdateQuery = ` - UPDATE job_queue - SET ... - WHERE id = ...`; - } - - public complete(id: unknown, output: any, error?: string): void { - // - } -} diff --git a/src/Strategy.ts b/src/Strategy.ts deleted file mode 100644 index 4dfac5baf..000000000 --- a/src/Strategy.ts +++ /dev/null @@ -1,27 +0,0 @@ -// ******************************************************************************* -// * ELLMERS: Embedding Large Language Model Experiential Retrieval Service * -// * * -// * Copyright Steven Roussey * -// * Licensed under the Apache License, Version 2.0 (the "License"); * -// ******************************************************************************* - -import { Instruct } from "./Instruct"; -import { Model } from "./Model"; - -/** - * A strategy is a combination of a model and an instruction for that model. - * This combination is used to generate embeddings for a document, and later - * to generate embeddings for a query. - * - * A node (a block of text or clip of image) can have multiple embeddings, - * though perferably only one in use at a time. This allows for updating - * an embedding strategy while keeping the old one around for live use. - * - * It also allows for testing multiple strategies for a given dataset. - */ -export interface Strategy { - embeddingModel: Model; - instruct: Instruct; -} - -export type StrategyList = Strategy[]; diff --git a/src/Task.ts b/src/Task.ts deleted file mode 100644 index 15835e414..000000000 --- a/src/Task.ts +++ /dev/null @@ -1,330 +0,0 @@ -// ******************************************************************************* -// * ELLMERS: Embedding Large Language Model Experiential Retrieval Service * -// * * -// * Copyright Steven Roussey * -// * Licensed under the Apache License, Version 2.0 (the "License"); * -// ******************************************************************************* - -import { EventEmitter } from "eventemitter3"; -import { deepEqual } from "./util/Misc"; - -class InputOutput { - provanance: string[] = []; - constructor(public input: I, public output: O, task: T) { - this.provanance.push(task.type); - } -} - -/** - * WARNING! - * TODO! - * - * Task input and output is not type safe. It is super brittle and hacky. I am thinking about a visual - * UI editor for tasks where you can map inputs and outputs, see what will run before you run it, etc. - * - * Also, task provenance is not tracked which is terrible for keeping state and caching intermediate results. - */ - -export enum TaskStatus { - PENDING = "NEW", - PROCESSING = "PROCESSING", - COMPLETED = "COMPLETED", - FAILED = "FAILED", -} - -/** - * TaskEvents - * - * There is no job queue at the moement. - */ -export type TaskEvents = "start" | "complete" | "error" | "progress"; - -// =============================================================================== - -export type StreamableTaskKind = "TASK" | "TASK_LIST" | "STRATEGY"; -export type StreamableTaskType = string; - -// =============================================================================== - -export type TaskStreamable = Task | TaskList | Strategy; -export type TaskStream = TaskStreamable[]; - -// =============================================================================== - -export interface ITaskSimple { - isCompound: false; -} -export interface ITaskCompound { - isCompound: true; - tasks: TaskStream; -} -export type ITask = ITaskSimple & ITaskCompound; - -export interface TaskConfig { - name?: string; - id?: unknown; - output_name?: string; -} - -type TaskConfigFull = TaskConfig & { output_name: string }; - -export interface TaskInput { - [key: string]: any; -} -export interface TaskOutput { - [key: string]: any; -} - -abstract class TaskBase { - events = new EventEmitter(); - on(name: TaskEvents, fn: (...args: any[]) => void) { - this.events.on.call(this.events, name, fn); - } - off(name: TaskEvents, fn: (...args: any[]) => void) { - this.events.off.call(this.events, name, fn); - } - emit(name: TaskEvents, ...args: any[]) { - this.events.emit.call(this.events, name, ...args); - } - /** - * Does this task have subtasks? - */ - abstract isCompound: boolean; - /** - * The defaults for the task. If no overrides at run time, then this would be equal to the - * input - */ - defaults: TaskInput = {}; - /** - * The input to the task at the time of the task run. This takes defaults from construction - * time and overrides from run time. It is the input that created the output. - */ - input: TaskInput = {}; - /** - * The output of the task at the time of the task run. This is the result of the task. - * The the defaults and overrides are combined to match the required input of the task. - */ - output: TaskInput = {}; - /** - * Configuration for the task, might include things like name and id for the database - */ - config: TaskConfigFull = { output_name: "out" }; - status: TaskStatus = TaskStatus.PENDING; - progress: number = 0; - createdAt: Date = new Date(); - completedAt: Date | null = null; - error: string | undefined = undefined; - - /** - * - * This calculates the input to the task at the time of the task run. This takes defaults from - * construction and applies run time overrides (which may be output from a previous run if this - * is a serial task or strategy). Caller needs to decide if should set to this classes input - * or not. - */ - withDefaults(...overrides: (Partial | undefined)[]): T { - return Object.assign({}, this.defaults, ...overrides) as T; - } - - constructor(config: TaskConfig = {}, defaults: TaskInput = {}) { - Object.defineProperty(this, "events", { enumerable: false }); - this.defaults = defaults; - this.input = this.withDefaults(); - this.config = Object.assign( - { - id: - this.constructor.name + - ":" + - Math.random().toString(36).substring(2, 9), - name: this.constructor.name, - }, - this.config, - config - ); - this.on("start", () => { - this.status = TaskStatus.PROCESSING; - }); - this.on("complete", () => { - this.completedAt = new Date(); - this.status = TaskStatus.COMPLETED; - }); - this.on("error", (error) => { - this.completedAt = new Date(); - this.status = TaskStatus.FAILED; - this.error = error; - }); - } - - abstract run(overrides?: TaskInput): Promise; -} - -export abstract class Task extends TaskBase implements ITaskSimple { - readonly kind = "TASK"; - readonly type: StreamableTaskType = "Task"; - readonly isCompound = false; -} - -// =============================================================================== - -export enum TaskListOrdering { - SERIAL = "SERIAL", - PARALLEL = "PARALLEL", -} - -export abstract class MultiTaskBase extends TaskBase implements ITaskCompound { - readonly isCompound = true; - abstract ordering: TaskListOrdering; - tasks: TaskStream = []; - started = 0; - completed = 0; - total = 0; - errors = 0; - - constructor( - config: TaskConfig = {}, - tasks: TaskStream = [], - defaults: TaskInput = {} - ) { - super(config, defaults); - this.setTasks(tasks); - } - - setTasks(tasks: TaskStream) { - if (this.tasks.length) { - this.tasks.forEach((task) => { - task.off("complete", this.#completeTask); - task.off("error", this.#errorTask); - }); - } - this.tasks = tasks; - tasks.forEach((task) => { - task.on("complete", this.#completeTask); - task.on("error", this.#errorTask); - }); - } - - generateTasks(_tasks?: TaskStream) {} - - async #run_serial(overrides?: TaskInput) { - try { - this.emit("start"); - this.input = this.withDefaults(overrides); - // TODO: dont regenerate if defaults are the same as input (only check what matters) - if (this.generateTasks && !deepEqual(this.input, this.defaults)) - this.generateTasks(); // only strategy should do this - const total = this.tasks.length; - let taskInput = {}; - for (const task of this.tasks) { - await task.run(taskInput); - if (this.tasks[this.tasks.length - 1] == task) { - // if last task, their result is our result - this.output = task.output; - break; - } - taskInput = Object.assign({}, task.output); - this.emit("progress", this.completed / total); - if (this.errors) { - this.emit("error", this.error); - break; - } - } - this.emit("complete"); - return this.output; - } catch (e) { - this.emit("error", String(e)); - return this.output; - } - } - - async #run_parallel(overrides?: TaskInput) { - this.emit("start"); - - this.input = this.withDefaults(overrides); - // TODO: dont regenerate if defaults are the same as input (only check what matters) - if (this.generateTasks && !deepEqual(this.input, this.defaults)) - this.generateTasks(); // only strategy should do this - - let taskInput = {}; - - const total = this.tasks.length; - await Promise.all( - this.tasks.map(async (task) => { - await task.run(taskInput); - this.emit("progress", this.completed / total); - }) - ); - - const outputs = this.tasks.map((task) => task.output || {}) || []; - const result: TaskInput = {}; - outputs.forEach((item) => { - Object.keys(item).forEach((key) => { - if (!result[key]) { - result[key] = []; - } - result[key].push(item[key]); - }); - }); - - this.output = result; - if (this.errors === total) this.emit("error", this.error); - this.emit("complete"); - return this.output; - } - - async run(overrides?: TaskInput) { - if (this.ordering === TaskListOrdering.SERIAL) { - return this.#run_serial(overrides); - } else { - return this.#run_parallel(overrides); - } - } - - #completeTask() { - this.completed++; - this.emit("progress", this.completed / this.total); - } - - #errorTask(error: string) { - this.errors++; - this.error = this.error ? this.error + " & " + error : error; - } -} - -abstract class TaskList extends MultiTaskBase { - readonly kind = "TASK_LIST"; - readonly type: StreamableTaskType = "TaskList"; - declare _tasks: Task[]; -} - -export class SerialTaskList extends TaskList { - readonly type: StreamableTaskType = "SerialTaskList"; - ordering = TaskListOrdering.SERIAL; -} - -export class ParallelTaskList extends TaskList { - readonly type: StreamableTaskType = "ParallelTaskList"; - ordering = TaskListOrdering.PARALLEL; -} - -// =============================================================================== - -abstract class Strategy extends MultiTaskBase { - readonly kind = "STRATEGY"; - readonly type: StreamableTaskType = "Strategy"; - ordering = TaskListOrdering.SERIAL; - constructor(config: TaskConfig = {}, defaults: TaskInput = {}) { - super(config, [], defaults); - this.generateTasks(); - } - abstract generateTasks(): void; -} - -export abstract class SerialStrategy extends Strategy { - readonly type: StreamableTaskType = "SerialStrategy"; - ordering = TaskListOrdering.SERIAL; -} - -export abstract class ParallelStrategy extends Strategy { - readonly type: StreamableTaskType = "ParallelStrategy"; - ordering = TaskListOrdering.PARALLEL; -} diff --git a/src/embeddings/GenerateEmbeddings.ts b/src/embeddings/GenerateEmbeddings.ts deleted file mode 100644 index b55867d98..000000000 --- a/src/embeddings/GenerateEmbeddings.ts +++ /dev/null @@ -1,61 +0,0 @@ -// ******************************************************************************* -// * ELLMERS: Embedding Large Language Model Experiential Retrieval Service * -// * * -// * Copyright Steven Roussey * -// * Licensed under the Apache License, Version 2.0 (the "License"); * -// ******************************************************************************* - -import { ModelProcessorEnum } from "#/Model"; -import { TextDocument } from "#/Document"; -import type { StrategyList } from "#/Strategy"; -import { - generateTransformerJsEmbedding, - generateTransformerJsRewrite, -} from "./TransformerJsService"; - -export async function generateEmbeddings( - strategies: StrategyList, - document: TextDocument, - isQuery: boolean -) { - for (const node of document.nodes) { - for (const { embeddingModel, instruct } of strategies) { - let text = node.content; - if (instruct.model) { - switch (instruct.model.type) { - case ModelProcessorEnum.LOCAL_ONNX_TRANSFORMERJS: - text = await generateTransformerJsRewrite(node, instruct, isQuery); - break; - default: - throw new Error("Instruct Model type not supported yet"); - } - } - switch (embeddingModel.type) { - case ModelProcessorEnum.LOCAL_ONNX_TRANSFORMERJS: - await generateTransformerJsEmbedding( - node, - text, - embeddingModel, - instruct - ); - break; - default: - throw new Error("Embedding Model type not supported yet"); - } - } - } -} - -export async function generateDocumentEmbeddings( - strategies: StrategyList, - document: TextDocument -) { - return generateEmbeddings(strategies, document, false); -} - -export async function generateQueryEmbeddings( - strategies: StrategyList, - document: TextDocument -) { - return generateEmbeddings(strategies, document, true); -} diff --git a/src/embeddings/TransformerJsService.ts b/src/embeddings/TransformerJsService.ts deleted file mode 100644 index 9dd7b4c33..000000000 --- a/src/embeddings/TransformerJsService.ts +++ /dev/null @@ -1,99 +0,0 @@ -// ******************************************************************************* -// * ELLMERS: Embedding Large Language Model Experiential Retrieval Service * -// * * -// * Copyright Steven Roussey * -// * Licensed under the Apache License, Version 2.0 (the "License"); * -// ******************************************************************************* - -import { pipeline, type PipelineType } from "@sroussey/transformers"; -import { Model } from "#/Model"; -import type { Instruct } from "#/Instruct"; -import { NodeEmbedding, TextNode } from "#/Document"; -import { ONNXTransformerJsModel } from "#/tasks/HuggingFaceLocalTasks"; - -const modelPipelinesCache: Record = {}; - -export const getPipeline = async ( - model: ONNXTransformerJsModel, - progress_callback?: (progress: any) => void -) => { - if (!modelPipelinesCache[model.name]) { - modelPipelinesCache[model.name] = await pipeline( - model.pipeline as PipelineType, - model.name, - { - progress_callback, - } - ); - } - return modelPipelinesCache[model.name]; -}; - -export async function generateTransformerJsEmbedding( - node: TextNode, - rewrittenText: string, - model: Model, - instruct: Instruct -) { - const generateEmbedding = await getPipeline(model as ONNXTransformerJsModel); - - const text = rewrittenText || node.content; - - const output = await generateEmbedding(text, { - pooling: "mean", - normalize: model.normalize, - temperature: instruct.parameters?.temperature, - }); - - const vector = Array.from(output.data); - - if (vector.length !== model.dimensions) { - throw new Error( - `Embedding vector length does not match model dimensions v${vector.length} != m${model.dimensions}` - ); - } - - node.embeddings.push( - new NodeEmbedding(model.name, instruct.name, text, vector, model.normalize) - ); -} - -export async function generateTransformerJsRewrite( - node: TextNode, - instruct: Instruct, - query: boolean -): Promise { - let instruction = query - ? instruct.queryInstruction - : instruct.storageInstruction; - if (!instruct.model) { - return node.content; - } else { - instruction = instruction ? instruction + ":\n" : ""; - } - - const rewriter = await getPipeline(instruct.model as ONNXTransformerJsModel); - - const output = await rewriter(node.content); - - let result = ""; - - switch ((instruct.model as ONNXTransformerJsModel).pipeline) { - case "text-generation": - result = output.generated_text; - break; - case "zero-shot-classification": - result = output.labels.join(", "); - break; - case "question-answering": - result = output.answer; - break; - case "summarization": - result = output?.[0]?.summary_text; - break; - default: - throw new Error("rewrite model pipeline not supported yet"); - } - - return result; -} diff --git a/src/stuff.ts b/src/stuff.ts deleted file mode 100644 index a7fc36e2e..000000000 --- a/src/stuff.ts +++ /dev/null @@ -1,55 +0,0 @@ -interface StorageService { - getDocument(documentId: number): Document; - getDocumentNode(documentId: number, nodeId: number): DocumentNode; - getDocumentNodeEmbedding( - documentId: number, - nodeId: number, - instructId: number, - modelId: number - ): DocumentNodeEmbedding; - getModels(): Model[]; - getInstructs(): Instruct[]; -} - -enum InvokationEventType { - START, - STOP, - EMBEDDING, - FIRST_TOKEN, - LAST_TOKEN, - TOKENS, - TOKENS_PER_SECOND, -} - -interface InvokationEvent { - type: InvokationEventType; - instructId: number; - modelId: number; - token: string; - tokens: string[]; - tokensPerSecond: number; -} - -interface DocumentInvokationEvent extends InvokationEvent { - documentId: number; - nodeId: number; -} - -interface PromptInvokationEvent extends InvokationEvent { - prompt: string; -} - -interface EmbeddingService { - instructId: number; - modelId: number; - transform: (document: Document, node: DocumentNode) => DocumentNodeEmbedding; -} - -// Configuration for Deno runtime -// env.useBrowserCache = false; -// env.allowLocalModels = false; - -// const generateEmbedding = await pipeline( -// "feature-extraction", -// "Supabase/gte-small" -// ); diff --git a/src/tasks/BasicTasks.ts b/src/tasks/BasicTasks.ts deleted file mode 100644 index d317776e5..000000000 --- a/src/tasks/BasicTasks.ts +++ /dev/null @@ -1,70 +0,0 @@ -// ******************************************************************************* -// * ELLMERS: Embedding Large Language Model Experiential Retrieval Service * -// * * -// * Copyright Steven Roussey * -// * Licensed under the Apache License, Version 2.0 (the "License"); * -// ******************************************************************************* - -import { TaskConfig, Task, TaskInput } from "#/Task"; - -export interface RenameTaskInput { - output_remap_array: { - from: string; - to: string; - }[]; -} - -/** - * Uses config to map multiple values from inputs to outputs - */ -export class RenameTask extends Task { - readonly type: string = "RenameTask"; - constructor( - config: TaskConfig = {}, - defaults: RenameTaskInput = { output_remap_array: [] } - ) { - config.name ||= - "RenameTask" + - (defaults?.output_remap_array?.length - ? defaults?.output_remap_array - .map(({ from, to }) => `: from ${from} to ${to}`) - .join(", ") - : ""); - super(config, defaults); - } - async run(overrides?: RenameTaskInput) { - this.emit("start"); - this.input = this.withDefaults(overrides); - this.output = {}; - for (const { from, to } of this.input.output_remap_array) { - if (from != "output_remap_array") { - this.output[to] = this.input[from]; - } - } - this.emit("complete"); - return this.output; - } -} - -// =============================================================================== - -export class LambdaTask extends Task { - #runner: (input: TaskInput) => Promise; - readonly type: string = "LambdaTask"; - constructor( - config: TaskConfig & { - run: () => Promise; - }, - defaults: TaskInput = {} - ) { - super(config, defaults); - this.#runner = config.run; - } - async run(overrides?: TaskInput) { - this.emit("start"); - this.input = this.withDefaults(overrides); - this.output = await this.#runner(this.input); - this.emit("complete"); - return this.output; - } -} diff --git a/src/tasks/FactoryTasks.ts b/src/tasks/FactoryTasks.ts deleted file mode 100644 index 75440ab10..000000000 --- a/src/tasks/FactoryTasks.ts +++ /dev/null @@ -1,156 +0,0 @@ -// ******************************************************************************* -// * ELLMERS: Embedding Large Language Model Experiential Retrieval Service * -// * * -// * Copyright Steven Roussey * -// * Licensed under the Apache License, Version 2.0 (the "License"); * -// ******************************************************************************* - -import { Model } from "#/Model"; -import { TaskConfig, Task, TaskInput, StreamableTaskType } from "#/Task"; -import { - ONNXTransformerJsModel, - HuggingFaceLocal_EmbeddingTask, - HuggingFaceLocal_QuestionAnswerTask, - HuggingFaceLocal_SummarizationTask, - HuggingFaceLocal_TextGenerationTask, - HuggingFaceLocal_TextRewriterTask, - HuggingFaceLocal_DownloadTask, -} from "./HuggingFaceLocalTasks"; -import { - MediaPipeTfJsLocal_DownloadTask, - MediaPipeTfJsLocal_EmbeddingTask, - MediaPipeTfJsModel, -} from "./MediaPipeLocalTasks"; - -export interface ModelFactoryTaskInput { - model: Model; -} - -abstract class ModelFactoryTask extends Task { - declare input: ModelFactoryTaskInput; - constructor(config: TaskConfig = {}, defaults: ModelFactoryTaskInput) { - super(config, defaults); - } - - run(overrides?: TaskInput): Promise { - throw new Error("ModelFactoryTask:run() method not implemented."); - } -} - -interface DownloadTaskInput { - model: Model; -} - -export class DownloadTask extends ModelFactoryTask { - declare input: DownloadTaskInput; - readonly type: StreamableTaskType = "DownloadTask"; - constructor(config: TaskConfig = {}, defaults: DownloadTaskInput) { - super(config, defaults); - const { model } = this.input; - if (model instanceof ONNXTransformerJsModel) { - return new HuggingFaceLocal_DownloadTask(this.config, { model }); - } - if (model instanceof MediaPipeTfJsModel) { - return new MediaPipeTfJsLocal_DownloadTask(this.config, { model }); - } - } -} - -export interface EmbeddingTaskInput { - text: string; - model: Model; -} -/** - * This is a task that generates an embedding for a single piece of text - */ -export class EmbeddingTask extends ModelFactoryTask { - declare input: EmbeddingTaskInput; - readonly type: StreamableTaskType = "EmbeddingTask"; - constructor(config: TaskConfig = {}, defaults: EmbeddingTaskInput) { - super(config, defaults); - const { text, model } = this.input; - if (model instanceof ONNXTransformerJsModel) { - return new HuggingFaceLocal_EmbeddingTask(this.config, { text, model }); - } - if (model instanceof MediaPipeTfJsModel) { - return new MediaPipeTfJsLocal_EmbeddingTask(this.config, { text, model }); - } - } -} - -export interface TextGenerationTaskInput { - text: string; - model: Model; -} -export class TextGenerationTask extends ModelFactoryTask { - readonly type: StreamableTaskType = "TextGenerationTask"; - declare input: TextGenerationTaskInput; - constructor(config: TaskConfig = {}, input: TextGenerationTaskInput) { - super(config, input); - const { text, model } = this.input; - if (model instanceof ONNXTransformerJsModel) { - return new HuggingFaceLocal_TextGenerationTask(this.config, { - text, - model, - }); - } - } -} - -export class SummarizeTask extends ModelFactoryTask { - declare input: TextGenerationTaskInput; - readonly type: StreamableTaskType = "SummarizeTask"; - constructor(config: TaskConfig = {}, input: TextGenerationTaskInput) { - super(config, input); - const { text, model } = this.input; - if (model instanceof ONNXTransformerJsModel) { - return new HuggingFaceLocal_SummarizationTask(this.config, { - text, - model, - }); - } - } -} - -export interface RewriterTaskInput { - text: string; - prompt: string; - model: Model; -} - -export class RewriterTask extends ModelFactoryTask { - readonly type: StreamableTaskType = "RewriterTask"; - declare input: RewriterTaskInput; - constructor(config: TaskConfig = {}, input: RewriterTaskInput) { - super(config, input); - const { text, model, prompt } = this.input; - if (model instanceof ONNXTransformerJsModel) { - return new HuggingFaceLocal_TextRewriterTask(this.config, { - text, - prompt, - model, - }); - } - } -} - -export interface QuestionAnswerTaskInput { - text: string; - context: string; - model: Model; -} -export class QuestionAnswerTask extends ModelFactoryTask { - declare input: QuestionAnswerTaskInput; - readonly type: StreamableTaskType = "QuestionAnswerTask"; - constructor(config: TaskConfig = {}, input: QuestionAnswerTaskInput) { - super(config, input); - const { text, model, context } = this.input; - if (model instanceof ONNXTransformerJsModel) { - return new HuggingFaceLocal_QuestionAnswerTask(this.config, { - text, - context, - model, - }); - } - } -} diff --git a/src/tasks/HuggingFaceLocalTasks.ts b/src/tasks/HuggingFaceLocalTasks.ts deleted file mode 100644 index 87ecf8208..000000000 --- a/src/tasks/HuggingFaceLocalTasks.ts +++ /dev/null @@ -1,331 +0,0 @@ -// ******************************************************************************* -// * ELLMERS: Embedding Large Language Model Experiential Retrieval Service * -// * * -// * Copyright Steven Roussey * -// * Licensed under the Apache License, Version 2.0 (the "License"); * -// ******************************************************************************* - -import { Model, ModelProcessorEnum, ModelUseCaseEnum } from "#/Model"; -import { StreamableTaskType, Task, TaskConfig } from "#/Task"; -import { - pipeline, - type PipelineType, - type FeatureExtractionPipeline, - type TextGenerationPipeline, - type TextGenerationSingle, - type SummarizationPipeline, - type SummarizationSingle, - type QuestionAnsweringPipeline, - type DocumentQuestionAnsweringSingle, - env, -} from "@sroussey/transformers"; - -env.backends.onnx.logLevel = "error"; -env.backends.onnx.debug = false; - -export class ONNXTransformerJsModel extends Model { - constructor( - name: string, - useCase: ModelUseCaseEnum[], - public pipeline: string, - options?: Partial> - ) { - super(name, useCase, options); - } - readonly type = ModelProcessorEnum.LOCAL_ONNX_TRANSFORMERJS; -} - -/** - * - * This is a helper function to get a pipeline for a model and assign a - * progress callback to the task. - * - * @param task - * @param model - * @param options - */ -const getPipeline = async ( - task: Task, - model: ONNXTransformerJsModel, - { quantized, config }: { quantized: boolean; config: any } = { - quantized: true, - config: null, - } -) => { - return await pipeline(model.pipeline as PipelineType, model.name, { - quantized, - config, - progress_callback: (details: { - file: string; - status: string; - name: string; - progress: number; - loaded: number; - total: number; - }) => { - const { progress, file } = details; - task.progress = progress; - task.emit("progress", progress, file); - }, - }); -}; - -// =============================================================================== - -interface DownloadTaskInput { - model: ONNXTransformerJsModel; -} -export class HuggingFaceLocal_DownloadTask extends Task { - declare input: DownloadTaskInput; - declare defaults: Partial; - readonly type: StreamableTaskType = "DownloadTask"; - constructor(config: TaskConfig = {}, defaults: DownloadTaskInput) { - config.name ||= `Downloading ${defaults.model.name}`; - super(config, defaults); - } - - public async run(overrides?: Partial) { - this.input = this.withDefaults(overrides); - try { - this.emit("start"); - await getPipeline(this, this.input.model); - this.emit("complete"); - } catch (e) { - this.emit("error", String(e)); - } - return this.output; - } -} - -// =============================================================================== - -interface EmbeddingTaskInput { - text: string; - model: ONNXTransformerJsModel; -} -/** - * This is a task that generates an embedding for a single piece of text - * - * Model pipeline must be "feature-extraction" - */ -export class HuggingFaceLocal_EmbeddingTask extends Task { - declare input: EmbeddingTaskInput; - declare defaults: Partial; - readonly type: StreamableTaskType = "EmbeddingTask"; - constructor(config: TaskConfig = {}, defaults: EmbeddingTaskInput) { - config.name ||= `Embedding content via ${defaults.model.name}`; - config.output_name ||= "vector"; - super(config, defaults); - } - - public async run(overrides?: Partial) { - this.input = this.withDefaults(overrides); - - this.emit("start"); - - const generateEmbedding = (await getPipeline( - this, - this.input.model - )) as FeatureExtractionPipeline; - - var vector = await generateEmbedding(this.input.text, { - pooling: "mean", - normalize: this.input.model.normalize, - }); - - if (vector.size !== this.input.model.dimensions) { - this.emit( - "error", - `Embedding vector length does not match model dimensions v${vector.size} != m${this.input.model.dimensions}` - ); - } else { - this.output = { [this.config.output_name]: vector.data }; - this.emit("complete"); - } - return this.output; - } -} - -// =============================================================================== - -interface TextGenerationTaskInput { - text: string; - model: ONNXTransformerJsModel; -} -abstract class TextGenerationTaskBase extends Task { - declare input: TextGenerationTaskInput; - constructor(config: TaskConfig = {}, input: TextGenerationTaskInput) { - config.name ||= `Text generation content via ${input.model.name} : ${input.model.pipeline}`; - config.output_name ||= "text"; - super(config, input); - } -} - -// =============================================================================== - -/** - * This generates text from a prompt - * - * Model pipeline must be "text-generation" or "text2text-generation" - */ -export class HuggingFaceLocal_TextGenerationTask extends TextGenerationTaskBase { - readonly type: StreamableTaskType = "TextGenerationTask"; - public async run(overrides?: Partial) { - this.input = this.withDefaults(overrides); - - this.emit("start"); - - const generateText = (await getPipeline( - this, - this.input.model - )) as TextGenerationPipeline; - - let results = await generateText(this.input.text); - if (!Array.isArray(results)) { - results = [results]; - } - - this.output = { - [this.config.output_name]: (results[0] as TextGenerationSingle) - ?.generated_text, - }; - this.emit("complete"); - return this.output; - } -} - -// =============================================================================== - -interface RewriterTaskInput { - text: string; - prompt: string; - model: ONNXTransformerJsModel; -} - -/** - * This is a special case of text generation that takes a prompt and text to rewrite - * - * Model pipeline must be "text-generation" or "text2text-generation" - */ -export class HuggingFaceLocal_TextRewriterTask extends TextGenerationTaskBase { - declare input: RewriterTaskInput; - readonly type: StreamableTaskType = "RewriterTask"; - constructor(config: TaskConfig = {}, input: RewriterTaskInput) { - const { model } = input; - config.name ||= `Text to text rewriting content via ${model.name} : ${model.pipeline}`; - config.output_name ||= "text"; - super(config, input); - } - - public async run(overrides?: Partial) { - this.input = this.withDefaults(overrides); - this.emit("start"); - - const generateText = (await getPipeline( - this, - this.input.model - )) as TextGenerationPipeline; - - // This lib doesn't support this kind of rewriting with a separate prompt vs text - const promptedtext = - (this.input.prompt ? this.input.prompt + "\n" : "") + this.input.text; - let results = await generateText(promptedtext); - if (!Array.isArray(results)) { - results = [results]; - } - - const text = (results[0] as TextGenerationSingle)?.generated_text; - if (text == promptedtext) { - this.output = {}; - this.emit("error", "Rewriter failed to generate new text"); - } else { - this.output = { [this.config.output_name]: text }; - this.emit("complete"); - } - - return this.output; - } -} - -// =============================================================================== - -/** - * This is a special case of text generation that takes a context and a question - * - * Model pipeline must be "summarization" - */ - -export class HuggingFaceLocal_SummarizationTask extends TextGenerationTaskBase { - readonly type: StreamableTaskType = "SummarizeTask"; - public async run(overrides?: Partial) { - this.emit("start"); - - this.input = this.withDefaults(overrides); - - const generateSummary = (await getPipeline( - this, - this.input.model - )) as SummarizationPipeline; - - let results = await generateSummary(this.input.text); - if (!Array.isArray(results)) { - results = [results]; - } - - this.output = { - [this.config.output_name]: (results[0] as SummarizationSingle) - ?.summary_text, - }; - this.emit("complete"); - return this.output; - } -} - -// =============================================================================== - -interface QuestionAnswerTaskInput { - text: string; - context: string; - model: ONNXTransformerJsModel; - topk?: number; -} -/** - * This is a special case of text generation that takes a context and a question - * - * Model pipeline must be "question-answering" - */ -export class HuggingFaceLocal_QuestionAnswerTask extends TextGenerationTaskBase { - declare input: QuestionAnswerTaskInput; - readonly type: StreamableTaskType = "QuestionAnswerTask"; - constructor(config: TaskConfig = {}, input: QuestionAnswerTaskInput) { - config.name = - config.name || `Question and Answer content via ${input.model.name}`; - config.output_name ||= "text"; - super(config, input); - } - - public async run(overrides?: Partial) { - this.emit("start"); - - this.input = this.withDefaults(overrides); - - const generateAnswer = (await getPipeline( - this, - this.input.model - )) as QuestionAnsweringPipeline; - - let results = await generateAnswer(this.input.text, this.input.context, { - topk: this.input.topk ?? 1, - }); - if (!Array.isArray(results)) { - results = [results]; - } - - this.output = { - [this.config.output_name]: (results[0] as DocumentQuestionAnsweringSingle) - ?.answer, - }; - this.emit("complete"); - return this.output; - } -} diff --git a/src/tasks/JsonTask.ts b/src/tasks/JsonTask.ts deleted file mode 100644 index 30c0c2572..000000000 --- a/src/tasks/JsonTask.ts +++ /dev/null @@ -1,184 +0,0 @@ -// ******************************************************************************* -// * ELLMERS: Embedding Large Language Model Experiential Retrieval Service * -// * * -// * Copyright Steven Roussey * -// * Licensed under the Apache License, Version 2.0 (the "License"); * -// ******************************************************************************* - -import { - ParallelTaskList, - SerialStrategy, - SerialTaskList, - StreamableTaskType, - TaskConfig, - TaskStreamable, -} from "#/Task"; -import { findModelByName } from "#/storage/InMemoryStorage"; -import { RenameTask, RenameTaskInput } from "./BasicTasks"; -import { - EmbeddingTask, - EmbeddingTaskInput, - QuestionAnswerTask, - QuestionAnswerTaskInput, - RewriterTask, - RewriterTaskInput, - SummarizeTask, - TextGenerationTask, - TextGenerationTaskInput, -} from "./FactoryTasks"; -import { - EmbeddingStrategy, - EmbeddingStrategyInput, - RewriterEmbeddingStrategy, - RewriterEmbeddingStrategyInput, - RewriterStrategy, - RewriterStrategyInput, - SummarizeStrategy, - SummarizeStrategyInput, -} from "./Strategies"; - -const AllRegisteredTasks = new Map(); - -AllRegisteredTasks.set("SerialTaskList", SerialTaskList); -AllRegisteredTasks.set("ParallelTaskList", ParallelTaskList); - -AllRegisteredTasks.set("RenameTask", RenameTask); - -AllRegisteredTasks.set("EmbeddingTask", EmbeddingTask); -AllRegisteredTasks.set("RewriterTask", RewriterTask); -AllRegisteredTasks.set("TextGenerationTask", TextGenerationTask); -AllRegisteredTasks.set("SummarizeTask", SummarizeTask); -AllRegisteredTasks.set("QuestionAnswerTask", QuestionAnswerTask); - -AllRegisteredTasks.set("EmbeddingStrategy", EmbeddingStrategy); -AllRegisteredTasks.set("RewriterStrategy", RewriterStrategy); -AllRegisteredTasks.set("SummarizeStrategy", SummarizeStrategy); -AllRegisteredTasks.set("RewriterEmbeddingStrategy", RewriterEmbeddingStrategy); - -type TaskListJsonInput = { - run: "SerialTaskList" | "ParallelTaskList"; - config?: TaskConfig; - tasks: TaskJsonInput[]; -}; - -type SimpleTasks = { - run: "RenameTask"; - config?: TaskConfig; - input: RenameTaskInput; -}; - -type ChangeToString = { - [P in keyof T]: P extends K[number] - ? string - : T[P] extends object - ? ChangeToString - : T[P]; -}; -type ChangeToStringArray = { - [P in keyof T]: P extends K[number] - ? string | string[] - : T[P] extends object - ? ChangeToString - : T[P]; -}; -type FactoryHelper = { - run: R; - config?: TaskConfig; - input: ChangeToString; -}; - -type StrategyHelper = { - run: R; - config?: TaskConfig; - input: ChangeToStringArray< - T, - ["model", "models", "prompt_model", "embed_model"] - >; -}; - -type FactoryTasksJsonInput = - | FactoryHelper - | FactoryHelper - | FactoryHelper - | FactoryHelper - | FactoryHelper; - -type StrategyJSONInput = - | StrategyHelper - | StrategyHelper - | StrategyHelper - | StrategyHelper; - -export type TaskJsonInput = - | StrategyJSONInput - | TaskListJsonInput - | SimpleTasks - | FactoryTasksJsonInput; - -function makeArrayOfModel(model: string | string[] | undefined) { - if (!model) return undefined; - const modelstrs = Array.isArray(model) ? model : [model]; - const models = modelstrs.map((s) => { - const found = findModelByName(s); - if (!found) throw new Error(`Model not found: ${s}`); - return found; - }); - return models; -} -function convertJson(json: TaskJsonInput): TaskStreamable { - const { run, config } = json; - const runTask = AllRegisteredTasks.get(run); - if (!runTask) throw new Error("Task not found"); - let result: TaskStreamable; - if (run == "SerialTaskList" || run == "ParallelTaskList") { - const tasks = json.tasks.map(convertJson); - result = new runTask(config, tasks); - } else if ( - run == "EmbeddingTask" || - run == "RewriterTask" || - run == "SummarizeTask" || - run == "TextGenerationTask" || - run == "QuestionAnswerTask" - ) { - const input = json.input; - const model = findModelByName(input.model); - if (!model) throw new Error(`Model not found: ${input.model}`); - result = new runTask(config, { ...input, model }); - } else if (run == "RewriterStrategy") { - const input = json.input; - const model = makeArrayOfModel(input.model); - result = new runTask(config, { ...input, model }); - } else if (run == "RewriterEmbeddingStrategy") { - const input = json.input; - const embed_model = makeArrayOfModel(input.embed_model); - const prompt_model = makeArrayOfModel(input.prompt_model); - result = new runTask(config, { ...input, embed_model, prompt_model }); - } else if (run == "RenameTask") { - result = new runTask(config, json.input); - } else { - throw new Error(`Unknown task type: ${run}`); - } - return result; -} - -export class JsonStrategy extends SerialStrategy { - declare input: { tasks: TaskJsonInput[] }; - readonly type: StreamableTaskType = "JsonStrategy"; - constructor( - config: TaskConfig = {}, - defaults?: TaskJsonInput | TaskJsonInput[] - ) { - const tasks = Array.isArray(defaults) ? defaults : [defaults]; - super(config, { tasks }); - } - generateTasks() { - let tasks: TaskStreamable[]; - try { - tasks = this.input.tasks.map(convertJson); - } catch (e) { - throw new Error(`Error converting json: ${String(e)}`); - } - if (!tasks) throw new Error("Task not found"); - this.setTasks(tasks); - } -} diff --git a/src/tasks/MediaPipeLocalTasks.ts b/src/tasks/MediaPipeLocalTasks.ts deleted file mode 100644 index 17538c550..000000000 --- a/src/tasks/MediaPipeLocalTasks.ts +++ /dev/null @@ -1,111 +0,0 @@ -// ******************************************************************************* -// * ELLMERS: Embedding Large Language Model Experiential Retrieval Service * -// * * -// * Copyright Steven Roussey * -// * Licensed under the Apache License, Version 2.0 (the "License"); * -// ******************************************************************************* - -import { Model, ModelProcessorEnum, ModelUseCaseEnum } from "#/Model"; -import { StreamableTaskType, Task, TaskConfig } from "#/Task"; -import { FilesetResolver, TextEmbedder } from "@mediapipe/tasks-text"; - -export class MediaPipeTfJsModel extends Model { - constructor( - name: string, - useCase: ModelUseCaseEnum[], - public url: string, - options?: Partial< - Pick - > - ) { - super(name, useCase, options); - } - readonly type = ModelProcessorEnum.MEDIA_PIPE_TFJS_MODEL; -} - -// =============================================================================== - -interface DownloadTaskInput { - model: MediaPipeTfJsModel; -} -export class MediaPipeTfJsLocal_DownloadTask extends Task { - declare input: DownloadTaskInput; - declare defaults: Partial; - readonly type: StreamableTaskType = "DownloadTask"; - constructor(config: TaskConfig = {}, defaults: DownloadTaskInput) { - config.name ||= `Downloading ${defaults.model.name}`; - super(config, defaults); - } - - public async run(overrides?: Partial) { - this.input = this.withDefaults(overrides); - try { - this.emit("start"); - const textFiles = await FilesetResolver.forTextTasks( - "https://cdn.jsdelivr.net/npm/@mediapipe/tasks-text@latest/wasm/" - ); - await TextEmbedder.createFromOptions(textFiles, { - baseOptions: { - modelAssetPath: this.input.model.url, - }, - quantize: true, - }); - this.emit("complete"); - } catch (e) { - this.emit("error", String(e)); - } - return this.output; - } -} - -// =============================================================================== - -interface EmbeddingTaskInput { - text: string; - model: MediaPipeTfJsModel; -} -/** - * This is a task that generates an embedding for a single piece of text - * - * Model pipeline must be "feature-extraction" - */ -export class MediaPipeTfJsLocal_EmbeddingTask extends Task { - declare input: EmbeddingTaskInput; - declare defaults: Partial; - readonly type: StreamableTaskType = "EmbeddingTask"; - constructor(config: TaskConfig = {}, defaults: EmbeddingTaskInput) { - config.name ||= `Embedding content via ${defaults.model.name}`; - config.output_name ||= "vector"; - super(config, defaults); - } - - public async run(overrides?: Partial) { - this.input = this.withDefaults(overrides); - - this.emit("start"); - - const textFiles = await FilesetResolver.forTextTasks( - "https://cdn.jsdelivr.net/npm/@mediapipe/tasks-text@latest/wasm/" - ); - const textEmbedder = await TextEmbedder.createFromOptions(textFiles, { - baseOptions: { - modelAssetPath: this.input.model.url, - }, - quantize: true, - }); - - const output = textEmbedder.embed(this.input.text); - const vector = output.embeddings[0].floatEmbedding; - - if (vector?.length !== this.input.model.dimensions) { - this.emit( - "error", - `Embedding vector length does not match model dimensions v${vector?.length} != m${this.input.model.dimensions}` - ); - } else { - this.output = { [this.config.output_name]: vector }; - this.emit("complete"); - } - return this.output; - } -} diff --git a/src/tasks/Strategies.ts b/src/tasks/Strategies.ts deleted file mode 100644 index 7968fc1b1..000000000 --- a/src/tasks/Strategies.ts +++ /dev/null @@ -1,166 +0,0 @@ -// ******************************************************************************* -// * ELLMERS: Embedding Large Language Model Experiential Retrieval Service * -// * * -// * Copyright Steven Roussey * -// * Licensed under the Apache License, Version 2.0 (the "License"); * -// ******************************************************************************* - -/* - -TODO: Still need to save the task tree to disc to restore later, though the JSON -strategy might be the thing to use. - -*/ - -import { Model } from "#/Model"; -import { - ParallelStrategy, - ParallelTaskList, - SerialTaskList, - StreamableTaskType, - TaskConfig, - TaskStream, -} from "#/Task"; -import { forceArray } from "#/util/Misc"; -import { EmbeddingTask, RewriterTask, SummarizeTask } from "./FactoryTasks"; - -export interface EmbeddingStrategyInput { - text: string; - models: Model[]; -} -export class EmbeddingStrategy extends ParallelStrategy { - declare input: EmbeddingStrategyInput; - readonly type: StreamableTaskType = "EmbeddingStrategy"; - constructor(config: TaskConfig = {}, defaults?: EmbeddingStrategyInput) { - super(config, defaults); - } - - generateTasks() { - const tasks = this.input.models.map( - (model) => new EmbeddingTask({}, { text: this.input.text, model }) - ); - - this.setTasks(tasks); - } -} - -export interface SummarizeStrategyInput { - text: string; - models: Model[]; -} -export class SummarizeStrategy extends ParallelStrategy { - declare input: SummarizeStrategyInput; - readonly type: StreamableTaskType = "SummarizeStrategy"; - - constructor(config: TaskConfig = {}, defaults?: SummarizeStrategyInput) { - super(config, defaults); - } - - generateTasks() { - const tasks = this.input.models.map( - (model) => new SummarizeTask({}, { text: this.input.text, model }) - ); - this.setTasks(tasks); - } -} - -export interface RewriterStrategyInput { - text: string; - prompt?: string | string[]; - model?: Model | Model[]; - prompt_model_pair?: { prompt: string; model: Model }[]; -} -export class RewriterStrategy extends ParallelStrategy { - declare input: RewriterStrategyInput; - readonly type: StreamableTaskType = "RewriterStrategy"; - - constructor(config: TaskConfig = {}, defaults?: RewriterStrategyInput) { - super(config, defaults); - } - - generateTasks() { - const name = this.config.name || `Vary Rewriter content`; - const { text, prompt_model_pair, model, prompt } = this.input; - let pairs: { prompt: string; model: Model }[] = []; - if (prompt_model_pair) { - pairs = forceArray(prompt_model_pair); - } else { - if (!prompt || !model) throw new Error("Invalid input"); - const models = forceArray(model); - const prompts = forceArray(prompt); - for (const model of models) { - for (const prompt of prompts) { - pairs.push({ prompt, model }); - } - } - } - const tasks = pairs.map( - ({ prompt, model }) => new RewriterTask({}, { text, prompt, model }) - ); - - this.setTasks(tasks); - } -} - -export interface RewriterEmbeddingStrategyInput { - text: string; - prompt?: string | string[]; - prompt_model?: Model | Model[]; - embed_model?: Model | Model[]; - prompt_model_tuple?: { - prompt: string; - prompt_model: Model; - embed_model: Model; - }[]; -} - -export class RewriterEmbeddingStrategy extends ParallelStrategy { - declare input: RewriterEmbeddingStrategyInput; - readonly type: StreamableTaskType = "RewriterEmbeddingStrategy"; - constructor(config: TaskConfig, defaults: RewriterEmbeddingStrategyInput) { - super(config, defaults); - } - - generateTasks() { - const name = this.config.name || `RewriterEmbeddingStrategy`; - const { text, prompt_model_tuple, prompt, embed_model, prompt_model } = - this.input; - let tasks: TaskStream = []; - if (prompt_model_tuple) { - const tuples = forceArray(prompt_model_tuple); - tasks = tuples.map(({ prompt, prompt_model, embed_model }) => { - return new SerialTaskList({ name }, [ - new RewriterTask( - { name: name + " Rewriter" }, - { text, prompt, model: prompt_model } - ), - new EmbeddingTask( - { name: name + " Embedding" }, - { text, model: embed_model } - ), - ]); - }); - } else { - if (!prompt || !prompt_model || !embed_model) - throw new Error("Invalid input"); - const prompt_models = forceArray(prompt_model); - const embed_models = forceArray(embed_model); - const prompts = forceArray(prompt); - for (const prompt of prompts) { - tasks.push( - new ParallelTaskList({ name }, [ - new RewriterStrategy( - { name: name + " Rewriter" }, - { text, prompt, model: prompt_models } - ), - new EmbeddingStrategy( - { name: name + " Embedding" }, - { text, models: embed_models } - ), - ]) - ); - } - } - this.setTasks(tasks); - } -} diff --git a/tsconfig.json b/tsconfig.json index 216523716..05206ff66 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,19 +6,15 @@ "moduleResolution": "bundler", "moduleDetection": "force", "allowImportingTsExtensions": true, - "noEmit": true, "composite": true, "strict": true, "downlevelIteration": true, "skipLibCheck": true, - "jsx": "react-jsx", "allowSyntheticDefaultImports": true, "forceConsistentCasingInFileNames": true, "allowJs": true, - "types": ["bun-types"], - "baseUrl": "./src", - "paths": { - "#/*": ["./*"] - } + "declaration": true, + "emitDeclarationOnly": true, + "types": ["bun-types"] } }