Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Clear regular prompt message if using Re-Ask Option #616

Open
wants to merge 23 commits into
base: main
Choose a base branch
from

Conversation

rjwignar
Copy link
Collaborator

@rjwignar rjwignar commented Apr 25, 2024

This fixes #587

Background

7cc1e2f allowed web handlers and slash commands to be executed with the Re-ask option (issue described in #541) by passing the prompt text to the onPrompt call in MessagesView::onResubmitClick().

Before this change, retrying with a regular prompt message would trigger the regular re-try routine (since the prompt text wasn't passed to the onPrompt call in MessagesView::onResubmitClick():

} else {
// If there isn't any prompt text, see if the final message in the chat was a human
// message or a function response. If it was either, we'll allow sending that through
// again (e.g., if you modified something and want to retry, or want to share the
// response from the function). Otherwise bail now.
const finalMessage = chat.messages({ includeAppMessages: false }).at(-1);
if (
!(
finalMessage instanceof ChatCraftHumanMessage ||
finalMessage instanceof ChatCraftFunctionResultMessage
)
) {
return;
}
}

After this change however, if re-asking with a regular prompt message, the updated message will be passed to onPrompt (as prompt) and add a duplicate message to the chat (line 278):

if (prompt) {
// Add this prompt message to the chat
promptMessage = new ChatCraftHumanMessage({ text: prompt, imageUrls, user });
await chat.addMessage(promptMessage);

Changes

I added a boolean parameter to ChatBase::onPrompt called retry (false by default):

    async (prompt?: string, imageUrls?: string[], retry: boolean = false) => {
      // ...
      try {
        // If retrying, set the prompt to null to avoid duplicate message
        if (retry) {
          prompt = "";
        }
        // If the prompt text exist, package it up as a human message and add to the chat
        // ...

If retry is True, the prompt will be cleared to prevent message duplication.

retry is False by default because I only want to explicitly set it to True when calling onPrompt() from onResubmitClick():

type MessagesViewProps = {
  // ...
  onPrompt: (prompt?: string, imageUrls?: string[], retry?: boolean) => void;
};
// ... 

function MessagesView({
          onResubmitClick={async (promptText?: string) => {
            await deleteMessages(message.id, "after");
            onPrompt(promptText, undefined, true); // pass prompt text and true for retry, don't include any imageURLs
          }}
          // ...
        })

I had to update the onPrompt signature in MessagesViewProps for syntax reasons. If there's a better way to implement this I'm open to suggestions.

Comparison to Production (updated with Image as Input observations)

Below are the observations I've made on the Re-Ask options before and after 7cc1e2f, as well as for this Pull Request:

Task Observations pre- 7cc1e2f (tested using adcfd2d) Observations post- 7cc1e2f (tested using Production as of e4967a9) (Prompt duplication bug observed) PR Observations
Ask with "help" (to invoke /help) Executes /help command Executes /help command Executes /help command
Re-ask with "help" (to invoke /help) Response from LLM (undesired) Executes /help command Executes /help command
Ask with web handler Executes web handler Executes web handler Executes web handler
Re-ask with web handler Response from LLM (undesired) Executes web handler Executes web handler
Ask with slash command (/import) Invokes /import Invokes /import Invokes /import
Re-ask with slash command (/import) Response from LLM (undesired) Invokes /import Invokes /import
Ask with regular prompt Response from LLM Response from LLM Response from LLM
Re-ask with regular prompt Response from LLM Response from LLM, duplicates new message (undesired) Response from LLM, no message duplication
Ask with Image as Input (no prompt) Appropriate LLM response Appropriate LLM response Appropriate LLM response
Re-ask with Image as Input (no prompt, same image 2) Appropriate LLM Response Appropriate LLM Response (no message duplication as no prompt present, expected) Appropriate LLM (no message duplication as no prompt present, expected)
Ask with prompt and Image as Input Appropriate LLM Response Appropriate LLM Response Appropriate LLM Response
Re-ask with prompt and Image as Input (same image 2) Appropriate LLM Response (no message duplication, desired result) Appropriate LLM Response (message duplication with text only, undesired 3) Appropriate LLM Response (no message duplication, desired result)

My PR preserves the changes from 7cc1e2f but prevent the message duplication when re-trying with a regular prompt message.

@rjwignar
Copy link
Collaborator Author

rjwignar commented Apr 25, 2024

Moving to draft because I haven't yet checked how re-asking with an Image has been affected through 7cc1e2f and/or my PR (if at all).
Will mark this PR as ready in the morning once I've checked.

@rjwignar rjwignar self-assigned this Apr 25, 2024
@rjwignar rjwignar marked this pull request as draft April 25, 2024 04:58
@tarasglek
Copy link
Owner

tarasglek commented Apr 25, 2024

seems to work well! thanks for jumping on this

@rjwignar
Copy link
Collaborator Author

I've added my Observations for Re-asking with Image as Input 1 before and after the message duplication bug.

Task Observations pre- 7cc1e2f (tested using adcfd2d) Observations post- 7cc1e2f (tested using Production as of e4967a9) (Prompt duplication bug observed) PR Observations
Ask with Image as Input (no prompt) Appropriate LLM response Appropriate LLM response Appropriate LLM response
Re-ask with Image as Input (no prompt, same image 2) Appropriate LLM Response Appropriate LLM Response (no message duplication as no prompt present, expected) Appropriate LLM (no message duplication as no prompt present, expected)
Ask with prompt and Image as Input Appropriate LLM Response Appropriate LLM Response Appropriate LLM Response
Re-ask with prompt and Image as Input (same image 2) Appropriate LLM Response (no message duplication, desired result) Appropriate LLM Response (message duplication with text only, undesired 3) Appropriate LLM Response (no message duplication, desired result)

1 Image as Input tested using gpt-4-1106-vision-preview
2 Message editing allows text editing only, not image
3 Duplication example provided below:
image

Future Considerations

As of e4967a9, we currently don't pass any imageUrls (from Image as Input) in the onPrompt() call in onResubmitClick() during the Re-Ask Option.

However, if this changes in the future, we might see this bug again, in the form of Input Image(s) duplication.
If this bug appears in the future, we might be able to fix it by extending the changes in this PR to clear imageUrls on retry, with something like:

        // If retrying, empty the prompt and imageUrls to avoid duplicate message
        if (retry) {
          prompt = "";
          imageUrls = [];
        }

Copy link

cloudflare-workers-and-pages bot commented Apr 25, 2024

Deploying chatcraft-org with  Cloudflare Pages  Cloudflare Pages

Latest commit: f46f172
Status: ✅  Deploy successful!
Preview URL: https://bbb872a0.console-overthinker-dev.pages.dev
Branch Preview URL: https://issue-587.console-overthinker-dev.pages.dev

View logs

@rjwignar rjwignar marked this pull request as ready for review April 25, 2024 16:51
@tarasglek
Copy link
Owner

@humphd this looks good to me, can you take a look?

Copy link
Collaborator

@humphd humphd left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is fine, but I'd change the onPrompt signature.

@@ -99,7 +99,7 @@ function MessagesView({
isLoading={isLoading}
onResubmitClick={async (promptText?: string) => {
await deleteMessages(message.id, "after");
onPrompt(promptText);
onPrompt(promptText, undefined, true); // pass prompt text and true for retry, don't include any imageURLs
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A better pattern to use for this is to convert the args for onPrompt to be an Object:

async ({ prompt, imageUrls, retry = false }: { prompt?: string; imageUrls?; string[], retry?; boolean } = {}) => {

Now you can call it like this:

onPrompt({ prompt: promptText, retry: true });

This deals with undefined/optional args much better, and makes it self-documenting.

Copy link
Collaborator Author

@rjwignar rjwignar May 1, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@humphd
I updated the onPrompt signature in ChatBase and MessagesView, but to get this working I also had to make the same updates to the onPrompt and onSendClick (and any calls to these) signatures in various other components due to errors like the ones below:
image
image

These changes still fix the duplication issues observed and preserve the other behaviours I've tested.
The onPrompt/onSendClick signature updates don't seem like they'll break anything (as long as any associated calls have also been updated accordingly), but I can't confirm anything at this time.

Should we put my recent commits (everything from bbdc1ab onward) on the backburner for a follow-up PR?

Copy link
Collaborator

@humphd humphd left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could also define a reusable type for these options, since you have to use them in so many places.

prompt?: string;
imageUrls?: string[];
retry?: boolean;
}) => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A trick you can use when all properties on the object are optional is to set the options object to have a default value:

    async ({
      prompt,
      imageUrls,
      retry = false,
    }: {
      prompt?: string;
      imageUrls?: string[];
      retry?: boolean;
    } = {}) => {

The value of this is that it lets callers do onPrompt() and it will work.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something like this?

async ({ 
    prompt = "", 
    imageUrls = [], 
    retry = false,
//...

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, see my code above. I'm setting the value to {} by default.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh! I see it now. My bad

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I followed your advice and added a reusable type called OnPromptFunction.
I tried the original trick you suggested but got this error when I applied it to the signature on other files on some files:
image

So I ended up using a different syntax suggested by ChatCraft:

export type OnPromptFunction = (options?: {
  prompt?: string;
  imageUrls?: string[];
  retry?: boolean;
}) => void;

I wasn't sure where to place the reusable type, so I placed it in a new file, src/lib/OnPromptFunction.ts.

src/Chat/ChatBase.tsx Outdated Show resolved Hide resolved
src/components/Message/FunctionResultMessage.tsx Outdated Show resolved Hide resolved
src/Chat/ChatBase.tsx Outdated Show resolved Hide resolved
const onPrompt: OnPromptFunction = useCallback(
async (options?: { prompt?: string; imageUrls?: string[]; retry?: boolean }) => {
let prompt = options?.prompt;
const { imageUrls, retry } = options || {};
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm still confused why you don't use the pattern I mentioned and assign = {} as the default instead of doing this.

Copy link
Collaborator Author

@rjwignar rjwignar May 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The pattern was fine for the onPrompt implementation in ChatBase, but when I tried using the same pattern throughout the onPrompt/onSendClick props TypeScript reports this error (props/type can't have default values):
image

To create a common type for both the onPrompt implementation and the onPrompt component props I had to use an options parameter and make that optional.

Although, I think if we apply the OnPromptFunction to the component props only, we can still use the original pattern in the onPrompt implementation, we just won't be able to turn that pattern into a reusable type because it uses a parameter initializer/default value(s)

@@ -5,11 +5,12 @@ import DesktopPromptForm from "./DesktopPromptForm";
import useMobileBreakpoint from "../../hooks/use-mobile-breakpoint";
import { useSettings } from "../../hooks/use-settings";
import { ChatCraftChat } from "../../lib/ChatCraftChat";
import { OnPromptFunction } from "../../lib/OnPromptFunction";
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move your exported types into this file.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I hope I did this correctly.
I put PromptFunctionOptions inside OnPromptFunction.ts with OnPromptFunction.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Re-asking Human message duplicates the message
3 participants