SwiftClaude

SwiftClaude 是一个用于 Anthropic Claude API 的 Swift 客户端。

法律声明

CLAUDE 和 ANTHROPIC 是 Anthropic, PBC 的商标。本项目与 Anthropic 无关联,亦未获得其赞助。

成熟度

此软件包非常新,且未经充分测试。SwiftClaude 处于 1.0 版本之前,这意味着 API 可能会根据社区的反馈而更改。

用法

以下是 SwiftClaude 的快速入门介绍。更多详情,请查看 .xcode/SwiftClaudeAppPackage 中定义的示例项目,特别是 HaikuGenerator

基本用法

以下示例假设您已创建了一个值 let claude: Claude。有关如何创建 Claude,请参阅 身份验证

SwiftClaude 的核心抽象是 Conversation,这是一个您需要在项目中实现的协议

import SwiftClaude

struct Conversation: Claude.Conversation {
  var messages: [Message]
}

var conversation = Conversation(
  messages: [
    .user("Write me a haiku about a really well-made tool.")
  ]
)

一旦您拥有了对话,您可以要求 Claude 提供下一条消息

let message = claude.nextMessage(in: conversation)

消息有一个简单的异步 API,您可以等待完整文本或分块处理它

/// Print the full text
print(try await message.text())

/// Print the text as segments come in
for try await segment in message.textSegments {
  print(segment, terminator: "")
  fflush(stdout)
}
print()

消息也是 Observable 的,这意味着您可以直接在 SwiftUI 中使用它们

Text(message.currentText)

对于大多数属性,SwiftClaude 提供了异步版本和 Observable 版本。Observable 版本通常以 current 为前缀。

如果您想实现对话的多个轮次,只需将新消息添加到对话中,然后添加响应并请求一条新消息。

conversation.messages += [
  .assistant(message),
  .user("That was great! Can you write me one more, this time about track saws?")
]
let nextMessage = claude.nextMessage(in: conversation)

工具使用

SwiftClaude 对通过 @Tool 宏实现的工具使用提供了出色的支持。

定义工具就像创建一个带有 invoke 方法并附加 @Tool 宏的类型一样简单。以下是 HaikuGenerator 中的一个示例

@Tool
struct EmojiTool {

  /// Displays an emoji
  /// Great for spicing up a haiku!
  func invoke(
    _ emoji: String
  ) -> String {
    emoji
  }

}

invoke 函数上的注释在这里尤为重要,SwiftClaude 实际上会将其发送给 Claude,以帮助 Claude 理解如何最好地调用您定义的函数。有关如何最好地记录您的工具的信息,请查阅 Anthropic 的文档

如果您希望工具的输入更复杂,您可以使用 @ToolInput 宏来定义自定义结构体或枚举。例如,这里有一个 Command 工具,它可以使 Claude 能够向前、向后导航或导航到指定的 URL

@ToolInput
enum Command {
  case goBack
  case goForward
  case navigate(to: String)
}

您可以通过简单地将其作为参数添加到 invoke 函数中,在工具中使用它

@Tool
struct Browser {

  /// Controls a browser
  func invoke(
    _ command: Command
  ) -> String {
    /// Execute `command`
  }

}

要在对话中使用它,您需要指定您想要用于 ToolOutput 的类型

private struct Conversation: Claude.Conversation {

  var messages: [Message] = []

  typealias ToolOutput = String

} 

String 是用于工具输出的最简单类型,但如果您想利用更复杂的功能(如 视觉),您也可以使用 ToolInvocationResultContent,甚至自定义类型。有关工作示例,请查阅 .xcode/SwiftClaudeAppPackage 中的 ComputerUseDemo

您还需要向 Claude 提供它可以访问的工具列表

let message = claude.nextMessage(
  in: conversation,
  tools: Tools {
    CatEmojiTool()
    EmojiTool()
  }
)

当 Claude 请求工具调用时,这些工具默认情况下不会被调用,并且需要显式地在特定工具上调用 requestInvocation,或在消息上调用 requestToolInvocations。我们建议在调用工具之前提示用户,以确保 Claude 的请求与用户的意图一致。对于仅提供上下文或显示 UI 的简单工具,您可以指定一个 ToolInvocationStrategy,使此过程更简单。例如,whenToolInputAvailable 将在成功解码输入时自动调用工具

