Merge pull request #9 from transitive-bullshit/feedback

old-agentic-v1^2
Philipp Burckhardt 2023-06-04 11:50:47 -04:00 zatwierdzone przez GitHub
commit f8bd19bdda
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
6 zmienionych plików z 427 dodań i 15 usunięć

Wyświetl plik

@ -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()

33
examples/human-feedback.ts vendored 100644
Wyświetl plik

@ -0,0 +1,33 @@
import 'dotenv/config'
import { OpenAIClient } from 'openai-fetch'
import { z } from 'zod'
import { Agentic, HumanFeedbackSingle } from '../src'
async function main() {
const openai = new OpenAIClient({ apiKey: process.env.OPENAI_API_KEY! })
const ai = new Agentic({ openai })
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 })
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(hf.result)
}
main()

Wyświetl plik

@ -39,6 +39,9 @@
},
"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",
"js-tiktoken": "^1.0.6",
"jsonrepair": "^3.1.0",

Wyświetl plik

@ -8,6 +8,15 @@ 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
@ -534,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
@ -865,12 +928,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 +950,6 @@ packages:
engines: {node: '>=8'}
dependencies:
color-convert: 2.0.1
dev: true
/ansi-styles@6.2.1:
resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==}
@ -1128,13 +1188,16 @@ 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==}
engines: {node: '>= 8.10.0'}
@ -1187,6 +1250,11 @@ packages:
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==}
engines: {node: '>=8'}
@ -1203,6 +1271,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'}
@ -1235,7 +1308,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 +1315,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==}
@ -1433,7 +1504,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==}
@ -1541,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==}
@ -1706,6 +1775,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
@ -1739,6 +1817,13 @@ 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'}
@ -1982,7 +2067,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 +2134,13 @@ 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
/ignore-by-default@2.1.0:
resolution: {integrity: sha512-yiWd4GVmJp0Q6ghmM2B/V3oZGRmjrKLXvHR3TE1nfoXsmoggllfZUQe74EN0fJdPFZu2NIvNdrMMLm3OsV7Ohw==}
engines: {node: '>=10 <11 || >=12 <13 || >=14'}
@ -2188,7 +2279,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==}
@ -2678,6 +2768,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:
@ -2846,6 +2941,11 @@ packages:
word-wrap: 1.2.3
dev: true
/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'}
@ -3247,6 +3347,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:
@ -3267,6 +3372,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
@ -3469,7 +3578,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==}
@ -3519,7 +3627,6 @@ packages:
engines: {node: '>=8'}
dependencies:
ansi-regex: 5.0.1
dev: true
/strip-ansi@7.0.1:
resolution: {integrity: sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==}
@ -3591,7 +3698,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==}
@ -3629,6 +3735,13 @@ packages:
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'}
@ -3760,7 +3873,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==}
@ -3903,7 +4015,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==}

Wyświetl plik

@ -7,3 +7,4 @@ export * from './tokenizer'
export * from './services/metaphor'
export * from './tools/metaphor'
export * from './tools/feedback'

Wyświetl plik

@ -0,0 +1,232 @@
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 { BaseTask } from '../task'
/**
* Actions the user can take in the feedback selection prompt.
*/
const UserActions = {
Accept: 'accept',
Edit: 'edit',
Decline: 'decline',
Select: 'select',
Exit: 'exit'
} as const
type UserActions = (typeof UserActions)[keyof typeof UserActions]
/**
* Messages to display to the user for each action.
*/
const UserActionMessages: Record<UserActions, string> = {
[UserActions.Accept]: 'Accept inputs',
[UserActions.Edit]: 'Edit (open in editor)',
[UserActions.Decline]: 'Decline',
[UserActions.Select]: 'Select inputs to keep',
[UserActions.Exit]: 'Exit'
}
/**
* Prompt the user to select one of a list of options.
*/
async function askUser(
message: string,
choices: UserActions[]
): Promise<UserActions> {
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 = <T extends ZodTypeAny>(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> extends BaseTask<
ZodTypeAny,
ZodTypeAny
> {
private choiceSchema: T
constructor(choiceSchema: T) {
super()
this.choiceSchema = choiceSchema
}
public get inputSchema() {
return this.choiceSchema
}
public get outputSchema() {
return FeedbackSingleOutputSchema(this.choiceSchema)
}
private actionHandlers = {
[UserActions.Accept]: (
input: types.ParsedData<typeof this.inputSchema>
) => ({ result: input, accepted: true }),
[UserActions.Edit]: async (
input: types.ParsedData<typeof this.inputSchema>
) => {
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<typeof this.inputSchema>
): Promise<types.ParsedData<typeof this.outputSchema>> {
try {
input = this.inputSchema.parse(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
}
}
}
/**
* Output schema when prompting the user to accept, select from, edit, or decline a list of inputs.
*/
export const FeedbackSelectOutputSchema = <T extends ZodTypeAny>(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> extends BaseTask<
ZodTypeAny,
ZodTypeAny
> {
private choiceSchema: T
constructor(choiceSchema: T) {
super()
this.choiceSchema = choiceSchema
}
public get inputSchema() {
return z.array(this.choiceSchema)
}
public get outputSchema() {
return FeedbackSelectOutputSchema(this.choiceSchema)
}
private actionHandlers = {
[UserActions.Accept]: (
input: types.ParsedData<typeof this.inputSchema>
) => ({ results: input, accepted: true }),
[UserActions.Edit]: async (
input: types.ParsedData<typeof this.inputSchema>
) => {
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<typeof this.inputSchema>
) => {
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<typeof this.inputSchema>
): Promise<types.ParsedData<typeof this.outputSchema>> {
try {
input = this.inputSchema.parse(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
}
}
}