Demo地址:GPTAPI_Demo

学自:

  • Sensei
  • Swift AsyncThrowingStream 和 AsyncStream 代码实例详解

ChatGPT敞开API后,运用起来比官网快多了,怎么运用网上很多教程,这儿就不赘述了,主要介绍一下如安在iOS完结类似官网那种一个接一个字地展现的效果。

Swift - 使用AsyncThrowingStream实现ChatGPT的回答效果

运用AsyncThrowingStream

AsyncThrowingStream是能够导致抛出过错的元素流(详细介绍和用法能够看这篇文章:Swift AsyncThrowingStream 和 AsyncStream 代码实例详解)。

结合URLSession,不必等悉数数据都拼接好才拿到最终成果,能够实时获取服务器传过来的数据,给多少就展现多少,跟水流一样。

Talk is cheap. Show me the code.

  1. 首要封装一个恳求东西类
enum GPTAPI {
    /// ChatGPT API URL
    static let apiURL = "https://api.openai.com/v1/chat/completions"
    /// ChatGPT API Model
    static let apiModel = "gpt-3.5-turbo"
    /// ChatGPT API Key
    static let apiKey = Your OpenAI API Key
}
// MARK: - 流式获取一个答复
@available(iOS 15.0, *)
extension GPTAPI {
    static func ask(_ problem: String) async throws -> AsyncThrowingStream<String, Swift.Error> {
        let messages: [[String: Any]] = [
            [
                // 假如需求联系上下文,拼接时GPT方就运用"assistant"
                "role": "user", // 我发起的问题,所以角色便是我 --- "user"
                "content": problem
            ],
        ]
        let json: [String: Any] = [
            "model": apiModel,
            "messages": messages,
            "temperature" = 0.5,
            "stream" = true, // 想流式接收数据必须填写该参数!
        }
        guard let jsonData = try? JSONSerialization.data(withJSONObject: json) else {
            throw Self.Error.parameterWrong
        }
        let url = URL(string: apiURL)!
        var request = URLRequest(url: url)
        request.httpMethod = "POST"
        request.setValue("Bearer \(apiKey)", forHTTPHeaderField: "Authorization")
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
        request.httpBody = jsonData
        let (result, rsp) = try await URLSession.shared.bytes(for: request)
        guard let response = rsp as? HTTPURLResponse else {
            throw Self.Error.networkFailed
        }
        guard 200...299 ~= response.statusCode else {
            throw Self.Error.invalidResponse
        }
        return AsyncThrowingStream<String, Swift.Error> { continuation in
            Task(priority: .userInitiated) {
                do {
                    for try await line in result.lines {
                        /*
                         data: {"id":"chatcmpl-7BfPTGeZOaiHlReEcIhKaiOCNDwiH","object":"chat.completion.chunk","created":1683015143,"model":"gpt-3.5-turbo-0301","choices":[{"delta":{"content":"xxxxx"},"index":0,"finish_reason":null}]}
                         */
                        guard line.hasPrefix("data: "),
                              let data = line.dropFirst(6).data(using: .utf8) // 丢掉前6个字符 --- "data: "
                        else {
              print("有一帧解析失利了")
              continue
            }
                        // 解析某一帧数据
                        let json = JSON(data)
                        if let content = json["choices"][0]["delta"]["content"].string {
                            continuation.yield(content)
                        }
                        if let finishReason = json["choices"][0]["finish_reason"].string, finishReason == "stop" {
                            // 悉数拿完了
                            break
                        }
                    }
                    // 悉数解析完结,完毕
                    continuation.finish()
                } catch {
                    // 发生过错,完毕
                    continuation.finish(throwing: error)
                }
                // 流停止后的回调
                continuation.onTermination = { @Sendable status in
                    print("Stream terminated with status: \(status)")
                }
            }
        }
    }
}
  1. OK,开始问询
Task.detached {
    do {
        let stream: AsyncThrowingStream = try await GPTAPI.ask("帮我用Swift写一个斐波那契数算法")
        // 先清空上次答复
        await MainActor.run { 
            self.text = ""
        }
        // 拼接数据流
        for try await text in stream {
            await MainActor.run {
                self.text += text
            }
        }
    } catch {
        await MainActor.run {
            self.text = "恳求失利 - \(error.localizedDescription)"
        }
    }
    // 来到这儿,阐明恳求已完毕
}
  1. 完结效果

Swift - 使用AsyncThrowingStream实现ChatGPT的回答效果

OK,完结。

最终

剩余的无非便是UI、Markdown解析、数据存储和恳求上下文的拼接,弄好便是一个ChatGPT的App了。

这仅仅AsyncThrowingStream的根本运用,还有许多高级功能和杂乱的用法,需求持续学习并深化了解。再次安利这篇文章:Swift AsyncThrowingStream 和 AsyncStream 代码实例详解),还有大神写好的ChatGPT客户端Sensei,本文代码都是参阅他的。

Demo地址:GPTAPI_Demo