From e722c5b9d7756a2a8e40cd43084c07a1ba5c9281 Mon Sep 17 00:00:00 2001 From: Philipp Burckhardt Date: Tue, 13 Jun 2023 18:14:12 -0400 Subject: [PATCH] fix: improve typing --- src/human-feedback/cli.ts | 20 ++---- src/human-feedback/feedback.ts | 110 +++++++++++++++++++++++++-------- src/human-feedback/slack.ts | 7 ++- src/human-feedback/twilio.ts | 7 ++- 4 files changed, 98 insertions(+), 46 deletions(-) diff --git a/src/human-feedback/cli.ts b/src/human-feedback/cli.ts index e4a23e0..39fac15 100644 --- a/src/human-feedback/cli.ts +++ b/src/human-feedback/cli.ts @@ -3,28 +3,16 @@ import editor from '@inquirer/editor' import input from '@inquirer/input' import select from '@inquirer/select' -import { Agentic } from '@/agentic' - import { HumanFeedbackMechanism, - HumanFeedbackOptions, + HumanFeedbackType, UserActionMessages, UserActions } from './feedback' -export class HumanFeedbackMechanismCLI extends HumanFeedbackMechanism { - constructor({ - agentic, - options - }: { - agentic: Agentic - options: HumanFeedbackOptions - }) { - super({ agentic, options }) - this._agentic = agentic - this._options = options - } - +export class HumanFeedbackMechanismCLI< + T extends HumanFeedbackType +> extends HumanFeedbackMechanism { /** * Prompt the user to select one of a list of options. */ diff --git a/src/human-feedback/feedback.ts b/src/human-feedback/feedback.ts index 27d12f7..7d1e69d 100644 --- a/src/human-feedback/feedback.ts +++ b/src/human-feedback/feedback.ts @@ -1,6 +1,5 @@ import { Agentic } from '@/agentic' import { BaseTask } from '@/task' -import { TaskResponseMetadata } from '@/types' import { HumanFeedbackMechanismCLI } from './cli' @@ -30,20 +29,18 @@ export const UserActionMessages: Record = { */ export type HumanFeedbackType = 'confirm' | 'selectOne' | 'selectN' -type HumanFeedbackMechanismConstructor = new ( +type HumanFeedbackMechanismConstructor = new ( ...args: any[] -) => T +) => HumanFeedbackMechanism /** * Options for human feedback. */ -export type HumanFeedbackOptions< - T extends HumanFeedbackMechanism = HumanFeedbackMechanism -> = { +export type HumanFeedbackOptions = { /** * What type of feedback to request. */ - type?: HumanFeedbackType + type?: T /** * Whether the user can bail out of the feedback loop. @@ -66,17 +63,75 @@ export type HumanFeedbackOptions< mechanism?: HumanFeedbackMechanismConstructor } -export abstract class HumanFeedbackMechanism { +export interface BaseHumanFeedbackMetadata { + /** + * Edited output by the user (if applicable). + */ + editedOutput?: string + + /** + * Annotation left by the user (if applicable). + */ + annotation?: string +} + +export interface HumanFeedbackConfirmMetadata + extends BaseHumanFeedbackMetadata { + /** + * The type of feedback requested. + */ + type: 'confirm' + + /** + * Whether the user accepted the output. + */ + accepted: boolean +} + +export interface HumanFeedbackSelectOneMetadata + extends BaseHumanFeedbackMetadata { + /** + * The type of feedback requested. + */ + type: 'selectOne' + + /** + * The selected output. + */ + chosen: any +} + +export interface HumanFeedbackSelectNMetadata + extends BaseHumanFeedbackMetadata { + /** + * The type of feedback requested. + */ + type: 'selectN' + + /** + * The selected outputs. + */ + selected: any[] +} + +export type FeedbackTypeToMetadata = + T extends 'confirm' + ? HumanFeedbackConfirmMetadata + : T extends 'selectOne' + ? HumanFeedbackSelectOneMetadata + : HumanFeedbackSelectNMetadata + +export abstract class HumanFeedbackMechanism { protected _agentic: Agentic - protected _options: HumanFeedbackOptions + protected _options: Required> constructor({ agentic, options }: { agentic: Agentic - options: HumanFeedbackOptions + options: Required> }) { this._agentic = agentic this._options = options @@ -95,7 +150,7 @@ export abstract class HumanFeedbackMechanism { choices: UserActions[] ): Promise - public async interact(response: any, metadata: TaskResponseMetadata) { + public async interact(response: any): Promise> { const stringified = JSON.stringify(response, null, 2) const msg = [ 'The following output was generated:', @@ -125,33 +180,33 @@ export abstract class HumanFeedbackMechanism { choices.push(UserActions.Exit) } - const feedback = + const choice = choices.length === 1 ? UserActions.Select : await this.askUser(msg, choices) - metadata.feedback = {} + const feedback: Record = {} - switch (feedback) { + switch (choice) { case UserActions.Accept: - metadata.feedback.accepted = true + feedback.accepted = true break case UserActions.Edit: { const editedOutput = await this.edit(stringified) - metadata.feedback.editedOutput = editedOutput + feedback.editedOutput = editedOutput break } case UserActions.Decline: - metadata.feedback.accepted = false + feedback.accepted = false break case UserActions.Select: if (this._options.type === 'selectN') { - metadata.feedback.selected = await this.selectN(response) + feedback.selected = await this.selectN(response) } else if (this._options.type === 'selectOne') { - metadata.feedback.chosen = await this.selectOne(response) + feedback.chosen = await this.selectOne(response) } break @@ -160,21 +215,23 @@ export abstract class HumanFeedbackMechanism { throw new Error('Exiting...') default: - throw new Error(`Unexpected feedback: ${feedback}`) + throw new Error(`Unexpected choice: ${choice}`) } if (this._options.annotations) { const annotation = await this.annotate() if (annotation) { - metadata.feedback.annotation = annotation + feedback.annotation = annotation } } + + return feedback as FeedbackTypeToMetadata } } -export function withHumanFeedback( +export function withHumanFeedback( task: BaseTask, - options: HumanFeedbackOptions = {} + options: HumanFeedbackOptions = {} ) { task = task.clone() @@ -182,7 +239,7 @@ export function withHumanFeedback( const instanceDefaults = task.agentic.humanFeedbackDefaults // Use Object.assign to merge the options, instance defaults, and hard-coded defaults - const finalOptions: HumanFeedbackOptions = Object.assign( + const finalOptions: HumanFeedbackOptions = Object.assign( { type: 'confirm', bail: false, @@ -212,8 +269,9 @@ export function withHumanFeedback( task.callWithMetadata = async function (input?: T) { const response = await originalCall(input) - // Process the response and add feedback to metadata - await feedbackMechanism.interact(response.result, response.metadata) + const feedback = await feedbackMechanism.interact(response.result) + + response.metadata = { ...response.metadata, feedback } return response } diff --git a/src/human-feedback/slack.ts b/src/human-feedback/slack.ts index 7464036..d344001 100644 --- a/src/human-feedback/slack.ts +++ b/src/human-feedback/slack.ts @@ -4,11 +4,14 @@ import { SlackClient } from '@/services/slack' import { HumanFeedbackMechanism, HumanFeedbackOptions, + HumanFeedbackType, UserActionMessages, UserActions } from './feedback' -export class HumanFeedbackMechanismSlack extends HumanFeedbackMechanism { +export class HumanFeedbackMechanismSlack< + T extends HumanFeedbackType +> extends HumanFeedbackMechanism { private slackClient: SlackClient constructor({ @@ -16,7 +19,7 @@ export class HumanFeedbackMechanismSlack extends HumanFeedbackMechanism { options }: { agentic: Agentic - options: HumanFeedbackOptions + options: Required> }) { super({ agentic, options }) this.slackClient = new SlackClient() diff --git a/src/human-feedback/twilio.ts b/src/human-feedback/twilio.ts index f69ffdc..45ae8cb 100644 --- a/src/human-feedback/twilio.ts +++ b/src/human-feedback/twilio.ts @@ -4,11 +4,14 @@ import { TwilioConversationClient } from '@/services/twilio-conversation' import { HumanFeedbackMechanism, HumanFeedbackOptions, + HumanFeedbackType, UserActionMessages, UserActions } from './feedback' -export class HumanFeedbackMechanismTwilio extends HumanFeedbackMechanism { +export class HumanFeedbackMechanismTwilio< + T extends HumanFeedbackType +> extends HumanFeedbackMechanism { private twilioClient: TwilioConversationClient constructor({ @@ -16,7 +19,7 @@ export class HumanFeedbackMechanismTwilio extends HumanFeedbackMechanism { options }: { agentic: Agentic - options: HumanFeedbackOptions + options: Required> }) { super({ agentic, options }) this.twilioClient = new TwilioConversationClient()