-
Notifications
You must be signed in to change notification settings - Fork 4.4k
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
Feature: Add ability to stop/interupt stream #1221
Comments
Interesting. I wonder if you could try ReadableStream.cancel() method, as ReadableStream() is what we use in the frontend: The spec says that sending a cancel event should attempt to cancel the underlying mechanism, so I think it would close the TCP connection? I'm not sure if you'd still pay for the full tokens of the call though. That's worth experimenting with, if you haven't yet. Otherwise I fear you'd need to move to something with better bidirectional support like web sockets. |
Thanks for the prompt response! I was trying to cancel the asyncio thread in Quart, but cancelling the stream make much more sense :) I've been able to implement this just now, and it seems to work quite well. As far as I can tell, it also stops the response coming back from the model, but I am not sure if there's a way I can check if I was still billed for the full token count, but it doesn't bother me that much, the user experience is more important :)
Then I've just added a button that calls onStopClick()
|
Very cool! You might be able to monitor token usage in your Azure OpenAI metrics dashboard. |
Looks nice, very cool indeed. We will try it on our end and give you feedback! |
Hi all,
I could not interrupt my chat the way you shared, but I just found a workaround like this : export class ClassForReadNDJSONStream {
reader: ReadableStreamDefaultReader;
responseBody: ReadableStream<any>;
constructor(responseBody: ReadableStream<any>) {
this.responseBody = responseBody;
this.reader = responseBody.getReader();
}
async *readStream() {
let runningText = "";
let decoder = new TextDecoder("utf-8");
while (true) {
const { done, value } = await this.reader.read();
if (done) break;
var text = decoder.decode(value, { stream: true });
const objects = text.split("\n");
for (const obj of objects) {
try {
runningText += obj;
let result = JSON.parse(runningText);
yield result;
runningText = "";
} catch (e) {
// Not a valid JSON object
}
}
}
}
} and const c4readNDSJonstream = new ClassForReadNDJSONStream(responseBody);
setResponseStream(c4readNDSJonstream); // Using your method with ClassForReadNDJSONStream signature
for await (const event of c4readNDSJonstream.readStream()) {
// other code ... Based on @pamelafox readNDJSONStream lib but expose reader to allow reader.releaseLock() (and then responseBody.cancel()) in onStopClick() to interrupt chat answer. What do you think about it ? |
Yes, actually, I did get that error :) I ended up implementing an abort controller instead. |
Would be able to share the code ? @TaylorN15 |
Add the AbortController Add the below into
Add the AbortSignal to the props of
And the logic for handling the abort, also in
Create a function for when the button is clicked, and call from where you want, I have mine in the question container, so that the stop button appears instead of the send button, if the answer is streaming
|
Thanks a lot @TaylorN15 AbortController works properly for this usage. Just in case, I passed signal directly to chatApi function, it allows you to cancel chat at any time (before you enter the "for await" loop). export async function chatApi(request: ChatAppRequest, idToken: string | undefined, signal: AbortSignal): Promise<Response> {
return await fetch(`${BACKEND_URI}/chat`, {
method: "POST",
headers: getHeaders(idToken),
body: JSON.stringify(request),
signal: signal
});
} |
Nice, does yours actually stop the stream on the backend? It seems that most of the time, mine doesn’t. It doesn’t bother me much, but it would be nice to reduce the token usage :) |
Did you add any code to the backend to handle the abort? |
No, I didn't add anything to the backend, as fetch api natively handles AbortController signals. |
@TrentinMarc I am curious about how you implemented this. I tried following the code layed out here, but when I cancel the stream in the chat I get the following error message on the screen: I was wondering if there is a more graceful approach to handling this that you may be aware of. |
@ssjdan27 I did not check the recent updates on this project as I stopped working with, but when we were working on it, the simpliest way was to add the signal parameter to the fecth() function performed by chapApi(). Perhaps the asynchronus process has been edited in the meantime ? If you run out of solution, let's share more informations about your problem :) |
@TrentinMarc Thank you for your response and I believe I figured it out! I will share my code below and can maybe get some input on it. Excuse my formatting. I currently do not have this code in Github, but if more people are interested, I could potentially make a PR for it so that the changes are easier to see. This is what chatAPI looks like in api.ts:
I made some changed to QuestionInput.tsx in order to conditionally render the stop streaming button. Here are the changes in QuestionInput.tsx:
The rest of the changes are made in Chat.tsx:
Here is the handleAsyncRequest function.
Here are the changes made in makeApiRequest function as well:
I also added another conditional step to the rendering output:
|
Please create an Pull Request |
@cforce I will work on it! I will do my best to get that open within the next few days. |
This issue is for a: (mark with an
x
)I would like to have a button that will stop/interrupt the stream being rendered (ChatGPT has this). So if the response is going downhill, the user would be able to stop it before it goes any further. I've tried implementing something myself, but it doesn't quite work with the ASyncGenerator.
The text was updated successfully, but these errors were encountered: