From bf4e52499b65295ab840b744133c46fd55e224cb Mon Sep 17 00:00:00 2001 From: Philipp Burckhardt Date: Fri, 2 Jun 2023 09:48:47 -0400 Subject: [PATCH 1/5] feat: add human-in-the-loop functionality --- examples/human-feedback.ts | 31 ++++++ package.json | 1 + pnpm-lock.yaml | 200 +++++++++++++++++++++++++++++++------ src/feedback.ts | 46 +++++++++ src/index.ts | 1 + 5 files changed, 251 insertions(+), 28 deletions(-) create mode 100644 examples/human-feedback.ts create mode 100644 src/feedback.ts diff --git a/examples/human-feedback.ts b/examples/human-feedback.ts new file mode 100644 index 0000000..6a70a7d --- /dev/null +++ b/examples/human-feedback.ts @@ -0,0 +1,31 @@ +import 'dotenv/config' +import { OpenAIClient } from 'openai-fetch' +import { z } from 'zod' + +import { Agentic, HumanFeedback } 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' }) + + retry = await feedback.process(out).requestFeedback() + } + console.log(feedback.getResult()) +} + +main() diff --git a/package.json b/package.json index 5aee3a6..a0b1fb3 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "dependencies": { "@anthropic-ai/sdk": "^0.4.3", "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 2f3fda0..e4de41b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,6 +11,9 @@ dependencies: 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 @@ -865,12 +868,10 @@ packages: engines: {node: '>=8'} dependencies: type-fest: 0.21.3 - dev: true /ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} - dev: true /ansi-regex@6.0.1: resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} @@ -889,7 +890,6 @@ packages: engines: {node: '>=8'} dependencies: color-convert: 2.0.1 - dev: true /ansi-styles@6.2.1: resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} @@ -1030,6 +1030,14 @@ 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 @@ -1052,6 +1060,13 @@ 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} @@ -1128,12 +1143,14 @@ packages: dependencies: ansi-styles: 4.3.0 supports-color: 7.2.0 - dev: true /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==} + dev: false /chokidar@3.5.3: resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} @@ -1185,7 +1202,11 @@ 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==} + engines: {node: '>=6'} + dev: false /cli-truncate@2.1.0: resolution: {integrity: sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==} @@ -1203,6 +1224,11 @@ packages: string-width: 5.1.2 dev: true + /cli-width@4.0.0: + resolution: {integrity: sha512-ZksGS2xpa/bYkNzN3BAw1wEjsLV/ZKOf/CCrJ/QOBsxx6fOARIkwTutxp1XIOIohi6HKmOFjMoK/XaqDVUpEEw==} + engines: {node: '>= 12'} + dev: false + /cliui@8.0.1: resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} engines: {node: '>=12'} @@ -1212,6 +1238,11 @@ 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'} @@ -1235,7 +1266,6 @@ packages: engines: {node: '>=7.0.0'} dependencies: color-name: 1.1.4 - dev: true /color-name@1.1.3: resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} @@ -1243,7 +1273,6 @@ packages: /color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - dev: true /colorette@2.0.20: resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} @@ -1362,6 +1391,12 @@ 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'} @@ -1433,7 +1468,6 @@ packages: /emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} - dev: true /emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} @@ -1556,7 +1590,6 @@ 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==} @@ -1706,6 +1739,15 @@ packages: engines: {node: '>=12.0.0'} dev: true + /external-editor@3.1.0: + resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} + engines: {node: '>=4'} + dependencies: + chardet: 0.7.0 + iconv-lite: 0.4.24 + tmp: 0.0.33 + dev: false + /fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} dev: true @@ -1745,7 +1787,6 @@ packages: 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==} @@ -1982,7 +2023,6 @@ packages: /has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} - dev: true /has-property-descriptors@1.0.0: resolution: {integrity: sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==} @@ -2050,6 +2090,17 @@ packages: hasBin: true dev: true + /iconv-lite@0.4.24: + resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} + engines: {node: '>=0.10.0'} + dependencies: + 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'} @@ -2092,7 +2143,27 @@ packages: /inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - dev: true + + /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 /internal-slot@1.0.5: resolution: {integrity: sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==} @@ -2188,7 +2259,6 @@ packages: /is-fullwidth-code-point@3.0.0: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} - dev: true /is-fullwidth-code-point@4.0.0: resolution: {integrity: sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==} @@ -2202,6 +2272,11 @@ 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'} @@ -2307,10 +2382,14 @@ 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==} @@ -2531,7 +2610,14 @@ packages: /lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} - dev: true + + /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 /log-update@4.0.0: resolution: {integrity: sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==} @@ -2639,7 +2725,6 @@ 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==} @@ -2678,6 +2763,11 @@ packages: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} dev: true + /mute-stream@1.0.0: + resolution: {integrity: sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + dev: false + /mz@2.7.0: resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} dependencies: @@ -2818,7 +2908,6 @@ 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==} @@ -2846,6 +2935,26 @@ 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'} + dev: false + /p-defer@1.0.0: resolution: {integrity: sha512-wB3wfAxZpk2AzOfUMJNL+d36xothRSyj8EXOa4f6GMqYDN9BJaaSISbsk+wS9abmnebVw95C2Kb5t85UmpCxuw==} engines: {node: '>=4'} @@ -3143,6 +3252,15 @@ 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'} @@ -3216,7 +3334,6 @@ packages: dependencies: onetime: 5.1.2 signal-exit: 3.0.7 - dev: true /retry@0.13.1: resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==} @@ -3247,6 +3364,11 @@ packages: fsevents: 2.3.2 dev: true + /run-async@3.0.0: + resolution: {integrity: sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==} + engines: {node: '>=0.12.0'} + dev: false + /run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} dependencies: @@ -3257,7 +3379,10 @@ packages: resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==} dependencies: tslib: 2.5.2 - dev: true + + /safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + dev: false /safe-regex-test@1.0.0: resolution: {integrity: sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==} @@ -3267,6 +3392,10 @@ packages: is-regex: 1.1.4 dev: true + /safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + dev: false + /semver@5.7.1: resolution: {integrity: sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==} hasBin: true @@ -3331,7 +3460,6 @@ 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==} @@ -3469,7 +3597,6 @@ packages: emoji-regex: 8.0.0 is-fullwidth-code-point: 3.0.0 strip-ansi: 6.0.1 - dev: true /string-width@5.1.2: resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} @@ -3514,12 +3641,17 @@ 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'} dependencies: ansi-regex: 5.0.1 - dev: true /strip-ansi@7.0.1: resolution: {integrity: sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==} @@ -3591,7 +3723,6 @@ packages: engines: {node: '>=8'} dependencies: has-flag: 4.0.0 - dev: true /supports-preserve-symlinks-flag@1.0.0: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} @@ -3622,13 +3753,19 @@ 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==} engines: {node: '>=4'} dev: true + /tmp@0.0.33: + resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} + engines: {node: '>=0.6.0'} + dependencies: + os-tmpdir: 1.0.2 + dev: false + /to-fast-properties@2.0.0: resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} engines: {node: '>=4'} @@ -3676,7 +3813,6 @@ 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==} @@ -3760,7 +3896,6 @@ packages: /type-fest@0.21.3: resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} engines: {node: '>=10'} - dev: true /type-fest@1.4.0: resolution: {integrity: sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==} @@ -3815,6 +3950,10 @@ 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: @@ -3822,6 +3961,12 @@ 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 @@ -3903,7 +4048,6 @@ packages: ansi-styles: 4.3.0 string-width: 4.2.3 strip-ansi: 6.0.1 - dev: true /wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} diff --git a/src/feedback.ts b/src/feedback.ts new file mode 100644 index 0000000..cd4c741 --- /dev/null +++ b/src/feedback.ts @@ -0,0 +1,46 @@ +import inquirer from 'inquirer' + +class HumanFeedback { + private completions: string[] + private selectedCompletion: string + + // Process the completions returned by the AI + process(completions: string[]) { + this.completions = completions + return this + } + + // Request feedback from the user + async requestFeedback() { + const choices = this.completions.map((completion, index) => ({ + name: completion, + value: index + })) + + const feedback = await inquirer.prompt([ + { + type: 'list', + name: 'userResponse', + message: 'Pick the best completion:', + choices: [...choices, new inquirer.Separator(), 'Retry', 'Decline'] + } + ]) + + switch (feedback.userResponse) { + case 'Retry': + return true + case 'Decline': + return process.exit(0) + default: + this.selectedCompletion = this.completions[feedback.userResponse] + return false + } + } + + // Return the selected completion + getResult() { + return this.selectedCompletion + } +} + +export { HumanFeedback } diff --git a/src/index.ts b/src/index.ts index 45bc925..5a32c77 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,6 +4,7 @@ export * from './llm' export * from './openai' export * from './anthropic' export * from './tokenizer' +export * from './feedback' export * from './services/metaphor' export * from './tools/metaphor' From 294011e410c89c1ad4ee2c416bb0e64fa72140db Mon Sep 17 00:00:00 2001 From: Philipp Burckhardt Date: Sat, 3 Jun 2023 05:37:13 -0400 Subject: [PATCH 2/5] feat: add human feedback tool --- src/feedback.ts | 46 --------- src/index.ts | 2 +- src/tools/feedback.ts | 210 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 211 insertions(+), 47 deletions(-) delete mode 100644 src/feedback.ts create mode 100644 src/tools/feedback.ts diff --git a/src/feedback.ts b/src/feedback.ts deleted file mode 100644 index cd4c741..0000000 --- a/src/feedback.ts +++ /dev/null @@ -1,46 +0,0 @@ -import inquirer from 'inquirer' - -class HumanFeedback { - private completions: string[] - private selectedCompletion: string - - // Process the completions returned by the AI - process(completions: string[]) { - this.completions = completions - return this - } - - // Request feedback from the user - async requestFeedback() { - const choices = this.completions.map((completion, index) => ({ - name: completion, - value: index - })) - - const feedback = await inquirer.prompt([ - { - type: 'list', - name: 'userResponse', - message: 'Pick the best completion:', - choices: [...choices, new inquirer.Separator(), 'Retry', 'Decline'] - } - ]) - - switch (feedback.userResponse) { - case 'Retry': - return true - case 'Decline': - return process.exit(0) - default: - this.selectedCompletion = this.completions[feedback.userResponse] - return false - } - } - - // Return the selected completion - getResult() { - return this.selectedCompletion - } -} - -export { HumanFeedback } diff --git a/src/index.ts b/src/index.ts index 5a32c77..0378c34 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,7 +4,7 @@ export * from './llm' export * from './openai' export * from './anthropic' export * from './tokenizer' -export * from './feedback' export * from './services/metaphor' export * from './tools/metaphor' +export * from './tools/feedback' diff --git a/src/tools/feedback.ts b/src/tools/feedback.ts new file mode 100644 index 0000000..37cf136 --- /dev/null +++ b/src/tools/feedback.ts @@ -0,0 +1,210 @@ +import { checkbox, editor, select } from '@inquirer/prompts' +import { ZodTypeAny, z } from 'zod' + +import * as types from './../types' +import { BaseTaskCallBuilder } from './../task' + +enum UserActions { + Accept = 'accept', + Edit = 'edit', + Decline = 'decline', + Select = 'select' +} + +const UserActionMessages = { + [UserActions.Accept]: 'Accept inputs', + [UserActions.Edit]: 'Edit (open in editor)', + [UserActions.Decline]: 'Decline', + [UserActions.Select]: 'Select inputs to keep' +} + +export const FeedbackSingleInputSchema = (choice: T) => + z.object({ + choice + }) + +export const FeedbackSingleOutputSchema = (result: T) => + z.object({ + result: result, + accepted: z.boolean() + }) + +export class HumanFeedbackSingle< + T extends ZodTypeAny = ZodTypeAny +> extends BaseTaskCallBuilder { + private choiceSchema: T + + constructor(choiceSchema: T) { + super() + this.choiceSchema = choiceSchema + } + + public get inputSchema() { + return FeedbackSingleInputSchema(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') + } + } + + public async call( + input: types.ParsedData + ): Promise> { + try { + input = this.inputSchema.parse(input) + return this.handleChoice(input) + } catch (err) { + console.error('Error parsing input:', err) + throw err + } + } +} + +export const FeedbackSelectInputSchema = (choice: T) => + z.object({ + choices: z.array(choice) + }) + +export const FeedbackSelectOutputSchema = (result: T) => + z.object({ + results: z.array(result), + accepted: z.boolean() + }) + +export class HumanFeedbackSelect< + T extends ZodTypeAny = ZodTypeAny +> extends BaseTaskCallBuilder { + private choiceSchema: T + + constructor(choiceSchema: T) { + super() + this.choiceSchema = choiceSchema + } + + public get inputSchema() { + return FeedbackSelectInputSchema(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') + } + } + + public async call( + input: types.ParsedData + ): Promise> { + try { + input = this.inputSchema.parse(input) + return this.handleChoices(input) + } catch (err) { + console.error('Error parsing input:', err) + throw err + } + } +} From 0a4b9ad39b75b8f95fc0f3c4e48582994eaf344f Mon Sep 17 00:00:00 2001 From: Philipp Burckhardt Date: Sun, 4 Jun 2023 11:31:03 -0400 Subject: [PATCH 3/5] 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 0000000..60c4677 --- /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 6a70a7d..d964630 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 a0b1fb3..fa87d13 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 e4de41b..5a9ae17 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 37cf136..d7342c0 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 From 5282ee3b8891cfa1e0a713602e0d5b3d81a9d982 Mon Sep 17 00:00:00 2001 From: Philipp Burckhardt Date: Sun, 4 Jun 2023 11:45:20 -0400 Subject: [PATCH 4/5] chore: update code style --- src/tools/feedback.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tools/feedback.ts b/src/tools/feedback.ts index d7342c0..fffb0cc 100644 --- a/src/tools/feedback.ts +++ b/src/tools/feedback.ts @@ -3,8 +3,8 @@ import editor from '@inquirer/editor' import select from '@inquirer/select' import { ZodTypeAny, z } from 'zod' -import * as types from './../types' -import { BaseTaskCallBuilder } from './../task' +import * as types from '../types' +import { BaseTaskCallBuilder } from '../task' /** * Actions the user can take in the feedback selection prompt. From 4ad35513db25712471e445690151eaee9d46ad43 Mon Sep 17 00:00:00 2001 From: Philipp Burckhardt Date: Sun, 4 Jun 2023 11:48:15 -0400 Subject: [PATCH 5/5] fix: update imported class name --- src/tools/feedback.ts | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/tools/feedback.ts b/src/tools/feedback.ts index fffb0cc..a76e2a5 100644 --- a/src/tools/feedback.ts +++ b/src/tools/feedback.ts @@ -4,7 +4,7 @@ import select from '@inquirer/select' import { ZodTypeAny, z } from 'zod' import * as types from '../types' -import { BaseTaskCallBuilder } from '../task' +import { BaseTask } from '../task' /** * Actions the user can take in the feedback selection prompt. @@ -58,9 +58,10 @@ export const FeedbackSingleOutputSchema = (result: T) => /** * Prompt the user to accept, edit, or decline a single input. */ -export class HumanFeedbackSingle< - T extends ZodTypeAny -> extends BaseTaskCallBuilder { +export class HumanFeedbackSingle extends BaseTask< + ZodTypeAny, + ZodTypeAny +> { private choiceSchema: T constructor(choiceSchema: T) { @@ -141,9 +142,10 @@ export const FeedbackSelectOutputSchema = (result: T) => /** * Prompt the user to accept, select from, edit, or decline a list of inputs. */ -export class HumanFeedbackSelect< - T extends ZodTypeAny -> extends BaseTaskCallBuilder { +export class HumanFeedbackSelect extends BaseTask< + ZodTypeAny, + ZodTypeAny +> { private choiceSchema: T constructor(choiceSchema: T) {