From f887ea239e4874124429c460a31a18809141b879 Mon Sep 17 00:00:00 2001 From: Philipp Burckhardt Date: Fri, 16 Jun 2023 20:03:02 -0400 Subject: [PATCH] feat: allow passing array of sections instead of entire message --- src/human-feedback/twilio.ts | 37 ++++++++++++++++------------- src/services/twilio-conversation.ts | 16 +++++++++---- src/utils.ts | 17 +++++++++++-- 3 files changed, 46 insertions(+), 24 deletions(-) diff --git a/src/human-feedback/twilio.ts b/src/human-feedback/twilio.ts index 017952d4..3801db84 100644 --- a/src/human-feedback/twilio.ts +++ b/src/human-feedback/twilio.ts @@ -57,17 +57,18 @@ export class HumanFeedbackMechanismTwilio< message: string, choices: HumanFeedbackUserActions[] ): Promise { - message += '\n\n' - message += choices - .map( - (choice, idx) => `${idx} - ${HumanFeedbackUserActionMessages[choice]}` - ) - .join('\n') - message += '\n\n' - message += 'Reply with the number of your choice.' const response = await this._twilioClient.sendAndWaitForReply({ name: 'human-feedback-ask', - text: message, + text: [ + message, + choices + .map( + (choice, idx) => + `${idx} - ${HumanFeedbackUserActionMessages[choice]}` + ) + .join('\n'), + 'Reply with the number of your choice.' + ], timeoutMs: this._options.timeoutMs, validate: (message) => { const choice = parseInt(message.body) @@ -87,10 +88,11 @@ export class HumanFeedbackMechanismTwilio< const { body: selectedOutput } = await this._twilioClient.sendAndWaitForReply({ name: 'human-feedback-select', - text: - 'Pick one output:' + - response.map((r, idx) => `\n${idx} - ${JSON.stringify(r)}`).join('') + - '\n\nReply with the number of your choice.', + text: [ + 'Pick one output:', + response.map((r, idx) => `\n${idx} - ${JSON.stringify(r)}`).join(''), + 'Reply with the number of your choice.' + ], timeoutMs: this._options.timeoutMs, validate: (message) => { const choice = parseInt(message.body) @@ -110,10 +112,11 @@ export class HumanFeedbackMechanismTwilio< const { body: selectedOutput } = await this._twilioClient.sendAndWaitForReply({ name: 'human-feedback-select', - text: - 'Select outputs:' + - response.map((r, idx) => `\n${idx} - ${JSON.stringify(r)}`).join('') + - '\n\nReply with a comma-separated list of the output numbers of your choice.', + text: [ + 'Select outputs:', + response.map((r, idx) => `\n${idx} - ${JSON.stringify(r)}`).join(''), + 'Reply with a comma-separated list of the output numbers of your choice.' + ], timeoutMs: this._options.timeoutMs, validate: (message) => { const choices = message.body.split(',') diff --git a/src/services/twilio-conversation.ts b/src/services/twilio-conversation.ts index 41f90b46..7bd0dd77 100644 --- a/src/services/twilio-conversation.ts +++ b/src/services/twilio-conversation.ts @@ -1,7 +1,7 @@ import defaultKy from 'ky' import { DEFAULT_BOT_NAME } from '@/constants' -import { chunkString, sleep } from '@/utils' +import { chunkMultipleStrings, chunkString, sleep } from '@/utils' export const TWILIO_CONVERSATION_API_BASE_URL = 'https://conversations.twilio.com/v1' @@ -101,9 +101,9 @@ export type TwilioSendAndWaitOptions = { recipientPhoneNumber?: string /** - * The text of the message to send. + * The text of the message to send (or an array of strings to send as separate messages). */ - text: string + text: string | string[] /** * Friendly name of the conversation. @@ -245,10 +245,16 @@ export class TwilioConversationClient { text }: { conversationSid: string - text: string + text: string | string[] maxChunkLength?: number }) { - const chunks = chunkString(text, TWILIO_SMS_LENGTH_SOFT_LIMIT) + let chunks + if (Array.isArray(text)) { + chunks = chunkMultipleStrings(text, TWILIO_SMS_LENGTH_SOFT_LIMIT) + } else { + chunks = chunkString(text, TWILIO_SMS_LENGTH_SOFT_LIMIT) + } + const out: TwilioConversationMessage[] = [] for (const chunk of chunks) { const sent = await this.sendMessage({ diff --git a/src/utils.ts b/src/utils.ts index 67285b0a..10ee3131 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -84,8 +84,7 @@ export function chunkString(text: string, maxLength: number): string[] { if (word.length > maxLength) { // Truncate the word if it's too long and indicate that it was truncated: chunks.push(word.substring(0, maxLength - 3) + '...') - } else if ((chunk + word + 1).length > maxLength) { - // +1 accounts for the space between words + } else if ((chunk + ' ' + word).length > maxLength) { chunks.push(chunk.trim()) chunk = word } else { @@ -100,6 +99,20 @@ export function chunkString(text: string, maxLength: number): string[] { return chunks } +/** + * Chunks an array of strings into an array of chunks while preserving existing sections. + * + * @param textSections - array of strings to chunk + * @param maxLength - maximum length of each chunk + * @returns array of chunks + */ +export function chunkMultipleStrings( + textSections: string[], + maxLength: number +): string[] { + return textSections.map((section) => chunkString(section, maxLength)).flat() +} + /** * Stringifies a JSON value for use in an LLM prompt. *