Demo地址:GPTAPI_Demo
学自:
- Sensei
- Swift AsyncThrowingStream 和 AsyncStream 代码实例详解
ChatGPT敞开API后,运用起来比官网快多了,怎么运用网上很多教程,这儿就不赘述了,主要介绍一下如安在iOS完结类似官网那种一个接一个字地展现的效果。
运用AsyncThrowingStream
AsyncThrowingStream
是能够导致抛出过错的元素流(详细介绍和用法能够看这篇文章:Swift AsyncThrowingStream 和 AsyncStream 代码实例详解)。
结合URLSession
,不必等悉数数据都拼接好才拿到最终成果,能够实时获取服务器传过来的数据,给多少就展现多少,跟水流一样。
Talk is cheap. Show me the code.
- 首要封装一个恳求东西类
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)")
}
}
}
}
}
- 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)"
}
}
// 来到这儿,阐明恳求已完毕
}
- 完结效果
OK,完结。
最终
剩余的无非便是UI、Markdown解析、数据存储和恳求上下文的拼接,弄好便是一个ChatGPT的App了。
这仅仅AsyncThrowingStream
的根本运用,还有许多高级功能和杂乱的用法,需求持续学习并深化了解。再次安利这篇文章:Swift AsyncThrowingStream 和 AsyncStream 代码实例详解),还有大神写好的ChatGPT客户端Sensei,本文代码都是参阅他的。
Demo地址:GPTAPI_Demo