From 0a4b9ad39b75b8f95fc0f3c4e48582994eaf344f Mon Sep 17 00:00:00 2001 From: Philipp Burckhardt Date: Sun, 4 Jun 2023 11:31:03 -0400 Subject: [PATCH] fix: update feedback implementation --- examples/human-feedback-select.ts | 32 ++++ examples/human-feedback.ts | 36 ++-- package.json | 4 +- pnpm-lock.yaml | 201 ++++++++++------------ src/tools/feedback.ts | 268 ++++++++++++++++-------------- 5 files changed, 282 insertions(+), 259 deletions(-) create mode 100644 examples/human-feedback-select.ts diff --git a/examples/human-feedback-select.ts b/examples/human-feedback-select.ts new file mode 100644 index 00000000..60c4677c --- /dev/null +++ b/examples/human-feedback-select.ts @@ -0,0 +1,32 @@ +import 'dotenv/config' +import { OpenAIClient } from 'openai-fetch' +import { z } from 'zod' + +import { Agentic, HumanFeedbackSelect } from '../src' + +async function main() { + const openai = new OpenAIClient({ apiKey: process.env.OPENAI_API_KEY! }) + const ai = new Agentic({ openai }) + + const jokes = ai + .gpt3(`Tell me {{num}} jokes about {{topic}}`) + .input( + z.object({ + topic: z.string(), + num: z.number().int().default(5).optional() + }) + ) + .output(z.array(z.string())) + .modelParams({ temperature: 0.9 }) + + const feedback = new HumanFeedbackSelect(z.string()) + let out = await jokes.call({ topic: 'statisticians' }) + let hf = await feedback.call(out) + while (!hf.accepted) { + out = await jokes.call({ topic: 'statisticians' }) + hf = await feedback.call(out) + } + console.log(hf.results) +} + +main() diff --git a/examples/human-feedback.ts b/examples/human-feedback.ts index 6a70a7dd..d9646303 100644 --- a/examples/human-feedback.ts +++ b/examples/human-feedback.ts @@ -2,30 +2,32 @@ import 'dotenv/config' import { OpenAIClient } from 'openai-fetch' import { z } from 'zod' -import { Agentic, HumanFeedback } from '../src' +import { Agentic, HumanFeedbackSingle } from '../src' async function main() { const openai = new OpenAIClient({ apiKey: process.env.OPENAI_API_KEY! }) const ai = new Agentic({ openai }) - const feedback = new HumanFeedback() - let retry = true - while (retry) { - const out = await ai - .gpt3(`Give me {{numFacts}} random facts about {{topic}}`) - .input( - z.object({ - topic: z.string(), - numFacts: z.number().int().default(5).optional() - }) - ) - .output(z.object({ facts: z.array(z.string()) })) - .modelParams({ temperature: 0.9 }) - .call({ topic: 'cats' }) + const topicFacts = ai + .gpt3(`Give me {{numFacts}} random facts about {{topic}}`) + .input( + z.object({ + topic: z.string(), + numFacts: z.number().int().default(5).optional() + }) + ) + .output(z.object({ facts: z.array(z.string()) })) + .modelParams({ temperature: 0.9 }) - retry = await feedback.process(out).requestFeedback() + const feedback = new HumanFeedbackSingle(topicFacts.outputSchema) + + let out = await topicFacts.call({ topic: 'cats' }) + let hf = await feedback.call(out) + while (!hf.accepted) { + out = await topicFacts.call({ topic: 'cats' }) + hf = await feedback.call(out) } - console.log(feedback.getResult()) + console.log(hf.result) } main() diff --git a/package.json b/package.json index a0b1fb34..fa87d135 100644 --- a/package.json +++ b/package.json @@ -39,8 +39,10 @@ }, "dependencies": { "@anthropic-ai/sdk": "^0.4.3", + "@inquirer/checkbox": "^1.3.0", + "@inquirer/editor": "^1.1.0", + "@inquirer/select": "^1.2.0", "handlebars": "^4.7.7", - "inquirer": "^9.2.6", "js-tiktoken": "^1.0.6", "jsonrepair": "^3.1.0", "ky": "^0.33.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e4de41b1..5a9ae174 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,12 +8,18 @@ dependencies: '@anthropic-ai/sdk': specifier: ^0.4.3 version: 0.4.3 + '@inquirer/checkbox': + specifier: ^1.3.0 + version: 1.3.0 + '@inquirer/editor': + specifier: ^1.1.0 + version: 1.1.0 + '@inquirer/select': + specifier: ^1.2.0 + version: 1.2.0 handlebars: specifier: ^4.7.7 version: 4.7.7 - inquirer: - specifier: ^9.2.6 - version: 9.2.6 js-tiktoken: specifier: ^1.0.6 version: 1.0.6 @@ -537,6 +543,60 @@ packages: resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==} dev: true + /@inquirer/checkbox@1.3.0: + resolution: {integrity: sha512-kfYE5BH7vO0j2IwSgxzDmKPzQm/OpLnIZEEbOetYM+k4+YKTbSqeqCu7VZl3d8/rtotgJQc7gb8u2pIVeJh3Mg==} + engines: {node: '>=14.18.0'} + dependencies: + '@inquirer/core': 2.0.0 + '@inquirer/type': 1.1.0 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + figures: 3.2.0 + dev: false + + /@inquirer/core@2.0.0: + resolution: {integrity: sha512-NnLGihYWEFVdFIoEDPwGO0jB5phuNcxTUHSNq5geyiOVQOnWNuX9x2rhPPeiikE/5fNXIBmqojD0+PiD9whtXw==} + engines: {node: '>=14.18.0'} + dependencies: + '@inquirer/type': 1.1.0 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + cli-spinners: 2.9.0 + cli-width: 4.0.0 + figures: 3.2.0 + mute-stream: 1.0.0 + run-async: 3.0.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 6.2.0 + dev: false + + /@inquirer/editor@1.1.0: + resolution: {integrity: sha512-ll6NfzutAuzRwNELERhECZCnAIzb2DdnRaNwtJ3Gfy6MrQBdYpFzGLwDyxB8+yf2iJoMOmsKWnzGbmvWNvSUhw==} + engines: {node: '>=14.18.0'} + dependencies: + '@inquirer/core': 2.0.0 + '@inquirer/type': 1.1.0 + chalk: 4.1.2 + external-editor: 3.1.0 + dev: false + + /@inquirer/select@1.2.0: + resolution: {integrity: sha512-2CqhtE40GFmRXDFzJeMvSowKcO2/yvIzgSpL44+Hl/SAO/1FJgmHNAFGBuqX0RbohYPnSpF8eftgiy16fA3RJw==} + engines: {node: '>=14.18.0'} + dependencies: + '@inquirer/core': 2.0.0 + '@inquirer/type': 1.1.0 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + figures: 3.2.0 + dev: false + + /@inquirer/type@1.1.0: + resolution: {integrity: sha512-XMaorygt2o/mXinZg/OOz6d3JKuV3o4jRc/3KDiVPeKLLkjiO4iJErbLKtKn+Od2ZC2lbiFQkrIuloVpEubisA==} + engines: {node: '>=14.18.0'} + dev: false + /@ioredis/commands@1.2.0: resolution: {integrity: sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==} dev: true @@ -1030,14 +1090,6 @@ packages: engines: {node: '>=8'} dev: true - /bl@4.1.0: - resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} - dependencies: - buffer: 5.7.1 - inherits: 2.0.4 - readable-stream: 3.6.2 - dev: false - /blueimp-md5@2.19.0: resolution: {integrity: sha512-DRQrD6gJyy8FbiE4s+bDoXS9hiW3Vbx5uCdwvcCf3zLHL+Iv7LtGHLpr+GZV8rHG8tK766FGYBwRbu8pELTt+w==} dev: true @@ -1060,13 +1112,6 @@ packages: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} dev: true - /buffer@5.7.1: - resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} - dependencies: - base64-js: 1.5.1 - ieee754: 1.2.1 - dev: false - /bundle-require@4.0.1(esbuild@0.17.19): resolution: {integrity: sha512-9NQkRHlNdNpDBGmLpngF3EFDcwodhMUuLz9PaWYciVcQF9SE4LFjM2DB/xV1Li5JiuDMv7ZUWuC3rGbqR0MAXQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -1147,6 +1192,7 @@ packages: /chalk@5.2.0: resolution: {integrity: sha512-ree3Gqw/nazQAPuJJEy+avdl7QfZMcUvmHIKgEZkGL+xOBzRvup5Hxo6LHuMceSxOabuJLJm5Yp/92R9eMmMvA==} engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + dev: true /chardet@0.7.0: resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} @@ -1202,6 +1248,7 @@ packages: engines: {node: '>=8'} dependencies: restore-cursor: 3.1.0 + dev: true /cli-spinners@2.9.0: resolution: {integrity: sha512-4/aL9X3Wh0yiMQlE+eeRhWP6vclO3QRtw1JHKIT0FFUs5FjpFmESqtMvYZ0+lbzBw900b95mS0hohy+qn2VK/g==} @@ -1238,11 +1285,6 @@ packages: wrap-ansi: 7.0.0 dev: true - /clone@1.0.4: - resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} - engines: {node: '>=0.8'} - dev: false - /cluster-key-slot@1.1.2: resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==} engines: {node: '>=0.10.0'} @@ -1391,12 +1433,6 @@ packages: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} dev: true - /defaults@1.0.4: - resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==} - dependencies: - clone: 1.0.4 - dev: false - /define-properties@1.2.0: resolution: {integrity: sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==} engines: {node: '>= 0.4'} @@ -1575,7 +1611,6 @@ packages: /escape-string-regexp@1.0.5: resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} engines: {node: '>=0.8.0'} - dev: true /escape-string-regexp@2.0.0: resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} @@ -1590,6 +1625,7 @@ packages: /escape-string-regexp@5.0.0: resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} engines: {node: '>=12'} + dev: true /eslint-scope@5.1.1: resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} @@ -1781,12 +1817,20 @@ packages: reusify: 1.0.4 dev: true + /figures@3.2.0: + resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==} + engines: {node: '>=8'} + dependencies: + escape-string-regexp: 1.0.5 + dev: false + /figures@5.0.0: resolution: {integrity: sha512-ej8ksPF4x6e5wvK9yevct0UCXh8TTFlWGVLlgjZuoBH1HwjIfKE/IdL5mq89sFA7zELi1VhKpmtDnrs7zWyeyg==} engines: {node: '>=14'} dependencies: escape-string-regexp: 5.0.0 is-unicode-supported: 1.3.0 + dev: true /file-entry-cache@6.0.1: resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} @@ -2097,10 +2141,6 @@ packages: safer-buffer: 2.1.2 dev: false - /ieee754@1.2.1: - resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} - dev: false - /ignore-by-default@2.1.0: resolution: {integrity: sha512-yiWd4GVmJp0Q6ghmM2B/V3oZGRmjrKLXvHR3TE1nfoXsmoggllfZUQe74EN0fJdPFZu2NIvNdrMMLm3OsV7Ohw==} engines: {node: '>=10 <11 || >=12 <13 || >=14'} @@ -2143,27 +2183,7 @@ packages: /inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - - /inquirer@9.2.6: - resolution: {integrity: sha512-y71l237eJJKS4rl7sQcEUiMhrR0pB/ZnRMMTxLpjJhWL4hdWCT03a6jJnC1w6qIPSRZWEozuieGt3v7XaEJYFw==} - engines: {node: '>=14.18.0'} - dependencies: - ansi-escapes: 4.3.2 - chalk: 5.2.0 - cli-cursor: 3.1.0 - cli-width: 4.0.0 - external-editor: 3.1.0 - figures: 5.0.0 - lodash: 4.17.21 - mute-stream: 1.0.0 - ora: 5.4.1 - run-async: 3.0.0 - rxjs: 7.8.1 - string-width: 4.2.3 - strip-ansi: 6.0.1 - through: 2.3.8 - wrap-ansi: 6.2.0 - dev: false + dev: true /internal-slot@1.0.5: resolution: {integrity: sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==} @@ -2272,11 +2292,6 @@ packages: is-extglob: 2.1.1 dev: true - /is-interactive@1.0.0: - resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==} - engines: {node: '>=8'} - dev: false - /is-negative-zero@2.0.2: resolution: {integrity: sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==} engines: {node: '>= 0.4'} @@ -2382,14 +2397,10 @@ packages: has-tostringtag: 1.0.0 dev: true - /is-unicode-supported@0.1.0: - resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} - engines: {node: '>=10'} - dev: false - /is-unicode-supported@1.3.0: resolution: {integrity: sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==} engines: {node: '>=12'} + dev: true /is-weakref@1.0.2: resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==} @@ -2610,14 +2621,7 @@ packages: /lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} - - /log-symbols@4.1.0: - resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} - engines: {node: '>=10'} - dependencies: - chalk: 4.1.2 - is-unicode-supported: 0.1.0 - dev: false + dev: true /log-update@4.0.0: resolution: {integrity: sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==} @@ -2725,6 +2729,7 @@ packages: /mimic-fn@2.1.0: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} engines: {node: '>=6'} + dev: true /mimic-fn@4.0.0: resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} @@ -2908,6 +2913,7 @@ packages: engines: {node: '>=6'} dependencies: mimic-fn: 2.1.0 + dev: true /onetime@6.0.0: resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} @@ -2935,21 +2941,6 @@ packages: word-wrap: 1.2.3 dev: true - /ora@5.4.1: - resolution: {integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==} - engines: {node: '>=10'} - dependencies: - bl: 4.1.0 - chalk: 4.1.2 - cli-cursor: 3.1.0 - cli-spinners: 2.9.0 - is-interactive: 1.0.0 - is-unicode-supported: 0.1.0 - log-symbols: 4.1.0 - strip-ansi: 6.0.1 - wcwidth: 1.0.1 - dev: false - /os-tmpdir@1.0.2: resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} engines: {node: '>=0.10.0'} @@ -3252,15 +3243,6 @@ packages: type-fest: 1.4.0 dev: true - /readable-stream@3.6.2: - resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} - engines: {node: '>= 6'} - dependencies: - inherits: 2.0.4 - string_decoder: 1.3.0 - util-deprecate: 1.0.2 - dev: false - /readdirp@3.6.0: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} @@ -3334,6 +3316,7 @@ packages: dependencies: onetime: 5.1.2 signal-exit: 3.0.7 + dev: true /retry@0.13.1: resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==} @@ -3379,10 +3362,7 @@ packages: resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==} dependencies: tslib: 2.5.2 - - /safe-buffer@5.2.1: - resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} - dev: false + dev: true /safe-regex-test@1.0.0: resolution: {integrity: sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==} @@ -3460,6 +3440,7 @@ packages: /signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + dev: true /signal-exit@4.0.2: resolution: {integrity: sha512-MY2/qGx4enyjprQnFaZsHib3Yadh3IXyV2C321GY0pjGfVBu4un0uDJkwgdxqO+Rdx8JMT8IfJIRwbYVz3Ob3Q==} @@ -3641,12 +3622,6 @@ packages: es-abstract: 1.21.2 dev: true - /string_decoder@1.3.0: - resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} - dependencies: - safe-buffer: 5.2.1 - dev: false - /strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} @@ -3753,6 +3728,7 @@ packages: /through@2.3.8: resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} + dev: true /time-zone@1.0.0: resolution: {integrity: sha512-TIsDdtKo6+XrPtiTm1ssmMngN1sAhyKnTO2kunQWqNPWIVvCm15Wmw4SWInwTVgJ5u/Tr04+8Ei9TNcw4x4ONA==} @@ -3813,6 +3789,7 @@ packages: /tslib@2.5.2: resolution: {integrity: sha512-5svOrSA2w3iGFDs1HibEVBGbDrAY82bFQ3HZ3ixB+88nsbsWQoKqDRb5UBYAUPEzbBn6dAp5gRNXglySbx1MlA==} + dev: true /tsup@6.7.0(typescript@5.0.4): resolution: {integrity: sha512-L3o8hGkaHnu5TdJns+mCqFsDBo83bJ44rlK7e6VdanIvpea4ArPcU3swWGsLVbXak1PqQx/V+SSmFPujBK+zEQ==} @@ -3950,10 +3927,6 @@ packages: punycode: 2.3.0 dev: true - /util-deprecate@1.0.2: - resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - dev: false - /validate-npm-package-license@3.0.4: resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} dependencies: @@ -3961,12 +3934,6 @@ packages: spdx-expression-parse: 3.0.1 dev: true - /wcwidth@1.0.1: - resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} - dependencies: - defaults: 1.0.4 - dev: false - /webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} dev: false diff --git a/src/tools/feedback.ts b/src/tools/feedback.ts index 37cf136e..d7342c06 100644 --- a/src/tools/feedback.ts +++ b/src/tools/feedback.ts @@ -1,36 +1,65 @@ -import { checkbox, editor, select } from '@inquirer/prompts' +import checkbox from '@inquirer/checkbox' +import editor from '@inquirer/editor' +import select from '@inquirer/select' import { ZodTypeAny, z } from 'zod' import * as types from './../types' import { BaseTaskCallBuilder } from './../task' -enum UserActions { - Accept = 'accept', - Edit = 'edit', - Decline = 'decline', - Select = 'select' -} +/** + * Actions the user can take in the feedback selection prompt. + */ +const UserActions = { + Accept: 'accept', + Edit: 'edit', + Decline: 'decline', + Select: 'select', + Exit: 'exit' +} as const -const UserActionMessages = { +type UserActions = (typeof UserActions)[keyof typeof UserActions] + +/** + * Messages to display to the user for each action. + */ +const UserActionMessages: Record = { [UserActions.Accept]: 'Accept inputs', [UserActions.Edit]: 'Edit (open in editor)', [UserActions.Decline]: 'Decline', - [UserActions.Select]: 'Select inputs to keep' + [UserActions.Select]: 'Select inputs to keep', + [UserActions.Exit]: 'Exit' } -export const FeedbackSingleInputSchema = (choice: T) => - z.object({ - choice +/** + * Prompt the user to select one of a list of options. + */ +async function askUser( + message: string, + choices: UserActions[] +): Promise { + return await select({ + message, + choices: choices.map((choice) => ({ + name: UserActionMessages[choice], + value: choice + })) }) +} +/** + * Output schema when prompting the user to accept, edit, or decline a single input. + */ export const FeedbackSingleOutputSchema = (result: T) => z.object({ result: result, accepted: z.boolean() }) +/** + * Prompt the user to accept, edit, or decline a single input. + */ export class HumanFeedbackSingle< - T extends ZodTypeAny = ZodTypeAny + T extends ZodTypeAny > extends BaseTaskCallBuilder { private choiceSchema: T @@ -40,64 +69,59 @@ export class HumanFeedbackSingle< } public get inputSchema() { - return FeedbackSingleInputSchema(this.choiceSchema) + return this.choiceSchema } public get outputSchema() { return FeedbackSingleOutputSchema(this.choiceSchema) } - private async handleChoice( - input: types.ParsedData - ): Promise> { - const feedback = await select({ - message: [ - 'The following input was generated:', - JSON.stringify(input.choice, null, 2), - 'What would you like to do?' - ].join('\n'), - choices: [ - { - name: UserActionMessages[UserActions.Accept], - value: UserActions.Accept - }, - { - name: UserActionMessages[UserActions.Edit], - value: UserActions.Edit - }, - { - name: UserActionMessages[UserActions.Decline], - value: UserActions.Decline - } - ] - }) - switch (feedback) { - case UserActions.Edit: { - // Open the completion in the user's default editor - const editedInput = await editor({ - message: 'Edit the input:', - default: JSON.stringify(input.choice) - }) - return { - result: this.choiceSchema.parse(JSON.parse(editedInput)), - accepted: true - } - } - case UserActions.Decline: - return { result: null, accepted: false } - case UserActions.Accept: - return { result: input, accepted: true } - default: - throw new Error('Invalid feedback choice') + private actionHandlers = { + [UserActions.Accept]: ( + input: types.ParsedData + ) => ({ result: input, accepted: true }), + [UserActions.Edit]: async ( + input: types.ParsedData + ) => { + const editedInput = await editor({ + message: 'Edit the input:', + default: JSON.stringify(input) + }) + return this.outputSchema.parse({ + result: JSON.parse(editedInput), + accepted: true + }) + }, + [UserActions.Decline]: () => ({ result: null, accepted: false }), + [UserActions.Exit]: () => { + throw new Error('Exiting...') } } + /** + * Prompts the user to give feedback for the given input and handles their response. + */ public async call( input: types.ParsedData ): Promise> { try { input = this.inputSchema.parse(input) - return this.handleChoice(input) + const msg = [ + 'The following input was generated:', + JSON.stringify(input, null, 2), + 'What would you like to do?' + ].join('\n') + const feedback = await askUser(msg, [ + UserActions.Accept, + UserActions.Edit, + UserActions.Decline, + UserActions.Exit + ]) + const handler = this.actionHandlers[feedback] + if (!handler) { + throw new Error(`Unexpected feedback: ${feedback}`) + } + return handler(input) } catch (err) { console.error('Error parsing input:', err) throw err @@ -105,19 +129,20 @@ export class HumanFeedbackSingle< } } -export const FeedbackSelectInputSchema = (choice: T) => - z.object({ - choices: z.array(choice) - }) - +/** + * Output schema when prompting the user to accept, select from, edit, or decline a list of inputs. + */ export const FeedbackSelectOutputSchema = (result: T) => z.object({ results: z.array(result), accepted: z.boolean() }) +/** + * Prompt the user to accept, select from, edit, or decline a list of inputs. + */ export class HumanFeedbackSelect< - T extends ZodTypeAny = ZodTypeAny + T extends ZodTypeAny > extends BaseTaskCallBuilder { private choiceSchema: T @@ -127,81 +152,76 @@ export class HumanFeedbackSelect< } public get inputSchema() { - return FeedbackSelectInputSchema(this.choiceSchema) + return z.array(this.choiceSchema) } public get outputSchema() { return FeedbackSelectOutputSchema(this.choiceSchema) } - private async handleChoices( - input: types.ParsedData - ): Promise> { - // Case: input is an array of strings - const feedback = await select({ - message: [ - 'The following inputs were generated:', - ...input.choices.map( - (choice, index) => `${index + 1}. ${JSON.stringify(choice, null, 2)}` - ), - 'What would you like to do?' - ].join('\n'), - choices: [ - { - name: UserActionMessages[UserActions.Accept], - value: UserActions.Accept - }, - { - name: UserActionMessages[UserActions.Edit], - value: UserActions.Edit - }, - { - name: UserActionMessages[UserActions.Decline], - value: UserActions.Decline - }, - { - name: UserActionMessages[UserActions.Select], - value: UserActions.Select - } - ] - }) - switch (feedback) { - case UserActions.Edit: { - const edited = await editor({ - message: 'Edit the input:', - default: JSON.stringify(input.choices, null, 2) - }) - return { results: JSON.parse(edited), accepted: true } - } - case UserActions.Select: { - const choices = input.choices.map((completion) => ({ - name: completion, - value: completion - })) - const chosen = await checkbox({ - message: 'Pick items to keep:', - choices: [...choices] - }) - if (chosen.length === 0) { - return { results: [], accepted: false } - } - return { results: chosen, accepted: true } - } - case UserActions.Decline: - return { results: [], accepted: false } - case UserActions.Accept: - return { results: input.choices, accepted: true } - default: - throw new Error('Invalid feedback choice') + private actionHandlers = { + [UserActions.Accept]: ( + input: types.ParsedData + ) => ({ results: input, accepted: true }), + [UserActions.Edit]: async ( + input: types.ParsedData + ) => { + const editedInput = await editor({ + message: 'Edit the input:', + default: JSON.stringify(input, null, 2) + }) + return this.outputSchema.parse({ + results: JSON.parse(editedInput), + accepted: true + }) + }, + [UserActions.Select]: async ( + input: types.ParsedData + ) => { + const choices = input.map((completion) => ({ + name: completion, + value: completion + })) + const chosen = await checkbox({ + message: 'Pick items to keep:', + choices, + pageSize: choices.length + }) + return { results: chosen.length === 0 ? [] : chosen, accepted: true } + }, + [UserActions.Decline]: () => ({ results: [], accepted: false }), + [UserActions.Exit]: () => { + throw new Error('Exiting...') } } + /** + * Prompts the user to give feedback for the given list of inputs and handles their response. + */ public async call( input: types.ParsedData ): Promise> { try { input = this.inputSchema.parse(input) - return this.handleChoices(input) + const message = [ + 'The following inputs were generated:', + ...input.map( + (choice, index) => `${index + 1}. ${JSON.stringify(choice, null, 2)}` + ), + 'What would you like to do?' + ].join('\n') + const feedback = await askUser(message, [ + UserActions.Accept, + UserActions.Select, + UserActions.Edit, + UserActions.Decline, + UserActions.Exit + ]) + const handler = this.actionHandlers[feedback] + if (!handler) { + throw new Error(`Unexpected feedback: ${feedback}`) + } + return handler(input) } catch (err) { console.error('Error parsing input:', err) throw err