1. 项目概述为什么我们需要一个 Swift 版的 OpenAI 客户端如果你是一名 iOS 或 macOS 开发者最近肯定没少和 OpenAI 的 API 打交道。无论是想给 App 加个智能对话功能还是集成一个图片生成模块OpenAI 提供的 API 都是目前最直接、最强大的选择。但官方只提供了 Python 和 Node.js 的 SDK在 Swift 项目里直接调用意味着你得自己处理网络请求、JSON 编解码、错误处理等一系列繁琐的底层工作。这就像你想做一顿大餐却得先从种菜开始。OpenAIKit的出现就是为了解决这个痛点。它是一个纯 Swift 编写的开源库封装了与 OpenAI API 交互的所有细节。简单来说它让你能用几行 Swift 代码就完成之前可能需要上百行才能搞定的 API 调用。从创建聊天对话、生成文本补全到调用 DALL·E 生成图片再到处理语音转文本它都提供了类型安全、符合 Swift 习惯的接口。对于 Swift 开发者而言这不仅仅是节省了时间更重要的是它让集成 AI 功能变得和调用本地框架一样自然和可靠。2. 核心设计思路与架构解析2.1 设计哲学简洁、安全与类型安全OpenAIKit的设计遵循了几个核心原则这也是它区别于简单网络封装器的关键。首先简洁性。它的 API 设计高度模仿了 OpenAI 官方 Python SDK 的命名和结构降低了学习成本。例如创建一个文本补全你只需要调用openAIClient.completions.create(...)直观明了。其次安全性。项目 README 开篇就强调了 API Key 的安全问题。它强烈建议通过环境变量注入密钥而非硬编码在源码中。这不仅仅是“最佳实践”而是必须遵守的准则。因为 OpenAI 的 API Key 权限极高一旦泄露攻击者可以查看账单、滥用额度甚至操控你的组织设置。OpenAIKit本身不存储密钥它只是从你提供的Configuration对象中读取这迫使开发者必须思考密钥的管理策略。第三类型安全与现代化。库全面拥抱 Swift 的现代并发特性async/await所有网络请求都是异步的。同时它利用 Swift 的强类型系统将 API 的请求参数和响应模型都定义成了结构体struct。这意味着你在编码时就能获得编译器的帮助传错了参数类型、漏了必填字段编译器都会提前报错而不是等到运行时才发现 API 返回了一个模糊的错误。2.2 底层网络抽象支持 SwiftNIO 与 URLSession 双引擎这是OpenAIKit一个非常务实的设计。它没有把自己绑定在某个特定的网络库上而是抽象出了一套协议同时支持两种主流的 Swift 网络客户端。SwiftNIO 模式这是服务端 Swift 项目如 Vapor的标配。OpenAIKit使用AsyncHTTPClient这个基于 SwiftNIO 的库。它的优势在于高性能和高并发特别适合在服务端环境中处理大量请求。文档里特意提到了一个关键点通常建议为整个应用生命周期创建一个共享的HTTPClient并在应用关闭时优雅地关闭它。这是因为创建和销毁 HTTP 客户端是有开销的复用客户端可以提升性能。代码示例中的defer块和syncShutdown()就是确保资源正确释放的“标准操作”。URLSession 模式这是苹果平台iOS, macOS 等的原生网络库。对于绝大多数客户端 App 来说直接使用URLSession是最简单、最无需引入额外依赖的选择。OpenAIKit通过一个不同的初始化方法让你可以传入自定义的URLSession实例。这给了客户端开发者极大的灵活性你可以配置缓存策略、超时时间、Cookie 存储等与 App 现有的网络层无缝集成。这种双引擎设计体现了库作者对 Swift 全栈开发生态的深刻理解让同一个工具能在服务器和客户端场景下都游刃有余。2.3 模块化覆盖从 GPT 到 DALL·E再到语音根据 README 中的清单OpenAIKit几乎覆盖了 OpenAI API 的所有核心端点Endpoint。我们可以将其分为几大类核心语言模型Completions补全经典的文本生成接口对应 GPT-3 系列模型如text-davinci-003。你给一段提示Prompt它帮你补全后面的内容。Chat聊天这是目前最流行的接口对应 GPT-3.5-Turbo 和 GPT-4 模型。它使用基于消息system,user,assistant的对话格式能处理多轮上下文是构建聊天机器人的基础。Edits编辑根据指令修改文本例如“将这段文字翻译成法语并使其更正式”。多模态能力Images图像集成 DALL·E 模型根据文字描述生成图像或对已有图像进行编辑、生成变体。Speech to text语音转文本集成 Whisper 模型将音频文件转换为文字。这对于构建语音助手、会议记录等应用至关重要。辅助与基础设施Embeddings嵌入将文本转换为高维向量。这是构建语义搜索、文本分类、推荐系统等高级 AI 应用的基石。Moderations审核检查文本内容是否违反 OpenAI 的使用政策用于构建安全的内容过滤器。Models模型查询可用模型列表获取模型详情。Files文件管理上传到 OpenAI 的文件主要用于微调Fine-tuning任务。注意清单中显示Fine-tunes微调和Function calling函数调用尚未实现。如果你需要这些前沿或高级功能可能需要等待库更新或考虑自行扩展。不过对于绝大多数应用场景聊天、生成、嵌入、审核OpenAIKit已经提供了完备的支持。3. 从零开始在项目中集成与基础使用3.1 环境准备与依赖管理假设你正在开发一个 iOS App我们来看看如何一步步集成OpenAIKit。第一步创建项目与添加依赖如果你使用 Xcode 的 Swift Package ManagerSPM来管理依赖这是现在的主流方式操作非常简单在 Xcode 中打开你的项目点击项目导航器中的项目文件。选择你的 App Target切换到“Package Dependencies”选项卡。点击“”按钮在搜索框中粘贴仓库地址https://github.com/dylanshine/openai-kit.git。Xcode 会自动获取包信息。在“Dependency Rule”中通常选择“Up to Next Major Version”并填写1.0.0这表示允许自动更新到2.0.0以下的所有版本。点击“Add Package”Xcode 会解析依赖。完成后在弹窗中勾选OpenAIKit库将其添加到你的 Target 中。如果你用的是服务端项目在Package.swift文件中添加依赖正如 README 所示dependencies: [ .package(url: https://github.com/dylanshine/openai-kit.git, from: 1.0.0) ], targets: [ .target(name: YourTargetName, dependencies: [ .product(name: OpenAIKit, package: openai-kit), ]), ]第二步安全地管理 API Key绝对不要将 API Key 写在代码里我们采用环境变量注入的方式。 对于 iOS 开发一个常见且安全的方法是使用 Xcode 的配置Configuration和模式Scheme在项目导航器中选中你的 Target进入“Build Settings”选项卡。点击“”按钮选择“Add User-Defined Setting”。命名一个设置例如OPENAI_API_KEY。为不同的配置Debug/Release设置不同的值。在 Debug 模式下你可以填入测试用的 Key在 Release 模式下这个值应该留空并通过 CI/CD 流程在构建时注入。在代码中通过Bundle.main的infoDictionary来读取虽然不完全等同于环境变量但原理类似。更推荐的方式是创建一个配置文件如Config.plist将其加入.gitignore然后在代码中读取。对于服务端项目如 Vapor使用.env文件是标准做法在项目根目录创建.env文件。写入你的密钥OPENAI_API_KEYsk-your-actual-key-here。至关重要确保.env文件在.gitignore中防止意外提交。在代码中通过ProcessInfo.processInfo.environment字典来读取正如 README 示例所示。3.2 客户端初始化实战根据你的项目类型选择不同的初始化方式。场景一iOS/macOS App使用 URLSession这是客户端应用最典型的场景。你通常会在一个单例或依赖注入容器中创建并持有OpenAIKit.Client实例。import OpenAIKit class AIService { // 单例模式全局共享一个客户端 static let shared AIService() private let client: OpenAIKit.Client private init() { // 1. 安全地获取 API Key这里以从 Info.plist 读取为例 guard let apiKey Bundle.main.infoDictionary?[OPENAI_API_KEY] as? String, !apiKey.isEmpty else { fatalError(请在 Info.plist 中配置 OPENAI_API_KEY) } // 2. 创建配置。组织 ID 可选如果你属于某个 OpenAI 组织才需要。 let configuration Configuration(apiKey: apiKey) // 3. 使用默认的 URLSession 初始化客户端 // 你也可以自定义 URLSessionConfiguration例如设置超时时间 let session URLSession(configuration: .default) self.client OpenAIKit.Client(session: session, configuration: configuration) } // 后续的 API 调用方法... }场景二Vapor 服务端应用使用 SwiftNIO在 Vapor 项目中我们通常利用其依赖注入系统在应用启动时配置客户端并在路由处理器中使用。// 通常在 configure.swift 中 import Vapor import OpenAIKit public func configure(_ app: Application) throws { // 1. 从环境变量读取配置 guard let apiKey Environment.get(OPENAI_API_KEY) else { app.logger.critical(未设置 OPENAI_API_KEY 环境变量) throw Abort(.internalServerError) } let organization Environment.get(OPENAI_ORGANIZATION) // 可选 // 2. 创建共享的 HTTPClient遵循 Vapor 的生命周期管理 let httpClient HTTPClient(eventLoopGroupProvider: .shared(app.eventLoopGroup)) // 在应用关闭时清理 app.http.client.shared.defer { try httpClient.syncShutdown() } // 3. 创建 OpenAI 配置和客户端 let configuration Configuration(apiKey: apiKey, organization: organization) let openAIClient OpenAIKit.Client(httpClient: httpClient, configuration: configuration) // 4. 将客户端注册为服务方便在路由中获取 app.openAI openAIClient } // 扩展 Application 以便存储客户端 extension Application { private struct OpenAIKey: StorageKey { typealias Value OpenAIKit.Client } var openAI: OpenAIKit.Client? { get { self.storage[OpenAIKey.self] } set { self.storage[OpenAIKey.self] newValue } } } // 在路由处理器中使用 app.get(ask) { req - EventLoopFutureString in guard let openAI req.application.openAI else { throw Abort(.internalServerError, reason: OpenAI 服务未配置) } // 注意在 Vapor 的 EventLoopFuture 环境中需要使用 flatMapThrowing 等方式适配 async/await // 更现代的做法是使用 Vapor 4 的 req.eventLoop.performWithTask return req.eventLoop.performWithTask { let completion try await openAI.completions.create(model: .gpt3(.davinci), prompts: [Hello, world]) return completion.choices.first?.text ?? No response } }实操心得在服务端使用 SwiftNIO 模式时务必注意HTTPClient的生命周期管理。一个常见的错误是在每个请求中都创建新的HTTPClient这会导致端口和内存资源迅速耗尽。最佳实践是在应用启动时创建一个共享实例并确保在应用关闭时或测试结束时调用syncShutdown()进行清理。defer语句是保证这一点的好帮手。4. 核心 API 使用详解与最佳实践4.1 文本生成Completions 与 Chat 的抉择这是最常用的功能。OpenAIKit提供了completions和chat两个端点它们有什么区别又该如何选择Completions补全对应模型主要是 GPT-3 系列如text-davinci-003,text-curie-001。GPT-4 通常不通过此端点调用。交互模式单次提示Prompt。你给它一段文本它接着往下写。使用场景文案续写、代码补全、大纲生成等“单向延伸”的任务。它的接口相对简单。let completion try await openAIClient.completions.create( model: .gpt3(.davinci), // 指定模型 prompts: [Once upon a time in a land far away,], // 提示词 maxTokens: 50, // 生成的最大令牌数约等于单词数 temperature: 0.7 // 创造性0.0最确定1.0最随机 ) print(completion.choices.first?.text ?? )Chat聊天对应模型gpt-3.5-turbo,gpt-4,gpt-4-turbo等。交互模式基于消息列表的对话。消息有角色之分system设定助手行为、user用户输入、assistant助手历史回复。使用场景所有需要多轮对话、上下文记忆的聊天机器人、智能客服、复杂任务分解等。这是目前的主流和推荐方式因为性价比更高gpt-3.5-turbo效果强且便宜。let messages: [Chat.Message] [ .system(content: 你是一位乐于助人的诗歌助手。), .user(content: 为我写一首关于 Swift 编程语言的俳句。) ] let chatCompletion try await openAIClient.chats.create( model: .gpt3_5Turbo, // 或 .gpt4 messages: messages, maxTokens: 100, temperature: 0.8 ) if let responseMessage chatCompletion.choices.first?.message { print(responseMessage.content) }如何选择无脑选 Chat对于绝大多数新的对话式应用直接使用 Chat 端点配合gpt-3.5-turbo或gpt-4。它设计更现代能处理上下文且成本通常更低。考虑 Completions如果你的任务是非常简单的“提示-补全”且对老模型如text-davinci-003有特定需求或已有大量基于此模型调优的提示词可以继续使用。4.2 图像生成玩转 DALL·EOpenAIKit的images端点让生成图像变得异常简单。核心方法是.create它对应 DALL·E 的“根据描述生成图像”。do { let imageResult try await openAIClient.images.create( prompt: A serene landscape painting of a misty mountain lake at sunrise, digital art, // 描述词 numberOfImages: 2, // 生成数量最多10张注意成本 size: .size1024 // 图片尺寸.size256, .size512, .size1024 // responseFormat: .url // 返回图片URL默认一小时后过期 // responseFormat: .b64_json // 返回Base64编码的图片数据 ) for data in imageResult.data { if let url data.url { print(生成的图片URL: \(url)) // 在iOS中你可以用 Kingfisher/SDWebImage 等库加载这个URL // 例如imageView.kf.setImage(with: url) } else if let b64Json data.b64Json { // 处理Base64字符串解码成Data然后创建UIImage if let imageData Data(base64Encoded: b64Json), let uiImage UIImage(data: imageData) { // 更新UI DispatchQueue.main.async { self.imageView.image uiImage } } } } } catch { print(生成图片失败: \(error)) }重要注意事项成本与速率限制DALL·E 生成图片是按张数和分辨率收费的1024x1024最贵。频繁调用容易触发速率限制。务必在客户端实现适当的加载状态和错误重试机制。内容政策OpenAI 有严格的图像生成内容政策。避免生成涉及名人、暴力、色情或政治敏感内容的提示词否则请求会被拒绝。Base64 vs URLresponseFormat默认为.url返回一个临时链接一小时有效。这对于快速预览很方便。如果你需要永久保存图片应该在收到 URL 后立即将其下载到自己的服务器或存储服务中。选择.b64_json会直接返回图片数据适合需要立即在客户端显示且不想额外发起网络请求的场景但请注意 JSON 数据量会很大。4.3 嵌入与语义搜索构建智能的“理解”能力Embeddings嵌入是解锁高级 AI 应用的关键。它将一段文本一个词、一句话、一篇文章转换成一个高维向量一组数字。语义相似的文本其向量在空间中的距离也更近。一个简单的使用示例let response try await openAIClient.embeddings.create( model: .textEmbeddingAda002, // 推荐使用的嵌入模型性价比高 input: The food was delicious and the waiter was friendly. // 可以是一个字符串也可以是字符串数组 ) if let embedding response.data.first?.embedding { // embedding 是一个 [Double] 数组长度取决于模型如 ada-002 是 1536 维 print(得到嵌入向量维度\(embedding.count)) // 你可以将这个向量存储到数据库如 PostgreSQL 的 vector 类型或 Redis }实际应用场景假设你有一个新闻 App想要实现“搜索相关文章”的功能而不仅仅是关键词匹配。预处理离线将你数据库里的所有新闻文章通过embeddings接口转换为向量并存储起来。查询时在线当用户输入搜索词“一场激动人心的体育赛事”时同样将这个查询词转换为向量。计算相似度在数据库中计算查询向量与所有文章向量的余弦相似度Cosine Similarity或点积。返回结果将相似度最高的几篇文章返回给用户。即使用户的搜索词里没有“篮球”、“NBA”等具体词汇只要文章语义上与“激动人心的体育赛事”相关就能被检索出来。OpenAIKit为你完成了最关键的第1步和第2步——将文本转化为机器可理解的数学形式。剩下的向量存储和相似度计算你需要借助其他数据库如pgvector扩展的 PostgreSQL、Redis 的 RediSearch、或专业的向量数据库如 Pinecone、Weaviate来完成。5. 错误处理、调试与性能优化5.1 结构化错误处理OpenAIKit抛出的错误类型是OpenAIKit.APIErrorResponse。它包含了 API 返回的详细信息对于调试至关重要。do { let result try await openAIClient.chats.create(model: .gpt4, messages: messages) // 处理成功结果 } catch let error as OpenAIKit.APIErrorResponse { // 专门处理 API 错误 print(API 错误类型: \(error.type)) print(错误信息: \(error.message)) print(错误代码: \(error.code ?? \N/A\)) // 根据错误类型提示用户或进行重试 switch error.type { case invalid_request_error: // 可能是参数错误提示用户检查输入 showAlert(请求参数有误) case rate_limit_error: // 速率限制提示用户稍后再试并实施指数退避重试 showAlert(请求过于频繁请稍后再试) scheduleRetry(after: 2.0) // 自定义的重试逻辑 case insufficient_quota: // 额度不足需要充值 showAlert(API 额度已用尽) default: showAlert(请求失败: \(error.message)) } } catch { // 处理其他类型的错误如网络错误、解码错误等 print(其他错误: \(error.localizedDescription)) }常见错误类型及应对策略错误类型 (error.type)可能原因建议处理方式invalid_request_error请求参数缺失、格式错误、模型不存在等。检查请求体 JSON 格式、参数值是否在允许范围内如temperature应在 0-2 之间。authentication_errorAPI Key 无效、过期或没有权限。确认 API Key 是否正确是否有访问目标模型的权限如 GPT-4 可能需要单独申请。rate_limit_error短时间内请求过多超过 RPM每分钟请求数或 TPM每分钟令牌数限制。实现指数退避重试机制。这是必须的首次重试等待 1-2 秒下次加倍并设置最大重试次数。insufficient_quota账户余额或免费额度已用完。提示用户需要充值或升级套餐。server_errorOpenAI 服务器内部错误。等待一段时间后重试。5.2 调试与日志记录在生产环境中详细的日志对于排查问题必不可少。由于OpenAIKit底层使用的是URLSession或AsyncHTTPClient你可以通过这些库的配置来开启日志。对于 URLSession客户端你可以自定义URLSessionConfiguration但更常见的做法是在网络层拦截请求和响应。你可以使用URLProtocol子类或者更方便地使用像Alamofire这样的网络库它内置了强大的日志功能来创建URLSession然后再传给OpenAIKit.Client。对于 AsyncHTTPClient服务端AsyncHTTPClient支持日志记录。你可以在创建HTTPClient时传入一个Logger实例。import Logging var logger Logger(label: com.yourcompany.openai-client) logger.logLevel .debug // 设置为 .debug 或 .trace 以获取详细日志 let httpClient HTTPClient( eventLoopGroupProvider: .shared(eventLoopGroup), configuration: HTTPClient.Configuration(logger: logger) // 传入 logger )这样所有发出的 HTTP 请求和收到的响应头出于安全默认不会记录响应体都会被记录下来方便你查看实际的请求 URL、Header 和状态码。5.3 性能优化与成本控制要点复用客户端实例如前所述无论是URLSession还是HTTPClient都应该在应用生命周期内复用。频繁创建和销毁会带来不必要的开销。合理设置超时OpenAI 的 API 响应时间受模型、输入长度和服务器负载影响。为网络请求设置合理的超时时间如 60 秒避免长时间阻塞 UI 或线程。// 对于 URLSession let config URLSessionConfiguration.default config.timeoutIntervalForRequest 60.0 config.timeoutIntervalForResource 120.0 let session URLSession(configuration: config) // 对于 AsyncHTTPClient var clientConfig HTTPClient.Configuration() clientConfig.timeout.connect .seconds(30) clientConfig.timeout.read .seconds(60) let httpClient HTTPClient(eventLoopGroupProvider: .shared(group), configuration: clientConfig)控制令牌使用Token Usage这是成本控制的核心。API 按输入和输出的总令牌数收费。监控用量每次 API 调用的响应中都包含usage字段记录了本次消耗的令牌数。你应该记录这些数据用于监控和预算控制。let chatResponse try await openAIClient.chats.create(...) let totalTokens chatResponse.usage.totalTokens print(本次调用消耗了 \(totalTokens) 个令牌。)设置max_tokens务必为生成类请求Completions, Chat设置合理的maxTokens上限防止生成过长内容导致意外高费用。精简输入在构建system或user消息时尽量言简意赅。不必要的上下文会增加令牌消耗。实现请求队列与限流如果你的应用可能同时发起大量请求例如用户批量处理文档务必在客户端实现一个简单的请求队列或限流机制避免瞬间触发 OpenAI 的速率限制Rate Limit导致所有后续请求失败。可以使用DispatchSemaphore或第三方库如RateLimiter来控制并发请求数。6. 进阶话题与项目扩展6.1 处理流式响应StreamingOpenAI 的 Chat Completions API 支持流式响应stream: true这意味着你可以像接收视频流一样逐字逐句地收到模型的回复而不是等待整个回复生成完毕。这对于打造类似 ChatGPT 的实时打字机效果体验至关重要。遗憾的是根据OpenAIKit的 README 和当前源码截至我知识截止日期它似乎尚未原生支持流式响应。这是一个重要的功能缺口。临时解决方案与扩展思路如果你急需此功能有两条路降级使用如果不要求实时性可以继续使用非流式接口一次性获取全部回复。自行扩展或寻找替代你可以考虑直接使用URLSession或AsyncHTTPClient的流式处理能力手动构建请求和解析 Server-Sent Events (SSE) 格式的响应。这需要你深入研究 OpenAI 的流式 API 文档。或者你可以关注OpenAIKit项目的 Issues 和 Pull Requests看是否有社区贡献了流式支持也可以考虑其他更成熟的 Swift OpenAI 客户端库如OpenAIby MacPaw它们可能已经实现了该功能。6.2 与 SwiftUI 的集成模式在 SwiftUI 应用中通常结合ObservableObject或新的Observable宏来管理 AI 服务状态。import SwiftUI import OpenAIKit MainActor class ChatViewModel: ObservableObject { Published var messages: [ChatMessage] [] // 你的UI模型 Published var isThinking false Published var errorMessage: String? private let openAIService: AIService // 前面封装的服务类 init(service: AIService .shared) { self.openAIService service } func sendMessage(_ text: String) async { let userMessage ChatMessage(id: UUID(), role: .user, content: text) messages.append(userMessage) isThinking true errorMessage nil do { // 1. 准备消息历史注意控制上下文长度 let openAIMessages messages.suffix(10).map { msg in // 只保留最近10条作为上下文 Chat.Message(role: .init(rawValue: msg.role.rawValue)!, content: msg.content) } // 2. 调用 API let response try await openAIService.client.chats.create( model: .gpt3_5Turbo, messages: openAIMessages, maxTokens: 500 ) // 3. 处理回复 if let assistantContent response.choices.first?.message.content { let assistantMessage ChatMessage(id: UUID(), role: .assistant, content: assistantContent) messages.append(assistantMessage) } } catch let error as OpenAIKit.APIErrorResponse { errorMessage AI 服务错误: \(error.message) } catch { errorMessage 网络或未知错误: \(error.localizedDescription) } isThinking false } } // 在 SwiftUI View 中使用 struct ChatView: View { StateObject private var viewModel ChatViewModel() State private var inputText var body: some View { VStack { List(viewModel.messages) { message in MessageBubble(message: message) } .overlay { if viewModel.isThinking { ProgressView() } } HStack { TextField(输入消息..., text: $inputText) Button(发送) { Task { await viewModel.sendMessage(inputText) inputText } } .disabled(viewModel.isThinking || inputText.isEmpty) } .padding() if let error viewModel.errorMessage { Text(error).foregroundColor(.red) } } } }6.3 异步任务的管理与取消在 iOS 开发中用户可能会快速切换界面或者发送消息后立刻取消。我们需要妥善管理这些异步的 AI 请求避免资源浪费和潜在的错误。使用Task和取消标识class ChatViewModel: ObservableObject { // ... 其他属性 ... private var currentTask: TaskVoid, Never? // 持有当前任务引用 func sendMessage(_ text: String) { // 如果已有任务在运行先取消它 currentTask?.cancel() currentTask Task { // 在任务开始前检查是否已被取消 guard !Task.isCancelled else { return } // ... 准备消息更新 UI ... do { // 调用可能耗时的 API let response try await openAIService.client.chats.create(...) // 关键在更新 UI 前再次检查任务是否被取消。 // 因为用户可能在请求过程中离开了页面。 guard !Task.isCancelled else { return } // 更新 UI必须在主线程 await MainActor.run { self.messages.append(assistantMessage) } } catch { // 如果错误是因为任务被取消我们选择静默处理不更新错误状态 if (error as? URLError)?.code .cancelled { print(请求被用户取消) return } // 处理其他错误... } finally { // 请求结束清理任务引用 if !Task.isCancelled { await MainActor.run { self.isThinking false self.currentTask nil } } } } } func cancelRequest() { currentTask?.cancel() currentTask nil isThinking false } }通过持有Task的引用并在适当的时候调用cancel()我们可以实现用户友好的中断操作。同时在await调用前后检查Task.isCancelled可以确保被取消的任务不会再去更新已经无效的 UI 状态。OpenAIKit作为一个基础工具库为你扫平了与 OpenAI API 通信的障碍。但要构建一个健壮、高效、用户体验良好的 AI 应用你还需要在它的基础上仔细处理错误、管理状态、控制成本、并优化性能。希望这篇详细的指南能帮助你在 Swift 生态中更自信地驾驭 AI 能力。