let message = claude.nextMessage(
  in: conversation,
  tools: Tools {
    CatEmojiTool()
    EmojiTool()
  },
  invokeTools: .whenInputAvailable
) 

使用工具的对话需要处理一些额外的用例,因为它们可能包含的不仅仅是文本。您现在需要处理内容块,而不是仅仅处理文本或文本段落。文本内容块具有与纯文本消息类似的 API,工具使用块可以通过多种方式处理。异步 API 看起来像这样

for try await block in message.contentBlocks {
  switch block {
  case .textBlock(let textBlock):
    for try await textFragment in textBlock.textFragments {
      print(textFragment, terminator: "")
      fflush(stdout)
    }
  case .toolUseBlock(let toolUseBlock):
    print("[Using \(toolUseBlock.toolName): \(try await toolUseBlock.output())]")
  }
}
print()

与纯文本消息一样,带有工具调用的消息也是 Observable 的,并且可以在构建于 Observation 之上的框架(如 SwiftUI)中使用(通过以 current 为前缀的属性)。这是一个 SwiftUI 示例

ForEach(assistant.currentContentBlocks) { block in
  switch block {
  case .textBlock(let textBlock):
    Text(textBlock.currentText)
  case .toolUseBlock(let toolUseBlock):
    if let output = toolUseBlock.toolUse.currentOutput {
      Text("[Using \(toolUseBlock.toolUse.toolName): \(output)]")
    } else {
      Text("[Using \(toolUseBlock.toolUse.toolName)]")
    }
  }
}

注意:消息和内容块也都符合 Identifiable 协议,以便更轻松地与 SwiftUI 一起使用。

工具结果需要发送回 Claude 才能继续对话。最简单的方法是像这样检查 conversation.nextStep()

repeat {
  let message = claude.nextMessage(
    in: conversation, 
    tools: Tools {  }
  )
  conversation.append(message)
} while try await conversation.nextStep() == .toolUseResult

视觉

SwiftClaude 支持视觉。在 Apple 平台上,您可以直接在用户消息中包含 UIImageNSImage

conversation.messages.append(
  .user("Describe this image: \(image)")
)

默认情况下,SwiftClaude 会按照 Anthropic 的建议 调整图像大小。您可以通过指定 imagePreprocessingMode: .disabled 来禁用此行为。

提示缓存(Beta 版)

SwiftClaude 支持提示缓存。您可以将 Claude.Beta.CacheBreakpoint 添加到大多数结果构建器 API。您还可以将 cacheBreakpoint: Claude.Beta.CacheBreakpoint() 参数添加到字符串插值或 append API。

身份验证

⚠️ 您应格外小心,确保 API 密钥的私密性。 在任何情况下都不应发布嵌入 API 密钥的应用程序。 未能保护密钥的私密性可能会导致意外收费、速率限制或其他后果。

通过 SwiftClaude 使用 Claude API 进行身份验证需要使用 Claude.Authenticator。在 Apple 平台上,我们鼓励您使用 KeychainAuthenticator,它将 API 密钥安全地存储在钥匙串中。

let authenticator = Claude.KeychainAuthenticator(
  namespace: "com.codebygeorge.SwiftClaude.HaikuGenerator",
  identifier: "api-key"
)
/// Call `authenticator.save(…)` with your API key 

一旦您拥有了身份验证器,您就可以创建一个 Claude

let claude = Claude(authenticator: authenticator)

设置

SwiftClaude 需要 Swift 6 和 macOS 15 或 iOS 18。

SwiftClaude 是使用 Swift Package Manager 构建的。要包含它,请在您的 Package.swift 中添加以下内容

let package = Package(
  
  dependencies: [
    .package(url: "git@github.com:GeorgeLyon/SwiftClaude", branch: "main")
  ],
  
)

然后,将 SwiftClaude 作为依赖项添加到您的目标

    .target(
      
      dependencies: [
        "SwiftClaude"
      ],
      
    ),

您还可以参考 .xcode/SwiftClaudeAppPackage 中的项目以获取更多详细信息。