Skip to content

How to cancel closure openAI.chatsStream(query: query) #70

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

Closed
iTarek opened this issue May 16, 2023 · 2 comments · Fixed by #251
Closed

How to cancel closure openAI.chatsStream(query: query) #70

iTarek opened this issue May 16, 2023 · 2 comments · Fixed by #251

Comments

@iTarek
Copy link

iTarek commented May 16, 2023

Is there any way to cancel chatsStream closure?

openAI.chatsStream(query: query) { partialResult in
    switch partialResult {
    case .success(let result):
        print(result.choices)
    case .failure(let error):
        //Handle chunk error here
    }
} completion: { error in
    //Handle streaming error here
}

I call this function inside SwiftUI OnAppear and when the sheet dismissed the closure keep running, is there any solution for that?

@ljoukov
Copy link
Contributor

ljoukov commented Jun 20, 2023

Yes, nothing special is needed, just cancel the Task which runs the query.

For myself I got a little wrapper as I feel I needed a bit more obvious semantics:

@MainActor
class ChatQueryTask: ObservableObject {
  private var task: Task<Void, Never>?

  enum Status {
    case completed
    case interrupted
    case failed
  }

  func query(
    messages: [Chat],
    onContent: @MainActor @escaping (String) -> Void,
    onFinished: @MainActor @escaping (Status) -> Void) async
  {
    if let task = task {
      let _ = await task.result
    }
    let newTask = Task {
      let query = ChatQuery(model: .gpt4, messages: messages)
      do {
        var text = ""
        for try await result in openAI.chatsStream(query: query) {
          if let choice = result.choices.first {
            if let content = choice.delta.content {
              text += content
              onContent(text)
            }
          }
        }
        let isCancelled = Task.isCancelled
        onFinished(isCancelled ? .interrupted : .completed)
      } catch {
        onFinished(.failed)
      }
    }
    task = newTask
    let _ = await newTask.value
    task = nil
  }

  func cancel() async {
    if let task = task {
      task.cancel()
      let _ = await task.result
    }
  }
}

@cenkalti
Copy link

cenkalti commented Mar 2, 2024

If my understanding about Swift is correct, above solution will not work. This is because tasks are cooperative, meaning they are not preemptively interrupted or canceled by the system. Instead, a task must check for cancellation points or explicitly support cancellation for it to be interrupted from outside. You can request cancellation on a task from outside, but it is up to the task itself to periodically check if a cancellation has been requested and gracefully terminate if so. I don't see any cancellation is being handled inside chatsStream method. The method uses URLSession inside but I don't see it any calls to URLSession.cancel().

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 a pull request may close this issue.

3 participants