From 943be0c5d1e0cf04295ca68e986f6fa5e38f4c48 Mon Sep 17 00:00:00 2001 From: Guillaume Aquilina Date: Fri, 30 May 2025 09:25:29 -0400 Subject: [PATCH 1/4] feat: add use fallback --- packages/workflowai/src/WorkflowAI.ts | 4 +- .../workflowai/src/api/generated/openapi.ts | 5 ++ packages/workflowai/src/types.ts | 1 + tests/integration/agent.test.ts | 69 +++++++++++++++++++ 4 files changed, 78 insertions(+), 1 deletion(-) diff --git a/packages/workflowai/src/WorkflowAI.ts b/packages/workflowai/src/WorkflowAI.ts index aa6ad23..48d3b8d 100644 --- a/packages/workflowai/src/WorkflowAI.ts +++ b/packages/workflowai/src/WorkflowAI.ts @@ -37,7 +37,8 @@ function optionsToRunRequest( input: Record, options: Omit, 'fetch'> ): RunRequest { - const { version, stream, metadata, useCache, privateFields } = options; + const { version, stream, metadata, useCache, privateFields, useFallback } = + options; return { task_input: input, @@ -46,6 +47,7 @@ function optionsToRunRequest( metadata, use_cache: useCache || 'auto', private_fields: privateFields, + use_fallback: useFallback, }; } diff --git a/packages/workflowai/src/api/generated/openapi.ts b/packages/workflowai/src/api/generated/openapi.ts index 681acc8..a4ad900 100644 --- a/packages/workflowai/src/api/generated/openapi.ts +++ b/packages/workflowai/src/api/generated/openapi.ts @@ -83,6 +83,11 @@ export interface components { * @description Fields marked as private will not be saved, none by default. */ private_fields?: ('task_input' | string)[] | null; + /** + * Use Fallback + * @description Whether to use fallback models if the primary model fails. + */ + use_fallback?: 'never' | 'auto' | string[] | null; }; /** * TaskGroupProperties diff --git a/packages/workflowai/src/types.ts b/packages/workflowai/src/types.ts index fecf810..b7f7370 100644 --- a/packages/workflowai/src/types.ts +++ b/packages/workflowai/src/types.ts @@ -14,6 +14,7 @@ export type RunOptions = { stream?: Stream; fetch?: FetchOptions; privateFields?: ('task_input' | 'task_output' | string)[]; + useFallback?: 'never' | 'auto' | string[]; }; export type FileContentType = diff --git a/tests/integration/agent.test.ts b/tests/integration/agent.test.ts index 5d41102..13a13ad 100644 --- a/tests/integration/agent.test.ts +++ b/tests/integration/agent.test.ts @@ -202,4 +202,73 @@ describe('run', () => { expect(error.errorCode).toBeFalsy(); } }); + + it('correctly handles useFallback when passed to the run function', async () => { + const run1Fixture = await readFile(fixturePath('run1.json'), 'utf-8'); + mockFetch.mockResponseOnce(run1Fixture); + + const result = await run({ animal: 'platypus' }, { useFallback: 'never' }); + expect(result.output).toEqual({ + is_cute: true, + is_dangerous: true, + explanation_of_reasoning: 'Plat plat', + }); + + expect(mockFetch.mock.calls.length).toEqual(1); + const req = mockFetch.mock.calls[0][0] as Request; + expect(req.url).toEqual( + 'https://run.workflowai.com/v1/_/agents/animal-classification/schemas/4/run' + ); + expect(req.method).toEqual('POST'); + expect(req.headers.get('Authorization')).toEqual('Bearer hello'); + const body = await req.json(); + expect(body).toEqual({ + version: 'production', + stream: false, + task_input: { + animal: 'platypus', + }, + use_cache: 'auto', + use_fallback: 'never', + }); + }); + + it('correctly handles useFallback when passed to the agent', async () => { + const run2 = workflowAI.agent< + AnimalClassificationInput, + AnimalClassificationOutput + >({ + id: 'animal-classification', + schemaId: 4, + version: 'production', + useFallback: ['gpt-4o-mini', 'gpt-4o'], + }); + const run1Fixture = await readFile(fixturePath('run1.json'), 'utf-8'); + mockFetch.mockResponseOnce(run1Fixture); + + const result = await run2({ animal: 'platypus' }); + expect(result.output).toEqual({ + is_cute: true, + is_dangerous: true, + explanation_of_reasoning: 'Plat plat', + }); + + expect(mockFetch.mock.calls.length).toEqual(1); + const req = mockFetch.mock.calls[0][0] as Request; + expect(req.url).toEqual( + 'https://run.workflowai.com/v1/_/agents/animal-classification/schemas/4/run' + ); + expect(req.method).toEqual('POST'); + expect(req.headers.get('Authorization')).toEqual('Bearer hello'); + const body = await req.json(); + expect(body).toEqual({ + version: 'production', + stream: false, + task_input: { + animal: 'platypus', + }, + use_cache: 'auto', + use_fallback: ['gpt-4o-mini', 'gpt-4o'], + }); + }); }); From adfe01113705f1a35005621df427a3bc3adea7e6 Mon Sep 17 00:00:00 2001 From: Guillaume Aquilina Date: Fri, 30 May 2025 09:28:51 -0400 Subject: [PATCH 2/4] feat: add readme for use fallback --- packages/workflowai/README.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/workflowai/README.md b/packages/workflowai/README.md index da93341..4881f10 100644 --- a/packages/workflowai/README.md +++ b/packages/workflowai/README.md @@ -48,13 +48,19 @@ const getCapitalInfo = workflowAI.agent< id: 'get-capital-info', schemaId: 1, version: '1.4', - // Cache options: + // Cache options (can also be passed to the run function): // - "auto" (default): if a previous run exists with the same version and input, and if // the temperature is 0, the cached output is returned // - "always": the cached output is returned when available, regardless // of the temperature value // - "never": the cache is never used useCache: 'auto', + // Customize model fallback, (can also be passed to the run function): + // - defaults to 'auto' meaning that the model to fallback to is picked by WorkflowAI + // The selected model is in the same price range and depends on the error that was triggered + // - "never": the fallback is never used + // - list of model names: models to try in order after the primary model fails + useFallback: ['gpt-4o-mini', 'gpt-4o'], }); // Run Your AI agent @@ -69,6 +75,12 @@ async function getCapitalInfoRun() { data: { duration_seconds, cost_usd, version }, } = await getCapitalInfo(input); + // Also possible to pass options to the run function: + // const { output } = await getCapitalInfo(input, { + // useCache: 'always', + // useFallback: 'never', + // }); + console.log(output); console.log('\nModel: ', version?.properties?.model); console.log('Cost: $', cost_usd); From 5c1b94fcaffaa58f18d3eea8bb32e4b8a132ac87 Mon Sep 17 00:00:00 2001 From: Guillaume Aquilina Date: Fri, 30 May 2025 09:31:41 -0400 Subject: [PATCH 3/4] chore: bump sdk --- packages/workflowai/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/workflowai/package.json b/packages/workflowai/package.json index 943efac..1516152 100644 --- a/packages/workflowai/package.json +++ b/packages/workflowai/package.json @@ -1,6 +1,6 @@ { "name": "@workflowai/workflowai", - "version": "1.6.7", + "version": "1.6.8", "type": "module", "description": "WorkflowAI JS SDK", "author": "WorkflowAI", From 7582f0d946ba680a4a10197e7291853e6d56048a Mon Sep 17 00:00:00 2001 From: Guillaume Aquilina Date: Fri, 30 May 2025 09:31:52 -0400 Subject: [PATCH 4/4] fix: vulnerabilities --- package-lock.json | 99 ++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 90 insertions(+), 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index 93f7d59..60926bb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11994,6 +11994,54 @@ "dev": true, "license": "MIT" }, + "node_modules/tinyglobby": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", + "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "fdir": "^6.4.4", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.4.5", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.5.tgz", + "integrity": "sha512-4BG7puHpVsIYxZUbiUE3RqGloLaSSwzYie5jvasC4LWuBWzZawynvYouhjbQKw2JuIGYdm0DzIxl8iVidKlUEw==", + "dev": true, + "license": "MIT", + "peer": true, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/tinyrainbow": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz", @@ -12418,9 +12466,9 @@ "dev": true }, "node_modules/undici": { - "version": "5.28.5", - "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.5.tgz", - "integrity": "sha512-zICwjrDrcrUE0pyyJc1I2QzBkLM8FINsgOrt6WjA+BgajVq9Nxu2PbFFXUrAggLfDXlZGZBVZYw7WNV5KiBiBA==", + "version": "5.29.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.29.0.tgz", + "integrity": "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==", "dev": true, "license": "MIT", "dependencies": { @@ -12550,16 +12598,19 @@ } }, "node_modules/vite": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.2.2.tgz", - "integrity": "sha512-yW7PeMM+LkDzc7CgJuRLMW2Jz0FxMOsVJ8Lv3gpgW9WLcb9cTW+121UEr1hvmfR7w3SegR5ItvYyzVz1vxNJgQ==", + "version": "6.3.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", + "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==", "dev": true, "license": "MIT", "peer": true, "dependencies": { "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", "postcss": "^8.5.3", - "rollup": "^4.30.1" + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" }, "bin": { "vite": "bin/vite.js" @@ -12642,6 +12693,36 @@ } } }, + "node_modules/vite/node_modules/fdir": { + "version": "6.4.5", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.5.tgz", + "integrity": "sha512-4BG7puHpVsIYxZUbiUE3RqGloLaSSwzYie5jvasC4LWuBWzZawynvYouhjbQKw2JuIGYdm0DzIxl8iVidKlUEw==", + "dev": true, + "license": "MIT", + "peer": true, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/walker": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", @@ -13000,7 +13081,7 @@ }, "packages/react": { "name": "@workflowai/react", - "version": "0.1.0-beta.0", + "version": "0.1.0-beta.1", "devDependencies": { "@types/react": "^18.2.0", "@types/react-dom": "^18.2.0", @@ -13028,7 +13109,7 @@ }, "packages/workflowai": { "name": "@workflowai/workflowai", - "version": "1.6.7", + "version": "1.6.8", "dependencies": { "fetch-event-stream": "^0.1.5", "fetch-retry": "^6.0.0",